From 40a9ddad4e5597b0cd68adb168b57f4ffe7b7030 Mon Sep 17 00:00:00 2001
From: Myeongseon Choi
Date: Thu, 5 Dec 2024 15:04:56 +0900
Subject: [PATCH 001/317] update test_fstring.py from cpython 3.12.7 add
expectedFailure to tag what should rustpython do add comment for some
syntaxerror which make test run broken
---
Lib/test/test_fstring.py | 1800 +++++++++++++++++++++++++-------------
1 file changed, 1181 insertions(+), 619 deletions(-)
diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py
index b1cd9b1604..b3a38dd652 100644
--- a/Lib/test/test_fstring.py
+++ b/Lib/test/test_fstring.py
@@ -8,15 +8,18 @@
# Unicode identifiers in tests is allowed by PEP 3131.
import ast
+import datetime
import os
import re
import types
import decimal
import unittest
+import warnings
+from test import support
from test.support.os_helper import temp_cwd
-from test.support.script_helper import assert_python_failure
+from test.support.script_helper import assert_python_failure, assert_python_ok
-a_global = 'global variable'
+a_global = "global variable"
# You could argue that I'm too strict in looking for specific error
# values with assertRaisesRegex, but without it it's way too easy to
@@ -25,6 +28,7 @@
# worthwhile tradeoff. When I switched to this method, I found many
# examples where I wasn't testing what I thought I was.
+
class TestCase(unittest.TestCase):
def assertAllRaise(self, exception_type, regex, error_strings):
for str in error_strings:
@@ -36,43 +40,45 @@ def test__format__lookup(self):
# Make sure __format__ is looked up on the type, not the instance.
class X:
def __format__(self, spec):
- return 'class'
+ return "class"
x = X()
# Add a bound __format__ method to the 'y' instance, but not
# the 'x' instance.
y = X()
- y.__format__ = types.MethodType(lambda self, spec: 'instance', y)
+ y.__format__ = types.MethodType(lambda self, spec: "instance", y)
- self.assertEqual(f'{y}', format(y))
- self.assertEqual(f'{y}', 'class')
+ self.assertEqual(f"{y}", format(y))
+ self.assertEqual(f"{y}", "class")
self.assertEqual(format(x), format(y))
# __format__ is not called this way, but still make sure it
# returns what we expect (so we can make sure we're bypassing
# it).
- self.assertEqual(x.__format__(''), 'class')
- self.assertEqual(y.__format__(''), 'instance')
+ self.assertEqual(x.__format__(""), "class")
+ self.assertEqual(y.__format__(""), "instance")
# This is how __format__ is actually called.
- self.assertEqual(type(x).__format__(x, ''), 'class')
- self.assertEqual(type(y).__format__(y, ''), 'class')
+ self.assertEqual(type(x).__format__(x, ""), "class")
+ self.assertEqual(type(y).__format__(y, ""), "class")
def test_ast(self):
# Inspired by http://bugs.python.org/issue24975
class X:
def __init__(self):
self.called = False
+
def __call__(self):
self.called = True
return 4
+
x = X()
expr = """
a = 10
f'{a * x()}'"""
t = ast.parse(expr)
- c = compile(t, '', 'exec')
+ c = compile(t, "", "exec")
# Make sure x was not called.
self.assertFalse(x.called)
@@ -278,7 +284,6 @@ def test_ast_line_numbers_duplicate_expression(self):
self.assertEqual(binop.right.col_offset, 27)
def test_ast_numbers_fstring_with_formatting(self):
-
t = ast.parse('f"Here is that pesky {xxx:.3f} again"')
self.assertEqual(len(t.body), 1)
self.assertEqual(t.body[0].lineno, 1)
@@ -300,6 +305,8 @@ def test_ast_numbers_fstring_with_formatting(self):
self.assertEqual(name.col_offset, 22)
self.assertEqual(name.end_col_offset, 25)
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_ast_line_numbers_multiline_fstring(self):
# See bpo-30465 for details.
expr = """
@@ -329,13 +336,13 @@ def test_ast_line_numbers_multiline_fstring(self):
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,19 +373,21 @@ def test_ast_line_numbers_multiline_fstring(self):
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)
self.assertEqual(t.body[0].value.values[1].value.col_offset, 11)
self.assertEqual(t.body[0].value.values[1].value.end_col_offset, 16)
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_ast_line_numbers_with_parentheses(self):
expr = """
x = (
@@ -387,6 +396,20 @@ def test_ast_line_numbers_with_parentheses(self):
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 +420,38 @@ def test_ast_line_numbers_with_parentheses(self):
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 +470,9 @@ def test_ast_line_numbers_with_parentheses(self):
# 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,427 +482,673 @@ def test_ast_line_numbers_with_parentheses(self):
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_ast_fstring_empty_format_spec(self):
+ expr = "f'{expr:}'"
+
+ mod = ast.parse(expr)
+ self.assertEqual(type(mod), ast.Module)
+ self.assertEqual(len(mod.body), 1)
+
+ fstring = mod.body[0].value
+ self.assertEqual(type(fstring), ast.JoinedStr)
+ self.assertEqual(len(fstring.values), 1)
+
+ fv = fstring.values[0]
+ self.assertEqual(type(fv), ast.FormattedValue)
+
+ format_spec = fv.format_spec
+ self.assertEqual(type(format_spec), ast.JoinedStr)
+ self.assertEqual(len(format_spec.values), 0)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_docstring(self):
def f():
- f'''Not a docstring'''
+ f"""Not a docstring"""
+
self.assertIsNone(f.__doc__)
+
def g():
- '''Not a docstring''' \
- f''
+ """Not a docstring""" f""
+
self.assertIsNone(g.__doc__)
def test_literal_eval(self):
- with self.assertRaisesRegex(ValueError, 'malformed node or string'):
+ with self.assertRaisesRegex(ValueError, "malformed node or string"):
ast.literal_eval("f'x'")
def test_ast_compile_time_concat(self):
- x = ['']
+ x = [""]
expr = """x[0] = 'foo' f'{3}'"""
t = ast.parse(expr)
- c = compile(t, '', 'exec')
+ c = compile(t, "", "exec")
exec(c)
- self.assertEqual(x[0], 'foo3')
+ self.assertEqual(x[0], "foo3")
def test_compile_time_concat_errors(self):
- self.assertAllRaise(SyntaxError,
- 'cannot mix bytes and nonbytes literals',
- [r"""f'' b''""",
- r"""b'' f''""",
- ])
+ self.assertAllRaise(
+ SyntaxError,
+ "cannot mix bytes and nonbytes literals",
+ [
+ r"""f'' b''""",
+ r"""b'' f''""",
+ ],
+ )
def test_literal(self):
- self.assertEqual(f'', '')
- self.assertEqual(f'a', 'a')
- self.assertEqual(f' ', ' ')
+ self.assertEqual(f"", "")
+ self.assertEqual(f"a", "a")
+ self.assertEqual(f" ", " ")
def test_unterminated_string(self):
- self.assertAllRaise(SyntaxError, 'f-string: unterminated string',
- [r"""f'{"x'""",
- r"""f'{"x}'""",
- r"""f'{("x'""",
- r"""f'{("x}'""",
- ])
-
+ self.assertAllRaise(
+ SyntaxError,
+ "unterminated string",
+ [
+ r"""f'{"x'""",
+ r"""f'{"x}'""",
+ r"""f'{("x'""",
+ r"""f'{("x}'""",
+ ],
+ )
+
+ @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
def test_mismatched_parens(self):
- self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' "
- r"does not match opening parenthesis '\('",
- ["f'{((}'",
- ])
- self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\)' "
- r"does not match opening parenthesis '\['",
- ["f'{a[4)}'",
- ])
- self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\]' "
- r"does not match opening parenthesis '\('",
- ["f'{a(4]}'",
- ])
- self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' "
- r"does not match opening parenthesis '\['",
- ["f'{a[4}'",
- ])
- self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' "
- r"does not match opening parenthesis '\('",
- ["f'{a(4}'",
- ])
- self.assertRaises(SyntaxError, eval, "f'{" + "("*500 + "}'")
+ self.assertAllRaise(
+ SyntaxError,
+ r"closing parenthesis '\}' " r"does not match opening parenthesis '\('",
+ [
+ "f'{((}'",
+ ],
+ )
+ self.assertAllRaise(
+ SyntaxError,
+ r"closing parenthesis '\)' " r"does not match opening parenthesis '\['",
+ [
+ "f'{a[4)}'",
+ ],
+ )
+ self.assertAllRaise(
+ SyntaxError,
+ r"closing parenthesis '\]' " r"does not match opening parenthesis '\('",
+ [
+ "f'{a(4]}'",
+ ],
+ )
+ self.assertAllRaise(
+ SyntaxError,
+ r"closing parenthesis '\}' " r"does not match opening parenthesis '\['",
+ [
+ "f'{a[4}'",
+ ],
+ )
+ self.assertAllRaise(
+ SyntaxError,
+ r"closing parenthesis '\}' " r"does not match opening parenthesis '\('",
+ [
+ "f'{a(4}'",
+ ],
+ )
+ self.assertRaises(SyntaxError, eval, "f'{" + "(" * 500 + "}'")
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
+ 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 create_nested_fstring(n):
+ if n == 0:
+ return "1+1"
+ prev = create_nested_fstring(n - 1)
+ return f'f"{{{prev}}}"'
+
+ self.assertAllRaise(
+ SyntaxError, "too many nested f-strings", [create_nested_fstring(160)]
+ )
+
+ def test_syntax_error_in_nested_fstring(self):
+ # See gh-104016 for more information on this crash
+ self.assertAllRaise(
+ SyntaxError, "invalid syntax", ['f"{1 1:' + ('{f"1:' * 199)]
+ )
def test_double_braces(self):
- self.assertEqual(f'{{', '{')
- self.assertEqual(f'a{{', 'a{')
- self.assertEqual(f'{{b', '{b')
- self.assertEqual(f'a{{b', 'a{b')
- self.assertEqual(f'}}', '}')
- self.assertEqual(f'a}}', 'a}')
- self.assertEqual(f'}}b', '}b')
- self.assertEqual(f'a}}b', 'a}b')
- self.assertEqual(f'{{}}', '{}')
- self.assertEqual(f'a{{}}', 'a{}')
- self.assertEqual(f'{{b}}', '{b}')
- self.assertEqual(f'{{}}c', '{}c')
- self.assertEqual(f'a{{b}}', 'a{b}')
- self.assertEqual(f'a{{}}c', 'a{}c')
- self.assertEqual(f'{{b}}c', '{b}c')
- self.assertEqual(f'a{{b}}c', 'a{b}c')
-
- self.assertEqual(f'{{{10}', '{10')
- self.assertEqual(f'}}{10}', '}10')
- self.assertEqual(f'}}{{{10}', '}{10')
- self.assertEqual(f'}}a{{{10}', '}a{10')
-
- self.assertEqual(f'{10}{{', '10{')
- self.assertEqual(f'{10}}}', '10}')
- self.assertEqual(f'{10}}}{{', '10}{')
- self.assertEqual(f'{10}}}a{{' '}', '10}a{}')
+ self.assertEqual(f"{{", "{")
+ self.assertEqual(f"a{{", "a{")
+ self.assertEqual(f"{{b", "{b")
+ self.assertEqual(f"a{{b", "a{b")
+ self.assertEqual(f"}}", "}")
+ self.assertEqual(f"a}}", "a}")
+ self.assertEqual(f"}}b", "}b")
+ self.assertEqual(f"a}}b", "a}b")
+ self.assertEqual(f"{{}}", "{}")
+ self.assertEqual(f"a{{}}", "a{}")
+ self.assertEqual(f"{{b}}", "{b}")
+ self.assertEqual(f"{{}}c", "{}c")
+ self.assertEqual(f"a{{b}}", "a{b}")
+ self.assertEqual(f"a{{}}c", "a{}c")
+ self.assertEqual(f"{{b}}c", "{b}c")
+ self.assertEqual(f"a{{b}}c", "a{b}c")
+
+ self.assertEqual(f"{{{10}", "{10")
+ self.assertEqual(f"}}{10}", "}10")
+ self.assertEqual(f"}}{{{10}", "}{10")
+ self.assertEqual(f"}}a{{{10}", "}a{10")
+
+ self.assertEqual(f"{10}{{", "10{")
+ self.assertEqual(f"{10}}}", "10}")
+ self.assertEqual(f"{10}}}{{", "10}{")
+ self.assertEqual(f"{10}}}a{{" "}", "10}a{}")
# Inside of strings, don't interpret doubled brackets.
- self.assertEqual(f'{"{{}}"}', '{{}}')
+ self.assertEqual(f'{"{{}}"}', "{{}}")
- self.assertAllRaise(TypeError, 'unhashable type',
- ["f'{ {{}} }'", # dict in a set
- ])
+ self.assertAllRaise(
+ TypeError,
+ "unhashable type",
+ [
+ "f'{ {{}} }'", # dict in a set
+ ],
+ )
def test_compile_time_concat(self):
- x = 'def'
- self.assertEqual('abc' f'## {x}ghi', 'abc## defghi')
- self.assertEqual('abc' f'{x}' 'ghi', 'abcdefghi')
- self.assertEqual('abc' f'{x}' 'gh' f'i{x:4}', 'abcdefghidef ')
- self.assertEqual('{x}' f'{x}', '{x}def')
- self.assertEqual('{x' f'{x}', '{xdef')
- self.assertEqual('{x}' f'{x}', '{x}def')
- self.assertEqual('{{x}}' f'{x}', '{{x}}def')
- self.assertEqual('{{x' f'{x}', '{{xdef')
- self.assertEqual('x}}' f'{x}', 'x}}def')
- self.assertEqual(f'{x}' 'x}}', 'defx}}')
- self.assertEqual(f'{x}' '', 'def')
- self.assertEqual('' f'{x}' '', 'def')
- self.assertEqual('' f'{x}', 'def')
- self.assertEqual(f'{x}' '2', 'def2')
- self.assertEqual('1' f'{x}' '2', '1def2')
- self.assertEqual('1' f'{x}', '1def')
- self.assertEqual(f'{x}' f'-{x}', 'def-def')
- self.assertEqual('' f'', '')
- self.assertEqual('' f'' '', '')
- self.assertEqual('' f'' '' f'', '')
- self.assertEqual(f'', '')
- self.assertEqual(f'' '', '')
- 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
- ])
+ x = "def"
+ self.assertEqual("abc" f"## {x}ghi", "abc## defghi")
+ self.assertEqual("abc" f"{x}" "ghi", "abcdefghi")
+ self.assertEqual("abc" f"{x}" "gh" f"i{x:4}", "abcdefghidef ")
+ self.assertEqual("{x}" f"{x}", "{x}def")
+ self.assertEqual("{x" f"{x}", "{xdef")
+ self.assertEqual("{x}" f"{x}", "{x}def")
+ self.assertEqual("{{x}}" f"{x}", "{{x}}def")
+ self.assertEqual("{{x" f"{x}", "{{xdef")
+ self.assertEqual("x}}" f"{x}", "x}}def")
+ self.assertEqual(f"{x}" "x}}", "defx}}")
+ self.assertEqual(f"{x}" "", "def")
+ self.assertEqual("" f"{x}" "", "def")
+ self.assertEqual("" f"{x}", "def")
+ self.assertEqual(f"{x}" "2", "def2")
+ self.assertEqual("1" f"{x}" "2", "1def2")
+ self.assertEqual("1" f"{x}", "1def")
+ self.assertEqual(f"{x}" f"-{x}", "def-def")
+ self.assertEqual("" f"", "")
+ self.assertEqual("" f"" "", "")
+ self.assertEqual("" f"" "" f"", "")
+ self.assertEqual(f"", "")
+ self.assertEqual(f"" "", "")
+ self.assertEqual(f"" "" f"", "")
+ self.assertEqual(f"" "" f"" "", "")
+
+ # 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.
+ # TODO: RUSTPYTHON SyntaxError
+ # self.assertEqual(f'{' f'}', " f")
+
+ self.assertAllRaise(
+ SyntaxError,
+ "expecting '}'",
+ [
+ '''f'{3' f"}"''', # can't concat to get a valid f-string
+ ],
+ )
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_comments(self):
# These aren't comments, since they're in strings.
- d = {'#': 'hash'}
- self.assertEqual(f'{"#"}', '#')
- self.assertEqual(f'{d["#"]}', 'hash')
-
- self.assertAllRaise(SyntaxError, "f-string expression part cannot include '#'",
- ["f'{1#}'", # error because the expression becomes "(1#)"
- "f'{3(#)}'",
- "f'{#}'",
- ])
- self.assertAllRaise(SyntaxError, r"f-string: unmatched '\)'",
- ["f'{)#}'", # When wrapped in parens, this becomes
- # '()#)'. Make sure that doesn't compile.
- ])
+ d = {"#": "hash"}
+ self.assertEqual(f'{"#"}', "#")
+ self.assertEqual(f'{d["#"]}', "hash")
+
+ self.assertAllRaise(
+ SyntaxError,
+ "'{' was never closed",
+ [
+ "f'{1#}'", # error because everything after '#' is a comment
+ "f'{#}'",
+ "f'one: {1#}'",
+ "f'{1# one} {2 this is a comment still#}'",
+ ],
+ )
+ self.assertAllRaise(
+ SyntaxError,
+ r"f-string: unmatched '\)'",
+ [
+ "f'{)#}'", # When wrapped in parens, this becomes
+ # '()#)'. Make sure that doesn't compile.
+ ],
+ )
+ self.assertEqual(
+ f"""A complex trick: {
+2 # two
+}""",
+ "A complex trick: 2",
+ )
+ self.assertEqual(
+ f"""
+{
+40 # forty
++ # plus
+2 # two
+}""",
+ "\n42",
+ )
+ self.assertEqual(
+ f"""
+{
+40 # forty
++ # plus
+2 # two
+}""",
+ "\n42",
+ )
+# TODO: RUSTPYTHON SyntaxError
+# self.assertEqual(
+# f"""
+# # this is not a comment
+# { # the following operation it's
+# 3 # this is a number
+# * 2}""",
+# "\n# this is not a comment\n6",
+# )
+ self.assertEqual(
+ f"""
+{# f'a {comment}'
+86 # constant
+# nothing more
+}""",
+ "\n86",
+ )
+
+ self.assertAllRaise(
+ SyntaxError,
+ r"f-string: valid expression required before '}'",
+ [
+ """f'''
+{
+# only a comment
+}'''
+""", # this is equivalent to f'{}'
+ ],
+ )
def test_many_expressions(self):
# Create a string with many expressions in it. Note that
# because we have a space in here as a literal, we're actually
# going to use twice as many ast nodes: one for each literal
# plus one for each expression.
- def build_fstr(n, extra=''):
- return "f'" + ('{x} ' * n) + extra + "'"
+ def build_fstr(n, extra=""):
+ return "f'" + ("{x} " * n) + extra + "'"
- x = 'X'
+ x = "X"
width = 1
# Test around 256.
for i in range(250, 260):
- self.assertEqual(eval(build_fstr(i)), (x+' ')*i)
+ self.assertEqual(eval(build_fstr(i)), (x + " ") * i)
# Test concatenating 2 largs fstrings.
- self.assertEqual(eval(build_fstr(255)*256), (x+' ')*(255*256))
+ self.assertEqual(eval(build_fstr(255) * 256), (x + " ") * (255 * 256))
- s = build_fstr(253, '{x:{width}} ')
- self.assertEqual(eval(s), (x+' ')*254)
+ s = build_fstr(253, "{x:{width}} ")
+ self.assertEqual(eval(s), (x + " ") * 254)
# Test lots of expressions and constants, concatenated.
s = "f'{1}' 'x' 'y'" * 1024
- self.assertEqual(eval(s), '1xy' * 1024)
+ self.assertEqual(eval(s), "1xy" * 1024)
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_format_specifier_expressions(self):
width = 10
precision = 4
- value = decimal.Decimal('12.34567')
- self.assertEqual(f'result: {value:{width}.{precision}}', 'result: 12.35')
- self.assertEqual(f'result: {value:{width!r}.{precision}}', 'result: 12.35')
- self.assertEqual(f'result: {value:{width:0}.{precision:1}}', 'result: 12.35')
- self.assertEqual(f'result: {value:{1}{0:0}.{precision:1}}', 'result: 12.35')
- self.assertEqual(f'result: {value:{ 1}{ 0:0}.{ precision:1}}', 'result: 12.35')
- self.assertEqual(f'{10:#{1}0x}', ' 0xa')
- self.assertEqual(f'{10:{"#"}1{0}{"x"}}', ' 0xa')
- 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.assertAllRaise(SyntaxError, "f-string: expecting '}'",
- ["""f'{"s"!r{":10"}}'""",
-
- # This looks like a nested format spec.
- ])
-
- self.assertAllRaise(SyntaxError, "f-string: invalid syntax",
- [# 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.
- """f'{"s"!{"r"}}'""",
- ])
+ value = decimal.Decimal("12.34567")
+ self.assertEqual(f"result: {value:{width}.{precision}}", "result: 12.35")
+ self.assertEqual(f"result: {value:{width!r}.{precision}}", "result: 12.35")
+ self.assertEqual(
+ f"result: {value:{width:0}.{precision:1}}", "result: 12.35"
+ )
+ self.assertEqual(
+ f"result: {value:{1}{0:0}.{precision:1}}", "result: 12.35"
+ )
+ self.assertEqual(
+ f"result: {value:{ 1}{ 0:0}.{ precision:1}}", "result: 12.35"
+ )
+ self.assertEqual(f"{10:#{1}0x}", " 0xa")
+ self.assertEqual(f'{10:{"#"}1{0}{"x"}}', " 0xa")
+ 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")
+
+ # TODO: RUSTPYTHON SyntaxError
+ # self.assertEqual(
+ # f"result: {value:{width:{0}}.{precision:1}}", "result: 12.35"
+ # )
+
+
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: expecting ':' or '}'",
+ [
+ """f'{"s"!r{":10"}}'""",
+ # This looks like a nested format spec.
+ ],
+ )
+
+
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: expecting a valid expression after '{'",
+ [ # Invalid syntax inside a nested spec.
+ "f'{4:{/5}}'",
+ ],
+ )
+
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: invalid conversion character",
+ [ # No expansion inside conversion or for
+ # the : or ! itself.
+ """f'{"s"!{"r"}}'""",
+ ],
+ )
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_custom_format_specifier(self):
+ class CustomFormat:
+ def __format__(self, format_spec):
+ return format_spec
+
+ self.assertEqual(f"{CustomFormat():\n}", "\n")
+ self.assertEqual(f"{CustomFormat():\u2603}", "☃")
+ with self.assertWarns(SyntaxWarning):
+ exec(r'f"{F():¯\_(ツ)_/¯}"', {"F": CustomFormat})
def test_side_effect_order(self):
class X:
def __init__(self):
self.i = 0
+
def __format__(self, spec):
self.i += 1
return str(self.i)
x = X()
- self.assertEqual(f'{x} {x}', '1 2')
+ self.assertEqual(f"{x} {x}", "1 2")
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_missing_expression(self):
- self.assertAllRaise(SyntaxError, 'f-string: empty expression not allowed',
- ["f'{}'",
- "f'{ }'"
- "f' {} '",
- "f'{10:{ }}'",
- "f' { } '",
-
- # The Python parser ignores also the following
- # whitespace characters in additional to a space.
- "f'''{\t\f\r\n}'''",
- ])
-
- # Different error messeges are raised when a specfier ('!', ':' or '=') is used after an empty expression
- self.assertAllRaise(SyntaxError, "f-string: expression required before '!'",
- ["f'{!r}'",
- "f'{ !r}'",
- "f'{!}'",
- "f'''{\t\f\r\n!a}'''",
-
- # Catch empty expression before the
- # missing closing brace.
- "f'{!'",
- "f'{!s:'",
-
- # Catch empty expression before the
- # invalid conversion.
- "f'{!x}'",
- "f'{ !xr}'",
- "f'{!x:}'",
- "f'{!x:a}'",
- "f'{ !xr:}'",
- "f'{ !xr:a}'",
- ])
-
- self.assertAllRaise(SyntaxError, "f-string: expression required before ':'",
- ["f'{:}'",
- "f'{ :!}'",
- "f'{:2}'",
- "f'''{\t\f\r\n:a}'''",
- "f'{:'",
- ])
-
- self.assertAllRaise(SyntaxError, "f-string: expression required before '='",
- ["f'{=}'",
- "f'{ =}'",
- "f'{ =:}'",
- "f'{ =!}'",
- "f'''{\t\f\r\n=}'''",
- "f'{='",
- ])
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: valid expression required before '}'",
+ [
+ "f'{}'",
+ "f'{ }'" "f' {} '",
+ "f'{10:{ }}'",
+ "f' { } '",
+ # The Python parser ignores also the following
+ # whitespace characters in additional to a space.
+ "f'''{\t\f\r\n}'''",
+ ],
+ )
+
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: valid expression required before '!'",
+ [
+ "f'{!r}'",
+ "f'{ !r}'",
+ "f'{!}'",
+ "f'''{\t\f\r\n!a}'''",
+ # Catch empty expression before the
+ # missing closing brace.
+ "f'{!'",
+ "f'{!s:'",
+ # Catch empty expression before the
+ # invalid conversion.
+ "f'{!x}'",
+ "f'{ !xr}'",
+ "f'{!x:}'",
+ "f'{!x:a}'",
+ "f'{ !xr:}'",
+ "f'{ !xr:a}'",
+ ],
+ )
+
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: valid expression required before ':'",
+ [
+ "f'{:}'",
+ "f'{ :!}'",
+ "f'{:2}'",
+ "f'''{\t\f\r\n:a}'''",
+ "f'{:'",
+ "F'{[F'{:'}[F'{:'}]]]",
+ ],
+ )
+
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: valid expression required before '='",
+ [
+ "f'{=}'",
+ "f'{ =}'",
+ "f'{ =:}'",
+ "f'{ =!}'",
+ "f'''{\t\f\r\n=}'''",
+ "f'{='",
+ ],
+ )
# Different error message is raised for other whitespace characters.
- self.assertAllRaise(SyntaxError, r"invalid non-printable character U\+00A0",
- ["f'''{\xa0}'''",
- "\xa0",
- ])
+ self.assertAllRaise(
+ SyntaxError,
+ r"invalid non-printable character U\+00A0",
+ [
+ "f'''{\xa0}'''",
+ "\xa0",
+ ],
+ )
# TODO: RUSTPYTHON
@unittest.expectedFailure
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',
- ["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}'",
- ])
+ self.assertEqual(f"{3,}", "(3,)")
+
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: expecting a valid expression after '{'",
+ [
+ "f'{,}'",
+ ],
+ )
+
+ self.assertAllRaise(
+ SyntaxError,
+ r"f-string: unmatched '\)'",
+ [
+ "f'{3)+(4}'",
+ ],
+ )
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_newlines_before_syntax_error(self):
- self.assertAllRaise(SyntaxError, "invalid syntax",
- ["f'{.}'", "\nf'{.}'", "\n\nf'{.}'"])
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: expecting a valid expression after '{'",
+ ["f'{.}'", "\nf'{.}'", "\n\nf'{.}'"],
+ )
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_backslashes_in_string_part(self):
- self.assertEqual(f'\t', '\t')
- self.assertEqual(r'\t', '\\t')
- self.assertEqual(rf'\t', '\\t')
- self.assertEqual(f'{2}\t', '2\t')
- self.assertEqual(f'{2}\t{3}', '2\t3')
- self.assertEqual(f'\t{3}', '\t3')
-
- self.assertEqual(f'\u0394', '\u0394')
- self.assertEqual(r'\u0394', '\\u0394')
- self.assertEqual(rf'\u0394', '\\u0394')
- self.assertEqual(f'{2}\u0394', '2\u0394')
- self.assertEqual(f'{2}\u0394{3}', '2\u03943')
- self.assertEqual(f'\u0394{3}', '\u03943')
-
- self.assertEqual(f'\U00000394', '\u0394')
- self.assertEqual(r'\U00000394', '\\U00000394')
- self.assertEqual(rf'\U00000394', '\\U00000394')
- self.assertEqual(f'{2}\U00000394', '2\u0394')
- self.assertEqual(f'{2}\U00000394{3}', '2\u03943')
- self.assertEqual(f'\U00000394{3}', '\u03943')
-
- self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}', '\u0394')
- self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}', '2\u0394')
- self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}{3}', '2\u03943')
- self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}{3}', '\u03943')
- self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}', '2\u0394')
- self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}3', '2\u03943')
- self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}3', '\u03943')
-
- self.assertEqual(f'\x20', ' ')
- self.assertEqual(r'\x20', '\\x20')
- self.assertEqual(rf'\x20', '\\x20')
- self.assertEqual(f'{2}\x20', '2 ')
- self.assertEqual(f'{2}\x20{3}', '2 3')
- self.assertEqual(f'\x20{3}', ' 3')
-
- self.assertEqual(f'2\x20', '2 ')
- self.assertEqual(f'2\x203', '2 3')
- self.assertEqual(f'\x203', ' 3')
-
- with self.assertWarns(DeprecationWarning): # invalid escape sequence
+ self.assertEqual(f"\t", "\t")
+ self.assertEqual(r"\t", "\\t")
+ self.assertEqual(rf"\t", "\\t")
+ self.assertEqual(f"{2}\t", "2\t")
+ self.assertEqual(f"{2}\t{3}", "2\t3")
+ self.assertEqual(f"\t{3}", "\t3")
+
+ self.assertEqual(f"\u0394", "\u0394")
+ self.assertEqual(r"\u0394", "\\u0394")
+ self.assertEqual(rf"\u0394", "\\u0394")
+ self.assertEqual(f"{2}\u0394", "2\u0394")
+ self.assertEqual(f"{2}\u0394{3}", "2\u03943")
+ self.assertEqual(f"\u0394{3}", "\u03943")
+
+ self.assertEqual(f"\U00000394", "\u0394")
+ self.assertEqual(r"\U00000394", "\\U00000394")
+ self.assertEqual(rf"\U00000394", "\\U00000394")
+ self.assertEqual(f"{2}\U00000394", "2\u0394")
+ self.assertEqual(f"{2}\U00000394{3}", "2\u03943")
+ self.assertEqual(f"\U00000394{3}", "\u03943")
+
+ self.assertEqual(f"\N{GREEK CAPITAL LETTER DELTA}", "\u0394")
+ self.assertEqual(f"{2}\N{GREEK CAPITAL LETTER DELTA}", "2\u0394")
+ self.assertEqual(f"{2}\N{GREEK CAPITAL LETTER DELTA}{3}", "2\u03943")
+ self.assertEqual(f"\N{GREEK CAPITAL LETTER DELTA}{3}", "\u03943")
+ self.assertEqual(f"2\N{GREEK CAPITAL LETTER DELTA}", "2\u0394")
+ self.assertEqual(f"2\N{GREEK CAPITAL LETTER DELTA}3", "2\u03943")
+ self.assertEqual(f"\N{GREEK CAPITAL LETTER DELTA}3", "\u03943")
+
+ self.assertEqual(f"\x20", " ")
+ self.assertEqual(r"\x20", "\\x20")
+ self.assertEqual(rf"\x20", "\\x20")
+ self.assertEqual(f"{2}\x20", "2 ")
+ self.assertEqual(f"{2}\x20{3}", "2 3")
+ self.assertEqual(f"\x20{3}", " 3")
+
+ self.assertEqual(f"2\x20", "2 ")
+ self.assertEqual(f"2\x203", "2 3")
+ self.assertEqual(f"\x203", " 3")
+
+ with self.assertWarns(SyntaxWarning): # invalid escape sequence
value = eval(r"f'\{6*7}'")
- self.assertEqual(value, '\\42')
- self.assertEqual(f'\\{6*7}', '\\42')
- self.assertEqual(fr'\{6*7}', '\\42')
-
- AMPERSAND = 'spam'
+ self.assertEqual(value, "\\42")
+ with self.assertWarns(SyntaxWarning): # invalid escape sequence
+ value = eval(r"f'\g'")
+ self.assertEqual(value, "\\g")
+ self.assertEqual(f"\\{6*7}", "\\42")
+ self.assertEqual(rf"\{6*7}", "\\42")
+
+ AMPERSAND = "spam"
# Get the right unicode character (&), or pick up local variable
# depending on the number of backslashes.
- self.assertEqual(f'\N{AMPERSAND}', '&')
- self.assertEqual(f'\\N{AMPERSAND}', '\\Nspam')
- self.assertEqual(fr'\N{AMPERSAND}', '\\Nspam')
- self.assertEqual(f'\\\N{AMPERSAND}', '\\&')
+ self.assertEqual(f"\N{AMPERSAND}", "&")
+ self.assertEqual(f"\\N{AMPERSAND}", "\\Nspam")
+ self.assertEqual(rf"\N{AMPERSAND}", "\\Nspam")
+ self.assertEqual(f"\\\N{AMPERSAND}", "\\&")
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_misformed_unicode_character_name(self):
# These test are needed because unicode names are parsed
# differently inside f-strings.
- self.assertAllRaise(SyntaxError, r"\(unicode error\) 'unicodeescape' codec can't decode bytes in position .*: malformed \\N character escape",
- [r"f'\N'",
- r"f'\N '",
- r"f'\N '", # See bpo-46503.
- r"f'\N{'",
- r"f'\N{GREEK CAPITAL LETTER DELTA'",
-
- # Here are the non-f-string versions,
- # which should give the same errors.
- r"'\N'",
- r"'\N '",
- r"'\N '",
- r"'\N{'",
- r"'\N{GREEK CAPITAL LETTER DELTA'",
- ])
+ self.assertAllRaise(
+ SyntaxError,
+ r"\(unicode error\) 'unicodeescape' codec can't decode bytes in position .*: malformed \\N character escape",
+ [
+ r"f'\N'",
+ r"f'\N '",
+ r"f'\N '", # See bpo-46503.
+ r"f'\N{'",
+ r"f'\N{GREEK CAPITAL LETTER DELTA'",
+ # Here are the non-f-string versions,
+ # which should give the same errors.
+ r"'\N'",
+ r"'\N '",
+ r"'\N '",
+ r"'\N{'",
+ r"'\N{GREEK CAPITAL LETTER DELTA'",
+ ],
+ )
# TODO: RUSTPYTHON
@unittest.expectedFailure
- 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):
+ # TODO: RUSTPYTHON SyntaxError
+ # 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}'",
+ ],
+ )
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ 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.
"""
# \x7b is '{'.
- self.assertEqual(f'\x7b1+1}}', '{1+1}')
- self.assertEqual(f'\x7b1+1', '{1+1')
- self.assertEqual(f'\u007b1+1', '{1+1')
- self.assertEqual(f'\N{LEFT CURLY BRACKET}1+1\N{RIGHT CURLY BRACKET}', '{1+1}')
+ self.assertEqual(f"\x7b1+1}}", "{1+1}")
+ self.assertEqual(f"\x7b1+1", "{1+1")
+ self.assertEqual(f"\u007b1+1", "{1+1")
+ self.assertEqual(f"\N{LEFT CURLY BRACKET}1+1\N{RIGHT CURLY BRACKET}", "{1+1}")
def test_newlines_in_expressions(self):
- self.assertEqual(f'{0}', '0')
- self.assertEqual(rf'''{3+
-4}''', '7')
+ self.assertEqual(f"{0}", "0")
+ self.assertEqual(
+ rf"""{3+
+4}""",
+ "7",
+ )
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_lambda(self):
x = 5
self.assertEqual(f'{(lambda y:x*y)("8")!r}', "'88888'")
@@ -855,17 +1156,99 @@ def test_lambda(self):
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',
- ["f'{lambda x:x}'",
- ])
+ # 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}'",
+ "f'{lambda x:}'",
+ "f'{lambda :}'",
+ ],
+ )
+ # Ensure the detection of invalid lambdas doesn't trigger detection
+ # for valid lambdas in the second error pass
+ with self.assertRaisesRegex(SyntaxError, "invalid syntax"):
+ compile("lambda name_3=f'{name_4}': {name_3}\n1 $ 1", "", "exec")
+
+ # but don't emit the paren warning in general cases
+ with self.assertRaisesRegex(
+ SyntaxError, "f-string: expecting a valid expression after '{'"
+ ):
+ eval("f'{+ lambda:None}'")
+
+ def test_valid_prefixes(self):
+ self.assertEqual(f"{1}", "1")
+ self.assertEqual(Rf"{2}", "2")
+ self.assertEqual(Rf"{3}", "3")
+
+ def test_roundtrip_raw_quotes(self):
+ self.assertEqual(rf"\'", "\\'")
+ self.assertEqual(rf"\"", '\\"')
+ self.assertEqual(rf"\"\'", "\\\"\\'")
+ self.assertEqual(rf"\'\"", "\\'\\\"")
+ self.assertEqual(rf"\"\'\"", '\\"\\\'\\"')
+ self.assertEqual(rf"\'\"\'", "\\'\\\"\\'")
+ self.assertEqual(rf"\"\'\"\'", "\\\"\\'\\\"\\'")
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_fstring_backslash_before_double_bracket(self):
+ deprecated_cases = [
+ (r"f'\{{\}}'", "\\{\\}"),
+ (r"f'\{{'", "\\{"),
+ (r"f'\{{{1+1}'", "\\{2"),
+ (r"f'\}}{1+1}'", "\\}2"),
+ (r"f'{1+1}\}}'", "2\\}"),
+ ]
+
+ for case, expected_result in deprecated_cases:
+ with self.subTest(case=case, expected_result=expected_result):
+ with self.assertWarns(SyntaxWarning):
+ result = eval(case)
+ self.assertEqual(result, expected_result)
+ self.assertEqual(rf"\{{\}}", "\\{\\}")
+ self.assertEqual(rf"\{{", "\\{")
+ self.assertEqual(rf"\{{{1+1}", "\\{2")
+ self.assertEqual(rf"\}}{1+1}", "\\}2")
+ self.assertEqual(rf"{1+1}\}}", "2\\}")
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_fstring_backslash_before_double_bracket_warns_once(self):
+ with self.assertWarns(SyntaxWarning) as w:
+ eval(r"f'\{{'")
+ self.assertEqual(len(w.warnings), 1)
+ self.assertEqual(w.warnings[0].category, SyntaxWarning)
+
+ def test_fstring_backslash_prefix_raw(self):
+ self.assertEqual(f"\\", "\\")
+ self.assertEqual(f"\\\\", "\\\\")
+ self.assertEqual(rf"\\", r"\\")
+ self.assertEqual(rf"\\\\", r"\\\\")
+ self.assertEqual(rf"\\", r"\\")
+ self.assertEqual(rf"\\\\", r"\\\\")
+ self.assertEqual(Rf"\\", R"\\")
+ self.assertEqual(Rf"\\\\", R"\\\\")
+ self.assertEqual(Rf"\\", R"\\")
+ self.assertEqual(Rf"\\\\", R"\\\\")
+ self.assertEqual(Rf"\\", R"\\")
+ self.assertEqual(Rf"\\\\", 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
def fn(y):
- f'y:{yield y*2}'
- f'{yield}'
+ f"y:{yield y*2}"
+ f"{yield}"
g = fn(4)
self.assertEqual(next(g), 8)
@@ -873,257 +1256,329 @@ def fn(y):
def test_yield_send(self):
def fn(x):
- yield f'x:{yield (lambda i: x * i)}'
+ yield f"x:{yield (lambda i: x * i)}"
g = fn(10)
the_lambda = next(g)
self.assertEqual(the_lambda(4), 40)
- self.assertEqual(g.send('string'), 'x:string')
+ self.assertEqual(g.send("string"), "x:string")
- def test_expressions_with_triple_quoted_strings(self):
- self.assertEqual(f"{'''x'''}", 'x')
- # TODO: RUSTPYTHON self.assertEqual(f"{'''eric's'''}", "eric's")
+ # TODO: RUSTPYTHON SyntaxError
+ # def test_expressions_with_triple_quoted_strings(self):
+ # self.assertEqual(f"{'''x'''}", 'x')
+ # self.assertEqual(f"{'''eric's'''}", "eric's")
- # Test concatenation within an expression
- # TODO: RUSTPYTHON self.assertEqual(f'{"x" """eric"s""" "y"}', 'xeric"sy')
- # TODO: RUSTPYTHON self.assertEqual(f'{"x" """eric"s"""}', 'xeric"s')
- # TODO: RUSTPYTHON self.assertEqual(f'{"""eric"s""" "y"}', 'eric"sy')
- # TODO: RUSTPYTHON self.assertEqual(f'{"""x""" """eric"s""" "y"}', 'xeric"sy')
- # TODO: RUSTPYTHON self.assertEqual(f'{"""x""" """eric"s""" """y"""}', 'xeric"sy')
- # TODO: RUSTPYTHON self.assertEqual(f'{r"""x""" """eric"s""" """y"""}', 'xeric"sy')
+ # # Test concatenation within an expression
+ # self.assertEqual(f'{"x" """eric"s""" "y"}', 'xeric"sy')
+ # self.assertEqual(f'{"x" """eric"s"""}', 'xeric"s')
+ # self.assertEqual(f'{"""eric"s""" "y"}', 'eric"sy')
+ # self.assertEqual(f'{"""x""" """eric"s""" "y"}', 'xeric"sy')
+ # self.assertEqual(f'{"""x""" """eric"s""" """y"""}', 'xeric"sy')
+ # self.assertEqual(f'{r"""x""" """eric"s""" """y"""}', 'xeric"sy')
def test_multiple_vars(self):
x = 98
- y = 'abc'
- self.assertEqual(f'{x}{y}', '98abc')
+ y = "abc"
+ self.assertEqual(f"{x}{y}", "98abc")
- self.assertEqual(f'X{x}{y}', 'X98abc')
- self.assertEqual(f'{x}X{y}', '98Xabc')
- self.assertEqual(f'{x}{y}X', '98abcX')
+ self.assertEqual(f"X{x}{y}", "X98abc")
+ self.assertEqual(f"{x}X{y}", "98Xabc")
+ self.assertEqual(f"{x}{y}X", "98abcX")
- self.assertEqual(f'X{x}Y{y}', 'X98Yabc')
- self.assertEqual(f'X{x}{y}Y', 'X98abcY')
- self.assertEqual(f'{x}X{y}Y', '98XabcY')
+ self.assertEqual(f"X{x}Y{y}", "X98Yabc")
+ self.assertEqual(f"X{x}{y}Y", "X98abcY")
+ self.assertEqual(f"{x}X{y}Y", "98XabcY")
- self.assertEqual(f'X{x}Y{y}Z', 'X98YabcZ')
+ self.assertEqual(f"X{x}Y{y}Z", "X98YabcZ")
def test_closure(self):
def outer(x):
def inner():
- return f'x:{x}'
+ return f"x:{x}"
+
return inner
- self.assertEqual(outer('987')(), 'x:987')
- self.assertEqual(outer(7)(), 'x:7')
+ self.assertEqual(outer("987")(), "x:987")
+ self.assertEqual(outer(7)(), "x:7")
def test_arguments(self):
y = 2
+
def f(x, width):
- return f'x={x*y:{width}}'
+ return f"x={x*y:{width}}"
- self.assertEqual(f('foo', 10), 'x=foofoo ')
- x = 'bar'
- self.assertEqual(f(10, 10), 'x= 20')
+ self.assertEqual(f("foo", 10), "x=foofoo ")
+ x = "bar"
+ self.assertEqual(f(10, 10), "x= 20")
def test_locals(self):
value = 123
- self.assertEqual(f'v:{value}', 'v:123')
+ self.assertEqual(f"v:{value}", "v:123")
def test_missing_variable(self):
with self.assertRaises(NameError):
- f'v:{value}'
+ f"v:{value}"
def test_missing_format_spec(self):
class O:
def __format__(self, spec):
if not spec:
- return '*'
+ return "*"
return spec
- self.assertEqual(f'{O():x}', 'x')
- self.assertEqual(f'{O()}', '*')
- self.assertEqual(f'{O():}', '*')
+ self.assertEqual(f"{O():x}", "x")
+ self.assertEqual(f"{O()}", "*")
+ self.assertEqual(f"{O():}", "*")
- self.assertEqual(f'{3:}', '3')
- self.assertEqual(f'{3!s:}', '3')
+ self.assertEqual(f"{3:}", "3")
+ self.assertEqual(f"{3!s:}", "3")
def test_global(self):
- self.assertEqual(f'g:{a_global}', 'g:global variable')
- self.assertEqual(f'g:{a_global!r}', "g:'global variable'")
+ self.assertEqual(f"g:{a_global}", "g:global variable")
+ self.assertEqual(f"g:{a_global!r}", "g:'global variable'")
- a_local = 'local variable'
- self.assertEqual(f'g:{a_global} l:{a_local}',
- 'g:global variable l:local variable')
- self.assertEqual(f'g:{a_global!r}',
- "g:'global variable'")
- self.assertEqual(f'g:{a_global} l:{a_local!r}',
- "g:global variable l:'local variable'")
+ a_local = "local variable"
+ self.assertEqual(
+ f"g:{a_global} l:{a_local}", "g:global variable l:local variable"
+ )
+ self.assertEqual(f"g:{a_global!r}", "g:'global variable'")
+ self.assertEqual(
+ f"g:{a_global} l:{a_local!r}", "g:global variable l:'local variable'"
+ )
- self.assertIn("module 'unittest' from", f'{unittest}')
+ self.assertIn("module 'unittest' from", f"{unittest}")
def test_shadowed_global(self):
- a_global = 'really a local'
- self.assertEqual(f'g:{a_global}', 'g:really a local')
- self.assertEqual(f'g:{a_global!r}', "g:'really a local'")
-
- a_local = 'local variable'
- self.assertEqual(f'g:{a_global} l:{a_local}',
- 'g:really a local l:local variable')
- self.assertEqual(f'g:{a_global!r}',
- "g:'really a local'")
- self.assertEqual(f'g:{a_global} l:{a_local!r}',
- "g:really a local l:'local variable'")
+ a_global = "really a local"
+ self.assertEqual(f"g:{a_global}", "g:really a local")
+ self.assertEqual(f"g:{a_global!r}", "g:'really a local'")
+
+ a_local = "local variable"
+ self.assertEqual(
+ f"g:{a_global} l:{a_local}", "g:really a local l:local variable"
+ )
+ self.assertEqual(f"g:{a_global!r}", "g:'really a local'")
+ self.assertEqual(
+ f"g:{a_global} l:{a_local!r}", "g:really a local l:'local variable'"
+ )
def test_call(self):
def foo(x):
- return 'x=' + str(x)
+ return "x=" + str(x)
- self.assertEqual(f'{foo(10)}', 'x=10')
+ self.assertEqual(f"{foo(10)}", "x=10")
def test_nested_fstrings(self):
y = 5
- self.assertEqual(f'{f"{0}"*3}', '000')
- self.assertEqual(f'{f"{y}"*3}', '555')
+ self.assertEqual(f'{f"{0}"*3}', "000")
+ self.assertEqual(f'{f"{y}"*3}', "555")
def test_invalid_string_prefixes(self):
- single_quote_cases = ["fu''",
- "uf''",
- "Fu''",
- "fU''",
- "Uf''",
- "uF''",
- "ufr''",
- "urf''",
- "fur''",
- "fru''",
- "rfu''",
- "ruf''",
- "FUR''",
- "Fur''",
- "fb''",
- "fB''",
- "Fb''",
- "FB''",
- "bf''",
- "bF''",
- "Bf''",
- "BF''",]
+ single_quote_cases = [
+ "fu''",
+ "uf''",
+ "Fu''",
+ "fU''",
+ "Uf''",
+ "uF''",
+ "ufr''",
+ "urf''",
+ "fur''",
+ "fru''",
+ "rfu''",
+ "ruf''",
+ "FUR''",
+ "Fur''",
+ "fb''",
+ "fB''",
+ "Fb''",
+ "FB''",
+ "bf''",
+ "bF''",
+ "Bf''",
+ "BF''",
+ ]
double_quote_cases = [case.replace("'", '"') for case in single_quote_cases]
- self.assertAllRaise(SyntaxError, 'invalid syntax',
- single_quote_cases + double_quote_cases)
+ self.assertAllRaise(
+ SyntaxError, "invalid syntax", single_quote_cases + double_quote_cases
+ )
def test_leading_trailing_spaces(self):
- self.assertEqual(f'{ 3}', '3')
- self.assertEqual(f'{ 3}', '3')
- self.assertEqual(f'{3 }', '3')
- self.assertEqual(f'{3 }', '3')
+ self.assertEqual(f"{ 3}", "3")
+ self.assertEqual(f"{ 3}", "3")
+ self.assertEqual(f"{3 }", "3")
+ self.assertEqual(f"{3 }", "3")
- self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]}}',
- 'expr={1: 2}')
- self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]} }',
- 'expr={1: 2}')
+ self.assertEqual(f"expr={ {x: y for x, y in [(1, 2), ]}}", "expr={1: 2}")
+ self.assertEqual(f"expr={ {x: y for x, y in [(1, 2), ]} }", "expr={1: 2}")
def test_not_equal(self):
# There's a special test for this because there's a special
# case in the f-string parser to look for != as not ending an
# expression. Normally it would, while looking for !s or !r.
- self.assertEqual(f'{3!=4}', 'True')
- self.assertEqual(f'{3!=4:}', 'True')
- self.assertEqual(f'{3!=4!s}', 'True')
- self.assertEqual(f'{3!=4!s:.3}', 'Tru')
+ self.assertEqual(f"{3!=4}", "True")
+ self.assertEqual(f"{3!=4:}", "True")
+ self.assertEqual(f"{3!=4!s}", "True")
+ self.assertEqual(f"{3!=4!s:.3}", "Tru")
def test_equal_equal(self):
# Because an expression ending in = has special meaning,
# there's a special test for ==. Make sure it works.
- self.assertEqual(f'{0==1}', 'False')
+ self.assertEqual(f"{0==1}", "False")
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_conversions(self):
- self.assertEqual(f'{3.14:10.10}', ' 3.14')
- self.assertEqual(f'{3.14!s:10.10}', '3.14 ')
- self.assertEqual(f'{3.14!r:10.10}', '3.14 ')
- self.assertEqual(f'{3.14!a:10.10}', '3.14 ')
+ self.assertEqual(f"{3.14:10.10}", " 3.14")
+ self.assertEqual(f"{3.14!s:10.10}", "3.14 ")
+ self.assertEqual(f"{3.14!r:10.10}", "3.14 ")
+ self.assertEqual(f"{3.14!a:10.10}", "3.14 ")
- self.assertEqual(f'{"a"}', 'a')
+ self.assertEqual(f'{"a"}', "a")
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
+ # TODO: RUSTPYTHON SyntaxError
+ # 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")
# Not a conversion, but show that ! is allowed in a format spec.
- self.assertEqual(f'{3.14:!<10.10}', '3.14!!!!!!')
-
- self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character',
- ["f'{3!g}'",
- "f'{3!A}'",
- "f'{3!3}'",
- "f'{3!G}'",
- "f'{3!!}'",
- "f'{3!:}'",
- "f'{3! s}'", # no space before conversion char
- ])
-
- self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
- ["f'{x!s{y}}'",
- "f'{3!ss}'",
- "f'{3!ss:}'",
- "f'{3!ss:s}'",
- ])
+ self.assertEqual(f"{3.14:!<10.10}", "3.14!!!!!!")
+
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: expecting '}'",
+ [
+ "f'{3!'",
+ "f'{3!s'",
+ "f'{3!g'",
+ ],
+ )
+
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: missing conversion character",
+ [
+ "f'{3!}'",
+ "f'{3!:'",
+ "f'{3!:}'",
+ ],
+ )
+
+ for conv_identifier in "g", "A", "G", "ä", "ɐ":
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: invalid conversion character %r: "
+ "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,
+ "f-string: invalid conversion character 'ss': " "expected 's', 'r', or 'a'",
+ [
+ "f'{3!ss}'",
+ "f'{3!ss:}'",
+ "f'{3!ss:s}'",
+ ],
+ )
def test_assignment(self):
- self.assertAllRaise(SyntaxError, r'invalid syntax',
- ["f'' = 3",
- "f'{0}' = x",
- "f'{x}' = x",
- ])
+ self.assertAllRaise(
+ SyntaxError,
+ r"invalid syntax",
+ [
+ "f'' = 3",
+ "f'{0}' = x",
+ "f'{x}' = x",
+ ],
+ )
def test_del(self):
- self.assertAllRaise(SyntaxError, 'invalid syntax',
- ["del f''",
- "del '' f''",
- ])
+ self.assertAllRaise(
+ SyntaxError,
+ "invalid syntax",
+ [
+ "del f''",
+ "del '' f''",
+ ],
+ )
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_mismatched_braces(self):
- self.assertAllRaise(SyntaxError, "f-string: single '}' is not allowed",
- ["f'{{}'",
- "f'{{}}}'",
- "f'}'",
- "f'x}'",
- "f'x}x'",
- r"f'\u007b}'",
-
- # Can't have { or } in a format spec.
- "f'{3:}>10}'",
- "f'{3:}}>10}'",
- ])
-
- self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
- ["f'{3:{{>10}'",
- "f'{3'",
- "f'{3!'",
- "f'{3:'",
- "f'{3!s'",
- "f'{3!s:'",
- "f'{3!s:3'",
- "f'x{'",
- "f'x{x'",
- "f'{x'",
- "f'{3:s'",
- "f'{{{'",
- "f'{{}}{'",
- "f'{'",
- "f'x{<'", # See bpo-46762.
- "f'x{>'",
- "f'{i='", # See gh-93418.
- ])
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: single '}' is not allowed",
+ [
+ "f'{{}'",
+ "f'{{}}}'",
+ "f'}'",
+ "f'x}'",
+ "f'x}x'",
+ r"f'\u007b}'",
+ # Can't have { or } in a format spec.
+ "f'{3:}>10}'",
+ "f'{3:}}>10}'",
+ ],
+ )
+
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: expecting '}'",
+ [
+ "f'{3'",
+ "f'{3!'",
+ "f'{3:'",
+ "f'{3!s'",
+ "f'{3!s:'",
+ "f'{3!s:3'",
+ "f'x{'",
+ "f'x{x'",
+ "f'{x'",
+ "f'{3:s'",
+ "f'{{{'",
+ "f'{{}}{'",
+ "f'{'",
+ "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'{"}"}', '}')
- self.assertEqual(f'{3:{"}"}>10}', '}}}}}}}}}3')
- self.assertEqual(f'{2:{"{"}>10}', '{{{{{{{{{2')
+ self.assertEqual(f'{"{"}', "{")
+ self.assertEqual(f'{"}"}', "}")
+ self.assertEqual(f'{3:{"}"}>10}', "}}}}}}}}}3")
+ self.assertEqual(f'{2:{"{"}>10}', "{{{{{{{{{2")
def test_if_conditional(self):
# There's special logic in compile.c to test if the
@@ -1132,7 +1587,7 @@ def test_if_conditional(self):
def test_fstring(x, expected):
flag = 0
- if f'{x}':
+ if f"{x}":
flag = 1
else:
flag = 2
@@ -1140,7 +1595,7 @@ def test_fstring(x, expected):
def test_concat_empty(x, expected):
flag = 0
- if '' f'{x}':
+ if "" f"{x}":
flag = 1
else:
flag = 2
@@ -1148,141 +1603,153 @@ def test_concat_empty(x, expected):
def test_concat_non_empty(x, expected):
flag = 0
- if ' ' f'{x}':
+ if " " f"{x}":
flag = 1
else:
flag = 2
self.assertEqual(flag, expected)
- test_fstring('', 2)
- test_fstring(' ', 1)
+ test_fstring("", 2)
+ test_fstring(" ", 1)
- test_concat_empty('', 2)
- test_concat_empty(' ', 1)
+ test_concat_empty("", 2)
+ test_concat_empty(" ", 1)
- test_concat_non_empty('', 1)
- test_concat_non_empty(' ', 1)
+ test_concat_non_empty("", 1)
+ test_concat_non_empty(" ", 1)
def test_empty_format_specifier(self):
- x = 'test'
- self.assertEqual(f'{x}', 'test')
- self.assertEqual(f'{x:}', 'test')
- self.assertEqual(f'{x!s:}', 'test')
- self.assertEqual(f'{x!r:}', "'test'")
+ x = "test"
+ self.assertEqual(f"{x}", "test")
+ self.assertEqual(f"{x:}", "test")
+ self.assertEqual(f"{x!s:}", "test")
+ self.assertEqual(f"{x!r:}", "'test'")
# TODO: RUSTPYTHON d[0] error
@unittest.expectedFailure
def test_str_format_differences(self):
- d = {'a': 'string',
- 0: 'integer',
- }
+ d = {
+ "a": "string",
+ 0: "integer",
+ }
a = 0
- self.assertEqual(f'{d[0]}', 'integer')
- self.assertEqual(f'{d["a"]}', 'string')
- self.assertEqual(f'{d[a]}', 'integer')
- self.assertEqual('{d[a]}'.format(d=d), 'string')
- self.assertEqual('{d[0]}'.format(d=d), 'integer')
+ self.assertEqual(f"{d[0]}", "integer")
+ self.assertEqual(f'{d["a"]}', "string")
+ self.assertEqual(f"{d[a]}", "integer")
+ self.assertEqual("{d[a]}".format(d=d), "string")
+ self.assertEqual("{d[0]}".format(d=d), "integer")
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_errors(self):
# see issue 26287
- self.assertAllRaise(TypeError, 'unsupported',
- [r"f'{(lambda: 0):x}'",
- r"f'{(0,):x}'",
- ])
- self.assertAllRaise(ValueError, 'Unknown format code',
- [r"f'{1000:j}'",
- r"f'{1000:j}'",
- ])
+ self.assertAllRaise(
+ TypeError,
+ "unsupported",
+ [
+ r"f'{(lambda: 0):x}'",
+ r"f'{(0,):x}'",
+ ],
+ )
+ self.assertAllRaise(
+ ValueError,
+ "Unknown format code",
+ [
+ r"f'{1000:j}'",
+ r"f'{1000:j}'",
+ ],
+ )
def test_filename_in_syntaxerror(self):
# see issue 38964
with temp_cwd() as cwd:
- file_path = os.path.join(cwd, 't.py')
- with open(file_path, 'w', encoding="utf-8") as f:
- f.write('f"{a b}"') # This generates a SyntaxError
- _, _, stderr = assert_python_failure(file_path,
- PYTHONIOENCODING='ascii')
- self.assertIn(file_path.encode('ascii', 'backslashreplace'), stderr)
+ file_path = os.path.join(cwd, "t.py")
+ with open(file_path, "w", encoding="utf-8") as f:
+ f.write('f"{a b}"') # This generates a SyntaxError
+ _, _, stderr = assert_python_failure(file_path, PYTHONIOENCODING="ascii")
+ self.assertIn(file_path.encode("ascii", "backslashreplace"), stderr)
def test_loop(self):
for i in range(1000):
- self.assertEqual(f'i:{i}', 'i:' + str(i))
+ self.assertEqual(f"i:{i}", "i:" + str(i))
def test_dict(self):
- d = {'"': 'dquote',
- "'": 'squote',
- 'foo': 'bar',
- }
- self.assertEqual(f'''{d["'"]}''', 'squote')
- self.assertEqual(f"""{d['"']}""", 'dquote')
+ d = {
+ '"': "dquote",
+ "'": "squote",
+ "foo": "bar",
+ }
+ self.assertEqual(f"""{d["'"]}""", "squote")
+ self.assertEqual(f"""{d['"']}""", "dquote")
- self.assertEqual(f'{d["foo"]}', 'bar')
- self.assertEqual(f"{d['foo']}", 'bar')
+ self.assertEqual(f'{d["foo"]}', "bar")
+ self.assertEqual(f"{d['foo']}", "bar")
def test_backslash_char(self):
# Check eval of a backslash followed by a control char.
# See bpo-30682: this used to raise an assert in pydebug mode.
- self.assertEqual(eval('f"\\\n"'), '')
- self.assertEqual(eval('f"\\\r"'), '')
+ self.assertEqual(eval('f"\\\n"'), "")
+ self.assertEqual(eval('f"\\\r"'), "")
def test_debug_conversion(self):
- x = 'A string'
- self.assertEqual(f'{x=}', 'x=' + repr(x))
- self.assertEqual(f'{x =}', 'x =' + repr(x))
- self.assertEqual(f'{x=!s}', 'x=' + str(x))
- self.assertEqual(f'{x=!r}', 'x=' + repr(x))
- self.assertEqual(f'{x=!a}', 'x=' + ascii(x))
+ x = "A string"
+ self.assertEqual(f"{x=}", "x=" + repr(x))
+ self.assertEqual(f"{x =}", "x =" + repr(x))
+ self.assertEqual(f"{x=!s}", "x=" + str(x))
+ self.assertEqual(f"{x=!r}", "x=" + repr(x))
+ self.assertEqual(f"{x=!a}", "x=" + ascii(x))
x = 2.71828
- self.assertEqual(f'{x=:.2f}', 'x=' + format(x, '.2f'))
- self.assertEqual(f'{x=:}', 'x=' + format(x, ''))
- self.assertEqual(f'{x=!r:^20}', 'x=' + format(repr(x), '^20'))
- self.assertEqual(f'{x=!s:^20}', 'x=' + format(str(x), '^20'))
- self.assertEqual(f'{x=!a:^20}', 'x=' + format(ascii(x), '^20'))
+ self.assertEqual(f"{x=:.2f}", "x=" + format(x, ".2f"))
+ self.assertEqual(f"{x=:}", "x=" + format(x, ""))
+ self.assertEqual(f"{x=!r:^20}", "x=" + format(repr(x), "^20"))
+ self.assertEqual(f"{x=!s:^20}", "x=" + format(str(x), "^20"))
+ self.assertEqual(f"{x=!a:^20}", "x=" + format(ascii(x), "^20"))
x = 9
- self.assertEqual(f'{3*x+15=}', '3*x+15=42')
+ self.assertEqual(f"{3*x+15=}", "3*x+15=42")
# There is code in ast.c that deals with non-ascii expression values. So,
# use a unicode identifier to trigger that.
tenπ = 31.4
- self.assertEqual(f'{tenπ=:.2f}', 'tenπ=31.40')
+ self.assertEqual(f"{tenπ=:.2f}", "tenπ=31.40")
# Also test with Unicode in non-identifiers.
- self.assertEqual(f'{"Σ"=}', '"Σ"=\'Σ\'')
+ self.assertEqual(f'{"Σ"=}', "\"Σ\"='Σ'")
# Make sure nested fstrings still work.
- self.assertEqual(f'{f"{3.1415=:.1f}":*^20}', '*****3.1415=3.1*****')
+ self.assertEqual(f'{f"{3.1415=:.1f}":*^20}', "*****3.1415=3.1*****")
# Make sure text before and after an expression with = works
# correctly.
- pi = 'π'
- self.assertEqual(f'alpha α {pi=} ω omega', "alpha α pi='π' ω omega")
+ pi = "π"
+ self.assertEqual(f"alpha α {pi=} ω omega", "alpha α pi='π' ω omega")
# Check multi-line expressions.
- self.assertEqual(f'''{
+ self.assertEqual(
+ f"""{
3
-=}''', '\n3\n=3')
+=}""",
+ "\n3\n=3",
+ )
# Since = is handled specially, make sure all existing uses of
# it still work.
- self.assertEqual(f'{0==1}', 'False')
- self.assertEqual(f'{0!=1}', 'True')
- self.assertEqual(f'{0<=1}', 'True')
- self.assertEqual(f'{0>=1}', 'False')
- self.assertEqual(f'{(x:="5")}', '5')
- self.assertEqual(x, '5')
- self.assertEqual(f'{(x:=5)}', '5')
+ self.assertEqual(f"{0==1}", "False")
+ self.assertEqual(f"{0!=1}", "True")
+ self.assertEqual(f"{0<=1}", "True")
+ self.assertEqual(f"{0>=1}", "False")
+ self.assertEqual(f'{(x:="5")}', "5")
+ self.assertEqual(x, "5")
+ self.assertEqual(f"{(x:=5)}", "5")
self.assertEqual(x, 5)
- self.assertEqual(f'{"="}', '=')
+ self.assertEqual(f'{"="}', "=")
x = 20
# This isn't an assignment expression, it's 'x', with a format
# spec of '=10'. See test_walrus: you need to use parens.
- self.assertEqual(f'{x:=10}', ' 20')
+ self.assertEqual(f"{x:=10}", " 20")
# Test named function parameters, to make sure '=' parsing works
# there.
@@ -1291,36 +1758,54 @@ def f(a):
oldx = x
x = a
return oldx
+
x = 0
- self.assertEqual(f'{f(a="3=")}', '0')
- self.assertEqual(x, '3=')
- self.assertEqual(f'{f(a=4)}', '3=')
+ self.assertEqual(f'{f(a="3=")}', "0")
+ self.assertEqual(x, "3=")
+ self.assertEqual(f"{f(a=4)}", "3=")
self.assertEqual(x, 4)
+ # Check debug expressions in format spec
+ y = 20
+ self.assertEqual(f"{2:{y=}}", "yyyyyyyyyyyyyyyyyyy2")
+ self.assertEqual(
+ f"{datetime.datetime.now():h1{y=}h2{y=}h3{y=}}", "h1y=20h2y=20h3y=20"
+ )
+
# Make sure __format__ is being called.
class C:
def __format__(self, s):
- return f'FORMAT-{s}'
+ return f"FORMAT-{s}"
+
def __repr__(self):
- return 'REPR'
+ return "REPR"
- self.assertEqual(f'{C()=}', 'C()=REPR')
- self.assertEqual(f'{C()=!r}', 'C()=REPR')
- self.assertEqual(f'{C()=:}', 'C()=FORMAT-')
- self.assertEqual(f'{C()=: }', 'C()=FORMAT- ')
- self.assertEqual(f'{C()=:x}', 'C()=FORMAT-x')
- self.assertEqual(f'{C()=!r:*^20}', 'C()=********REPR********')
+ self.assertEqual(f"{C()=}", "C()=REPR")
+ self.assertEqual(f"{C()=!r}", "C()=REPR")
+ self.assertEqual(f"{C()=:}", "C()=FORMAT-")
+ self.assertEqual(f"{C()=: }", "C()=FORMAT- ")
+ self.assertEqual(f"{C()=:x}", "C()=FORMAT-x")
+ self.assertEqual(f"{C()=!r:*^20}", "C()=********REPR********")
+ self.assertEqual(f"{C():{20=}}", "FORMAT-20=20")
self.assertRaises(SyntaxError, eval, "f'{C=]'")
# Make sure leading and following text works.
- x = 'foo'
- self.assertEqual(f'X{x=}Y', 'Xx='+repr(x)+'Y')
+ x = "foo"
+ self.assertEqual(f"X{x=}Y", "Xx=" + repr(x) + "Y")
# Make sure whitespace around the = works.
- 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"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")
+
+# TODO: RUSTPYTHON SyntaxError
+# self.assertEqual(
+# f"{1+2 = # my comment
+# }",
+# "1+2 = \n 3",
+# )
# These next lines contains tabs. Backslash escapes don't
# work in f-strings.
@@ -1328,23 +1813,25 @@ def __repr__(self):
# this will be to dynamically created and exec the f-strings. But
# that's such a hassle I'll save it for another day. For now, convert
# the tabs to spaces just to shut up patchcheck.
- #self.assertEqual(f'X{x =}Y', 'Xx\t='+repr(x)+'Y')
- #self.assertEqual(f'X{x = }Y', 'Xx\t=\t'+repr(x)+'Y')
+ # self.assertEqual(f'X{x =}Y', 'Xx\t='+repr(x)+'Y')
+ # self.assertEqual(f'X{x = }Y', 'Xx\t=\t'+repr(x)+'Y')
def test_walrus(self):
x = 20
# This isn't an assignment expression, it's 'x', with a format
# spec of '=10'.
- self.assertEqual(f'{x:=10}', ' 20')
+ self.assertEqual(f"{x:=10}", " 20")
# This is an assignment expression, which requires parens.
- self.assertEqual(f'{(x:=10)}', '10')
+ self.assertEqual(f"{(x:=10)}", "10")
self.assertEqual(x, 10)
# TODO: RUSTPYTHON
@unittest.expectedFailure
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")
# TODO: RUSTPYTHON
@@ -1352,37 +1839,112 @@ def test_invalid_syntax_error_message(self):
def test_with_two_commas_in_format_specifier(self):
error_msg = re.escape("Cannot specify ',' with ','.")
with self.assertRaisesRegex(ValueError, error_msg):
- f'{1:,,}'
+ f"{1:,,}"
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_with_two_underscore_in_format_specifier(self):
error_msg = re.escape("Cannot specify '_' with '_'.")
with self.assertRaisesRegex(ValueError, error_msg):
- f'{1:__}'
+ f"{1:__}"
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_with_a_commas_and_an_underscore_in_format_specifier(self):
error_msg = re.escape("Cannot specify both ',' and '_'.")
with self.assertRaisesRegex(ValueError, error_msg):
- f'{1:,_}'
+ f"{1:,_}"
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_with_an_underscore_and_a_comma_in_format_specifier(self):
error_msg = re.escape("Cannot specify both ',' and '_'.")
with self.assertRaisesRegex(ValueError, error_msg):
- f'{1:_,}'
+ f"{1:_,}"
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
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__':
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_not_closing_quotes(self):
+ self.assertAllRaise(SyntaxError, "unterminated f-string literal", ['f"', "f'"])
+ self.assertAllRaise(
+ SyntaxError, "unterminated triple-quoted f-string literal", ['f"""', "f'''"]
+ )
+ # Ensure that the errors are reported at the correct line number.
+ data = '''\
+x = 1 + 1
+y = 2 + 2
+z = f"""
+sdfjnsdfjsdf
+sdfsdfs{1+
+2} dfigdf {3+
+4}sdufsd""
+'''
+ try:
+ compile(data, "?", "exec")
+ except SyntaxError as e:
+ self.assertEqual(e.text, 'z = f"""')
+ self.assertEqual(e.lineno, 3)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_syntax_error_after_debug(self):
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: expecting a valid expression after '{'",
+ [
+ "f'{1=}{;'",
+ "f'{1=}{+;'",
+ "f'{1=}{2}{;'",
+ "f'{1=}{3}{;'",
+ ],
+ )
+ self.assertAllRaise(
+ SyntaxError,
+ "f-string: expecting '=', or '!', or ':', or '}'",
+ [
+ "f'{1=}{1;'",
+ "f'{1=}{1;}'",
+ ],
+ )
+
+ def test_debug_in_file(self):
+ with temp_cwd():
+ script = "script.py"
+ with open("script.py", "w") as f:
+ f.write(f"""\
+print(f'''{{
+3
+=}}''')""")
+
+ _, stdout, _ = assert_python_ok(script)
+ self.assertEqual(
+ stdout.decode("utf-8").strip().replace("\r\n", "\n").replace("\r", "\n"),
+ "3\n=3",
+ )
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_syntax_warning_infinite_recursion_in_file(self):
+ with temp_cwd():
+ script = "script.py"
+ with open(script, "w") as f:
+ f.write(r"print(f'\{1}')")
+
+ _, stdout, stderr = assert_python_ok(script)
+ self.assertIn(rb"\1", stdout)
+ self.assertEqual(len(stderr.strip().splitlines()), 2)
+
+
+if __name__ == "__main__":
unittest.main()
From e2a55cbf34dc999c29f5e0984a24e727246f9fcd Mon Sep 17 00:00:00 2001
From: Shubham Patil
Date: Mon, 6 Jan 2025 19:35:14 +0530
Subject: [PATCH 002/317] Handle pre-release flag being empty for schedule
triggers in release workflow
GitHub workflow_dispatch input variables are always empty for other triggers
---
.github/workflows/release.yml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8933d2107d..70255d8082 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -127,12 +127,12 @@ jobs:
tag: ${{ github.ref_name }}
run: ${{ github.run_number }}
run: |
- if [[ "${{ github.event.inputs.pre-release }}" == "true" ]]; then
- RELEASE_TYPE_NAME=Pre-Release
- PRERELEASE_ARG=--prerelease
- else
+ if [[ "${{ github.event.inputs.pre-release }}" == "false" ]]; then
RELEASE_TYPE_NAME=Release
PRERELEASE_ARG=
+ else
+ RELEASE_TYPE_NAME=Pre-Release
+ PRERELEASE_ARG=--prerelease
fi
today=$(date '+%Y-%m-%d')
From d06459fa49b12cd0164844d1164d0ba97ec7db0a Mon Sep 17 00:00:00 2001
From: Bob McWhirter
Date: Wed, 8 Jan 2025 14:03:15 -0500
Subject: [PATCH 003/317] guard signal-handling init more broadly
If `install_signal_handlers` is false (due to embedding),
the VM still inits the signal stdlib and installs a lot
of signal-handling, including touching *ever* signal,
including SIGINT.
When running multiple concurrent interpreters with
varying inits at varying times, this can break the
hosting application's signal-handling so lovingly
set up before starting anything with RustPython.
---
vm/src/stdlib/signal.rs | 38 +++++++++++++++++++-------------------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/vm/src/stdlib/signal.rs b/vm/src/stdlib/signal.rs
index 83e9d861b2..73c9d978e6 100644
--- a/vm/src/stdlib/signal.rs
+++ b/vm/src/stdlib/signal.rs
@@ -110,28 +110,28 @@ pub(crate) mod _signal {
module: &Py,
vm: &VirtualMachine,
) {
- let sig_dfl = vm.new_pyobj(SIG_DFL as u8);
- let sig_ign = vm.new_pyobj(SIG_IGN as u8);
+ if vm.state.settings.install_signal_handlers {
+ let sig_dfl = vm.new_pyobj(SIG_DFL as u8);
+ let sig_ign = vm.new_pyobj(SIG_IGN as u8);
- for signum in 1..NSIG {
- let handler = unsafe { libc::signal(signum as i32, SIG_IGN) };
- if handler != SIG_ERR {
- unsafe { libc::signal(signum as i32, handler) };
+ for signum in 1..NSIG {
+ let handler = unsafe { libc::signal(signum as i32, SIG_IGN) };
+ if handler != SIG_ERR {
+ unsafe { libc::signal(signum as i32, handler) };
+ }
+ let py_handler = if handler == SIG_DFL {
+ Some(sig_dfl.clone())
+ } else if handler == SIG_IGN {
+ Some(sig_ign.clone())
+ } else {
+ None
+ };
+ vm.signal_handlers.as_deref().unwrap().borrow_mut()[signum] = py_handler;
}
- let py_handler = if handler == SIG_DFL {
- Some(sig_dfl.clone())
- } else if handler == SIG_IGN {
- Some(sig_ign.clone())
- } else {
- None
- };
- vm.signal_handlers.as_deref().unwrap().borrow_mut()[signum] = py_handler;
- }
- let int_handler = module
- .get_attr("default_int_handler", vm)
- .expect("_signal does not have this attr?");
- if vm.state.settings.install_signal_handlers {
+ let int_handler = module
+ .get_attr("default_int_handler", vm)
+ .expect("_signal does not have this attr?");
signal(libc::SIGINT, int_handler, vm).expect("Failed to set sigint handler");
}
}
From b7db23bbae1198cc0c97e35e0db02fca84371509 Mon Sep 17 00:00:00 2001
From: Noa
Date: Thu, 9 Jan 2025 15:59:44 -0600
Subject: [PATCH 004/317] Fix warnings for Rust 1.84
---
Cargo.lock | 21 ++++++++++-----------
common/src/int.rs | 6 +++---
compiler/codegen/src/compile.rs | 15 ++++++---------
derive-impl/src/pyclass.rs | 2 +-
derive-impl/src/util.rs | 2 +-
src/shell/helper.rs | 2 +-
stdlib/src/array.rs | 2 +-
stdlib/src/contextvars.rs | 14 ++++++--------
stdlib/src/unicodedata.rs | 2 +-
vm/src/anystr.rs | 7 ++-----
vm/src/builtins/asyncgenerator.rs | 2 +-
vm/src/builtins/descriptor.rs | 2 +-
vm/src/builtins/mappingproxy.rs | 2 +-
vm/src/builtins/str.rs | 9 +++------
vm/src/cformat.rs | 4 ++--
vm/src/protocol/mapping.rs | 2 +-
vm/src/stdlib/builtins.rs | 3 +--
vm/src/stdlib/itertools.rs | 2 +-
vm/src/stdlib/nt.rs | 2 +-
vm/src/stdlib/os.rs | 4 ++--
vm/src/types/slot.rs | 4 ++--
vm/src/vm/vm_ops.rs | 4 ++--
vm/src/warn.rs | 2 +-
wasm/lib/src/convert.rs | 2 +-
24 files changed, 52 insertions(+), 65 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 0a9e1c9508..5d5db7f5ca 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2946,9 +2946,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
-version = "0.2.93"
+version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
+checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
dependencies = [
"cfg-if",
"once_cell",
@@ -2957,13 +2957,12 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.93"
+version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
+checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
dependencies = [
"bumpalo",
"log",
- "once_cell",
"proc-macro2",
"quote",
"syn 2.0.77",
@@ -2984,9 +2983,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.93"
+version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
+checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -2994,9 +2993,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.93"
+version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
+checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [
"proc-macro2",
"quote",
@@ -3007,9 +3006,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.93"
+version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
+checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
[[package]]
name = "web-sys"
diff --git a/common/src/int.rs b/common/src/int.rs
index 0c19f55141..ca449ac708 100644
--- a/common/src/int.rs
+++ b/common/src/int.rs
@@ -60,9 +60,9 @@ pub fn bytes_to_int(lit: &[u8], mut base: u32) -> Option {
return Some(BigInt::zero());
}
}
- 16 => lit.get(1).map_or(false, |&b| matches!(b, b'x' | b'X')),
- 2 => lit.get(1).map_or(false, |&b| matches!(b, b'b' | b'B')),
- 8 => lit.get(1).map_or(false, |&b| matches!(b, b'o' | b'O')),
+ 16 => lit.get(1).is_some_and(|&b| matches!(b, b'x' | b'X')),
+ 2 => lit.get(1).is_some_and(|&b| matches!(b, b'b' | b'B')),
+ 8 => lit.get(1).is_some_and(|&b| matches!(b, b'o' | b'O')),
_ => false,
}
} else {
diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs
index dc9f8a976e..eeb4b7dec6 100644
--- a/compiler/codegen/src/compile.rs
+++ b/compiler/codegen/src/compile.rs
@@ -3102,19 +3102,18 @@ impl Compiler {
Expr::Tuple(ExprTuple { elts, .. }) => elts.iter().any(Self::contains_await),
Expr::Set(ExprSet { elts, .. }) => elts.iter().any(Self::contains_await),
Expr::Dict(ExprDict { keys, values, .. }) => {
- keys.iter()
- .any(|key| key.as_ref().map_or(false, Self::contains_await))
+ keys.iter().flatten().any(Self::contains_await)
|| values.iter().any(Self::contains_await)
}
Expr::Slice(ExprSlice {
lower, upper, step, ..
}) => {
- lower.as_ref().map_or(false, |l| Self::contains_await(l))
- || upper.as_ref().map_or(false, |u| Self::contains_await(u))
- || step.as_ref().map_or(false, |s| Self::contains_await(s))
+ lower.as_deref().is_some_and(Self::contains_await)
+ || upper.as_deref().is_some_and(Self::contains_await)
+ || step.as_deref().is_some_and(Self::contains_await)
}
Expr::Yield(ExprYield { value, .. }) => {
- value.as_ref().map_or(false, |v| Self::contains_await(v))
+ value.as_deref().is_some_and(Self::contains_await)
}
Expr::Await(ExprAwait { .. }) => true,
Expr::YieldFrom(ExprYieldFrom { value, .. }) => Self::contains_await(value),
@@ -3128,9 +3127,7 @@ impl Compiler {
..
}) => {
Self::contains_await(value)
- || format_spec
- .as_ref()
- .map_or(false, |fs| Self::contains_await(fs))
+ || format_spec.as_deref().is_some_and(Self::contains_await)
}
Expr::Name(located_ast::ExprName { .. }) => false,
Expr::Lambda(located_ast::ExprLambda { body, .. }) => Self::contains_await(body),
diff --git a/derive-impl/src/pyclass.rs b/derive-impl/src/pyclass.rs
index a2896ffeed..dedb3eb77a 100644
--- a/derive-impl/src/pyclass.rs
+++ b/derive-impl/src/pyclass.rs
@@ -348,7 +348,7 @@ fn generate_class_def(
&& if let Ok(Meta::List(l)) = attr.parse_meta() {
l.nested
.into_iter()
- .any(|n| n.get_ident().map_or(false, |p| p == "PyStructSequence"))
+ .any(|n| n.get_ident().is_some_and(|p| p == "PyStructSequence"))
} else {
false
}
diff --git a/derive-impl/src/util.rs b/derive-impl/src/util.rs
index 916b19db06..f016b0d1e9 100644
--- a/derive-impl/src/util.rs
+++ b/derive-impl/src/util.rs
@@ -558,7 +558,7 @@ impl AttributeExt for Attribute {
let has_name = list
.nested
.iter()
- .any(|nested_meta| nested_meta.get_path().map_or(false, |p| p.is_ident(name)));
+ .any(|nested_meta| nested_meta.get_path().is_some_and(|p| p.is_ident(name)));
if !has_name {
list.nested.push(new_item())
}
diff --git a/src/shell/helper.rs b/src/shell/helper.rs
index 34691e7995..c228dbdf65 100644
--- a/src/shell/helper.rs
+++ b/src/shell/helper.rs
@@ -22,7 +22,7 @@ fn split_idents_on_dot(line: &str) -> Option<(usize, Vec)> {
match c {
'.' => {
// check for a double dot
- if i != 0 && words.last().map_or(false, |s| s.is_empty()) {
+ if i != 0 && words.last().is_some_and(|s| s.is_empty()) {
return None;
}
reverse_string(words.last_mut().unwrap());
diff --git a/stdlib/src/array.rs b/stdlib/src/array.rs
index 9f4540aa24..9bd58f8043 100644
--- a/stdlib/src/array.rs
+++ b/stdlib/src/array.rs
@@ -1238,7 +1238,7 @@ mod array {
let res = match array_a.cmp(&array_b) {
// fast path for same ArrayContentType type
- Ok(partial_ord) => partial_ord.map_or(false, |ord| op.eval_ord(ord)),
+ Ok(partial_ord) => partial_ord.is_some_and(|ord| op.eval_ord(ord)),
Err(()) => {
let iter = Iterator::zip(array_a.iter(vm), array_b.iter(vm));
diff --git a/stdlib/src/contextvars.rs b/stdlib/src/contextvars.rs
index 8e1bfca005..40a59050b3 100644
--- a/stdlib/src/contextvars.rs
+++ b/stdlib/src/contextvars.rs
@@ -129,18 +129,16 @@ mod _contextvars {
super::CONTEXTS.with(|ctxs| {
let mut ctxs = ctxs.borrow_mut();
- if !ctxs
- .last()
- .map_or(false, |ctx| ctx.get_id() == zelf.get_id())
- {
+ // TODO: use Vec::pop_if once stabilized
+ if ctxs.last().is_some_and(|ctx| ctx.get_id() == zelf.get_id()) {
+ let _ = ctxs.pop();
+ Ok(())
+ } else {
let msg =
"cannot exit context: thread state references a different context object"
.to_owned();
- return Err(vm.new_runtime_error(msg));
+ Err(vm.new_runtime_error(msg))
}
-
- let _ = ctxs.pop();
- Ok(())
})?;
zelf.inner.entered.set(false);
diff --git a/stdlib/src/unicodedata.rs b/stdlib/src/unicodedata.rs
index 56105ceca6..70483073a7 100644
--- a/stdlib/src/unicodedata.rs
+++ b/stdlib/src/unicodedata.rs
@@ -85,7 +85,7 @@ mod unicodedata {
}
fn check_age(&self, c: char) -> bool {
- Age::of(c).map_or(false, |age| age.actual() <= self.unic_version)
+ Age::of(c).is_some_and(|age| age.actual() <= self.unic_version)
}
fn extract_char(&self, character: PyStrRef, vm: &VirtualMachine) -> PyResult
-