1import ast
2import types
3import decimal
4import unittest
5
6a_global = 'global variable'
7
8# You could argue that I'm too strict in looking for specific error
9#  values with assertRaisesRegex, but without it it's way too easy to
10#  make a syntax error in the test strings. Especially with all of the
11#  triple quotes, raw strings, backslashes, etc. I think it's a
12#  worthwhile tradeoff. When I switched to this method, I found many
13#  examples where I wasn't testing what I thought I was.
14
15class TestCase(unittest.TestCase):
16    def assertAllRaise(self, exception_type, regex, error_strings):
17        for str in error_strings:
18            with self.subTest(str=str):
19                with self.assertRaisesRegex(exception_type, regex):
20                    eval(str)
21
22    def test__format__lookup(self):
23        # Make sure __format__ is looked up on the type, not the instance.
24        class X:
25            def __format__(self, spec):
26                return 'class'
27
28        x = X()
29
30        # Add a bound __format__ method to the 'y' instance, but not
31        #  the 'x' instance.
32        y = X()
33        y.__format__ = types.MethodType(lambda self, spec: 'instance', y)
34
35        self.assertEqual(f'{y}', format(y))
36        self.assertEqual(f'{y}', 'class')
37        self.assertEqual(format(x), format(y))
38
39        # __format__ is not called this way, but still make sure it
40        #  returns what we expect (so we can make sure we're bypassing
41        #  it).
42        self.assertEqual(x.__format__(''), 'class')
43        self.assertEqual(y.__format__(''), 'instance')
44
45        # This is how __format__ is actually called.
46        self.assertEqual(type(x).__format__(x, ''), 'class')
47        self.assertEqual(type(y).__format__(y, ''), 'class')
48
49    def test_ast(self):
50        # Inspired by http://bugs.python.org/issue24975
51        class X:
52            def __init__(self):
53                self.called = False
54            def __call__(self):
55                self.called = True
56                return 4
57        x = X()
58        expr = """
59a = 10
60f'{a * x()}'"""
61        t = ast.parse(expr)
62        c = compile(t, '', 'exec')
63
64        # Make sure x was not called.
65        self.assertFalse(x.called)
66
67        # Actually run the code.
68        exec(c)
69
70        # Make sure x was called.
71        self.assertTrue(x.called)
72
73    def test_docstring(self):
74        def f():
75            f'''Not a docstring'''
76        self.assertIsNone(f.__doc__)
77        def g():
78            '''Not a docstring''' \
79            f''
80        self.assertIsNone(g.__doc__)
81
82    def test_literal_eval(self):
83        with self.assertRaisesRegex(ValueError, 'malformed node or string'):
84            ast.literal_eval("f'x'")
85
86    def test_ast_compile_time_concat(self):
87        x = ['']
88
89        expr = """x[0] = 'foo' f'{3}'"""
90        t = ast.parse(expr)
91        c = compile(t, '', 'exec')
92        exec(c)
93        self.assertEqual(x[0], 'foo3')
94
95    def test_compile_time_concat_errors(self):
96        self.assertAllRaise(SyntaxError,
97                            'cannot mix bytes and nonbytes literals',
98                            [r"""f'' b''""",
99                             r"""b'' f''""",
100                             ])
101
102    def test_literal(self):
103        self.assertEqual(f'', '')
104        self.assertEqual(f'a', 'a')
105        self.assertEqual(f' ', ' ')
106
107    def test_unterminated_string(self):
108        self.assertAllRaise(SyntaxError, 'f-string: unterminated string',
109                            [r"""f'{"x'""",
110                             r"""f'{"x}'""",
111                             r"""f'{("x'""",
112                             r"""f'{("x}'""",
113                             ])
114
115    def test_mismatched_parens(self):
116        self.assertAllRaise(SyntaxError, 'f-string: mismatched',
117                            ["f'{((}'",
118                             ])
119
120    def test_double_braces(self):
121        self.assertEqual(f'{{', '{')
122        self.assertEqual(f'a{{', 'a{')
123        self.assertEqual(f'{{b', '{b')
124        self.assertEqual(f'a{{b', 'a{b')
125        self.assertEqual(f'}}', '}')
126        self.assertEqual(f'a}}', 'a}')
127        self.assertEqual(f'}}b', '}b')
128        self.assertEqual(f'a}}b', 'a}b')
129        self.assertEqual(f'{{}}', '{}')
130        self.assertEqual(f'a{{}}', 'a{}')
131        self.assertEqual(f'{{b}}', '{b}')
132        self.assertEqual(f'{{}}c', '{}c')
133        self.assertEqual(f'a{{b}}', 'a{b}')
134        self.assertEqual(f'a{{}}c', 'a{}c')
135        self.assertEqual(f'{{b}}c', '{b}c')
136        self.assertEqual(f'a{{b}}c', 'a{b}c')
137
138        self.assertEqual(f'{{{10}', '{10')
139        self.assertEqual(f'}}{10}', '}10')
140        self.assertEqual(f'}}{{{10}', '}{10')
141        self.assertEqual(f'}}a{{{10}', '}a{10')
142
143        self.assertEqual(f'{10}{{', '10{')
144        self.assertEqual(f'{10}}}', '10}')
145        self.assertEqual(f'{10}}}{{', '10}{')
146        self.assertEqual(f'{10}}}a{{' '}', '10}a{}')
147
148        # Inside of strings, don't interpret doubled brackets.
149        self.assertEqual(f'{"{{}}"}', '{{}}')
150
151        self.assertAllRaise(TypeError, 'unhashable type',
152                            ["f'{ {{}} }'", # dict in a set
153                             ])
154
155    def test_compile_time_concat(self):
156        x = 'def'
157        self.assertEqual('abc' f'## {x}ghi', 'abc## defghi')
158        self.assertEqual('abc' f'{x}' 'ghi', 'abcdefghi')
159        self.assertEqual('abc' f'{x}' 'gh' f'i{x:4}', 'abcdefghidef ')
160        self.assertEqual('{x}' f'{x}', '{x}def')
161        self.assertEqual('{x' f'{x}', '{xdef')
162        self.assertEqual('{x}' f'{x}', '{x}def')
163        self.assertEqual('{{x}}' f'{x}', '{{x}}def')
164        self.assertEqual('{{x' f'{x}', '{{xdef')
165        self.assertEqual('x}}' f'{x}', 'x}}def')
166        self.assertEqual(f'{x}' 'x}}', 'defx}}')
167        self.assertEqual(f'{x}' '', 'def')
168        self.assertEqual('' f'{x}' '', 'def')
169        self.assertEqual('' f'{x}', 'def')
170        self.assertEqual(f'{x}' '2', 'def2')
171        self.assertEqual('1' f'{x}' '2', '1def2')
172        self.assertEqual('1' f'{x}', '1def')
173        self.assertEqual(f'{x}' f'-{x}', 'def-def')
174        self.assertEqual('' f'', '')
175        self.assertEqual('' f'' '', '')
176        self.assertEqual('' f'' '' f'', '')
177        self.assertEqual(f'', '')
178        self.assertEqual(f'' '', '')
179        self.assertEqual(f'' '' f'', '')
180        self.assertEqual(f'' '' f'' '', '')
181
182        self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
183                            ["f'{3' f'}'",  # can't concat to get a valid f-string
184                             ])
185
186    def test_comments(self):
187        # These aren't comments, since they're in strings.
188        d = {'#': 'hash'}
189        self.assertEqual(f'{"#"}', '#')
190        self.assertEqual(f'{d["#"]}', 'hash')
191
192        self.assertAllRaise(SyntaxError, "f-string expression part cannot include '#'",
193                            ["f'{1#}'",   # error because the expression becomes "(1#)"
194                             "f'{3(#)}'",
195                             "f'{#}'",
196                             "f'{)#}'",   # When wrapped in parens, this becomes
197                                          #  '()#)'.  Make sure that doesn't compile.
198                             ])
199
200    def test_many_expressions(self):
201        # Create a string with many expressions in it. Note that
202        #  because we have a space in here as a literal, we're actually
203        #  going to use twice as many ast nodes: one for each literal
204        #  plus one for each expression.
205        def build_fstr(n, extra=''):
206            return "f'" + ('{x} ' * n) + extra + "'"
207
208        x = 'X'
209        width = 1
210
211        # Test around 256.
212        for i in range(250, 260):
213            self.assertEqual(eval(build_fstr(i)), (x+' ')*i)
214
215        # Test concatenating 2 largs fstrings.
216        self.assertEqual(eval(build_fstr(255)*256), (x+' ')*(255*256))
217
218        s = build_fstr(253, '{x:{width}} ')
219        self.assertEqual(eval(s), (x+' ')*254)
220
221        # Test lots of expressions and constants, concatenated.
222        s = "f'{1}' 'x' 'y'" * 1024
223        self.assertEqual(eval(s), '1xy' * 1024)
224
225    def test_format_specifier_expressions(self):
226        width = 10
227        precision = 4
228        value = decimal.Decimal('12.34567')
229        self.assertEqual(f'result: {value:{width}.{precision}}', 'result:      12.35')
230        self.assertEqual(f'result: {value:{width!r}.{precision}}', 'result:      12.35')
231        self.assertEqual(f'result: {value:{width:0}.{precision:1}}', 'result:      12.35')
232        self.assertEqual(f'result: {value:{1}{0:0}.{precision:1}}', 'result:      12.35')
233        self.assertEqual(f'result: {value:{ 1}{ 0:0}.{ precision:1}}', 'result:      12.35')
234        self.assertEqual(f'{10:#{1}0x}', '       0xa')
235        self.assertEqual(f'{10:{"#"}1{0}{"x"}}', '       0xa')
236        self.assertEqual(f'{-10:-{"#"}1{0}x}', '      -0xa')
237        self.assertEqual(f'{-10:{"-"}#{1}0{"x"}}', '      -0xa')
238        self.assertEqual(f'{10:#{3 != {4:5} and width}x}', '       0xa')
239
240        self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
241                            ["""f'{"s"!r{":10"}}'""",
242
243                             # This looks like a nested format spec.
244                             ])
245
246        self.assertAllRaise(SyntaxError, "invalid syntax",
247                            [# Invalid syntax inside a nested spec.
248                             "f'{4:{/5}}'",
249                             ])
250
251        self.assertAllRaise(SyntaxError, "f-string: expressions nested too deeply",
252                            [# Can't nest format specifiers.
253                             "f'result: {value:{width:{0}}.{precision:1}}'",
254                             ])
255
256        self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character',
257                            [# No expansion inside conversion or for
258                             #  the : or ! itself.
259                             """f'{"s"!{"r"}}'""",
260                             ])
261
262    def test_side_effect_order(self):
263        class X:
264            def __init__(self):
265                self.i = 0
266            def __format__(self, spec):
267                self.i += 1
268                return str(self.i)
269
270        x = X()
271        self.assertEqual(f'{x} {x}', '1 2')
272
273    def test_missing_expression(self):
274        self.assertAllRaise(SyntaxError, 'f-string: empty expression not allowed',
275                            ["f'{}'",
276                             "f'{ }'"
277                             "f' {} '",
278                             "f'{!r}'",
279                             "f'{ !r}'",
280                             "f'{10:{ }}'",
281                             "f' { } '",
282
283                             # Catch the empty expression before the
284                             #  invalid conversion.
285                             "f'{!x}'",
286                             "f'{ !xr}'",
287                             "f'{!x:}'",
288                             "f'{!x:a}'",
289                             "f'{ !xr:}'",
290                             "f'{ !xr:a}'",
291
292                             "f'{!}'",
293                             "f'{:}'",
294
295                             # We find the empty expression before the
296                             #  missing closing brace.
297                             "f'{!'",
298                             "f'{!s:'",
299                             "f'{:'",
300                             "f'{:x'",
301                             ])
302
303    def test_parens_in_expressions(self):
304        self.assertEqual(f'{3,}', '(3,)')
305
306        # Add these because when an expression is evaluated, parens
307        #  are added around it. But we shouldn't go from an invalid
308        #  expression to a valid one. The added parens are just
309        #  supposed to allow whitespace (including newlines).
310        self.assertAllRaise(SyntaxError, 'invalid syntax',
311                            ["f'{,}'",
312                             "f'{,}'",  # this is (,), which is an error
313                             ])
314
315        self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
316                            ["f'{3)+(4}'",
317                             ])
318
319        self.assertAllRaise(SyntaxError, 'EOL while scanning string literal',
320                            ["f'{\n}'",
321                             ])
322
323    def test_backslashes_in_string_part(self):
324        self.assertEqual(f'\t', '\t')
325        self.assertEqual(r'\t', '\\t')
326        self.assertEqual(rf'\t', '\\t')
327        self.assertEqual(f'{2}\t', '2\t')
328        self.assertEqual(f'{2}\t{3}', '2\t3')
329        self.assertEqual(f'\t{3}', '\t3')
330
331        self.assertEqual(f'\u0394', '\u0394')
332        self.assertEqual(r'\u0394', '\\u0394')
333        self.assertEqual(rf'\u0394', '\\u0394')
334        self.assertEqual(f'{2}\u0394', '2\u0394')
335        self.assertEqual(f'{2}\u0394{3}', '2\u03943')
336        self.assertEqual(f'\u0394{3}', '\u03943')
337
338        self.assertEqual(f'\U00000394', '\u0394')
339        self.assertEqual(r'\U00000394', '\\U00000394')
340        self.assertEqual(rf'\U00000394', '\\U00000394')
341        self.assertEqual(f'{2}\U00000394', '2\u0394')
342        self.assertEqual(f'{2}\U00000394{3}', '2\u03943')
343        self.assertEqual(f'\U00000394{3}', '\u03943')
344
345        self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}', '\u0394')
346        self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}', '2\u0394')
347        self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}{3}', '2\u03943')
348        self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}{3}', '\u03943')
349        self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}', '2\u0394')
350        self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}3', '2\u03943')
351        self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}3', '\u03943')
352
353        self.assertEqual(f'\x20', ' ')
354        self.assertEqual(r'\x20', '\\x20')
355        self.assertEqual(rf'\x20', '\\x20')
356        self.assertEqual(f'{2}\x20', '2 ')
357        self.assertEqual(f'{2}\x20{3}', '2 3')
358        self.assertEqual(f'\x20{3}', ' 3')
359
360        self.assertEqual(f'2\x20', '2 ')
361        self.assertEqual(f'2\x203', '2 3')
362        self.assertEqual(f'\x203', ' 3')
363
364    def test_misformed_unicode_character_name(self):
365        # These test are needed because unicode names are parsed
366        # differently inside f-strings.
367        self.assertAllRaise(SyntaxError, r"\(unicode error\) 'unicodeescape' codec can't decode bytes in position .*: malformed \\N character escape",
368                            [r"f'\N'",
369                             r"f'\N{'",
370                             r"f'\N{GREEK CAPITAL LETTER DELTA'",
371
372                             # Here are the non-f-string versions,
373                             #  which should give the same errors.
374                             r"'\N'",
375                             r"'\N{'",
376                             r"'\N{GREEK CAPITAL LETTER DELTA'",
377                             ])
378
379    def test_no_backslashes_in_expression_part(self):
380        self.assertAllRaise(SyntaxError, 'f-string expression part cannot include a backslash',
381                            [r"f'{\'a\'}'",
382                             r"f'{\t3}'",
383                             r"f'{\}'",
384                             r"rf'{\'a\'}'",
385                             r"rf'{\t3}'",
386                             r"rf'{\}'",
387                             r"""rf'{"\N{LEFT CURLY BRACKET}"}'""",
388                             r"f'{\n}'",
389                             ])
390
391    def test_no_escapes_for_braces(self):
392        """
393        Only literal curly braces begin an expression.
394        """
395        # \x7b is '{'.
396        self.assertEqual(f'\x7b1+1}}', '{1+1}')
397        self.assertEqual(f'\x7b1+1', '{1+1')
398        self.assertEqual(f'\u007b1+1', '{1+1')
399        self.assertEqual(f'\N{LEFT CURLY BRACKET}1+1\N{RIGHT CURLY BRACKET}', '{1+1}')
400
401    def test_newlines_in_expressions(self):
402        self.assertEqual(f'{0}', '0')
403        self.assertEqual(rf'''{3+
4044}''', '7')
405
406    def test_lambda(self):
407        x = 5
408        self.assertEqual(f'{(lambda y:x*y)("8")!r}', "'88888'")
409        self.assertEqual(f'{(lambda y:x*y)("8")!r:10}', "'88888'   ")
410        self.assertEqual(f'{(lambda y:x*y)("8"):10}', "88888     ")
411
412        # lambda doesn't work without parens, because the colon
413        #  makes the parser think it's a format_spec
414        self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing',
415                            ["f'{lambda x:x}'",
416                             ])
417
418    def test_yield(self):
419        # Not terribly useful, but make sure the yield turns
420        #  a function into a generator
421        def fn(y):
422            f'y:{yield y*2}'
423
424        g = fn(4)
425        self.assertEqual(next(g), 8)
426
427    def test_yield_send(self):
428        def fn(x):
429            yield f'x:{yield (lambda i: x * i)}'
430
431        g = fn(10)
432        the_lambda = next(g)
433        self.assertEqual(the_lambda(4), 40)
434        self.assertEqual(g.send('string'), 'x:string')
435
436    def test_expressions_with_triple_quoted_strings(self):
437        self.assertEqual(f"{'''x'''}", 'x')
438        self.assertEqual(f"{'''eric's'''}", "eric's")
439
440        # Test concatenation within an expression
441        self.assertEqual(f'{"x" """eric"s""" "y"}', 'xeric"sy')
442        self.assertEqual(f'{"x" """eric"s"""}', 'xeric"s')
443        self.assertEqual(f'{"""eric"s""" "y"}', 'eric"sy')
444        self.assertEqual(f'{"""x""" """eric"s""" "y"}', 'xeric"sy')
445        self.assertEqual(f'{"""x""" """eric"s""" """y"""}', 'xeric"sy')
446        self.assertEqual(f'{r"""x""" """eric"s""" """y"""}', 'xeric"sy')
447
448    def test_multiple_vars(self):
449        x = 98
450        y = 'abc'
451        self.assertEqual(f'{x}{y}', '98abc')
452
453        self.assertEqual(f'X{x}{y}', 'X98abc')
454        self.assertEqual(f'{x}X{y}', '98Xabc')
455        self.assertEqual(f'{x}{y}X', '98abcX')
456
457        self.assertEqual(f'X{x}Y{y}', 'X98Yabc')
458        self.assertEqual(f'X{x}{y}Y', 'X98abcY')
459        self.assertEqual(f'{x}X{y}Y', '98XabcY')
460
461        self.assertEqual(f'X{x}Y{y}Z', 'X98YabcZ')
462
463    def test_closure(self):
464        def outer(x):
465            def inner():
466                return f'x:{x}'
467            return inner
468
469        self.assertEqual(outer('987')(), 'x:987')
470        self.assertEqual(outer(7)(), 'x:7')
471
472    def test_arguments(self):
473        y = 2
474        def f(x, width):
475            return f'x={x*y:{width}}'
476
477        self.assertEqual(f('foo', 10), 'x=foofoo    ')
478        x = 'bar'
479        self.assertEqual(f(10, 10), 'x=        20')
480
481    def test_locals(self):
482        value = 123
483        self.assertEqual(f'v:{value}', 'v:123')
484
485    def test_missing_variable(self):
486        with self.assertRaises(NameError):
487            f'v:{value}'
488
489    def test_missing_format_spec(self):
490        class O:
491            def __format__(self, spec):
492                if not spec:
493                    return '*'
494                return spec
495
496        self.assertEqual(f'{O():x}', 'x')
497        self.assertEqual(f'{O()}', '*')
498        self.assertEqual(f'{O():}', '*')
499
500        self.assertEqual(f'{3:}', '3')
501        self.assertEqual(f'{3!s:}', '3')
502
503    def test_global(self):
504        self.assertEqual(f'g:{a_global}', 'g:global variable')
505        self.assertEqual(f'g:{a_global!r}', "g:'global variable'")
506
507        a_local = 'local variable'
508        self.assertEqual(f'g:{a_global} l:{a_local}',
509                         'g:global variable l:local variable')
510        self.assertEqual(f'g:{a_global!r}',
511                         "g:'global variable'")
512        self.assertEqual(f'g:{a_global} l:{a_local!r}',
513                         "g:global variable l:'local variable'")
514
515        self.assertIn("module 'unittest' from", f'{unittest}')
516
517    def test_shadowed_global(self):
518        a_global = 'really a local'
519        self.assertEqual(f'g:{a_global}', 'g:really a local')
520        self.assertEqual(f'g:{a_global!r}', "g:'really a local'")
521
522        a_local = 'local variable'
523        self.assertEqual(f'g:{a_global} l:{a_local}',
524                         'g:really a local l:local variable')
525        self.assertEqual(f'g:{a_global!r}',
526                         "g:'really a local'")
527        self.assertEqual(f'g:{a_global} l:{a_local!r}',
528                         "g:really a local l:'local variable'")
529
530    def test_call(self):
531        def foo(x):
532            return 'x=' + str(x)
533
534        self.assertEqual(f'{foo(10)}', 'x=10')
535
536    def test_nested_fstrings(self):
537        y = 5
538        self.assertEqual(f'{f"{0}"*3}', '000')
539        self.assertEqual(f'{f"{y}"*3}', '555')
540
541    def test_invalid_string_prefixes(self):
542        self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing',
543                            ["fu''",
544                             "uf''",
545                             "Fu''",
546                             "fU''",
547                             "Uf''",
548                             "uF''",
549                             "ufr''",
550                             "urf''",
551                             "fur''",
552                             "fru''",
553                             "rfu''",
554                             "ruf''",
555                             "FUR''",
556                             "Fur''",
557                             "fb''",
558                             "fB''",
559                             "Fb''",
560                             "FB''",
561                             "bf''",
562                             "bF''",
563                             "Bf''",
564                             "BF''",
565                             ])
566
567    def test_leading_trailing_spaces(self):
568        self.assertEqual(f'{ 3}', '3')
569        self.assertEqual(f'{  3}', '3')
570        self.assertEqual(f'{3 }', '3')
571        self.assertEqual(f'{3  }', '3')
572
573        self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]}}',
574                         'expr={1: 2}')
575        self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]} }',
576                         'expr={1: 2}')
577
578    def test_not_equal(self):
579        # There's a special test for this because there's a special
580        #  case in the f-string parser to look for != as not ending an
581        #  expression. Normally it would, while looking for !s or !r.
582
583        self.assertEqual(f'{3!=4}', 'True')
584        self.assertEqual(f'{3!=4:}', 'True')
585        self.assertEqual(f'{3!=4!s}', 'True')
586        self.assertEqual(f'{3!=4!s:.3}', 'Tru')
587
588    def test_conversions(self):
589        self.assertEqual(f'{3.14:10.10}', '      3.14')
590        self.assertEqual(f'{3.14!s:10.10}', '3.14      ')
591        self.assertEqual(f'{3.14!r:10.10}', '3.14      ')
592        self.assertEqual(f'{3.14!a:10.10}', '3.14      ')
593
594        self.assertEqual(f'{"a"}', 'a')
595        self.assertEqual(f'{"a"!r}', "'a'")
596        self.assertEqual(f'{"a"!a}', "'a'")
597
598        # Not a conversion.
599        self.assertEqual(f'{"a!r"}', "a!r")
600
601        # Not a conversion, but show that ! is allowed in a format spec.
602        self.assertEqual(f'{3.14:!<10.10}', '3.14!!!!!!')
603
604        self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character',
605                            ["f'{3!g}'",
606                             "f'{3!A}'",
607                             "f'{3!3}'",
608                             "f'{3!G}'",
609                             "f'{3!!}'",
610                             "f'{3!:}'",
611                             "f'{3! s}'",  # no space before conversion char
612                             ])
613
614        self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
615                            ["f'{x!s{y}}'",
616                             "f'{3!ss}'",
617                             "f'{3!ss:}'",
618                             "f'{3!ss:s}'",
619                             ])
620
621    def test_assignment(self):
622        self.assertAllRaise(SyntaxError, 'invalid syntax',
623                            ["f'' = 3",
624                             "f'{0}' = x",
625                             "f'{x}' = x",
626                             ])
627
628    def test_del(self):
629        self.assertAllRaise(SyntaxError, 'invalid syntax',
630                            ["del f''",
631                             "del '' f''",
632                             ])
633
634    def test_mismatched_braces(self):
635        self.assertAllRaise(SyntaxError, "f-string: single '}' is not allowed",
636                            ["f'{{}'",
637                             "f'{{}}}'",
638                             "f'}'",
639                             "f'x}'",
640                             "f'x}x'",
641                             r"f'\u007b}'",
642
643                             # Can't have { or } in a format spec.
644                             "f'{3:}>10}'",
645                             "f'{3:}}>10}'",
646                             ])
647
648        self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
649                            ["f'{3:{{>10}'",
650                             "f'{3'",
651                             "f'{3!'",
652                             "f'{3:'",
653                             "f'{3!s'",
654                             "f'{3!s:'",
655                             "f'{3!s:3'",
656                             "f'x{'",
657                             "f'x{x'",
658                             "f'{x'",
659                             "f'{3:s'",
660                             "f'{{{'",
661                             "f'{{}}{'",
662                             "f'{'",
663                             ])
664
665        # But these are just normal strings.
666        self.assertEqual(f'{"{"}', '{')
667        self.assertEqual(f'{"}"}', '}')
668        self.assertEqual(f'{3:{"}"}>10}', '}}}}}}}}}3')
669        self.assertEqual(f'{2:{"{"}>10}', '{{{{{{{{{2')
670
671    def test_if_conditional(self):
672        # There's special logic in compile.c to test if the
673        #  conditional for an if (and while) are constants. Exercise
674        #  that code.
675
676        def test_fstring(x, expected):
677            flag = 0
678            if f'{x}':
679                flag = 1
680            else:
681                flag = 2
682            self.assertEqual(flag, expected)
683
684        def test_concat_empty(x, expected):
685            flag = 0
686            if '' f'{x}':
687                flag = 1
688            else:
689                flag = 2
690            self.assertEqual(flag, expected)
691
692        def test_concat_non_empty(x, expected):
693            flag = 0
694            if ' ' f'{x}':
695                flag = 1
696            else:
697                flag = 2
698            self.assertEqual(flag, expected)
699
700        test_fstring('', 2)
701        test_fstring(' ', 1)
702
703        test_concat_empty('', 2)
704        test_concat_empty(' ', 1)
705
706        test_concat_non_empty('', 1)
707        test_concat_non_empty(' ', 1)
708
709    def test_empty_format_specifier(self):
710        x = 'test'
711        self.assertEqual(f'{x}', 'test')
712        self.assertEqual(f'{x:}', 'test')
713        self.assertEqual(f'{x!s:}', 'test')
714        self.assertEqual(f'{x!r:}', "'test'")
715
716    def test_str_format_differences(self):
717        d = {'a': 'string',
718             0: 'integer',
719             }
720        a = 0
721        self.assertEqual(f'{d[0]}', 'integer')
722        self.assertEqual(f'{d["a"]}', 'string')
723        self.assertEqual(f'{d[a]}', 'integer')
724        self.assertEqual('{d[a]}'.format(d=d), 'string')
725        self.assertEqual('{d[0]}'.format(d=d), 'integer')
726
727    def test_invalid_expressions(self):
728        self.assertAllRaise(SyntaxError, 'invalid syntax',
729                            [r"f'{a[4)}'",
730                             r"f'{a(4]}'",
731                            ])
732
733    def test_errors(self):
734        # see issue 26287
735        self.assertAllRaise(TypeError, 'unsupported',
736                            [r"f'{(lambda: 0):x}'",
737                             r"f'{(0,):x}'",
738                             ])
739        self.assertAllRaise(ValueError, 'Unknown format code',
740                            [r"f'{1000:j}'",
741                             r"f'{1000:j}'",
742                            ])
743
744    def test_loop(self):
745        for i in range(1000):
746            self.assertEqual(f'i:{i}', 'i:' + str(i))
747
748    def test_dict(self):
749        d = {'"': 'dquote',
750             "'": 'squote',
751             'foo': 'bar',
752             }
753        self.assertEqual(f'''{d["'"]}''', 'squote')
754        self.assertEqual(f"""{d['"']}""", 'dquote')
755
756        self.assertEqual(f'{d["foo"]}', 'bar')
757        self.assertEqual(f"{d['foo']}", 'bar')
758
759if __name__ == '__main__':
760    unittest.main()
761