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