blob: 92995fd83e38d68ba4deca6c7e626445613c3cd4 [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}"}'""",
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400381 ])
382
Eric V. Smith451d0e32016-09-09 21:56:20 -0400383 def test_no_escapes_for_braces(self):
384 # \x7b is '{'. Make sure it doesn't start an expression.
385 self.assertEqual(f'\x7b2}}', '{2}')
386 self.assertEqual(f'\x7b2', '{2')
387 self.assertEqual(f'\u007b2', '{2')
388 self.assertEqual(f'\N{LEFT CURLY BRACKET}2\N{RIGHT CURLY BRACKET}', '{2}')
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400389
Eric V. Smith235a6f02015-09-19 14:51:32 -0400390 def test_newlines_in_expressions(self):
391 self.assertEqual(f'{0}', '0')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400392 self.assertEqual(rf'''{3+
3934}''', '7')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400394
395 def test_lambda(self):
396 x = 5
397 self.assertEqual(f'{(lambda y:x*y)("8")!r}', "'88888'")
398 self.assertEqual(f'{(lambda y:x*y)("8")!r:10}', "'88888' ")
399 self.assertEqual(f'{(lambda y:x*y)("8"):10}', "88888 ")
400
401 # lambda doesn't work without parens, because the colon
402 # makes the parser think it's a format_spec
403 self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing',
404 ["f'{lambda x:x}'",
405 ])
406
407 def test_yield(self):
408 # Not terribly useful, but make sure the yield turns
409 # a function into a generator
410 def fn(y):
411 f'y:{yield y*2}'
412
413 g = fn(4)
414 self.assertEqual(next(g), 8)
415
416 def test_yield_send(self):
417 def fn(x):
418 yield f'x:{yield (lambda i: x * i)}'
419
420 g = fn(10)
421 the_lambda = next(g)
422 self.assertEqual(the_lambda(4), 40)
423 self.assertEqual(g.send('string'), 'x:string')
424
425 def test_expressions_with_triple_quoted_strings(self):
426 self.assertEqual(f"{'''x'''}", 'x')
427 self.assertEqual(f"{'''eric's'''}", "eric's")
Eric V. Smith235a6f02015-09-19 14:51:32 -0400428
429 # Test concatenation within an expression
430 self.assertEqual(f'{"x" """eric"s""" "y"}', 'xeric"sy')
431 self.assertEqual(f'{"x" """eric"s"""}', 'xeric"s')
432 self.assertEqual(f'{"""eric"s""" "y"}', 'eric"sy')
433 self.assertEqual(f'{"""x""" """eric"s""" "y"}', 'xeric"sy')
434 self.assertEqual(f'{"""x""" """eric"s""" """y"""}', 'xeric"sy')
435 self.assertEqual(f'{r"""x""" """eric"s""" """y"""}', 'xeric"sy')
436
437 def test_multiple_vars(self):
438 x = 98
439 y = 'abc'
440 self.assertEqual(f'{x}{y}', '98abc')
441
442 self.assertEqual(f'X{x}{y}', 'X98abc')
443 self.assertEqual(f'{x}X{y}', '98Xabc')
444 self.assertEqual(f'{x}{y}X', '98abcX')
445
446 self.assertEqual(f'X{x}Y{y}', 'X98Yabc')
447 self.assertEqual(f'X{x}{y}Y', 'X98abcY')
448 self.assertEqual(f'{x}X{y}Y', '98XabcY')
449
450 self.assertEqual(f'X{x}Y{y}Z', 'X98YabcZ')
451
452 def test_closure(self):
453 def outer(x):
454 def inner():
455 return f'x:{x}'
456 return inner
457
458 self.assertEqual(outer('987')(), 'x:987')
459 self.assertEqual(outer(7)(), 'x:7')
460
461 def test_arguments(self):
462 y = 2
463 def f(x, width):
464 return f'x={x*y:{width}}'
465
466 self.assertEqual(f('foo', 10), 'x=foofoo ')
467 x = 'bar'
468 self.assertEqual(f(10, 10), 'x= 20')
469
470 def test_locals(self):
471 value = 123
472 self.assertEqual(f'v:{value}', 'v:123')
473
474 def test_missing_variable(self):
475 with self.assertRaises(NameError):
476 f'v:{value}'
477
478 def test_missing_format_spec(self):
479 class O:
480 def __format__(self, spec):
481 if not spec:
482 return '*'
483 return spec
484
485 self.assertEqual(f'{O():x}', 'x')
486 self.assertEqual(f'{O()}', '*')
487 self.assertEqual(f'{O():}', '*')
488
489 self.assertEqual(f'{3:}', '3')
490 self.assertEqual(f'{3!s:}', '3')
491
492 def test_global(self):
493 self.assertEqual(f'g:{a_global}', 'g:global variable')
494 self.assertEqual(f'g:{a_global!r}', "g:'global variable'")
495
496 a_local = 'local variable'
497 self.assertEqual(f'g:{a_global} l:{a_local}',
498 'g:global variable l:local variable')
499 self.assertEqual(f'g:{a_global!r}',
500 "g:'global variable'")
501 self.assertEqual(f'g:{a_global} l:{a_local!r}',
502 "g:global variable l:'local variable'")
503
504 self.assertIn("module 'unittest' from", f'{unittest}')
505
506 def test_shadowed_global(self):
507 a_global = 'really a local'
508 self.assertEqual(f'g:{a_global}', 'g:really a local')
509 self.assertEqual(f'g:{a_global!r}', "g:'really a local'")
510
511 a_local = 'local variable'
512 self.assertEqual(f'g:{a_global} l:{a_local}',
513 'g:really a local l:local variable')
514 self.assertEqual(f'g:{a_global!r}',
515 "g:'really a local'")
516 self.assertEqual(f'g:{a_global} l:{a_local!r}',
517 "g:really a local l:'local variable'")
518
519 def test_call(self):
520 def foo(x):
521 return 'x=' + str(x)
522
523 self.assertEqual(f'{foo(10)}', 'x=10')
524
525 def test_nested_fstrings(self):
526 y = 5
527 self.assertEqual(f'{f"{0}"*3}', '000')
528 self.assertEqual(f'{f"{y}"*3}', '555')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400529
530 def test_invalid_string_prefixes(self):
531 self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing',
532 ["fu''",
533 "uf''",
534 "Fu''",
535 "fU''",
536 "Uf''",
537 "uF''",
538 "ufr''",
539 "urf''",
540 "fur''",
541 "fru''",
542 "rfu''",
543 "ruf''",
544 "FUR''",
545 "Fur''",
Eric V. Smith451d0e32016-09-09 21:56:20 -0400546 "fb''",
547 "fB''",
548 "Fb''",
549 "FB''",
550 "bf''",
551 "bF''",
552 "Bf''",
553 "BF''",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400554 ])
555
556 def test_leading_trailing_spaces(self):
557 self.assertEqual(f'{ 3}', '3')
558 self.assertEqual(f'{ 3}', '3')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400559 self.assertEqual(f'{3 }', '3')
560 self.assertEqual(f'{3 }', '3')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400561
562 self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]}}',
563 'expr={1: 2}')
564 self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]} }',
565 'expr={1: 2}')
566
Eric V. Smith235a6f02015-09-19 14:51:32 -0400567 def test_not_equal(self):
568 # There's a special test for this because there's a special
569 # case in the f-string parser to look for != as not ending an
570 # expression. Normally it would, while looking for !s or !r.
571
572 self.assertEqual(f'{3!=4}', 'True')
573 self.assertEqual(f'{3!=4:}', 'True')
574 self.assertEqual(f'{3!=4!s}', 'True')
575 self.assertEqual(f'{3!=4!s:.3}', 'Tru')
576
577 def test_conversions(self):
578 self.assertEqual(f'{3.14:10.10}', ' 3.14')
579 self.assertEqual(f'{3.14!s:10.10}', '3.14 ')
580 self.assertEqual(f'{3.14!r:10.10}', '3.14 ')
581 self.assertEqual(f'{3.14!a:10.10}', '3.14 ')
582
583 self.assertEqual(f'{"a"}', 'a')
584 self.assertEqual(f'{"a"!r}', "'a'")
585 self.assertEqual(f'{"a"!a}', "'a'")
586
587 # Not a conversion.
588 self.assertEqual(f'{"a!r"}', "a!r")
589
590 # Not a conversion, but show that ! is allowed in a format spec.
591 self.assertEqual(f'{3.14:!<10.10}', '3.14!!!!!!')
592
Eric V. Smith235a6f02015-09-19 14:51:32 -0400593 self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character',
594 ["f'{3!g}'",
595 "f'{3!A}'",
Eric V. Smith451d0e32016-09-09 21:56:20 -0400596 "f'{3!3}'",
597 "f'{3!G}'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400598 "f'{3!!}'",
599 "f'{3!:}'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400600 "f'{3! s}'", # no space before conversion char
Eric V. Smith235a6f02015-09-19 14:51:32 -0400601 ])
602
603 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
604 ["f'{x!s{y}}'",
605 "f'{3!ss}'",
606 "f'{3!ss:}'",
607 "f'{3!ss:s}'",
608 ])
609
610 def test_assignment(self):
611 self.assertAllRaise(SyntaxError, 'invalid syntax',
612 ["f'' = 3",
613 "f'{0}' = x",
614 "f'{x}' = x",
615 ])
616
617 def test_del(self):
618 self.assertAllRaise(SyntaxError, 'invalid syntax',
619 ["del f''",
620 "del '' f''",
621 ])
622
623 def test_mismatched_braces(self):
624 self.assertAllRaise(SyntaxError, "f-string: single '}' is not allowed",
625 ["f'{{}'",
626 "f'{{}}}'",
627 "f'}'",
628 "f'x}'",
629 "f'x}x'",
630
631 # Can't have { or } in a format spec.
632 "f'{3:}>10}'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400633 "f'{3:}}>10}'",
634 ])
635
636 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
637 ["f'{3:{{>10}'",
638 "f'{3'",
639 "f'{3!'",
640 "f'{3:'",
641 "f'{3!s'",
642 "f'{3!s:'",
643 "f'{3!s:3'",
644 "f'x{'",
645 "f'x{x'",
Eric V. Smith451d0e32016-09-09 21:56:20 -0400646 "f'{x'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400647 "f'{3:s'",
648 "f'{{{'",
649 "f'{{}}{'",
650 "f'{'",
651 ])
652
Eric V. Smith235a6f02015-09-19 14:51:32 -0400653 # But these are just normal strings.
654 self.assertEqual(f'{"{"}', '{')
655 self.assertEqual(f'{"}"}', '}')
656 self.assertEqual(f'{3:{"}"}>10}', '}}}}}}}}}3')
657 self.assertEqual(f'{2:{"{"}>10}', '{{{{{{{{{2')
658
659 def test_if_conditional(self):
660 # There's special logic in compile.c to test if the
661 # conditional for an if (and while) are constants. Exercise
662 # that code.
663
664 def test_fstring(x, expected):
665 flag = 0
666 if f'{x}':
667 flag = 1
668 else:
669 flag = 2
670 self.assertEqual(flag, expected)
671
672 def test_concat_empty(x, expected):
673 flag = 0
674 if '' f'{x}':
675 flag = 1
676 else:
677 flag = 2
678 self.assertEqual(flag, expected)
679
680 def test_concat_non_empty(x, expected):
681 flag = 0
682 if ' ' f'{x}':
683 flag = 1
684 else:
685 flag = 2
686 self.assertEqual(flag, expected)
687
688 test_fstring('', 2)
689 test_fstring(' ', 1)
690
691 test_concat_empty('', 2)
692 test_concat_empty(' ', 1)
693
694 test_concat_non_empty('', 1)
695 test_concat_non_empty(' ', 1)
696
697 def test_empty_format_specifier(self):
698 x = 'test'
699 self.assertEqual(f'{x}', 'test')
700 self.assertEqual(f'{x:}', 'test')
701 self.assertEqual(f'{x!s:}', 'test')
702 self.assertEqual(f'{x!r:}', "'test'")
703
704 def test_str_format_differences(self):
705 d = {'a': 'string',
706 0: 'integer',
707 }
708 a = 0
709 self.assertEqual(f'{d[0]}', 'integer')
710 self.assertEqual(f'{d["a"]}', 'string')
711 self.assertEqual(f'{d[a]}', 'integer')
712 self.assertEqual('{d[a]}'.format(d=d), 'string')
713 self.assertEqual('{d[0]}'.format(d=d), 'integer')
714
715 def test_invalid_expressions(self):
716 self.assertAllRaise(SyntaxError, 'invalid syntax',
717 [r"f'{a[4)}'",
718 r"f'{a(4]}'",
719 ])
720
Eric V. Smith135d5f42016-02-05 18:23:08 -0500721 def test_errors(self):
722 # see issue 26287
723 self.assertAllRaise(TypeError, 'non-empty',
724 [r"f'{(lambda: 0):x}'",
725 r"f'{(0,):x}'",
726 ])
727 self.assertAllRaise(ValueError, 'Unknown format code',
728 [r"f'{1000:j}'",
729 r"f'{1000:j}'",
730 ])
731
Eric V. Smith235a6f02015-09-19 14:51:32 -0400732 def test_loop(self):
733 for i in range(1000):
734 self.assertEqual(f'i:{i}', 'i:' + str(i))
735
736 def test_dict(self):
737 d = {'"': 'dquote',
738 "'": 'squote',
739 'foo': 'bar',
740 }
Eric V. Smith235a6f02015-09-19 14:51:32 -0400741 self.assertEqual(f'''{d["'"]}''', 'squote')
742 self.assertEqual(f"""{d['"']}""", 'dquote')
743
744 self.assertEqual(f'{d["foo"]}', 'bar')
745 self.assertEqual(f"{d['foo']}", 'bar')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400746
747if __name__ == '__main__':
748 unittest.main()