Skip to content

Commit a8c32c9

Browse files
[mypyc] Add tests for str format() and fstring (#10729)
1 parent f98f782 commit a8c32c9

File tree

3 files changed

+251
-30
lines changed

3 files changed

+251
-30
lines changed

mypyc/test-data/fixtures/ir.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,8 @@ def zip(x: Iterable[T], y: Iterable[S], z: Iterable[V]) -> Iterator[Tuple[T, S,
270270
def eval(e: str) -> Any: ...
271271
def abs(x: float) -> float: ...
272272
def exit() -> None: ...
273+
def repr(o: object) -> str: ...
274+
def ascii(o: object) -> str: ...
273275

274276
# Dummy definitions.
275277
class classmethod: pass

mypyc/test-data/run-python38.test

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,18 @@ from native import Node, make, pairs
5050
assert pairs(make([1,2,3])) == [(1,2), (2,3)]
5151
assert pairs(make([1])) == []
5252
assert pairs(make([])) == []
53+
54+
[case testFStrings]
55+
from datetime import datetime
56+
57+
def test_fstring_equal_sign() -> None:
58+
today = datetime(year=2017, month=1, day=27)
59+
assert f"{today=:%B %d, %Y}" == 'today=January 27, 2017' # using date format specifier and debugging
60+
61+
foo = "bar"
62+
assert f"{ foo = }" == " foo = 'bar'" # preserves whitespace
63+
64+
line = "The mill's closed"
65+
assert f"{line = }" == 'line = "The mill\'s closed"'
66+
assert f"{line = :20}" == "line = The mill's closed "
67+
assert f"{line = !r:20}" == 'line = "The mill\'s closed" '

mypyc/test-data/run-strings.test

Lines changed: 234 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -47,36 +47,6 @@ assert match('', 'abc') == (False, False)
4747
[case testStringOps]
4848
from typing import List, Optional
4949

50-
var = 'mypyc'
51-
52-
num = 20
53-
54-
def test_fstring_simple() -> None:
55-
f1 = f'Hello {var}, this is a test'
56-
assert f1 == "Hello mypyc, this is a test"
57-
58-
def test_fstring_conversion() -> None:
59-
f2 = f'Hello {var!r}'
60-
assert f2 == "Hello 'mypyc'"
61-
f3 = f'Hello {var!a}'
62-
assert f3 == "Hello 'mypyc'"
63-
f4 = f'Hello {var!s}'
64-
assert f4 == "Hello mypyc"
65-
66-
def test_fstring_align() -> None:
67-
f5 = f'Hello {var:>20}'
68-
assert f5 == "Hello mypyc"
69-
f6 = f'Hello {var!r:>20}'
70-
assert f6 == "Hello 'mypyc'"
71-
f7 = f'Hello {var:>{num}}'
72-
assert f7 == "Hello mypyc"
73-
f8 = f'Hello {var!r:>{num}}'
74-
assert f8 == "Hello 'mypyc'"
75-
76-
def test_fstring_multi() -> None:
77-
f9 = f'Hello {var}, hello again {var}'
78-
assert f9 == "Hello mypyc, hello again mypyc"
79-
8050
def do_split(s: str, sep: Optional[str] = None, max_split: Optional[int] = None) -> List[str]:
8151
if sep is not None:
8252
if max_split is not None:
@@ -178,3 +148,237 @@ def test_str_to_bool() -> None:
178148
for x in 'a', 'foo', 'bar', 'some string':
179149
assert is_true(x)
180150
assert not is_false(x)
151+
152+
[case testFStrings]
153+
import decimal
154+
from datetime import datetime
155+
156+
var = 'mypyc'
157+
num = 20
158+
159+
def test_fstring_basics() -> None:
160+
assert f'Hello {var}, this is a test' == "Hello mypyc, this is a test"
161+
162+
large_num = 2**65
163+
assert f'number: {large_num}' == 'number: 36893488147419103232'
164+
neg_num = -3
165+
assert f'negative integer: {neg_num}' == 'negative integer: -3'
166+
assert f'negative integer: {-large_num}' == 'negative integer: -36893488147419103232'
167+
168+
bool_var1 = True
169+
bool_var2 = False
170+
assert f'bool: {bool_var1}, {bool_var2}' == 'bool: True, False'
171+
172+
x = bytes([1, 2, 3, 4])
173+
# assert f'bytes: {x}' == "bytes: b'\\x01\\x02\\x03\\x04'"
174+
# error: On Python 3 '{}'.format(b'abc') produces "b'abc'", not 'abc';
175+
# use '{!r}'.format(b'abc') if this is desired behavior
176+
177+
float_num = 123.4
178+
assert f'{float_num}' == '123.4'
179+
assert f'{float_num:.2f}' == '123.40'
180+
assert f'{float_num:.5f}' == '123.40000'
181+
assert f'{float_num:>10.2f}' == ' 123.40'
182+
assert f'{float_num:>10.5f}' == ' 123.40000'
183+
assert f'{float_num:>010.5f}' == '0123.40000'
184+
assert f'{float_num:>015.5f}' == '000000123.40000'
185+
assert f'{float_num:e}' == '1.234000e+02'
186+
187+
large_float = 1.23e30
188+
large_float2 = 1234123412341234123400000000000000000
189+
small_float = 1.23e-20
190+
assert f'{small_float}, {large_float}, {large_float2}' == '1.23e-20, 1.23e+30, 1234123412341234123400000000000000000'
191+
nan_num = float('nan')
192+
inf_num = float('inf')
193+
assert f'{nan_num}, {inf_num}' == 'nan, inf'
194+
195+
class A:
196+
def __init__(self, name, age):
197+
self.name = name
198+
self.age = age
199+
200+
def __repr__(self):
201+
return f"{self.name} is {self.age} years old."
202+
203+
def test_fstring_datatype() -> None:
204+
u = A('John Doe', 14)
205+
assert f'{u}' == 'John Doe is 14 years old.'
206+
d = {'name': 'John Doe', 'age': 14}
207+
assert f'{d}' == "{'name': 'John Doe', 'age': 14}"
208+
209+
def test_fstring_escape() -> None:
210+
assert f"{'inside'}" == 'inside'
211+
assert f'{"inside"}' == 'inside'
212+
assert f"""inside""" == 'inside'
213+
assert f'''inside''' == 'inside'
214+
assert f"\"{'inside'}\"" == '"inside"'
215+
assert f'\'{"inside"}\'' == "'inside'"
216+
217+
assert f'{{10}}' == '{10}'
218+
assert f'{{10 + 10}}' == '{10 + 10}'
219+
assert f'{{{10 + 10}}}' == '{20}'
220+
assert f'{{{{10 + 10}}}}' == '{{10 + 10}}'
221+
222+
def test_fstring_conversion() -> None:
223+
assert f'Hello {var!r}' == "Hello 'mypyc'"
224+
# repr() is equivalent to !r
225+
assert f'Hello {repr(var)}' == "Hello 'mypyc'"
226+
227+
assert f'Hello {var!a}' == "Hello 'mypyc'"
228+
# ascii() is equivalent to !a
229+
assert f'Hello {ascii(var)}' == "Hello 'mypyc'"
230+
231+
tmp_str = """this
232+
is a new line."""
233+
assert f'Test: {tmp_str!a}' == "Test: 'this\\n is a new line.'"
234+
235+
s = 'test: āĀēĒčČ..šŠūŪžŽ'
236+
assert f'{s}' == 'test: āĀēĒčČ..šŠūŪžŽ'
237+
assert f'{s!a}' == "'test: \\u0101\\u0100\\u0113\\u0112\\u010d\\u010c..\\u0161\\u0160\\u016b\\u016a\\u017e\\u017d'"
238+
239+
assert f'Hello {var!s}' == "Hello mypyc"
240+
assert f'Hello {num!s}' == "Hello 20"
241+
242+
def test_fstring_align() -> None:
243+
assert f'Hello {var:>20}' == "Hello mypyc"
244+
assert f'Hello {var!r:>20}' == "Hello 'mypyc'"
245+
assert f'Hello {var:>{num}}' == "Hello mypyc"
246+
assert f'Hello {var!r:>{num}}' == "Hello 'mypyc'"
247+
248+
def test_fstring_multi() -> None:
249+
assert f'Hello {var}, hello again {var}' == "Hello mypyc, hello again mypyc"
250+
a = 'py'
251+
s = f'my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}my{a}'
252+
assert s == 'mypymypymypymypymypymypymypymypymypymypymypymypymypymypymypymypymypymypymypymypymypymypymypymypy'
253+
254+
def test_fstring_python_doc() -> None:
255+
name = "Fred"
256+
assert f"He said his name is {name!r}." == "He said his name is 'Fred'."
257+
assert f"He said his name is {repr(name)}." == "He said his name is 'Fred'."
258+
259+
width = 10
260+
precision = 4
261+
value = decimal.Decimal("12.34567")
262+
assert f"result: {value:{width}.{precision}}" == 'result: 12.35' # nested field
263+
264+
today = datetime(year=2017, month=1, day=27)
265+
assert f"{today:%B %d, %Y}" == 'January 27, 2017' # using date format specifier
266+
267+
number = 1024
268+
assert f"{number:#0x}" == '0x400' # using integer format specifier
269+
270+
[case testStringFormatMethod]
271+
from typing import Tuple
272+
273+
def test_format_method_basics() -> None:
274+
assert "".format() == ""
275+
assert "abc".format() == "abc"
276+
277+
name = "Eric"
278+
age = 14
279+
assert "My name is {name}, I'm {age}.".format(name=name, age=age) == "My name is Eric, I'm 14."
280+
assert "My name is {A}, I'm {B}.".format(A=name, B=age) == "My name is Eric, I'm 14."
281+
assert "My name is {}, I'm {B}.".format(name, B=age) == "My name is Eric, I'm 14."
282+
283+
def test_format_method_numbers() -> None:
284+
s = 'int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}'.format(-233)
285+
assert s == 'int: -233; hex: -e9; oct: -351; bin: -11101001'
286+
num = 2**65
287+
s = 'int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}'.format(num)
288+
assert s == 'int: 36893488147419103232; hex: 20000000000000000; oct: 4000000000000000000000; bin: 100000000000000000000000000000000000000000000000000000000000000000'
289+
s = 'int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}'.format(-num)
290+
assert s == 'int: -36893488147419103232; hex: -20000000000000000; oct: -4000000000000000000000; bin: -100000000000000000000000000000000000000000000000000000000000000000'
291+
292+
large_num = 2**65
293+
assert 'number: {}'.format(large_num) == 'number: 36893488147419103232'
294+
neg_num = -3
295+
assert 'negative integer: {}'.format(neg_num) == 'negative integer: -3'
296+
assert 'negative integer: {}'.format(-large_num) == 'negative integer: -36893488147419103232'
297+
298+
299+
class Point:
300+
def __init__(self, x, y):
301+
self.x, self.y = x, y
302+
def __str__(self):
303+
return 'Point({self.x}, {self.y})'.format(self=self)
304+
305+
# Format examples from Python doc
306+
# https://docs.python.org/3/library/string.html#formatexamples
307+
def test_format_method_python_doc() -> None:
308+
# Accessing arguments by position:
309+
assert '{0}, {1}, {2}'.format('a', 'b', 'c') == 'a, b, c'
310+
assert '{}, {}, {}'.format('a', 'b', 'c') == 'a, b, c'
311+
assert '{2}, {1}, {0}'.format('a', 'b', 'c') == 'c, b, a'
312+
assert '{2}, {1}, {0}'.format(*'abc') == 'c, b, a' # unpacking argument sequence
313+
# assert '{0}{1}{0}'.format('abra', 'cad') = 'abracadabra' # arguments' indices can be repeated
314+
315+
# Accessing arguments by name:
316+
s = 'Coordinates: {latitude}, {longitude}'.format(latitude='37.24N', longitude='-115.81W')
317+
assert s == 'Coordinates: 37.24N, -115.81W'
318+
coord = {'latitude': '37.24N', 'longitude': '-115.81W'}
319+
assert 'Coordinates: {latitude}, {longitude}'.format(**coord) == 'Coordinates: 37.24N, -115.81W'
320+
321+
# Accessing arguments’ attributes:
322+
assert str(Point(4, 2)) == "Point(4, 2)"
323+
324+
# Accessing arguments’ items:
325+
coord2 = (3, 5)
326+
assert 'X: {0[0]}; Y: {0[1]}'.format(coord2) == 'X: 3; Y: 5'
327+
328+
# Replacing %s and %r:
329+
s = "repr() shows quotes: {!r}; str() doesn't: {!s}".format('test1', 'test2')
330+
assert s == "repr() shows quotes: 'test1'; str() doesn't: test2"
331+
332+
# Aligning the text and specifying a width:
333+
assert '{:<30}'.format('left aligned') == 'left aligned '
334+
assert '{:>30}'.format('right aligned') == ' right aligned'
335+
assert '{:^30}'.format('centered') == ' centered '
336+
assert '{:*^30}'.format('centered') == '***********centered***********' # use '*' as a fill char
337+
338+
# Replacing %+f, %-f, and % f and specifying a sign:
339+
assert '{:+f}; {:+f}'.format(3.14, -3.14) == '+3.140000; -3.140000' # show it always
340+
assert '{: f}; {: f}'.format(3.14, -3.14) == ' 3.140000; -3.140000' # show a space for positive numbers
341+
assert '{:-f}; {:-f}'.format(3.14, -3.14) == '3.140000; -3.140000' # show only the minus -- same as '{:f}; {:f}'
342+
343+
# Replacing %x and %o and converting the value to different bases:
344+
s = 'int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}'.format(42) # format also supports binary numbers
345+
assert s == 'int: 42; hex: 2a; oct: 52; bin: 101010'
346+
s = 'int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}'.format(42) # with 0x, 0o, or 0b as prefix:
347+
assert s == 'int: 42; hex: 0x2a; oct: 0o52; bin: 0b101010'
348+
349+
# Using the comma as a thousands separator:
350+
assert '{:,}'.format(1234567890) == '1,234,567,890'
351+
352+
# Expressing a percentage:
353+
points = 19.0
354+
total = 22.0
355+
assert 'Correct answers: {:.2%}'.format(points/total) == 'Correct answers: 86.36%'
356+
357+
# Using type-specific formatting:
358+
import datetime
359+
d = datetime.datetime(2010, 7, 4, 12, 15, 58)
360+
assert '{:%Y-%m-%d %H:%M:%S}'.format(d) == '2010-07-04 12:15:58'
361+
362+
# Nesting arguments and more complex examples:
363+
tmp_strs = []
364+
for align, text in zip('<^>', ['left', 'center', 'right']):
365+
tmp_strs.append('{0:{fill}{align}16}'.format(text, fill=align, align=align))
366+
assert tmp_strs == ['left<<<<<<<<<<<<', '^^^^^center^^^^^', '>>>>>>>>>>>right']
367+
368+
octets = [192, 168, 0, 1]
369+
assert '{:02X}{:02X}{:02X}{:02X}'.format(*octets) == 'C0A80001'
370+
371+
width = 5
372+
tmp_strs = []
373+
for num in range(5,12):
374+
tmp_str = ""
375+
for base in 'dXob':
376+
tmp_str += ('{0:{width}{base}}'.format(num, base=base, width=width))
377+
tmp_strs.append(tmp_str)
378+
assert tmp_strs == [' 5 5 5 101',\
379+
' 6 6 6 110',\
380+
' 7 7 7 111',\
381+
' 8 8 10 1000',\
382+
' 9 9 11 1001',\
383+
' 10 A 12 1010',\
384+
' 11 B 13 1011']

0 commit comments

Comments
 (0)