@@ -411,6 +411,50 @@ def test_ast_line_numbers_with_parentheses(self):
411
411
412
412
expr = """
413
413
x = (
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 = (
414
458
'PERL_MM_OPT', (
415
459
f'wat'
416
460
f'some_string={f(x)} '
@@ -444,7 +488,11 @@ def test_ast_line_numbers_with_parentheses(self):
444
488
self .assertEqual (wat2 .lineno , 5 )
445
489
self .assertEqual (wat2 .end_lineno , 6 )
446
490
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 )
448
496
449
497
def test_docstring (self ):
450
498
def f ():
@@ -578,8 +626,14 @@ def test_compile_time_concat(self):
578
626
self .assertEqual (f'' '' f'' , '' )
579
627
self .assertEqual (f'' '' f'' '' , '' )
580
628
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
+
581
635
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
583
637
])
584
638
585
639
def test_comments (self ):
@@ -743,10 +797,6 @@ def test_parens_in_expressions(self):
743
797
["f'{3)+(4}'" ,
744
798
])
745
799
746
- self .assertAllRaise (SyntaxError , 'unterminated string literal' ,
747
- ["f'{\n }'" ,
748
- ])
749
-
750
800
def test_newlines_before_syntax_error (self ):
751
801
self .assertAllRaise (SyntaxError , "invalid syntax" ,
752
802
["f'{.}'" , "\n f'{.}'" , "\n \n f'{.}'" ])
@@ -825,18 +875,39 @@ def test_misformed_unicode_character_name(self):
825
875
r"'\N{GREEK CAPITAL LETTER DELTA'" ,
826
876
])
827
877
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 }'" ,
838
890
])
839
891
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
+
840
911
def test_no_escapes_for_braces (self ):
841
912
"""
842
913
Only literal curly braces begin an expression.
@@ -1120,11 +1191,16 @@ def test_conversions(self):
1120
1191
"f'{3!:}'" ,
1121
1192
])
1122
1193
1123
- for conv in 'g' , 'A' , '3' , ' G' , '!' , ' ä' , 'ɐ' , 'ª ' :
1194
+ for conv_identifier in 'g' , 'A' , 'G' , 'ä' , 'ɐ' :
1124
1195
self .assertAllRaise (SyntaxError ,
1125
1196
"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 + "}'" ])
1128
1204
1129
1205
for conv in ' s' , ' s ' :
1130
1206
self .assertAllRaise (SyntaxError ,
0 commit comments