Skip to content

Commit 27c0d9b

Browse files
authored
bpo-40334: produce specialized errors for invalid del targets (GH-19911)
1 parent 86d6944 commit 27c0d9b

File tree

4 files changed

+352
-181
lines changed

4 files changed

+352
-181
lines changed

Grammar/python.gram

+10-3
Original file line numberDiff line numberDiff line change
@@ -583,15 +583,19 @@ ann_assign_subscript_attribute_target[expr_ty]:
583583
| a=t_primary '[' b=slices ']' !t_lookahead { _Py_Subscript(a, b, Store, EXTRA) }
584584

585585
del_targets[asdl_seq*]: a=','.del_target+ [','] { a }
586+
# The lookaheads to del_target_end ensure that we don't match expressions where a prefix of the
587+
# expression matches our rule, thereby letting these cases fall through to invalid_del_target.
586588
del_target[expr_ty] (memo):
587-
| a=t_primary '.' b=NAME !t_lookahead { _Py_Attribute(a, b->v.Name.id, Del, EXTRA) }
588-
| a=t_primary '[' b=slices ']' !t_lookahead { _Py_Subscript(a, b, Del, EXTRA) }
589+
| a=t_primary '.' b=NAME &del_target_end { _Py_Attribute(a, b->v.Name.id, Del, EXTRA) }
590+
| a=t_primary '[' b=slices ']' &del_target_end { _Py_Subscript(a, b, Del, EXTRA) }
589591
| del_t_atom
590592
del_t_atom[expr_ty]:
591-
| a=NAME { _PyPegen_set_expr_context(p, a, Del) }
593+
| a=NAME &del_target_end { _PyPegen_set_expr_context(p, a, Del) }
592594
| '(' a=del_target ')' { _PyPegen_set_expr_context(p, a, Del) }
593595
| '(' a=[del_targets] ')' { _Py_Tuple(a, Del, EXTRA) }
594596
| '[' a=[del_targets] ']' { _Py_List(a, Del, EXTRA) }
597+
| invalid_del_target
598+
del_target_end: ')' | ']' | ',' | ';' | NEWLINE
595599

596600
targets[asdl_seq*]: a=','.target+ [','] { a }
597601
target[expr_ty] (memo):
@@ -649,3 +653,6 @@ invalid_lambda_star_etc:
649653
invalid_double_type_comments:
650654
| TYPE_COMMENT NEWLINE TYPE_COMMENT NEWLINE INDENT {
651655
RAISE_SYNTAX_ERROR("Cannot have two type comments on def") }
656+
invalid_del_target:
657+
| a=star_expression &del_target_end {
658+
RAISE_SYNTAX_ERROR("cannot delete %s", _PyPegen_get_expr_name(a)) }

Lib/test/test_grammar.py

+17
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,23 @@ def test_del_stmt(self):
801801
del abc
802802
del x, y, (z, xyz)
803803

804+
x, y, z = "xyz"
805+
del x
806+
del y,
807+
del (z)
808+
del ()
809+
810+
a, b, c, d, e, f, g = "abcdefg"
811+
del a, (b, c), (d, (e, f))
812+
813+
a, b, c, d, e, f, g = "abcdefg"
814+
del a, [b, c], (d, [e, f])
815+
816+
abcd = list("abcd")
817+
del abcd[1:2]
818+
819+
compile("del a, (b[0].c, (d.e, f.g[1:2])), [h.i.j], ()", "<testcase>", "exec")
820+
804821
def test_pass_stmt(self):
805822
# 'pass'
806823
pass

Lib/test/test_syntax.py

+33-8
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,9 @@
6363
Traceback (most recent call last):
6464
SyntaxError: cannot assign to function call
6565
66-
# Pegen does not support this yet
67-
# >>> del f()
68-
# Traceback (most recent call last):
69-
# SyntaxError: cannot delete function call
66+
>>> del f()
67+
Traceback (most recent call last):
68+
SyntaxError: cannot delete function call
7069
7170
>>> a + 1 = 2
7271
Traceback (most recent call last):
@@ -665,7 +664,7 @@ def _check_error(self, code, errtext,
665664
self.fail("SyntaxError is not a %s" % subclass.__name__)
666665
mo = re.search(errtext, str(err))
667666
if mo is None:
668-
self.fail("SyntaxError did not contain '%r'" % (errtext,))
667+
self.fail("SyntaxError did not contain %r" % (errtext,))
669668
self.assertEqual(err.filename, filename)
670669
if lineno is not None:
671670
self.assertEqual(err.lineno, lineno)
@@ -677,10 +676,36 @@ def _check_error(self, code, errtext,
677676
def test_assign_call(self):
678677
self._check_error("f() = 1", "assign")
679678

680-
@support.skip_if_new_parser("Pegen does not produce a specialized error "
681-
"message yet")
682679
def test_assign_del(self):
683-
self._check_error("del f()", "delete")
680+
self._check_error("del (,)", "invalid syntax")
681+
self._check_error("del 1", "delete literal")
682+
self._check_error("del (1, 2)", "delete literal")
683+
self._check_error("del None", "delete None")
684+
self._check_error("del *x", "delete starred")
685+
self._check_error("del (*x)", "delete starred")
686+
self._check_error("del (*x,)", "delete starred")
687+
self._check_error("del [*x,]", "delete starred")
688+
self._check_error("del f()", "delete function call")
689+
self._check_error("del f(a, b)", "delete function call")
690+
self._check_error("del o.f()", "delete function call")
691+
self._check_error("del a[0]()", "delete function call")
692+
self._check_error("del x, f()", "delete function call")
693+
self._check_error("del f(), x", "delete function call")
694+
self._check_error("del [a, b, ((c), (d,), e.f())]", "delete function call")
695+
self._check_error("del (a if True else b)", "delete conditional")
696+
self._check_error("del +a", "delete operator")
697+
self._check_error("del a, +b", "delete operator")
698+
self._check_error("del a + b", "delete operator")
699+
self._check_error("del (a + b, c)", "delete operator")
700+
self._check_error("del (c[0], a + b)", "delete operator")
701+
self._check_error("del a.b.c + 2", "delete operator")
702+
self._check_error("del a.b.c[0] + 2", "delete operator")
703+
self._check_error("del (a, b, (c, d.e.f + 2))", "delete operator")
704+
self._check_error("del [a, b, (c, d.e.f[0] + 2)]", "delete operator")
705+
self._check_error("del (a := 5)", "delete named expression")
706+
# We don't have a special message for this, but make sure we don't
707+
# report "cannot delete name"
708+
self._check_error("del a += b", "invalid syntax")
684709

685710
def test_global_param_err_first(self):
686711
source = """if 1:

0 commit comments

Comments
 (0)