diff --git a/data/python.gram b/data/python.gram index b6853f2..4777ebb 100644 --- a/data/python.gram +++ b/data/python.gram @@ -1805,6 +1805,7 @@ kwargs[list]: starred_expression: | invalid_starred_expression | '*' a=expression { ast.Starred(value=a, ctx=Load, LOCATIONS) } + | '*' { self.raise_syntax_error("Invalid star expression") } kwarg_or_starred: | invalid_kwarg @@ -1921,10 +1922,10 @@ func_type_comment: # From here on, there are rules for invalid syntax with specialised error messages invalid_arguments[NoReturn]: - | a=args ',' '*' { - self.raise_syntax_error_known_location( + | ((','.(starred_expression | ( assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs) a=',' ','.(starred_expression !'=')+ { + self.raise_syntax_error_starting_from( "iterable argument unpacking follows keyword argument unpacking", - a[1][-1] if a[1] else a[0][-1], + a, ) } | a=expression b=for_if_clauses ',' [args | expression for_if_clauses] { diff --git a/src/pegen/parser_generator.py b/src/pegen/parser_generator.py index 934e4eb..7764900 100644 --- a/src/pegen/parser_generator.py +++ b/src/pegen/parser_generator.py @@ -113,7 +113,7 @@ def collect_todo(self) -> None: self.todo[rulename].collect_todo(self) done = set(alltodo) - def artifical_rule_from_rhs(self, rhs: Rhs) -> str: + def artificial_rule_from_rhs(self, rhs: Rhs) -> str: self.counter += 1 name = f"_tmp_{self.counter}" # TODO: Pick a nicer name. self.todo[name] = Rule(name, None, rhs) @@ -129,7 +129,7 @@ def artificial_rule_from_repeat(self, node: Plain, is_repeat1: bool) -> str: self.todo[name] = Rule(name, None, Rhs([Alt([NamedItem(None, node)])])) return name - def artifical_rule_from_gather(self, node: Gather) -> str: + def artificial_rule_from_gather(self, node: Gather) -> str: self.counter += 1 name = f"_gather_{self.counter}" self.counter += 1 diff --git a/src/pegen/python_generator.py b/src/pegen/python_generator.py index 28b450f..05c9959 100644 --- a/src/pegen/python_generator.py +++ b/src/pegen/python_generator.py @@ -134,7 +134,7 @@ def visit_Rhs(self, node: Rhs) -> Tuple[Optional[str], str]: if len(node.alts) == 1 and len(node.alts[0].items) == 1: self.cache[node] = self.visit(node.alts[0].items[0]) else: - name = self.gen.artifical_rule_from_rhs(node) + name = self.gen.artificial_rule_from_rhs(node) self.cache[node] = name, f"self.{name}()" return self.cache[node] @@ -186,7 +186,7 @@ def visit_Repeat1(self, node: Repeat1) -> Tuple[str, str]: def visit_Gather(self, node: Gather) -> Tuple[str, str]: if node in self.cache: return self.cache[node] - name = self.gen.artifical_rule_from_gather(node) + name = self.gen.artificial_rule_from_gather(node) self.cache[node] = name, f"self.{name}()" # No trailing comma here either! return self.cache[node] diff --git a/tests/python_parser/conftest.py b/tests/python_parser/conftest.py index 00b8a00..bfa7ec9 100644 --- a/tests/python_parser/conftest.py +++ b/tests/python_parser/conftest.py @@ -1,4 +1,5 @@ """"Conftest for pure python parser.""" + from pathlib import Path import pytest diff --git a/tests/python_parser/test_ast_parsing.py b/tests/python_parser/test_ast_parsing.py index 6646df5..59cb868 100644 --- a/tests/python_parser/test_ast_parsing.py +++ b/tests/python_parser/test_ast_parsing.py @@ -1,4 +1,5 @@ """Test pure Python parser against cpython parser.""" + import ast import difflib import io diff --git a/tests/python_parser/test_syntax_error_handling.py b/tests/python_parser/test_syntax_error_handling.py index f794f47..8581c90 100644 --- a/tests/python_parser/test_syntax_error_handling.py +++ b/tests/python_parser/test_syntax_error_handling.py @@ -1,4 +1,5 @@ """Test syntax errors for cases where the parser can generate helpful messages.""" + import sys import pytest @@ -93,57 +94,71 @@ def parse_invalid_syntax( [ ( "f'a = {}'", - "valid expression required before '}'" - if sys.version_info >= (3, 12) - else "f-string: empty expression not allowed", + ( + "valid expression required before '}'" + if sys.version_info >= (3, 12) + else "f-string: empty expression not allowed" + ), (1, 8) if sys.version_info >= (3, 12) else None, (1, 9) if sys.version_info >= (3, 12) else None, ), ( "f'a = {=}'", - "expression required before '='" - if sys.version_info >= (3, 11) - else "f-string: empty expression not allowed", + ( + "expression required before '='" + if sys.version_info >= (3, 11) + else "f-string: empty expression not allowed" + ), (1, 8) if sys.version_info >= (3, 12) else None, (1, 9) if sys.version_info >= (3, 12) else None, ), ( "f'a = {!}'", - "expression required before '!'" - if sys.version_info >= (3, 11) - else "f-string: empty expression not allowed", + ( + "expression required before '!'" + if sys.version_info >= (3, 11) + else "f-string: empty expression not allowed" + ), (1, 8) if sys.version_info >= (3, 12) else None, (1, 9) if sys.version_info >= (3, 12) else None, ), ( "f'a = {:}'", - "expression required before ':'" - if sys.version_info >= (3, 11) - else "f-string: empty expression not allowed", + ( + "expression required before ':'" + if sys.version_info >= (3, 11) + else "f-string: empty expression not allowed" + ), (1, 8) if sys.version_info >= (3, 12) else None, (1, 9) if sys.version_info >= (3, 12) else (1, 11), ), ( "f'a = {a=d}'", - "expecting '!', or ':', or '}'" - if sys.version_info >= (3, 12) - else "f-string: expecting '}'", + ( + "expecting '!', or ':', or '}'" + if sys.version_info >= (3, 12) + else "f-string: expecting '}'" + ), (1, 10) if sys.version_info >= (3, 12) else None, (1, 11) if sys.version_info >= (3, 12) else None, ), ( "f'a = { 1 + }'", - "f-string: expecting '=', or '!', or ':', or '}'" - if sys.version_info >= (3, 12) - else "f-string: invalid syntax", + ( + "f-string: expecting '=', or '!', or ':', or '}'" + if sys.version_info >= (3, 12) + else "f-string: invalid syntax" + ), (1, 11) if sys.version_info >= (3, 12) else (1, 7), (1, 12) if sys.version_info >= (3, 12) else (1, 8), ), ( "(\n\t'b'\n\tf'a = { 1 + }'\n)", - "f-string: expecting '=', or '!', or ':', or '}'" - if sys.version_info >= (3, 12) - else "f-string: invalid syntax", + ( + "f-string: expecting '=', or '!', or ':', or '}'" + if sys.version_info >= (3, 12) + else "f-string: invalid syntax" + ), (3, 12) if sys.version_info >= (3, 12) else (3, 7), (3, 13) if sys.version_info >= (3, 12) else (3, 8), ), @@ -210,8 +225,8 @@ def test_invalid_statements( ( "f(**a, *b)", "iterable argument unpacking follows keyword argument unpacking", - (1, 3), - (1, 6), + (1, 6) if sys.version_info >= (3, 12) else None, + (1, 10) if sys.version_info >= (3, 12) else None, ), # NOTE CPython bug, should report 15 as expected (we use None to omit the check) ("f(a for a in b, c)", "Generator expression must be parenthesized", (1, 3), (1, None)), diff --git a/tests/python_parser/test_unsupported_syntax.py b/tests/python_parser/test_unsupported_syntax.py index d2f6d2e..1201688 100644 --- a/tests/python_parser/test_unsupported_syntax.py +++ b/tests/python_parser/test_unsupported_syntax.py @@ -4,6 +4,7 @@ not broader since we would not be able to generate the proper ast nodes. """ + import io import tokenize