@@ -411,6 +411,50 @@ def test_ast_line_numbers_with_parentheses(self):
411411
412412 expr = """
413413x = (
414+ u'wat',
415+ u"wat",
416+ b'wat',
417+ b"wat",
418+ f'wat',
419+ f"wat",
420+ )
421+
422+ y = (
423+ u'''wat''',
424+ u\" \" \" wat\" \" \" ,
425+ b'''wat''',
426+ b\" \" \" wat\" \" \" ,
427+ f'''wat''',
428+ f\" \" \" wat\" \" \" ,
429+ )
430+ """
431+ t = ast .parse (expr )
432+ self .assertEqual (type (t ), ast .Module )
433+ self .assertEqual (len (t .body ), 2 )
434+ x , y = t .body
435+
436+ # Check the single quoted string offsets first.
437+ offsets = [
438+ (elt .col_offset , elt .end_col_offset )
439+ for elt in x .value .elts
440+ ]
441+ self .assertTrue (all (
442+ offset == (4 , 10 )
443+ for offset in offsets
444+ ))
445+
446+ # Check the triple quoted string offsets.
447+ offsets = [
448+ (elt .col_offset , elt .end_col_offset )
449+ for elt in y .value .elts
450+ ]
451+ self .assertTrue (all (
452+ offset == (4 , 14 )
453+ for offset in offsets
454+ ))
455+
456+ expr = """
457+ x = (
414458 'PERL_MM_OPT', (
415459 f'wat'
416460 f'some_string={f(x)} '
@@ -444,7 +488,11 @@ def test_ast_line_numbers_with_parentheses(self):
444488 self .assertEqual (wat2 .lineno , 5 )
445489 self .assertEqual (wat2 .end_lineno , 6 )
446490 self .assertEqual (wat2 .col_offset , 32 )
447- self .assertEqual (wat2 .end_col_offset , 18 )
491+ # wat ends at the offset 17, but the whole f-string
492+ # ends at the offset 18 (since the quote is part of the
493+ # f-string but not the wat string)
494+ self .assertEqual (wat2 .end_col_offset , 17 )
495+ self .assertEqual (fstring .end_col_offset , 18 )
448496
449497 def test_docstring (self ):
450498 def f ():
@@ -578,8 +626,14 @@ def test_compile_time_concat(self):
578626 self .assertEqual (f'' '' f'' , '' )
579627 self .assertEqual (f'' '' f'' '' , '' )
580628
629+ # This is not really [f'{'] + [f'}'] since we treat the inside
630+ # of braces as a purely new context, so it is actually f'{ and
631+ # then eval(' f') (a valid expression) and then }' which would
632+ # constitute a valid f-string.
633+ self .assertEqual (f'{ ' f' } ' , ' f' )
634+
581635 self .assertAllRaise (SyntaxError , "expecting '}'" ,
582- [" f'{3' f'}'" , # can't concat to get a valid f-string
636+ [''' f'{3' f"}"''' , # can't concat to get a valid f-string
583637 ])
584638
585639 def test_comments (self ):
@@ -743,10 +797,6 @@ def test_parens_in_expressions(self):
743797 ["f'{3)+(4}'" ,
744798 ])
745799
746- self .assertAllRaise (SyntaxError , 'unterminated string literal' ,
747- ["f'{\n }'" ,
748- ])
749-
750800 def test_newlines_before_syntax_error (self ):
751801 self .assertAllRaise (SyntaxError , "invalid syntax" ,
752802 ["f'{.}'" , "\n f'{.}'" , "\n \n f'{.}'" ])
@@ -825,18 +875,39 @@ def test_misformed_unicode_character_name(self):
825875 r"'\N{GREEK CAPITAL LETTER DELTA'" ,
826876 ])
827877
828- def test_no_backslashes_in_expression_part (self ):
829- self .assertAllRaise (SyntaxError , 'f-string expression part cannot include a backslash' ,
830- [r"f'{\'a\'}'" ,
831- r"f'{\t3}'" ,
832- r"f'{\}'" ,
833- r"rf'{\'a\'}'" ,
834- r"rf'{\t3}'" ,
835- r"rf'{\}'" ,
836- r"""rf'{"\N{LEFT CURLY BRACKET}"}'""" ,
837- r"f'{\n}'" ,
878+ def test_backslashes_in_expression_part (self ):
879+ self .assertEqual (f"{ (
880+ 1 +
881+ 2
882+ )} " , "3" )
883+
884+ self .assertEqual ("\N{LEFT CURLY BRACKET} " , '{' )
885+ self .assertEqual (f'{ "\N{LEFT CURLY BRACKET} " } ' , '{' )
886+ self .assertEqual (rf'{ "\N{LEFT CURLY BRACKET} " } ' , '{' )
887+
888+ self .assertAllRaise (SyntaxError , 'empty expression not allowed' ,
889+ ["f'{\n }'" ,
838890 ])
839891
892+ def test_invalid_backslashes_inside_fstring_context (self ):
893+ # All of these variations are invalid python syntax,
894+ # so they are also invalid in f-strings as well.
895+ cases = [
896+ formatting .format (expr = expr )
897+ for formatting in [
898+ "{expr}" ,
899+ "f'{{{expr}}}'" ,
900+ "rf'{{{expr}}}'" ,
901+ ]
902+ for expr in [
903+ r"\'a\'" ,
904+ r"\t3" ,
905+ r"\\" [0 ],
906+ ]
907+ ]
908+ self .assertAllRaise (SyntaxError , 'unexpected character after line continuation' ,
909+ cases )
910+
840911 def test_no_escapes_for_braces (self ):
841912 """
842913 Only literal curly braces begin an expression.
@@ -1120,11 +1191,16 @@ def test_conversions(self):
11201191 "f'{3!:}'" ,
11211192 ])
11221193
1123- for conv in 'g' , 'A' , '3' , ' G' , '!' , ' ä' , 'ɐ' , 'ª ' :
1194+ for conv_identifier in 'g' , 'A' , 'G' , 'ä' , 'ɐ' :
11241195 self .assertAllRaise (SyntaxError ,
11251196 "f-string: invalid conversion character %r: "
1126- "expected 's', 'r', or 'a'" % conv ,
1127- ["f'{3!" + conv + "}'" ])
1197+ "expected 's', 'r', or 'a'" % conv_identifier ,
1198+ ["f'{3!" + conv_identifier + "}'" ])
1199+
1200+ for conv_non_identifier in '3' , '!' :
1201+ self .assertAllRaise (SyntaxError ,
1202+ "f-string: invalid conversion character" ,
1203+ ["f'{3!" + conv_non_identifier + "}'" ])
11281204
11291205 for conv in ' s' , ' s ' :
11301206 self .assertAllRaise (SyntaxError ,
0 commit comments