X Tutup
Skip to content

Commit bae3d2e

Browse files
committed
merge fstring changes from moagstar
1 parent 4f83a87 commit bae3d2e

File tree

3 files changed

+34
-27
lines changed

3 files changed

+34
-27
lines changed

pytest/test_fstring.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,23 @@ def fstrings(draw):
8787
8888
:return: A valid f-string.
8989
"""
90+
character_strategy = st.characters(
91+
blacklist_characters='\r\n\'\\s{}',
92+
min_codepoint=1,
93+
max_codepoint=1000,
94+
)
9095
is_raw = draw(st.booleans())
9196
integer_strategy = st.integers(min_value=0, max_value=3)
9297
expression_count = draw(integer_strategy)
9398
content = []
9499
for _ in range(expression_count):
95100
expression = draw(expressions())
96-
# not yet : conversion not supported
97-
conversion = ''#draw(st.sampled_from(('', '!s', '!r', '!a',)))
101+
conversion = draw(st.sampled_from(('', '!s', '!r', '!a',)))
98102
has_specifier = draw(st.booleans())
99103
specifier = ':' + draw(format_specifiers()) if has_specifier else ''
100104
content.append('{{{}{}}}'.format(expression, conversion, specifier))
105+
content.append(draw(st.text(character_strategy)))
101106
content = ''.join(content)
102-
103107
return "f{}'{}'".format('r' if is_raw else '', content)
104108

105109

@@ -114,23 +118,27 @@ def test_format_specifiers(format_specifier):
114118
raise
115119

116120

121+
def run_test(text):
122+
expr = text + '\n'
123+
code = compile(expr, '<string>', 'single')
124+
deparsed = deparse_code(PYTHON_VERSION, code, compile_mode='single')
125+
recompiled = compile(deparsed.text, '<string>', 'single')
126+
if recompiled != code:
127+
assert 'dis(' + deparsed.text.strip('\n') + ')' == 'dis(' + expr.strip('\n') + ')'
128+
129+
117130
@pytest.mark.skipif(PYTHON_VERSION < 3.6, reason='need at least python 3.6')
118131
@hypothesis.given(fstrings())
119132
def test_uncompyle_fstring(fstring):
120133
"""Verify uncompyling fstring bytecode"""
134+
run_test(fstring)
121135

122-
# ignore fstring with no expressions an fsring with
123-
# no expressions just gets compiled to a normal string.
124-
hypothesis.assume('{' in fstring)
125136

126-
# BUG : At the moment a single expression is not supported
127-
# for example f'{abc}'.
128-
hypothesis.assume(fstring.count('{') > 1)
129-
130-
expr = fstring + '\n'
131-
code = compile(expr, '<string>', 'single')
132-
deparsed = deparse_code(PYTHON_VERSION, code, compile_mode='single')
133-
recompiled = compile(deparsed.text, '<string>', 'single')
134-
135-
if recompiled != code:
136-
assert deparsed.text == expr
137+
@pytest.mark.skipif(PYTHON_VERSION < 3.6, reason='need at least python 3.6')
138+
@pytest.mark.parametrize('fstring', [
139+
#"f'{abc}{abc!s}'",
140+
"f'{abc!s}'",
141+
])
142+
def test_uncompyle_direct(fstring):
143+
"""useful for debugging"""
144+
run_test(fstring)

uncompyle6/parsers/parse3.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -462,13 +462,9 @@ def add_custom_rules(self, tokens, customize):
462462
elif opname == 'FORMAT_VALUE':
463463
# Python 3.6+
464464
self.addRule("""
465-
formatted_value ::= expr FORMAT_VALUE
466-
formatted_value ::= expr FORMAT_VALUE
467-
str ::= LOAD_CONST
468-
formatted_value_or_str ::= formatted_value
469-
formatted_value_or_str ::= str
465+
expr ::= fstring_expr
466+
fstring_expr ::= expr FORMAT_VALUE
470467
""", nop_func)
471-
saw_format_value = True
472468

473469
elif opname in ('CALL_FUNCTION', 'CALL_FUNCTION_VAR',
474470
'CALL_FUNCTION_VAR_KW', 'CALL_FUNCTION_KW'):
@@ -493,7 +489,6 @@ def add_custom_rules(self, tokens, customize):
493489
rule = ('load_closure ::= %s%s' % (('LOAD_CLOSURE ' * v), opname))
494490
self.add_unique_rule(rule, opname, token.attr, customize)
495491
if opname_base == 'BUILD_LIST' and saw_format_value:
496-
saw_format_value = False
497492
format_or_str_n = "formatted_value_or_str_%s" % v
498493
self.addRule("""
499494
expr ::= joined_str

uncompyle6/semantics/pysource.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -624,9 +624,7 @@ def customize_for_version(is_pypy, version):
624624
# Python 3.6+ Additions
625625
#######################
626626
TABLE_DIRECT.update({
627-
'formatted_value': ( '{%c%c}', 0, 1),
628-
'FORMAT_VALUE': ( '%{pattr}', ),
629-
'joined_str': ( "f'%c'", 2),
627+
'fstring_expr': ( "f'{%c%{conversion}}'", 0),
630628
})
631629
return
632630

@@ -1778,6 +1776,12 @@ def n_except_cond2(self, node):
17781776
node[-2][0].type = 'unpack_w_parens'
17791777
self.default(node)
17801778

1779+
FSTRING_CONVERSION_MAP = {i+1: '!'+x for i, x in enumerate('sra')}
1780+
1781+
def n_fstring_expr(self, node):
1782+
node.conversion = self.FSTRING_CONVERSION_MAP.get(node.data[1].attr, '')
1783+
self.default(node)
1784+
17811785
def engine(self, entry, startnode):
17821786
"""The format template interpetation engine. See the comment at the
17831787
beginning of this module for the how we interpret format specifications such as

0 commit comments

Comments
 (0)
X Tutup