Skip to content

Commit f5c1058

Browse files
gh-153171: Produce specialized syntax errors in more cases
Report "'not' after an operator must be parenthesized" also for 'not' after shift, bitwise, comparison and power operators (each operator tier gets its own error rule; a single rule matching bare 'not' at the operand position would misfire when a valid leading 'not' expression is followed by garbage). Report "expected default value expression" also for a missing parameter default value before ':' in lambda expressions and for a missing type parameter default value (previously the latter reported a misleading "expected '('"). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1034e73 commit f5c1058

4 files changed

Lines changed: 1153 additions & 409 deletions

File tree

Grammar/python.gram

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ inversion[expr_ty] (memo):
783783
# --------------------
784784

785785
comparison[expr_ty]:
786+
| invalid_comparison
786787
| a=bitwise_or b=compare_op_bitwise_or_pair+ {
787788
_PyAST_Compare(
788789
a,
@@ -825,6 +826,7 @@ bitwise_or[expr_ty]:
825826

826827
bitwise_xor[expr_ty]:
827828
| a=bitwise_xor '^' b=bitwise_and { _PyAST_BinOp(a, BitXor, b, EXTRA) }
829+
| invalid_bitwise_xor
828830
| bitwise_and
829831

830832
bitwise_and[expr_ty]:
@@ -835,6 +837,7 @@ bitwise_and[expr_ty]:
835837
shift_expr[expr_ty]:
836838
| a=shift_expr '<<' b=sum { _PyAST_BinOp(a, LShift, b, EXTRA) }
837839
| a=shift_expr '>>' b=sum { _PyAST_BinOp(a, RShift, b, EXTRA) }
840+
| invalid_shift_expr
838841
| sum
839842

840843
# Arithmetic operators
@@ -863,6 +866,7 @@ factor[expr_ty] (memo):
863866

864867
power[expr_ty]:
865868
| a=await_primary '**' b=factor { _PyAST_BinOp(a, Pow, b, EXTRA) }
869+
| invalid_power
866870
| await_primary
867871

868872
# Primary elements
@@ -1373,7 +1377,7 @@ invalid_parameters:
13731377
| param_maybe_default+ '/' a='*' {
13741378
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "expected comma between / and *") }
13751379
invalid_default:
1376-
| a='=' &(')'|',') { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "expected default value expression") }
1380+
| a='=' &(')'|','|':') { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "expected default value expression") }
13771381
invalid_star_etc:
13781382
| a='*' (')' | ',' (')' | '**')) { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "named parameters must follow bare *") }
13791383
| '*' ',' TYPE_COMMENT { RAISE_SYNTAX_ERROR("bare * has associated type comment") }
@@ -1640,21 +1644,33 @@ invalid_arithmetic:
16401644
| sum ('+'|'-'|'*'|'/'|'%'|'//'|'@') a='not' b=inversion { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "'not' after an operator must be parenthesized") }
16411645
invalid_factor:
16421646
| ('+' | '-' | '~') a='not' b=factor { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "'not' after an operator must be parenthesized") }
1647+
invalid_comparison:
1648+
| bitwise_or ('=='|'!='|'<='|'<'|'>='|'>') a='not' b=inversion { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "'not' after an operator must be parenthesized") }
1649+
invalid_bitwise_xor:
1650+
| bitwise_xor '^' a='not' b=inversion { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "'not' after an operator must be parenthesized") }
1651+
invalid_shift_expr:
1652+
| shift_expr ('<<'|'>>') a='not' b=inversion { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "'not' after an operator must be parenthesized") }
1653+
invalid_power:
1654+
| await_primary '**' a='not' b=inversion { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "'not' after an operator must be parenthesized") }
16431655

16441656
invalid_type_params:
1657+
| '[' ','.type_param+ a='=' &(']' | ',') {
1658+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "expected default value expression") }
16451659
| '[' token=']' {
16461660
RAISE_SYNTAX_ERROR_STARTING_FROM(
16471661
token,
16481662
"Type parameter list cannot be empty")}
16491663

16501664
invalid_bitwise_and:
1665+
| bitwise_and '&' a='not' b=inversion { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "'not' after an operator must be parenthesized") }
16511666
| a=bitwise_and b='&' c='&' {
16521667
_PyPegen_tokens_are_adjacent(b, c)
16531668
? RAISE_SYNTAX_ERROR_KNOWN_RANGE(b, c, "invalid syntax. Maybe you meant 'and' or '&' instead of '&&'?")
16541669
: NULL
16551670
}
16561671

16571672
invalid_bitwise_or:
1673+
| bitwise_or '|' a='not' b=inversion { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "'not' after an operator must be parenthesized") }
16581674
| a=bitwise_or b='|' c='|' {
16591675
_PyPegen_tokens_are_adjacent(b, c)
16601676
? RAISE_SYNTAX_ERROR_KNOWN_RANGE(b, c, "invalid syntax. Maybe you meant 'or' or '|' instead of '||'?")

Lib/test/test_syntax.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,10 @@
657657
Traceback (most recent call last):
658658
SyntaxError: expected default value expression
659659
660+
>>> lambda a,d=: None
661+
Traceback (most recent call last):
662+
SyntaxError: expected default value expression
663+
660664
>>> lambda a,d=3,c: None
661665
Traceback (most recent call last):
662666
SyntaxError: parameter without a default follows parameter with a default
@@ -2284,6 +2288,38 @@
22842288
Traceback (most recent call last):
22852289
SyntaxError: 'not' after an operator must be parenthesized
22862290
2291+
>>> 1 << not 2
2292+
Traceback (most recent call last):
2293+
SyntaxError: 'not' after an operator must be parenthesized
2294+
2295+
>>> 1 >> not 2
2296+
Traceback (most recent call last):
2297+
SyntaxError: 'not' after an operator must be parenthesized
2298+
2299+
>>> 1 & not 2
2300+
Traceback (most recent call last):
2301+
SyntaxError: 'not' after an operator must be parenthesized
2302+
2303+
>>> 1 ^ not 2
2304+
Traceback (most recent call last):
2305+
SyntaxError: 'not' after an operator must be parenthesized
2306+
2307+
>>> 1 | not 2
2308+
Traceback (most recent call last):
2309+
SyntaxError: 'not' after an operator must be parenthesized
2310+
2311+
>>> 1 < not 2
2312+
Traceback (most recent call last):
2313+
SyntaxError: 'not' after an operator must be parenthesized
2314+
2315+
>>> 1 == not 2
2316+
Traceback (most recent call last):
2317+
SyntaxError: 'not' after an operator must be parenthesized
2318+
2319+
>>> 2 ** not 2
2320+
Traceback (most recent call last):
2321+
SyntaxError: 'not' after an operator must be parenthesized
2322+
22872323
# Check that we don't introduce misleading errors
22882324
>>> not 1 */ 2
22892325
Traceback (most recent call last):
@@ -2588,6 +2624,26 @@ def f(x: *b)
25882624
...
25892625
SyntaxError: Type parameter list cannot be empty
25902626
2627+
>>> type A[T=] = int
2628+
Traceback (most recent call last):
2629+
...
2630+
SyntaxError: expected default value expression
2631+
2632+
>>> def f[T: int =](): ...
2633+
Traceback (most recent call last):
2634+
...
2635+
SyntaxError: expected default value expression
2636+
2637+
>>> class A[*T=]: ...
2638+
Traceback (most recent call last):
2639+
...
2640+
SyntaxError: expected default value expression
2641+
2642+
>>> class A[**T=, U]: ...
2643+
Traceback (most recent call last):
2644+
...
2645+
SyntaxError: expected default value expression
2646+
25912647
>>> class A[]: ...
25922648
Traceback (most recent call last):
25932649
...
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Produce specialized syntax error messages in more cases
2+
where a generic or unrelated error was previously reported:
3+
for ``not`` after shift, bitwise, comparison and power operators
4+
(such as ``1 << not x``),
5+
for a missing parameter default value in lambda expressions
6+
(such as ``lambda x=: 0``),
7+
and for a missing type parameter default value
8+
(such as ``def f[T=](): pass``).

0 commit comments

Comments
 (0)