@@ -619,6 +619,34 @@ def test_unterminated_string(self):
619619 r"""f'{("x}'""" ,
620620 ])
621621
622+ def test_unclosed_multiline_replacement_field (self ):
623+ for prefix in ('f' , 't' , 'rf' , 'rt' ):
624+ for quote in ('"' , "'" ):
625+ triple = quote * 3
626+ cases = (
627+ # The apparent closing quotes open a string in the field.
628+ ('var = "abc"\n a = PREFIXQUOTE {var} extern "C" { QUOTE\n '
629+ 'b = QUOTE string QUOTE' , 2 ),
630+ # Parentheses and dictionaries inside the field must not
631+ # change which opening brace the diagnostic identifies.
632+ ('a = PREFIXQUOTE{\n (QUOTE' , 1 ),
633+ ('a = PREFIXQUOTE{\n {0: QUOTE' , 1 ),
634+ # Use the innermost format field or formatted string.
635+ ('a = PREFIXQUOTE{0:\n {1\n QUOTE' , 2 ),
636+ ('a = f"{\n PREFIXQUOTE{1\n QUOTE' , 2 ),
637+ # Account for parentheses outside the formatted string.
638+ ('a = (PREFIXQUOTE{1\n QUOTE' , 1 ),
639+ )
640+ for source , lineno in cases :
641+ source = source .replace ('PREFIX' , prefix ).replace ('QUOTE' , triple )
642+ with self .subTest (source = source ):
643+ with self .assertRaises (SyntaxError ) as cm :
644+ compile (source , '<test>' , 'exec' )
645+ self .assertEqual (
646+ cm .exception .msg ,
647+ f"{ prefix [- 1 ]} -string: expecting '}}' to close '{{' "
648+ f"on line { lineno } " )
649+
622650 @unittest .skipIf (support .is_wasi , "exhausts limited stack on WASI" )
623651 def test_mismatched_parens (self ):
624652 self .assertAllRaise (SyntaxError , r"closing parenthesis '\}' "
0 commit comments