bpo-43797: Improve syntax error for invalid comparisons (#25317)

* bpo-43797: Improve syntax error for invalid comparisons

* Update Lib/test/test_fstring.py

Co-authored-by: Guido van Rossum <gvanrossum@gmail.com>

* Apply review comments

* can't -> cannot

Co-authored-by: Guido van Rossum <gvanrossum@gmail.com>
diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py
index 8f12b1e..2e0b5f7 100644
--- a/Lib/test/test_cmd_line_script.py
+++ b/Lib/test/test_cmd_line_script.py
@@ -600,8 +600,8 @@ def test_syntaxerror_unindented_caret_position(self):
             script_name = _make_test_script(script_dir, 'script', script)
             exitcode, stdout, stderr = assert_python_failure(script_name)
             text = io.TextIOWrapper(io.BytesIO(stderr), 'ascii').read()
-            # Confirm that the caret is located under the first 1 character
-            self.assertIn("\n    1 + 1 = 2\n    ^", text)
+            # Confirm that the caret is located under the '=' sign
+            self.assertIn("\n    1 + 1 = 2\n          ^\n", text)
 
     def test_syntaxerror_indented_caret_position(self):
         script = textwrap.dedent("""\
@@ -613,7 +613,7 @@ def test_syntaxerror_indented_caret_position(self):
             exitcode, stdout, stderr = assert_python_failure(script_name)
             text = io.TextIOWrapper(io.BytesIO(stderr), 'ascii').read()
             # Confirm that the caret is located under the first 1 character
-            self.assertIn("\n    1 + 1 = 2\n    ^", text)
+            self.assertIn("\n    1 + 1 = 2\n          ^\n", text)
 
             # Try the same with a form feed at the start of the indented line
             script = (
@@ -624,7 +624,7 @@ def test_syntaxerror_indented_caret_position(self):
             exitcode, stdout, stderr = assert_python_failure(script_name)
             text = io.TextIOWrapper(io.BytesIO(stderr), "ascii").read()
             self.assertNotIn("\f", text)
-            self.assertIn("\n    1 + 1 = 2\n    ^", text)
+            self.assertIn("\n    1 + 1 = 2\n          ^\n", text)
 
     def test_syntaxerror_multi_line_fstring(self):
         script = 'foo = f"""{}\nfoo"""\n'
diff --git a/Lib/test/test_codeop.py b/Lib/test/test_codeop.py
index ecc46af..07b46af 100644
--- a/Lib/test/test_codeop.py
+++ b/Lib/test/test_codeop.py
@@ -275,7 +275,6 @@ def test_invalid(self):
         ai("a = 'a\\\n")
 
         ai("a = 1","eval")
-        ai("a = (","eval")
         ai("]","eval")
         ai("())","eval")
         ai("[}","eval")
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index 1e6f525..9dc3a81 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -260,7 +260,7 @@ def baz():
         check('[*x for x in xs]', 1, 2)
         check('foo(x for x in range(10), 100)', 1, 5)
         check('for 1 in []: pass', 1, 5)
-        check('(yield i) = 2', 1, 2)
+        check('(yield i) = 2', 1, 11)
         check('def f(*):\n  pass', 1, 8)
 
     @cpython_only
diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py
index 296cb05..caae1b7 100644
--- a/Lib/test/test_fstring.py
+++ b/Lib/test/test_fstring.py
@@ -990,7 +990,7 @@ def test_conversions(self):
                              ])
 
     def test_assignment(self):
-        self.assertAllRaise(SyntaxError, 'invalid syntax',
+        self.assertAllRaise(SyntaxError, r'invalid syntax',
                             ["f'' = 3",
                              "f'{0}' = x",
                              "f'{x}' = x",
@@ -1276,11 +1276,11 @@ def test_with_an_underscore_and_a_comma_in_format_specifier(self):
             f'{1:_,}'
 
     def test_syntax_error_for_starred_expressions(self):
-        error_msg = re.escape("can't use starred expression here")
+        error_msg = re.escape("cannot use starred expression here")
         with self.assertRaisesRegex(SyntaxError, error_msg):
             compile("f'{*a}'", "?", "exec")
 
-        error_msg = re.escape("can't use double starred expression here")
+        error_msg = re.escape("cannot use double starred expression here")
         with self.assertRaisesRegex(SyntaxError, error_msg):
             compile("f'{**a}'", "?", "exec")
 
diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py
index a634ccd..ebf8bb7 100644
--- a/Lib/test/test_generators.py
+++ b/Lib/test/test_generators.py
@@ -2013,7 +2013,7 @@ def printsolution(self, x):
 >>> def f(): (yield bar) = y
 Traceback (most recent call last):
   ...
-SyntaxError: cannot assign to yield expression
+SyntaxError: cannot assign to yield expression here. Maybe you meant '==' instead of '='?
 
 >>> def f(): (yield bar) += y
 Traceback (most recent call last):
diff --git a/Lib/test/test_genexps.py b/Lib/test/test_genexps.py
index 5c1a209..70fe2bb 100644
--- a/Lib/test/test_genexps.py
+++ b/Lib/test/test_genexps.py
@@ -103,7 +103,7 @@
     >>> dict(a = i for i in range(10))
     Traceback (most recent call last):
        ...
-    SyntaxError: invalid syntax
+    SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
 
 Verify that parenthesis are required when used as a keyword argument value
 
diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py
index 730c297..78b11c9 100644
--- a/Lib/test/test_syntax.py
+++ b/Lib/test/test_syntax.py
@@ -61,7 +61,7 @@
 
 >>> f() = 1
 Traceback (most recent call last):
-SyntaxError: cannot assign to function call
+SyntaxError: cannot assign to function call here. Maybe you meant '==' instead of '='?
 
 >>> yield = 1
 Traceback (most recent call last):
@@ -73,7 +73,7 @@
 
 >>> a + 1 = 2
 Traceback (most recent call last):
-SyntaxError: cannot assign to operator
+SyntaxError: cannot assign to expression here. Maybe you meant '==' instead of '='?
 
 >>> (x for x in x) = 1
 Traceback (most recent call last):
@@ -81,19 +81,19 @@
 
 >>> 1 = 1
 Traceback (most recent call last):
-SyntaxError: cannot assign to literal
+SyntaxError: cannot assign to literal here. Maybe you meant '==' instead of '='?
 
 >>> "abc" = 1
 Traceback (most recent call last):
-SyntaxError: cannot assign to literal
+SyntaxError: cannot assign to literal here. Maybe you meant '==' instead of '='?
 
 >>> b"" = 1
 Traceback (most recent call last):
-SyntaxError: cannot assign to literal
+SyntaxError: cannot assign to literal here. Maybe you meant '==' instead of '='?
 
 >>> ... = 1
 Traceback (most recent call last):
-SyntaxError: cannot assign to Ellipsis
+SyntaxError: cannot assign to Ellipsis here. Maybe you meant '==' instead of '='?
 
 >>> `1` = 1
 Traceback (most recent call last):
@@ -126,15 +126,15 @@
 
 >>> [a, b, c + 1] = [1, 2, 3]
 Traceback (most recent call last):
-SyntaxError: cannot assign to operator
+SyntaxError: cannot assign to expression
 
 >>> [a, b[1], c + 1] = [1, 2, 3]
 Traceback (most recent call last):
-SyntaxError: cannot assign to operator
+SyntaxError: cannot assign to expression
 
 >>> [a, b.c.d, c + 1] = [1, 2, 3]
 Traceback (most recent call last):
-SyntaxError: cannot assign to operator
+SyntaxError: cannot assign to expression
 
 >>> a if 1 else b = 1
 Traceback (most recent call last):
@@ -181,7 +181,7 @@
 
 >>> for (*a, b, c+1) in b: pass
 Traceback (most recent call last):
-SyntaxError: cannot assign to operator
+SyntaxError: cannot assign to expression
 
 >>> for (x, *(y, z.d())) in b: pass
 Traceback (most recent call last):
@@ -193,7 +193,7 @@
 
 >>> for a, b, (c + 1, d()): pass
 Traceback (most recent call last):
-SyntaxError: cannot assign to operator
+SyntaxError: cannot assign to expression
 
 >>> for i < (): pass
 Traceback (most recent call last):
@@ -217,7 +217,7 @@
 
 >>> with a as (*b, c, d+1): pass
 Traceback (most recent call last):
-SyntaxError: cannot assign to operator
+SyntaxError: cannot assign to expression
 
 >>> with a as (x, *(y, z.d())): pass
 Traceback (most recent call last):
@@ -465,7 +465,7 @@
 # SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
 # >>> f(True=2)
 # Traceback (most recent call last):
-# SyntaxError: cannot assign to True
+# SyntaxError: cannot assign to True here. Maybe you meant '==' instead of '='?
 >>> f(__debug__=1)
 Traceback (most recent call last):
 SyntaxError: cannot assign to __debug__
@@ -684,7 +684,7 @@
    ...   pass
    Traceback (most recent call last):
      ...
-   SyntaxError: cannot assign to function call
+   SyntaxError: cannot assign to function call here. Maybe you meant '==' instead of '='?
 
    >>> if 1:
    ...   pass
@@ -692,7 +692,7 @@
    ...   x() = 1
    Traceback (most recent call last):
      ...
-   SyntaxError: cannot assign to function call
+   SyntaxError: cannot assign to function call here. Maybe you meant '==' instead of '='?
 
    >>> if 1:
    ...   x() = 1
@@ -702,7 +702,7 @@
    ...   pass
    Traceback (most recent call last):
      ...
-   SyntaxError: cannot assign to function call
+   SyntaxError: cannot assign to function call here. Maybe you meant '==' instead of '='?
 
    >>> if 1:
    ...   pass
@@ -712,7 +712,7 @@
    ...   pass
    Traceback (most recent call last):
      ...
-   SyntaxError: cannot assign to function call
+   SyntaxError: cannot assign to function call here. Maybe you meant '==' instead of '='?
 
    >>> if 1:
    ...   pass
@@ -722,7 +722,7 @@
    ...   x() = 1
    Traceback (most recent call last):
      ...
-   SyntaxError: cannot assign to function call
+   SyntaxError: cannot assign to function call here. Maybe you meant '==' instead of '='?
 
     Missing ':' before suites:
 
@@ -843,6 +843,26 @@
    Traceback (most recent call last):
    SyntaxError: expected ':'
 
+   >>> if x = 3:
+   ...    pass
+   Traceback (most recent call last):
+   SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
+
+   >>> while x = 3:
+   ...    pass
+   Traceback (most recent call last):
+   SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
+
+   >>> if x.a = 3:
+   ...    pass
+   Traceback (most recent call last):
+   SyntaxError: cannot assign to attribute here. Maybe you meant '==' instead of '='?
+
+   >>> while x.a = 3:
+   ...    pass
+   Traceback (most recent call last):
+   SyntaxError: cannot assign to attribute here. Maybe you meant '==' instead of '='?
+
 Make sure that the old "raise X, Y[, Z]" form is gone:
    >>> raise X, Y
    Traceback (most recent call last):
@@ -894,19 +914,19 @@
 
 >>> {1, 2, 3} = 42
 Traceback (most recent call last):
-SyntaxError: cannot assign to set display
+SyntaxError: cannot assign to set display here. Maybe you meant '==' instead of '='?
 
 >>> {1: 2, 3: 4} = 42
 Traceback (most recent call last):
-SyntaxError: cannot assign to dict display
+SyntaxError: cannot assign to dict literal here. Maybe you meant '==' instead of '='?
 
 >>> f'{x}' = 42
 Traceback (most recent call last):
-SyntaxError: cannot assign to f-string expression
+SyntaxError: cannot assign to f-string expression here. Maybe you meant '==' instead of '='?
 
 >>> f'{x}-{y}' = 42
 Traceback (most recent call last):
-SyntaxError: cannot assign to f-string expression
+SyntaxError: cannot assign to f-string expression here. Maybe you meant '==' instead of '='?
 
 >>> from t import x,
 Traceback (most recent call last):
@@ -988,7 +1008,7 @@ def _check_error(self, code, errtext,
     def test_expression_with_assignment(self):
         self._check_error(
             "print(end1 + end2 = ' ')",
-            'expression cannot contain assignment, perhaps you meant "=="?',
+            "cannot assign to expression here. Maybe you meant '==' instead of '='?",
             offset=19
         )
 
@@ -1000,31 +1020,31 @@ def test_assign_call(self):
 
     def test_assign_del(self):
         self._check_error("del (,)", "invalid syntax")
-        self._check_error("del 1", "delete literal")
-        self._check_error("del (1, 2)", "delete literal")
-        self._check_error("del None", "delete None")
-        self._check_error("del *x", "delete starred")
-        self._check_error("del (*x)", "use starred expression")
-        self._check_error("del (*x,)", "delete starred")
-        self._check_error("del [*x,]", "delete starred")
-        self._check_error("del f()", "delete function call")
-        self._check_error("del f(a, b)", "delete function call")
-        self._check_error("del o.f()", "delete function call")
-        self._check_error("del a[0]()", "delete function call")
-        self._check_error("del x, f()", "delete function call")
-        self._check_error("del f(), x", "delete function call")
-        self._check_error("del [a, b, ((c), (d,), e.f())]", "delete function call")
-        self._check_error("del (a if True else b)", "delete conditional")
-        self._check_error("del +a", "delete operator")
-        self._check_error("del a, +b", "delete operator")
-        self._check_error("del a + b", "delete operator")
-        self._check_error("del (a + b, c)", "delete operator")
-        self._check_error("del (c[0], a + b)", "delete operator")
-        self._check_error("del a.b.c + 2", "delete operator")
-        self._check_error("del a.b.c[0] + 2", "delete operator")
-        self._check_error("del (a, b, (c, d.e.f + 2))", "delete operator")
-        self._check_error("del [a, b, (c, d.e.f[0] + 2)]", "delete operator")
-        self._check_error("del (a := 5)", "delete named expression")
+        self._check_error("del 1", "cannot delete literal")
+        self._check_error("del (1, 2)", "cannot delete literal")
+        self._check_error("del None", "cannot delete None")
+        self._check_error("del *x", "cannot delete starred")
+        self._check_error("del (*x)", "cannot use starred expression")
+        self._check_error("del (*x,)", "cannot delete starred")
+        self._check_error("del [*x,]", "cannot delete starred")
+        self._check_error("del f()", "cannot delete function call")
+        self._check_error("del f(a, b)", "cannot delete function call")
+        self._check_error("del o.f()", "cannot delete function call")
+        self._check_error("del a[0]()", "cannot delete function call")
+        self._check_error("del x, f()", "cannot delete function call")
+        self._check_error("del f(), x", "cannot delete function call")
+        self._check_error("del [a, b, ((c), (d,), e.f())]", "cannot delete function call")
+        self._check_error("del (a if True else b)", "cannot delete conditional")
+        self._check_error("del +a", "cannot delete expression")
+        self._check_error("del a, +b", "cannot delete expression")
+        self._check_error("del a + b", "cannot delete expression")
+        self._check_error("del (a + b, c)", "cannot delete expression")
+        self._check_error("del (c[0], a + b)", "cannot delete expression")
+        self._check_error("del a.b.c + 2", "cannot delete expression")
+        self._check_error("del a.b.c[0] + 2", "cannot delete expression")
+        self._check_error("del (a, b, (c, d.e.f + 2))", "cannot delete expression")
+        self._check_error("del [a, b, (c, d.e.f[0] + 2)]", "cannot delete expression")
+        self._check_error("del (a := 5)", "cannot delete named expression")
         # We don't have a special message for this, but make sure we don't
         # report "cannot delete name"
         self._check_error("del a += b", "invalid syntax")
diff --git a/Lib/test/test_unpack_ex.py b/Lib/test/test_unpack_ex.py
index 049e48b..bd79421 100644
--- a/Lib/test/test_unpack_ex.py
+++ b/Lib/test/test_unpack_ex.py
@@ -349,27 +349,27 @@
     >>> (*x),y = 1, 2 # doctest:+ELLIPSIS
     Traceback (most recent call last):
       ...
-    SyntaxError: can't use starred expression here
+    SyntaxError: cannot use starred expression here
 
     >>> (((*x))),y = 1, 2 # doctest:+ELLIPSIS
     Traceback (most recent call last):
       ...
-    SyntaxError: can't use starred expression here
+    SyntaxError: cannot use starred expression here
 
     >>> z,(*x),y = 1, 2, 4 # doctest:+ELLIPSIS
     Traceback (most recent call last):
       ...
-    SyntaxError: can't use starred expression here
+    SyntaxError: cannot use starred expression here
 
     >>> z,(*x) = 1, 2 # doctest:+ELLIPSIS
     Traceback (most recent call last):
       ...
-    SyntaxError: can't use starred expression here
+    SyntaxError: cannot use starred expression here
 
     >>> ((*x),y) = 1, 2 # doctest:+ELLIPSIS
     Traceback (most recent call last):
       ...
-    SyntaxError: can't use starred expression here
+    SyntaxError: cannot use starred expression here
 
 Some size constraints (all fail.)