Skip to content

Commit a15c9b3

Browse files
authored
bpo-40334: Always show the caret on SyntaxErrors (GH-20050)
This commit fixes SyntaxError locations when the caret is not displayed, by doing the following: - `col_number` always gets set to the location of the offending node/expr. When no caret is to be displayed, this gets achieved by setting the object holding the error line to None. - Introduce a new function `_PyPegen_raise_error_known_location`, which can be called, when an arbitrary `lineno`/`col_offset` needs to be passed. This function then gets used in the grammar (through some new macros and inline functions) so that SyntaxError locations of the new parser match that of the old.
1 parent de92769 commit a15c9b3

File tree

5 files changed

+293
-192
lines changed

5 files changed

+293
-192
lines changed

Grammar/python.gram

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -623,26 +623,31 @@ t_atom[expr_ty]:
623623
# From here on, there are rules for invalid syntax with specialised error messages
624624
incorrect_arguments:
625625
| args ',' '*' { RAISE_SYNTAX_ERROR("iterable argument unpacking follows keyword argument unpacking") }
626-
| expression for_if_clauses ',' [args | expression for_if_clauses] {
627-
RAISE_SYNTAX_ERROR("Generator expression must be parenthesized") }
626+
| a=expression for_if_clauses ',' [args | expression for_if_clauses] {
627+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "Generator expression must be parenthesized") }
628628
| a=args ',' args { _PyPegen_arguments_parsing_error(p, a) }
629629
invalid_kwarg:
630-
| expression '=' { RAISE_SYNTAX_ERROR("expression cannot contain assignment, perhaps you meant \"==\"?") }
630+
| a=expression '=' {
631+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
632+
a, "expression cannot contain assignment, perhaps you meant \"==\"?") }
631633
invalid_named_expression:
632634
| a=expression ':=' expression {
633-
RAISE_SYNTAX_ERROR("cannot use assignment expressions with %s", _PyPegen_get_expr_name(a)) }
635+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
636+
a, "cannot use assignment expressions with %s", _PyPegen_get_expr_name(a)) }
634637
invalid_assignment:
635-
| list ':' { RAISE_SYNTAX_ERROR("only single target (not list) can be annotated") }
636-
| tuple ':' { RAISE_SYNTAX_ERROR("only single target (not tuple) can be annotated") }
637-
| expression ':' expression ['=' annotated_rhs] {
638-
RAISE_SYNTAX_ERROR("illegal target for annotation") }
638+
| a=list ':' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "only single target (not list) can be annotated") }
639+
| a=tuple ':' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "only single target (not tuple) can be annotated") }
640+
| a=star_named_expression ',' star_named_expressions* ':' {
641+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "only single target (not tuple) can be annotated") }
642+
| a=expression ':' expression ['=' annotated_rhs] {
643+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "illegal target for annotation") }
639644
| a=expression ('=' | augassign) (yield_expr | star_expressions) {
640-
RAISE_SYNTAX_ERROR_NO_COL_OFFSET("cannot assign to %s", _PyPegen_get_expr_name(a)) }
645+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "cannot assign to %s", _PyPegen_get_expr_name(a)) }
641646
invalid_block:
642647
| NEWLINE !INDENT { RAISE_INDENTATION_ERROR("expected an indented block") }
643648
invalid_comprehension:
644-
| ('[' | '(' | '{') '*' expression for_if_clauses {
645-
RAISE_SYNTAX_ERROR("iterable unpacking cannot be used in comprehension") }
649+
| ('[' | '(' | '{') a=starred_expression for_if_clauses {
650+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "iterable unpacking cannot be used in comprehension") }
646651
invalid_parameters:
647652
| param_no_default* (slash_with_default | param_with_default+) param_no_default {
648653
RAISE_SYNTAX_ERROR("non-default argument follows default argument") }
@@ -655,4 +660,4 @@ invalid_double_type_comments:
655660
RAISE_SYNTAX_ERROR("Cannot have two type comments on def") }
656661
invalid_del_target:
657662
| a=star_expression &del_target_end {
658-
RAISE_SYNTAX_ERROR("cannot delete %s", _PyPegen_get_expr_name(a)) }
663+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "cannot delete %s", _PyPegen_get_expr_name(a)) }

Lib/test/test_exceptions.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -242,16 +242,13 @@ def baz():
242242
check('from __future__ import doesnt_exist', 1, 1)
243243
check('from __future__ import braces', 1, 1)
244244
check('x=1\nfrom __future__ import division', 2, 1)
245-
check('(yield i) = 2', 1, 1)
245+
check('foo(1=2)', 1, 5)
246+
check('def f():\n x, y: int', 2, 3)
247+
check('[*x for x in xs]', 1, 2)
248+
check('foo(x for x in range(10), 100)', 1, 5)
249+
check('(yield i) = 2', 1, 1 if support.use_old_parser() else 2)
246250
check('def f(*):\n pass', 1, 7 if support.use_old_parser() else 8)
247-
check('foo(1=2)', 1, 5 if support.use_old_parser() else 6)
248-
249-
@support.skip_if_new_parser("Pegen column offsets might be different")
250-
def testSyntaxErrorOffsetCustom(self):
251-
self.check('for 1 in []: pass', 1, 5)
252-
self.check('[*x for x in xs]', 1, 2)
253-
self.check('def f():\n x, y: int', 2, 3)
254-
self.check('foo(x for x in range(10), 100)', 1, 5)
251+
check('for 1 in []: pass', 1, 5 if support.use_old_parser() else 7)
255252

256253
@cpython_only
257254
def testSettingException(self):

0 commit comments

Comments
 (0)