diff options
Diffstat (limited to 'Lib/test')
-rw-r--r-- | Lib/test/test_ast.py | 5 | ||||
-rw-r--r-- | Lib/test/test_cmd_line_script.py | 6 | ||||
-rw-r--r-- | Lib/test/test_eof.py | 4 | ||||
-rw-r--r-- | Lib/test/test_exceptions.py | 3 | ||||
-rw-r--r-- | Lib/test/test_fstring.py | 311 | ||||
-rw-r--r-- | Lib/test/test_tokenize.py | 28 | ||||
-rw-r--r-- | Lib/test/test_type_comments.py | 2 |
7 files changed, 269 insertions, 90 deletions
diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 6c932e1..a579bfd 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -774,11 +774,6 @@ class AST_Tests(unittest.TestCase): ast.parse('with (CtxManager() as example): ...', feature_version=(3, 8)) ast.parse('with CtxManager() as example: ...', feature_version=(3, 8)) - def test_debug_f_string_feature_version(self): - ast.parse('f"{x=}"', feature_version=(3, 8)) - with self.assertRaises(SyntaxError): - ast.parse('f"{x=}"', feature_version=(3, 7)) - def test_assignment_expression_feature_version(self): ast.parse('(x := 0)', feature_version=(3, 8)) with self.assertRaises(SyntaxError): diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index f10d72e..d98e238 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -636,9 +636,9 @@ class CmdLineTest(unittest.TestCase): self.assertEqual( stderr.splitlines()[-3:], [ - b' foo"""', - b' ^', - b'SyntaxError: f-string: empty expression not allowed', + b' foo = f"""{}', + b' ^', + b'SyntaxError: f-string: valid expression required before \'}\'', ], ) diff --git a/Lib/test/test_eof.py b/Lib/test/test_eof.py index abcbf04..be4fd73 100644 --- a/Lib/test/test_eof.py +++ b/Lib/test/test_eof.py @@ -4,6 +4,7 @@ import sys from test import support from test.support import os_helper from test.support import script_helper +from test.support import warnings_helper import unittest class EOFTestCase(unittest.TestCase): @@ -36,10 +37,11 @@ class EOFTestCase(unittest.TestCase): rc, out, err = script_helper.assert_python_failure(file_name) self.assertIn(b'unterminated triple-quoted string literal (detected at line 3)', err) + @warnings_helper.ignore_warnings(category=SyntaxWarning) def test_eof_with_line_continuation(self): expect = "unexpected EOF while parsing (<string>, line 1)" try: - compile('"\\xhh" \\', '<string>', 'exec', dont_inherit=True) + compile('"\\Xhh" \\', '<string>', 'exec') except SyntaxError as msg: self.assertEqual(str(msg), expect) else: diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 684e888..4ef7dec 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -155,6 +155,7 @@ class ExceptionTests(unittest.TestCase): ckmsg(s, "'continue' not properly in loop") ckmsg("continue\n", "'continue' not properly in loop") + ckmsg("f'{6 0}'", "invalid syntax. Perhaps you forgot a comma?") def testSyntaxErrorMissingParens(self): def ckmsg(src, msg, exception=SyntaxError): @@ -227,7 +228,7 @@ class ExceptionTests(unittest.TestCase): check('Python = "\u1e54\xfd\u0163\u0125\xf2\xf1" +', 1, 20) check(b'# -*- coding: cp1251 -*-\nPython = "\xcf\xb3\xf2\xee\xed" +', 2, 19, encoding='cp1251') - check(b'Python = "\xcf\xb3\xf2\xee\xed" +', 1, 18) + check(b'Python = "\xcf\xb3\xf2\xee\xed" +', 1, 10) check('x = "a', 1, 5) check('lambda x: x = 2', 1, 1) check('f{a + b + c}', 1, 2) diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py index b3f6ef4..f571233 100644 --- a/Lib/test/test_fstring.py +++ b/Lib/test/test_fstring.py @@ -329,13 +329,13 @@ non-important content self.assertEqual(t.body[1].lineno, 3) self.assertEqual(t.body[1].value.lineno, 3) self.assertEqual(t.body[1].value.values[0].lineno, 3) - self.assertEqual(t.body[1].value.values[1].lineno, 3) - self.assertEqual(t.body[1].value.values[2].lineno, 3) + self.assertEqual(t.body[1].value.values[1].lineno, 4) + self.assertEqual(t.body[1].value.values[2].lineno, 6) self.assertEqual(t.body[1].col_offset, 0) self.assertEqual(t.body[1].value.col_offset, 0) - self.assertEqual(t.body[1].value.values[0].col_offset, 0) - self.assertEqual(t.body[1].value.values[1].col_offset, 0) - self.assertEqual(t.body[1].value.values[2].col_offset, 0) + self.assertEqual(t.body[1].value.values[0].col_offset, 4) + self.assertEqual(t.body[1].value.values[1].col_offset, 2) + self.assertEqual(t.body[1].value.values[2].col_offset, 11) # NOTE: the following lineno information and col_offset is correct for # expressions within FormattedValues. binop = t.body[1].value.values[1].value @@ -366,13 +366,13 @@ a = f''' self.assertEqual(t.body[0].lineno, 2) self.assertEqual(t.body[0].value.lineno, 2) self.assertEqual(t.body[0].value.values[0].lineno, 2) - self.assertEqual(t.body[0].value.values[1].lineno, 2) - self.assertEqual(t.body[0].value.values[2].lineno, 2) + self.assertEqual(t.body[0].value.values[1].lineno, 3) + self.assertEqual(t.body[0].value.values[2].lineno, 3) self.assertEqual(t.body[0].col_offset, 0) self.assertEqual(t.body[0].value.col_offset, 4) - self.assertEqual(t.body[0].value.values[0].col_offset, 4) - self.assertEqual(t.body[0].value.values[1].col_offset, 4) - self.assertEqual(t.body[0].value.values[2].col_offset, 4) + self.assertEqual(t.body[0].value.values[0].col_offset, 8) + self.assertEqual(t.body[0].value.values[1].col_offset, 10) + self.assertEqual(t.body[0].value.values[2].col_offset, 17) # Check {blech} self.assertEqual(t.body[0].value.values[1].value.lineno, 3) self.assertEqual(t.body[0].value.values[1].value.end_lineno, 3) @@ -387,6 +387,20 @@ x = ( t = ast.parse(expr) self.assertEqual(type(t), ast.Module) self.assertEqual(len(t.body), 1) + # check the joinedstr location + joinedstr = t.body[0].value + self.assertEqual(type(joinedstr), ast.JoinedStr) + self.assertEqual(joinedstr.lineno, 3) + self.assertEqual(joinedstr.end_lineno, 3) + self.assertEqual(joinedstr.col_offset, 4) + self.assertEqual(joinedstr.end_col_offset, 17) + # check the formatted value location + fv = t.body[0].value.values[1] + self.assertEqual(type(fv), ast.FormattedValue) + self.assertEqual(fv.lineno, 3) + self.assertEqual(fv.end_lineno, 3) + self.assertEqual(fv.col_offset, 7) + self.assertEqual(fv.end_col_offset, 16) # check the test(t) location call = t.body[0].value.values[1].value self.assertEqual(type(call), ast.Call) @@ -397,6 +411,50 @@ x = ( expr = """ x = ( + u'wat', + u"wat", + b'wat', + b"wat", + f'wat', + f"wat", +) + +y = ( + u'''wat''', + u\"\"\"wat\"\"\", + b'''wat''', + b\"\"\"wat\"\"\", + f'''wat''', + f\"\"\"wat\"\"\", +) + """ + t = ast.parse(expr) + self.assertEqual(type(t), ast.Module) + self.assertEqual(len(t.body), 2) + x, y = t.body + + # Check the single quoted string offsets first. + offsets = [ + (elt.col_offset, elt.end_col_offset) + for elt in x.value.elts + ] + self.assertTrue(all( + offset == (4, 10) + for offset in offsets + )) + + # Check the triple quoted string offsets. + offsets = [ + (elt.col_offset, elt.end_col_offset) + for elt in y.value.elts + ] + self.assertTrue(all( + offset == (4, 14) + for offset in offsets + )) + + expr = """ +x = ( 'PERL_MM_OPT', ( f'wat' f'some_string={f(x)} ' @@ -415,9 +473,9 @@ x = ( # check the first wat self.assertEqual(type(wat1), ast.Constant) self.assertEqual(wat1.lineno, 4) - self.assertEqual(wat1.end_lineno, 6) - self.assertEqual(wat1.col_offset, 12) - self.assertEqual(wat1.end_col_offset, 18) + self.assertEqual(wat1.end_lineno, 5) + self.assertEqual(wat1.col_offset, 14) + self.assertEqual(wat1.end_col_offset, 26) # check the call call = middle.value self.assertEqual(type(call), ast.Call) @@ -427,10 +485,14 @@ x = ( self.assertEqual(call.end_col_offset, 31) # check the second wat self.assertEqual(type(wat2), ast.Constant) - self.assertEqual(wat2.lineno, 4) + self.assertEqual(wat2.lineno, 5) self.assertEqual(wat2.end_lineno, 6) - self.assertEqual(wat2.col_offset, 12) - self.assertEqual(wat2.end_col_offset, 18) + self.assertEqual(wat2.col_offset, 32) + # wat ends at the offset 17, but the whole f-string + # ends at the offset 18 (since the quote is part of the + # f-string but not the wat string) + self.assertEqual(wat2.end_col_offset, 17) + self.assertEqual(fstring.end_col_offset, 18) def test_docstring(self): def f(): @@ -467,7 +529,7 @@ x = ( self.assertEqual(f' ', ' ') def test_unterminated_string(self): - self.assertAllRaise(SyntaxError, 'f-string: unterminated string', + self.assertAllRaise(SyntaxError, 'unterminated string', [r"""f'{"x'""", r"""f'{"x}'""", r"""f'{("x'""", @@ -475,28 +537,33 @@ x = ( ]) def test_mismatched_parens(self): - self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' " + self.assertAllRaise(SyntaxError, r"closing parenthesis '\}' " r"does not match opening parenthesis '\('", ["f'{((}'", ]) - self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\)' " + self.assertAllRaise(SyntaxError, r"closing parenthesis '\)' " r"does not match opening parenthesis '\['", ["f'{a[4)}'", ]) - self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\]' " + self.assertAllRaise(SyntaxError, r"closing parenthesis '\]' " r"does not match opening parenthesis '\('", ["f'{a(4]}'", ]) - self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' " + self.assertAllRaise(SyntaxError, r"closing parenthesis '\}' " r"does not match opening parenthesis '\['", ["f'{a[4}'", ]) - self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' " + self.assertAllRaise(SyntaxError, r"closing parenthesis '\}' " r"does not match opening parenthesis '\('", ["f'{a(4}'", ]) self.assertRaises(SyntaxError, eval, "f'{" + "("*500 + "}'") + def test_fstring_nested_too_deeply(self): + self.assertAllRaise(SyntaxError, + "f-string: expressions nested too deeply", + ['f"{1+2:{1+2:{1+1:{1}}}}"']) + def test_double_braces(self): self.assertEqual(f'{{', '{') self.assertEqual(f'a{{', 'a{') @@ -559,8 +626,14 @@ x = ( self.assertEqual(f'' '' f'', '') self.assertEqual(f'' '' f'' '', '') - self.assertAllRaise(SyntaxError, "f-string: expecting '}'", - ["f'{3' f'}'", # can't concat to get a valid f-string + # This is not really [f'{'] + [f'}'] since we treat the inside + # of braces as a purely new context, so it is actually f'{ and + # then eval(' f') (a valid expression) and then }' which would + # constitute a valid f-string. + self.assertEqual(f'{' f'}', ' f') + + self.assertAllRaise(SyntaxError, "expecting '}'", + ['''f'{3' f"}"''', # can't concat to get a valid f-string ]) def test_comments(self): @@ -618,25 +691,19 @@ x = ( self.assertEqual(f'{-10:-{"#"}1{0}x}', ' -0xa') self.assertEqual(f'{-10:{"-"}#{1}0{"x"}}', ' -0xa') self.assertEqual(f'{10:#{3 != {4:5} and width}x}', ' 0xa') + self.assertEqual(f'result: {value:{width:{0}}.{precision:1}}', 'result: 12.35') - self.assertAllRaise(SyntaxError, - """f-string: invalid conversion character 'r{"': """ - """expected 's', 'r', or 'a'""", + self.assertAllRaise(SyntaxError, "f-string: expecting ':' or '}'", ["""f'{"s"!r{":10"}}'""", - # This looks like a nested format spec. ]) - self.assertAllRaise(SyntaxError, "f-string: invalid syntax", + self.assertAllRaise(SyntaxError, + "f-string: expecting a valid expression after '{'", [# Invalid syntax inside a nested spec. "f'{4:{/5}}'", ]) - self.assertAllRaise(SyntaxError, "f-string: expressions nested too deeply", - [# Can't nest format specifiers. - "f'result: {value:{width:{0}}.{precision:1}}'", - ]) - self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character', [# No expansion inside conversion or for # the : or ! itself. @@ -655,7 +722,8 @@ x = ( self.assertEqual(f'{x} {x}', '1 2') def test_missing_expression(self): - self.assertAllRaise(SyntaxError, 'f-string: empty expression not allowed', + self.assertAllRaise(SyntaxError, + "f-string: valid expression required before '}'", ["f'{}'", "f'{ }'" "f' {} '", @@ -667,8 +735,8 @@ x = ( "f'''{\t\f\r\n}'''", ]) - # Different error messages are raised when a specifier ('!', ':' or '=') is used after an empty expression - self.assertAllRaise(SyntaxError, "f-string: expression required before '!'", + self.assertAllRaise(SyntaxError, + "f-string: valid expression required before '!'", ["f'{!r}'", "f'{ !r}'", "f'{!}'", @@ -689,7 +757,8 @@ x = ( "f'{ !xr:a}'", ]) - self.assertAllRaise(SyntaxError, "f-string: expression required before ':'", + self.assertAllRaise(SyntaxError, + "f-string: valid expression required before ':'", ["f'{:}'", "f'{ :!}'", "f'{:2}'", @@ -697,7 +766,8 @@ x = ( "f'{:'", ]) - self.assertAllRaise(SyntaxError, "f-string: expression required before '='", + self.assertAllRaise(SyntaxError, + "f-string: valid expression required before '='", ["f'{=}'", "f'{ =}'", "f'{ =:}'", @@ -715,24 +785,18 @@ x = ( def test_parens_in_expressions(self): self.assertEqual(f'{3,}', '(3,)') - # Add these because when an expression is evaluated, parens - # are added around it. But we shouldn't go from an invalid - # expression to a valid one. The added parens are just - # supposed to allow whitespace (including newlines). - self.assertAllRaise(SyntaxError, 'f-string: invalid syntax', + self.assertAllRaise(SyntaxError, + "f-string: expecting a valid expression after '{'", ["f'{,}'", - "f'{,}'", # this is (,), which is an error ]) self.assertAllRaise(SyntaxError, r"f-string: unmatched '\)'", ["f'{3)+(4}'", ]) - self.assertAllRaise(SyntaxError, 'unterminated string literal', - ["f'{\n}'", - ]) def test_newlines_before_syntax_error(self): - self.assertAllRaise(SyntaxError, "invalid syntax", + self.assertAllRaise(SyntaxError, + "f-string: expecting a valid expression after '{'", ["f'{.}'", "\nf'{.}'", "\n\nf'{.}'"]) def test_backslashes_in_string_part(self): @@ -776,7 +840,7 @@ x = ( self.assertEqual(f'2\x203', '2 3') self.assertEqual(f'\x203', ' 3') - with self.assertWarns(SyntaxWarning): # invalid escape sequence + with self.assertWarns(DeprecationWarning): # invalid escape sequence value = eval(r"f'\{6*7}'") self.assertEqual(value, '\\42') self.assertEqual(f'\\{6*7}', '\\42') @@ -809,18 +873,40 @@ x = ( r"'\N{GREEK CAPITAL LETTER DELTA'", ]) - def test_no_backslashes_in_expression_part(self): - self.assertAllRaise(SyntaxError, 'f-string expression part cannot include a backslash', - [r"f'{\'a\'}'", - r"f'{\t3}'", - r"f'{\}'", - r"rf'{\'a\'}'", - r"rf'{\t3}'", - r"rf'{\}'", - r"""rf'{"\N{LEFT CURLY BRACKET}"}'""", - r"f'{\n}'", + def test_backslashes_in_expression_part(self): + self.assertEqual(f"{( + 1 + + 2 + )}", "3") + + self.assertEqual("\N{LEFT CURLY BRACKET}", '{') + self.assertEqual(f'{"\N{LEFT CURLY BRACKET}"}', '{') + self.assertEqual(rf'{"\N{LEFT CURLY BRACKET}"}', '{') + + self.assertAllRaise(SyntaxError, + "f-string: valid expression required before '}'", + ["f'{\n}'", ]) + def test_invalid_backslashes_inside_fstring_context(self): + # All of these variations are invalid python syntax, + # so they are also invalid in f-strings as well. + cases = [ + formatting.format(expr=expr) + for formatting in [ + "{expr}", + "f'{{{expr}}}'", + "rf'{{{expr}}}'", + ] + for expr in [ + r"\'a\'", + r"\t3", + r"\\"[0], + ] + ] + self.assertAllRaise(SyntaxError, 'unexpected character after line continuation', + cases) + def test_no_escapes_for_braces(self): """ Only literal curly braces begin an expression. @@ -843,11 +929,69 @@ x = ( self.assertEqual(f'{(lambda y:x*y)("8"):10}', "88888 ") # lambda doesn't work without parens, because the colon - # makes the parser think it's a format_spec - self.assertAllRaise(SyntaxError, 'f-string: invalid syntax', + # makes the parser think it's a format_spec + # emit warning if we can match a format_spec + self.assertAllRaise(SyntaxError, + "f-string: lambda expressions are not allowed " + "without parentheses", ["f'{lambda x:x}'", + "f'{lambda :x}'", + "f'{lambda *arg, :x}'", + "f'{1, lambda:x}'", + ]) + + # but don't emit the paren warning in general cases + self.assertAllRaise(SyntaxError, + "f-string: expecting a valid expression after '{'", + ["f'{lambda x:}'", + "f'{lambda :}'", + "f'{+ lambda:None}'", ]) + def test_valid_prefixes(self): + self.assertEqual(F'{1}', "1") + self.assertEqual(FR'{2}', "2") + self.assertEqual(fR'{3}', "3") + + def test_roundtrip_raw_quotes(self): + self.assertEqual(fr"\'", "\\'") + self.assertEqual(fr'\"', '\\"') + self.assertEqual(fr'\"\'', '\\"\\\'') + self.assertEqual(fr'\'\"', '\\\'\\"') + self.assertEqual(fr'\"\'\"', '\\"\\\'\\"') + self.assertEqual(fr'\'\"\'', '\\\'\\"\\\'') + self.assertEqual(fr'\"\'\"\'', '\\"\\\'\\"\\\'') + + def test_fstring_backslash_before_double_bracket(self): + self.assertEqual(f'\{{\}}', '\\{\\}') + self.assertEqual(f'\{{', '\\{') + self.assertEqual(f'\{{{1+1}', '\\{2') + self.assertEqual(f'\}}{1+1}', '\\}2') + self.assertEqual(f'{1+1}\}}', '2\\}') + self.assertEqual(fr'\{{\}}', '\\{\\}') + self.assertEqual(fr'\{{', '\\{') + self.assertEqual(fr'\{{{1+1}', '\\{2') + self.assertEqual(fr'\}}{1+1}', '\\}2') + self.assertEqual(fr'{1+1}\}}', '2\\}') + + def test_fstring_backslash_prefix_raw(self): + self.assertEqual(f'\\', '\\') + self.assertEqual(f'\\\\', '\\\\') + self.assertEqual(fr'\\', r'\\') + self.assertEqual(fr'\\\\', r'\\\\') + self.assertEqual(rf'\\', r'\\') + self.assertEqual(rf'\\\\', r'\\\\') + self.assertEqual(Rf'\\', R'\\') + self.assertEqual(Rf'\\\\', R'\\\\') + self.assertEqual(fR'\\', R'\\') + self.assertEqual(fR'\\\\', R'\\\\') + self.assertEqual(FR'\\', R'\\') + self.assertEqual(FR'\\\\', R'\\\\') + + def test_fstring_format_spec_greedy_matching(self): + self.assertEqual(f"{1:}}}", "1}") + self.assertEqual(f"{1:>3{5}}}}", " 1}") + def test_yield(self): # Not terribly useful, but make sure the yield turns # a function into a generator @@ -1037,6 +1181,11 @@ x = ( self.assertEqual(f'{"a"!r}', "'a'") self.assertEqual(f'{"a"!a}', "'a'") + # Conversions can have trailing whitespace after them since it + # does not provide any significance + self.assertEqual(f"{3!s }", "3") + self.assertEqual(f'{3.14!s :10.10}', '3.14 ') + # Not a conversion. self.assertEqual(f'{"a!r"}', "a!r") @@ -1049,16 +1198,27 @@ x = ( "f'{3!g'", ]) - self.assertAllRaise(SyntaxError, 'f-string: missed conversion character', + self.assertAllRaise(SyntaxError, 'f-string: missing conversion character', ["f'{3!}'", "f'{3!:'", "f'{3!:}'", ]) - for conv in 'g', 'A', '3', 'G', '!', ' s', 's ', ' s ', 'ä', 'ɐ', 'ª': + for conv_identifier in 'g', 'A', 'G', 'ä', 'ɐ': self.assertAllRaise(SyntaxError, "f-string: invalid conversion character %r: " - "expected 's', 'r', or 'a'" % conv, + "expected 's', 'r', or 'a'" % conv_identifier, + ["f'{3!" + conv_identifier + "}'"]) + + for conv_non_identifier in '3', '!': + self.assertAllRaise(SyntaxError, + "f-string: invalid conversion character", + ["f'{3!" + conv_non_identifier + "}'"]) + + for conv in ' s', ' s ': + self.assertAllRaise(SyntaxError, + "f-string: conversion type must come right after the" + " exclamanation mark", ["f'{3!" + conv + "}'"]) self.assertAllRaise(SyntaxError, @@ -1097,8 +1257,7 @@ x = ( ]) self.assertAllRaise(SyntaxError, "f-string: expecting '}'", - ["f'{3:{{>10}'", - "f'{3'", + ["f'{3'", "f'{3!'", "f'{3:'", "f'{3!s'", @@ -1111,11 +1270,14 @@ x = ( "f'{{{'", "f'{{}}{'", "f'{'", - "f'x{<'", # See bpo-46762. - "f'x{>'", "f'{i='", # See gh-93418. ]) + self.assertAllRaise(SyntaxError, + "f-string: expecting a valid expression after '{'", + ["f'{3:{{>10}'", + ]) + # But these are just normal strings. self.assertEqual(f'{"{"}', '{') self.assertEqual(f'{"}"}', '}') @@ -1314,6 +1476,7 @@ x = ( self.assertEqual(f'X{x =}Y', 'Xx ='+repr(x)+'Y') self.assertEqual(f'X{x= }Y', 'Xx= '+repr(x)+'Y') self.assertEqual(f'X{x = }Y', 'Xx = '+repr(x)+'Y') + self.assertEqual(f"sadsd {1 + 1 = :{1 + 1:1d}f}", "sadsd 1 + 1 = 2.000000") # These next lines contains tabs. Backslash escapes don't # work in f-strings. @@ -1335,7 +1498,8 @@ x = ( self.assertEqual(x, 10) def test_invalid_syntax_error_message(self): - with self.assertRaisesRegex(SyntaxError, "f-string: invalid syntax"): + with self.assertRaisesRegex(SyntaxError, + "f-string: expecting '=', or '!', or ':', or '}'"): compile("f'{a $ b}'", "?", "exec") def test_with_two_commas_in_format_specifier(self): @@ -1359,12 +1523,11 @@ x = ( f'{1:_,}' def test_syntax_error_for_starred_expressions(self): - error_msg = re.escape("cannot use starred expression here") - with self.assertRaisesRegex(SyntaxError, error_msg): + with self.assertRaisesRegex(SyntaxError, "can't use starred expression here"): compile("f'{*a}'", "?", "exec") - error_msg = re.escape("cannot use double starred expression here") - with self.assertRaisesRegex(SyntaxError, error_msg): + with self.assertRaisesRegex(SyntaxError, + "f-string: expecting a valid expression after '{'"): compile("f'{**a}'", "?", "exec") if __name__ == '__main__': diff --git a/Lib/test/test_tokenize.py b/Lib/test/test_tokenize.py index 63c2501..283a7c2 100644 --- a/Lib/test/test_tokenize.py +++ b/Lib/test/test_tokenize.py @@ -1625,6 +1625,10 @@ class TestRoundtrip(TestCase): # 7 more testfiles fail. Remove them also until the failure is diagnosed. testfiles.remove(os.path.join(tempdir, "test_unicode_identifiers.py")) + + # TODO: Remove this once we can unparse PEP 701 syntax + testfiles.remove(os.path.join(tempdir, "test_fstring.py")) + for f in ('buffer', 'builtin', 'fileio', 'inspect', 'os', 'platform', 'sys'): testfiles.remove(os.path.join(tempdir, "test_%s.py") % f) @@ -1937,25 +1941,39 @@ c"""', """\ """) self.check_tokenize('f"abc"', """\ - STRING 'f"abc"' (1, 0) (1, 6) + FSTRING_START 'f"' (1, 0) (1, 2) + FSTRING_MIDDLE 'abc' (1, 2) (1, 5) + FSTRING_END '"' (1, 5) (1, 6) """) self.check_tokenize('fR"a{b}c"', """\ - STRING 'fR"a{b}c"' (1, 0) (1, 9) + FSTRING_START 'fR"' (1, 0) (1, 3) + FSTRING_MIDDLE 'a' (1, 3) (1, 4) + LBRACE '{' (1, 4) (1, 5) + NAME 'b' (1, 5) (1, 6) + RBRACE '}' (1, 6) (1, 7) + FSTRING_MIDDLE 'c' (1, 7) (1, 8) + FSTRING_END '"' (1, 8) (1, 9) """) self.check_tokenize('f"""abc"""', """\ - STRING 'f\"\"\"abc\"\"\"' (1, 0) (1, 10) + FSTRING_START 'f\"""' (1, 0) (1, 4) + FSTRING_MIDDLE 'abc' (1, 4) (1, 7) + FSTRING_END '\"""' (1, 7) (1, 10) """) self.check_tokenize(r'f"abc\ def"', """\ - STRING 'f"abc\\\\\\ndef"' (1, 0) (2, 4) + FSTRING_START \'f"\' (1, 0) (1, 2) + FSTRING_MIDDLE 'abc\\\\\\ndef' (1, 2) (2, 3) + FSTRING_END '"' (2, 3) (2, 4) """) self.check_tokenize(r'Rf"abc\ def"', """\ - STRING 'Rf"abc\\\\\\ndef"' (1, 0) (2, 4) + FSTRING_START 'Rf"' (1, 0) (1, 3) + FSTRING_MIDDLE 'abc\\\\\\ndef' (1, 3) (2, 3) + FSTRING_END '"' (2, 3) (2, 4) """) def test_function(self): diff --git a/Lib/test/test_type_comments.py b/Lib/test/test_type_comments.py index 8db7394..aba4a44 100644 --- a/Lib/test/test_type_comments.py +++ b/Lib/test/test_type_comments.py @@ -272,7 +272,7 @@ class TypeCommentTests(unittest.TestCase): pass def test_fstring(self): - for tree in self.parse_all(fstring, minver=6): + for tree in self.parse_all(fstring): pass def test_underscorednumber(self): |