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