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