blob: 68aa52663919fad1cdf70cb5721cf898e03ccf0b [file] [log] [blame]
Eric V. Smith235a6f02015-09-19 14:51:32 -04001import 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_literal_eval(self):
74 # With no expressions, an f-string is okay.
75 self.assertEqual(ast.literal_eval("f'x'"), 'x')
76 self.assertEqual(ast.literal_eval("f'x' 'y'"), 'xy')
77
78 # But this should raise an error.
79 with self.assertRaisesRegex(ValueError, 'malformed node or string'):
80 ast.literal_eval("f'x{3}'")
81
82 # As should this, which uses a different ast node
83 with self.assertRaisesRegex(ValueError, 'malformed node or string'):
84 ast.literal_eval("f'{3}'")
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_literal(self):
96 self.assertEqual(f'', '')
97 self.assertEqual(f'a', 'a')
98 self.assertEqual(f' ', ' ')
Eric V. Smith235a6f02015-09-19 14:51:32 -040099
100 def test_unterminated_string(self):
101 self.assertAllRaise(SyntaxError, 'f-string: unterminated string',
102 [r"""f'{"x'""",
103 r"""f'{"x}'""",
104 r"""f'{("x'""",
105 r"""f'{("x}'""",
106 ])
107
108 def test_mismatched_parens(self):
109 self.assertAllRaise(SyntaxError, 'f-string: mismatched',
110 ["f'{((}'",
111 ])
112
113 def test_double_braces(self):
114 self.assertEqual(f'{{', '{')
115 self.assertEqual(f'a{{', 'a{')
116 self.assertEqual(f'{{b', '{b')
117 self.assertEqual(f'a{{b', 'a{b')
118 self.assertEqual(f'}}', '}')
119 self.assertEqual(f'a}}', 'a}')
120 self.assertEqual(f'}}b', '}b')
121 self.assertEqual(f'a}}b', 'a}b')
Eric V. Smith451d0e32016-09-09 21:56:20 -0400122 self.assertEqual(f'{{}}', '{}')
123 self.assertEqual(f'a{{}}', 'a{}')
124 self.assertEqual(f'{{b}}', '{b}')
125 self.assertEqual(f'{{}}c', '{}c')
126 self.assertEqual(f'a{{b}}', 'a{b}')
127 self.assertEqual(f'a{{}}c', 'a{}c')
128 self.assertEqual(f'{{b}}c', '{b}c')
129 self.assertEqual(f'a{{b}}c', 'a{b}c')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400130
131 self.assertEqual(f'{{{10}', '{10')
132 self.assertEqual(f'}}{10}', '}10')
133 self.assertEqual(f'}}{{{10}', '}{10')
134 self.assertEqual(f'}}a{{{10}', '}a{10')
135
136 self.assertEqual(f'{10}{{', '10{')
137 self.assertEqual(f'{10}}}', '10}')
138 self.assertEqual(f'{10}}}{{', '10}{')
139 self.assertEqual(f'{10}}}a{{' '}', '10}a{}')
140
141 # Inside of strings, don't interpret doubled brackets.
142 self.assertEqual(f'{"{{}}"}', '{{}}')
143
144 self.assertAllRaise(TypeError, 'unhashable type',
145 ["f'{ {{}} }'", # dict in a set
146 ])
147
148 def test_compile_time_concat(self):
149 x = 'def'
150 self.assertEqual('abc' f'## {x}ghi', 'abc## defghi')
151 self.assertEqual('abc' f'{x}' 'ghi', 'abcdefghi')
152 self.assertEqual('abc' f'{x}' 'gh' f'i{x:4}', 'abcdefghidef ')
153 self.assertEqual('{x}' f'{x}', '{x}def')
154 self.assertEqual('{x' f'{x}', '{xdef')
155 self.assertEqual('{x}' f'{x}', '{x}def')
156 self.assertEqual('{{x}}' f'{x}', '{{x}}def')
157 self.assertEqual('{{x' f'{x}', '{{xdef')
158 self.assertEqual('x}}' f'{x}', 'x}}def')
159 self.assertEqual(f'{x}' 'x}}', 'defx}}')
160 self.assertEqual(f'{x}' '', 'def')
161 self.assertEqual('' f'{x}' '', 'def')
162 self.assertEqual('' f'{x}', 'def')
163 self.assertEqual(f'{x}' '2', 'def2')
164 self.assertEqual('1' f'{x}' '2', '1def2')
165 self.assertEqual('1' f'{x}', '1def')
166 self.assertEqual(f'{x}' f'-{x}', 'def-def')
167 self.assertEqual('' f'', '')
168 self.assertEqual('' f'' '', '')
169 self.assertEqual('' f'' '' f'', '')
170 self.assertEqual(f'', '')
171 self.assertEqual(f'' '', '')
172 self.assertEqual(f'' '' f'', '')
173 self.assertEqual(f'' '' f'' '', '')
174
175 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
176 ["f'{3' f'}'", # can't concat to get a valid f-string
177 ])
178
179 def test_comments(self):
180 # These aren't comments, since they're in strings.
181 d = {'#': 'hash'}
182 self.assertEqual(f'{"#"}', '#')
183 self.assertEqual(f'{d["#"]}', 'hash')
184
Eric V. Smith09835dc2016-09-11 18:58:20 -0400185 self.assertAllRaise(SyntaxError, "f-string expression part cannot include '#'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400186 ["f'{1#}'", # error because the expression becomes "(1#)"
187 "f'{3(#)}'",
Eric V. Smith09835dc2016-09-11 18:58:20 -0400188 "f'{#}'",
Eric V. Smith35a24c52016-09-11 19:01:22 -0400189 "f'{)#}'", # When wrapped in parens, this becomes
190 # '()#)'. Make sure that doesn't compile.
Eric V. Smith235a6f02015-09-19 14:51:32 -0400191 ])
192
193 def test_many_expressions(self):
194 # Create a string with many expressions in it. Note that
195 # because we have a space in here as a literal, we're actually
196 # going to use twice as many ast nodes: one for each literal
197 # plus one for each expression.
198 def build_fstr(n, extra=''):
199 return "f'" + ('{x} ' * n) + extra + "'"
200
201 x = 'X'
202 width = 1
203
204 # Test around 256.
205 for i in range(250, 260):
206 self.assertEqual(eval(build_fstr(i)), (x+' ')*i)
207
208 # Test concatenating 2 largs fstrings.
209 self.assertEqual(eval(build_fstr(255)*256), (x+' ')*(255*256))
210
211 s = build_fstr(253, '{x:{width}} ')
212 self.assertEqual(eval(s), (x+' ')*254)
213
214 # Test lots of expressions and constants, concatenated.
215 s = "f'{1}' 'x' 'y'" * 1024
216 self.assertEqual(eval(s), '1xy' * 1024)
217
218 def test_format_specifier_expressions(self):
219 width = 10
220 precision = 4
221 value = decimal.Decimal('12.34567')
222 self.assertEqual(f'result: {value:{width}.{precision}}', 'result: 12.35')
223 self.assertEqual(f'result: {value:{width!r}.{precision}}', 'result: 12.35')
224 self.assertEqual(f'result: {value:{width:0}.{precision:1}}', 'result: 12.35')
225 self.assertEqual(f'result: {value:{1}{0:0}.{precision:1}}', 'result: 12.35')
226 self.assertEqual(f'result: {value:{ 1}{ 0:0}.{ precision:1}}', 'result: 12.35')
227 self.assertEqual(f'{10:#{1}0x}', ' 0xa')
228 self.assertEqual(f'{10:{"#"}1{0}{"x"}}', ' 0xa')
229 self.assertEqual(f'{-10:-{"#"}1{0}x}', ' -0xa')
230 self.assertEqual(f'{-10:{"-"}#{1}0{"x"}}', ' -0xa')
231 self.assertEqual(f'{10:#{3 != {4:5} and width}x}', ' 0xa')
232
233 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
234 ["""f'{"s"!r{":10"}}'""",
235
236 # This looks like a nested format spec.
237 ])
238
239 self.assertAllRaise(SyntaxError, "invalid syntax",
Martin Panter263893c2016-07-28 01:25:31 +0000240 [# Invalid syntax inside a nested spec.
Eric V. Smith235a6f02015-09-19 14:51:32 -0400241 "f'{4:{/5}}'",
242 ])
243
244 self.assertAllRaise(SyntaxError, "f-string: expressions nested too deeply",
245 [# Can't nest format specifiers.
246 "f'result: {value:{width:{0}}.{precision:1}}'",
247 ])
248
249 self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character',
250 [# No expansion inside conversion or for
251 # the : or ! itself.
252 """f'{"s"!{"r"}}'""",
253 ])
254
255 def test_side_effect_order(self):
256 class X:
257 def __init__(self):
258 self.i = 0
259 def __format__(self, spec):
260 self.i += 1
261 return str(self.i)
262
263 x = X()
264 self.assertEqual(f'{x} {x}', '1 2')
265
266 def test_missing_expression(self):
267 self.assertAllRaise(SyntaxError, 'f-string: empty expression not allowed',
268 ["f'{}'",
269 "f'{ }'"
270 "f' {} '",
271 "f'{!r}'",
272 "f'{ !r}'",
273 "f'{10:{ }}'",
274 "f' { } '",
Eric V. Smith1d44c412015-09-23 07:49:00 -0400275
276 # Catch the empty expression before the
277 # invalid conversion.
278 "f'{!x}'",
279 "f'{ !xr}'",
280 "f'{!x:}'",
281 "f'{!x:a}'",
282 "f'{ !xr:}'",
283 "f'{ !xr:a}'",
Eric V. Smith548c4d32015-09-23 08:00:01 -0400284
285 "f'{!}'",
286 "f'{:}'",
Eric V. Smithb2080f62015-09-23 10:24:43 -0400287
288 # We find the empty expression before the
289 # missing closing brace.
290 "f'{!'",
291 "f'{!s:'",
292 "f'{:'",
293 "f'{:x'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400294 ])
295
296 def test_parens_in_expressions(self):
297 self.assertEqual(f'{3,}', '(3,)')
298
299 # Add these because when an expression is evaluated, parens
300 # are added around it. But we shouldn't go from an invalid
301 # expression to a valid one. The added parens are just
302 # supposed to allow whitespace (including newlines).
303 self.assertAllRaise(SyntaxError, 'invalid syntax',
304 ["f'{,}'",
305 "f'{,}'", # this is (,), which is an error
306 ])
307
308 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
309 ["f'{3)+(4}'",
310 ])
311
312 self.assertAllRaise(SyntaxError, 'EOL while scanning string literal',
313 ["f'{\n}'",
314 ])
315
Eric V. Smith451d0e32016-09-09 21:56:20 -0400316 def test_backslashes_in_string_part(self):
317 self.assertEqual(f'\t', '\t')
318 self.assertEqual(r'\t', '\\t')
319 self.assertEqual(rf'\t', '\\t')
320 self.assertEqual(f'{2}\t', '2\t')
321 self.assertEqual(f'{2}\t{3}', '2\t3')
322 self.assertEqual(f'\t{3}', '\t3')
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400323
Eric V. Smith451d0e32016-09-09 21:56:20 -0400324 self.assertEqual(f'\u0394', '\u0394')
325 self.assertEqual(r'\u0394', '\\u0394')
326 self.assertEqual(rf'\u0394', '\\u0394')
327 self.assertEqual(f'{2}\u0394', '2\u0394')
328 self.assertEqual(f'{2}\u0394{3}', '2\u03943')
329 self.assertEqual(f'\u0394{3}', '\u03943')
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400330
Eric V. Smith451d0e32016-09-09 21:56:20 -0400331 self.assertEqual(f'\U00000394', '\u0394')
332 self.assertEqual(r'\U00000394', '\\U00000394')
333 self.assertEqual(rf'\U00000394', '\\U00000394')
334 self.assertEqual(f'{2}\U00000394', '2\u0394')
335 self.assertEqual(f'{2}\U00000394{3}', '2\u03943')
336 self.assertEqual(f'\U00000394{3}', '\u03943')
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400337
Eric V. Smith451d0e32016-09-09 21:56:20 -0400338 self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}', '\u0394')
339 self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}', '2\u0394')
340 self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}{3}', '2\u03943')
341 self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}{3}', '\u03943')
342 self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}', '2\u0394')
343 self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}3', '2\u03943')
344 self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}3', '\u03943')
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400345
Eric V. Smith451d0e32016-09-09 21:56:20 -0400346 self.assertEqual(f'\x20', ' ')
347 self.assertEqual(r'\x20', '\\x20')
348 self.assertEqual(rf'\x20', '\\x20')
349 self.assertEqual(f'{2}\x20', '2 ')
350 self.assertEqual(f'{2}\x20{3}', '2 3')
351 self.assertEqual(f'\x20{3}', ' 3')
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400352
Eric V. Smith451d0e32016-09-09 21:56:20 -0400353 self.assertEqual(f'2\x20', '2 ')
354 self.assertEqual(f'2\x203', '2 3')
355 self.assertEqual(f'\x203', ' 3')
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400356
Eric V. Smith451d0e32016-09-09 21:56:20 -0400357 def test_misformed_unicode_character_name(self):
358 # These test are needed because unicode names are parsed
359 # differently inside f-strings.
360 self.assertAllRaise(SyntaxError, r"\(unicode error\) 'unicodeescape' codec can't decode bytes in position .*: malformed \\N character escape",
361 [r"f'\N'",
362 r"f'\N{'",
363 r"f'\N{GREEK CAPITAL LETTER DELTA'",
364
365 # Here are the non-f-string versions,
366 # which should give the same errors.
367 r"'\N'",
368 r"'\N{'",
369 r"'\N{GREEK CAPITAL LETTER DELTA'",
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400370 ])
371
Eric V. Smith451d0e32016-09-09 21:56:20 -0400372 def test_no_backslashes_in_expression_part(self):
373 self.assertAllRaise(SyntaxError, 'f-string expression part cannot include a backslash',
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400374 [r"f'{\'a\'}'",
375 r"f'{\t3}'",
Eric V. Smith451d0e32016-09-09 21:56:20 -0400376 r"f'{\}'",
377 r"rf'{\'a\'}'",
378 r"rf'{\t3}'",
379 r"rf'{\}'",
380 r"""rf'{"\N{LEFT CURLY BRACKET}"}'""",
Jason R. Coombs45cab8c2016-11-06 11:01:08 -0500381 r"f'{\n}'",
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400382 ])
383
Eric V. Smith451d0e32016-09-09 21:56:20 -0400384 def test_no_escapes_for_braces(self):
385 # \x7b is '{'. Make sure it doesn't start an expression.
386 self.assertEqual(f'\x7b2}}', '{2}')
387 self.assertEqual(f'\x7b2', '{2')
388 self.assertEqual(f'\u007b2', '{2')
389 self.assertEqual(f'\N{LEFT CURLY BRACKET}2\N{RIGHT CURLY BRACKET}', '{2}')
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400390
Eric V. Smith235a6f02015-09-19 14:51:32 -0400391 def test_newlines_in_expressions(self):
392 self.assertEqual(f'{0}', '0')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400393 self.assertEqual(rf'''{3+
3944}''', '7')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400395
396 def test_lambda(self):
397 x = 5
398 self.assertEqual(f'{(lambda y:x*y)("8")!r}', "'88888'")
399 self.assertEqual(f'{(lambda y:x*y)("8")!r:10}', "'88888' ")
400 self.assertEqual(f'{(lambda y:x*y)("8"):10}', "88888 ")
401
402 # lambda doesn't work without parens, because the colon
403 # makes the parser think it's a format_spec
404 self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing',
405 ["f'{lambda x:x}'",
406 ])
407
408 def test_yield(self):
409 # Not terribly useful, but make sure the yield turns
410 # a function into a generator
411 def fn(y):
412 f'y:{yield y*2}'
413
414 g = fn(4)
415 self.assertEqual(next(g), 8)
416
417 def test_yield_send(self):
418 def fn(x):
419 yield f'x:{yield (lambda i: x * i)}'
420
421 g = fn(10)
422 the_lambda = next(g)
423 self.assertEqual(the_lambda(4), 40)
424 self.assertEqual(g.send('string'), 'x:string')
425
426 def test_expressions_with_triple_quoted_strings(self):
427 self.assertEqual(f"{'''x'''}", 'x')
428 self.assertEqual(f"{'''eric's'''}", "eric's")
Eric V. Smith235a6f02015-09-19 14:51:32 -0400429
430 # Test concatenation within an expression
431 self.assertEqual(f'{"x" """eric"s""" "y"}', 'xeric"sy')
432 self.assertEqual(f'{"x" """eric"s"""}', 'xeric"s')
433 self.assertEqual(f'{"""eric"s""" "y"}', 'eric"sy')
434 self.assertEqual(f'{"""x""" """eric"s""" "y"}', 'xeric"sy')
435 self.assertEqual(f'{"""x""" """eric"s""" """y"""}', 'xeric"sy')
436 self.assertEqual(f'{r"""x""" """eric"s""" """y"""}', 'xeric"sy')
437
438 def test_multiple_vars(self):
439 x = 98
440 y = 'abc'
441 self.assertEqual(f'{x}{y}', '98abc')
442
443 self.assertEqual(f'X{x}{y}', 'X98abc')
444 self.assertEqual(f'{x}X{y}', '98Xabc')
445 self.assertEqual(f'{x}{y}X', '98abcX')
446
447 self.assertEqual(f'X{x}Y{y}', 'X98Yabc')
448 self.assertEqual(f'X{x}{y}Y', 'X98abcY')
449 self.assertEqual(f'{x}X{y}Y', '98XabcY')
450
451 self.assertEqual(f'X{x}Y{y}Z', 'X98YabcZ')
452
453 def test_closure(self):
454 def outer(x):
455 def inner():
456 return f'x:{x}'
457 return inner
458
459 self.assertEqual(outer('987')(), 'x:987')
460 self.assertEqual(outer(7)(), 'x:7')
461
462 def test_arguments(self):
463 y = 2
464 def f(x, width):
465 return f'x={x*y:{width}}'
466
467 self.assertEqual(f('foo', 10), 'x=foofoo ')
468 x = 'bar'
469 self.assertEqual(f(10, 10), 'x= 20')
470
471 def test_locals(self):
472 value = 123
473 self.assertEqual(f'v:{value}', 'v:123')
474
475 def test_missing_variable(self):
476 with self.assertRaises(NameError):
477 f'v:{value}'
478
479 def test_missing_format_spec(self):
480 class O:
481 def __format__(self, spec):
482 if not spec:
483 return '*'
484 return spec
485
486 self.assertEqual(f'{O():x}', 'x')
487 self.assertEqual(f'{O()}', '*')
488 self.assertEqual(f'{O():}', '*')
489
490 self.assertEqual(f'{3:}', '3')
491 self.assertEqual(f'{3!s:}', '3')
492
493 def test_global(self):
494 self.assertEqual(f'g:{a_global}', 'g:global variable')
495 self.assertEqual(f'g:{a_global!r}', "g:'global variable'")
496
497 a_local = 'local variable'
498 self.assertEqual(f'g:{a_global} l:{a_local}',
499 'g:global variable l:local variable')
500 self.assertEqual(f'g:{a_global!r}',
501 "g:'global variable'")
502 self.assertEqual(f'g:{a_global} l:{a_local!r}',
503 "g:global variable l:'local variable'")
504
505 self.assertIn("module 'unittest' from", f'{unittest}')
506
507 def test_shadowed_global(self):
508 a_global = 'really a local'
509 self.assertEqual(f'g:{a_global}', 'g:really a local')
510 self.assertEqual(f'g:{a_global!r}', "g:'really a local'")
511
512 a_local = 'local variable'
513 self.assertEqual(f'g:{a_global} l:{a_local}',
514 'g:really a local l:local variable')
515 self.assertEqual(f'g:{a_global!r}',
516 "g:'really a local'")
517 self.assertEqual(f'g:{a_global} l:{a_local!r}',
518 "g:really a local l:'local variable'")
519
520 def test_call(self):
521 def foo(x):
522 return 'x=' + str(x)
523
524 self.assertEqual(f'{foo(10)}', 'x=10')
525
526 def test_nested_fstrings(self):
527 y = 5
528 self.assertEqual(f'{f"{0}"*3}', '000')
529 self.assertEqual(f'{f"{y}"*3}', '555')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400530
531 def test_invalid_string_prefixes(self):
532 self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing',
533 ["fu''",
534 "uf''",
535 "Fu''",
536 "fU''",
537 "Uf''",
538 "uF''",
539 "ufr''",
540 "urf''",
541 "fur''",
542 "fru''",
543 "rfu''",
544 "ruf''",
545 "FUR''",
546 "Fur''",
Eric V. Smith451d0e32016-09-09 21:56:20 -0400547 "fb''",
548 "fB''",
549 "Fb''",
550 "FB''",
551 "bf''",
552 "bF''",
553 "Bf''",
554 "BF''",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400555 ])
556
557 def test_leading_trailing_spaces(self):
558 self.assertEqual(f'{ 3}', '3')
559 self.assertEqual(f'{ 3}', '3')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400560 self.assertEqual(f'{3 }', '3')
561 self.assertEqual(f'{3 }', '3')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400562
563 self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]}}',
564 'expr={1: 2}')
565 self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]} }',
566 'expr={1: 2}')
567
Eric V. Smith235a6f02015-09-19 14:51:32 -0400568 def test_not_equal(self):
569 # There's a special test for this because there's a special
570 # case in the f-string parser to look for != as not ending an
571 # expression. Normally it would, while looking for !s or !r.
572
573 self.assertEqual(f'{3!=4}', 'True')
574 self.assertEqual(f'{3!=4:}', 'True')
575 self.assertEqual(f'{3!=4!s}', 'True')
576 self.assertEqual(f'{3!=4!s:.3}', 'Tru')
577
578 def test_conversions(self):
579 self.assertEqual(f'{3.14:10.10}', ' 3.14')
580 self.assertEqual(f'{3.14!s:10.10}', '3.14 ')
581 self.assertEqual(f'{3.14!r:10.10}', '3.14 ')
582 self.assertEqual(f'{3.14!a:10.10}', '3.14 ')
583
584 self.assertEqual(f'{"a"}', 'a')
585 self.assertEqual(f'{"a"!r}', "'a'")
586 self.assertEqual(f'{"a"!a}', "'a'")
587
588 # Not a conversion.
589 self.assertEqual(f'{"a!r"}', "a!r")
590
591 # Not a conversion, but show that ! is allowed in a format spec.
592 self.assertEqual(f'{3.14:!<10.10}', '3.14!!!!!!')
593
Eric V. Smith235a6f02015-09-19 14:51:32 -0400594 self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character',
595 ["f'{3!g}'",
596 "f'{3!A}'",
Eric V. Smith451d0e32016-09-09 21:56:20 -0400597 "f'{3!3}'",
598 "f'{3!G}'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400599 "f'{3!!}'",
600 "f'{3!:}'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400601 "f'{3! s}'", # no space before conversion char
Eric V. Smith235a6f02015-09-19 14:51:32 -0400602 ])
603
604 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
605 ["f'{x!s{y}}'",
606 "f'{3!ss}'",
607 "f'{3!ss:}'",
608 "f'{3!ss:s}'",
609 ])
610
611 def test_assignment(self):
612 self.assertAllRaise(SyntaxError, 'invalid syntax',
613 ["f'' = 3",
614 "f'{0}' = x",
615 "f'{x}' = x",
616 ])
617
618 def test_del(self):
619 self.assertAllRaise(SyntaxError, 'invalid syntax',
620 ["del f''",
621 "del '' f''",
622 ])
623
624 def test_mismatched_braces(self):
625 self.assertAllRaise(SyntaxError, "f-string: single '}' is not allowed",
626 ["f'{{}'",
627 "f'{{}}}'",
628 "f'}'",
629 "f'x}'",
630 "f'x}x'",
631
632 # Can't have { or } in a format spec.
633 "f'{3:}>10}'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400634 "f'{3:}}>10}'",
635 ])
636
637 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
638 ["f'{3:{{>10}'",
639 "f'{3'",
640 "f'{3!'",
641 "f'{3:'",
642 "f'{3!s'",
643 "f'{3!s:'",
644 "f'{3!s:3'",
645 "f'x{'",
646 "f'x{x'",
Eric V. Smith451d0e32016-09-09 21:56:20 -0400647 "f'{x'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400648 "f'{3:s'",
649 "f'{{{'",
650 "f'{{}}{'",
651 "f'{'",
652 ])
653
Eric V. Smith235a6f02015-09-19 14:51:32 -0400654 # But these are just normal strings.
655 self.assertEqual(f'{"{"}', '{')
656 self.assertEqual(f'{"}"}', '}')
657 self.assertEqual(f'{3:{"}"}>10}', '}}}}}}}}}3')
658 self.assertEqual(f'{2:{"{"}>10}', '{{{{{{{{{2')
659
660 def test_if_conditional(self):
661 # There's special logic in compile.c to test if the
662 # conditional for an if (and while) are constants. Exercise
663 # that code.
664
665 def test_fstring(x, expected):
666 flag = 0
667 if f'{x}':
668 flag = 1
669 else:
670 flag = 2
671 self.assertEqual(flag, expected)
672
673 def test_concat_empty(x, expected):
674 flag = 0
675 if '' f'{x}':
676 flag = 1
677 else:
678 flag = 2
679 self.assertEqual(flag, expected)
680
681 def test_concat_non_empty(x, expected):
682 flag = 0
683 if ' ' f'{x}':
684 flag = 1
685 else:
686 flag = 2
687 self.assertEqual(flag, expected)
688
689 test_fstring('', 2)
690 test_fstring(' ', 1)
691
692 test_concat_empty('', 2)
693 test_concat_empty(' ', 1)
694
695 test_concat_non_empty('', 1)
696 test_concat_non_empty(' ', 1)
697
698 def test_empty_format_specifier(self):
699 x = 'test'
700 self.assertEqual(f'{x}', 'test')
701 self.assertEqual(f'{x:}', 'test')
702 self.assertEqual(f'{x!s:}', 'test')
703 self.assertEqual(f'{x!r:}', "'test'")
704
705 def test_str_format_differences(self):
706 d = {'a': 'string',
707 0: 'integer',
708 }
709 a = 0
710 self.assertEqual(f'{d[0]}', 'integer')
711 self.assertEqual(f'{d["a"]}', 'string')
712 self.assertEqual(f'{d[a]}', 'integer')
713 self.assertEqual('{d[a]}'.format(d=d), 'string')
714 self.assertEqual('{d[0]}'.format(d=d), 'integer')
715
716 def test_invalid_expressions(self):
717 self.assertAllRaise(SyntaxError, 'invalid syntax',
718 [r"f'{a[4)}'",
719 r"f'{a(4]}'",
720 ])
721
Eric V. Smith135d5f42016-02-05 18:23:08 -0500722 def test_errors(self):
723 # see issue 26287
Serhiy Storchaka13c8f322016-10-31 08:13:00 +0200724 self.assertAllRaise(TypeError, 'unsupported',
Eric V. Smith135d5f42016-02-05 18:23:08 -0500725 [r"f'{(lambda: 0):x}'",
726 r"f'{(0,):x}'",
727 ])
728 self.assertAllRaise(ValueError, 'Unknown format code',
729 [r"f'{1000:j}'",
730 r"f'{1000:j}'",
731 ])
732
Eric V. Smith235a6f02015-09-19 14:51:32 -0400733 def test_loop(self):
734 for i in range(1000):
735 self.assertEqual(f'i:{i}', 'i:' + str(i))
736
737 def test_dict(self):
738 d = {'"': 'dquote',
739 "'": 'squote',
740 'foo': 'bar',
741 }
Eric V. Smith235a6f02015-09-19 14:51:32 -0400742 self.assertEqual(f'''{d["'"]}''', 'squote')
743 self.assertEqual(f"""{d['"']}""", 'dquote')
744
745 self.assertEqual(f'{d["foo"]}', 'bar')
746 self.assertEqual(f"{d['foo']}", 'bar')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400747
748if __name__ == '__main__':
749 unittest.main()