Skip to content
22 changes: 22 additions & 0 deletions Grammar/python.gram
Original file line number Diff line number Diff line change
Expand Up @@ -801,8 +801,15 @@ compare_op_bitwise_or_pair[CmpopExprPair*]:
| in_bitwise_or
| isnot_bitwise_or
| is_bitwise_or
| invalid_eqeqeq

eq_bitwise_or[CmpopExprPair*]: '==' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Eq, a) }
invalid_eqeqeq:
| a='==' b='=' {
_PyPegen_tokens_are_adjacent(a, b)
? RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Maybe you meant 'is' instead of '==='?")
: NULL
}
noteq_bitwise_or[CmpopExprPair*]:
| (tok='!=' { _PyPegen_check_barry_as_flufl(p, tok) ? NULL : tok}) a=bitwise_or {_PyPegen_cmpop_expr_pair(p, NotEq, a) }
lte_bitwise_or[CmpopExprPair*]: '<=' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, LtE, a) }
Expand Down Expand Up @@ -1313,6 +1320,21 @@ invalid_assignment:
"'%s' is an illegal expression for augmented assignment",
_PyPegen_get_expr_name(a)
)}
| star_expressions a='=' b='<' {
_PyPegen_tokens_are_adjacent(a, b)
? RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Maybe you meant '<=' instead of '=<'?")
: NULL
}
| star_expressions a='=' b='>' {
_PyPegen_tokens_are_adjacent(a, b)
? RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Maybe you meant '>=' instead of '=>'?")
: NULL
}
| star_expressions a='=' b='!' {
_PyPegen_tokens_are_adjacent(a, b)
? RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Maybe you meant '!=' instead of '=!'?")
: NULL
}
invalid_ann_assign_target[expr_ty]:
| list
| tuple
Expand Down
73 changes: 73 additions & 0 deletions Lib/test/test_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -3505,6 +3505,79 @@ def test_ifexp_body_stmt_else_stmt(self):
]:
self._check_error(f"x = {lhs_stmt} if 1 else {rhs_stmt}", msg)

def test_diamond_operator(self):
self._check_error(
"1<>2",
r'Are you trying to overthrow the SC\? Use operator "!="!',
lineno=1,
end_lineno=1,
offset=2,
end_offset=4,
)
Comment thread
skirpichev marked this conversation as resolved.
self._check_error(
"1 < > 2",
"invalid syntax",
lineno=1,
end_lineno=1,
offset=5,
end_offset=6,
)

def test_diamond_operator_barry_as_flufl(self):
# Under barry_as_FLUFL, '<>' is the valid "not equal" operator
compile(
"from __future__ import barry_as_FLUFL\n1<>2",
"<test>", "exec",
)

def test_triple_equal(self):
self._check_error(
"a === b",
r"Maybe you meant 'is' instead of '==='\?",
lineno=1,
end_lineno=1,
offset=3,
end_offset=6,
)
self._check_error(
"a == = b",
"invalid syntax",
lineno=1,
end_lineno=1,
offset=6,
end_offset=7,
)

def test_eq_lt_typo(self):
self._check_error(
"a =< b",
r"Maybe you meant '<=' instead of '=<'\?",
lineno=1,
end_lineno=1,
offset=3,
end_offset=5,
)

def test_eq_gt_typo(self):
self._check_error(
"a => b",
r"Maybe you meant '>=' instead of '=>'\?",
lineno=1,
end_lineno=1,
offset=3,
end_offset=5,
)

def test_eq_bang_typo(self):
self._check_error(
"a =! b",
r"Maybe you meant '!=' instead of '=!'\?",
lineno=1,
end_lineno=1,
offset=3,
end_offset=5,
)


class LazyImportRestrictionTestCase(SyntaxErrorTestCase):
"""Test syntax restrictions for lazy imports."""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve :exc:`SyntaxError` messages for common operator typos coming from other languages: ``=<``, ``=>``, and ``=!`` now suggest ``<=``, ``>=``, and ``!=`` respectively, ``===`` suggests ``is``, and ``<>`` shows a tailored message suggesting ``!=``.
4 changes: 4 additions & 0 deletions Parser/action_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,10 @@ _PyPegen_check_barry_as_flufl(Parser *p, Token* t) {
return -1;
}
if (!(p->flags & PyPARSE_BARRY_AS_BDFL)) {
if (strcmp(tok_str, "<>") == 0) {
RAISE_SYNTAX_ERROR("invalid syntax. Are you trying to overthrow the SC? Use operator \"!=\"!");
return -1;
}
return strcmp(tok_str, "!=");
}
return 0;
Expand Down
Loading
Loading