diff --git a/CHANGES.rst b/CHANGES.rst index bdf8c845b024bede54a5409dce231618bf0792e5..c4e3043a31dca4e8d4a0e80ab390e8d38f1933d6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -50,6 +50,9 @@ Bugs fixed * abs(signed int) now returns a signed rather than unsigned int. (Github issue #1837) +* Compile time evaluations of (partially) constant f-strings could show incorrect + results. + Other changes ------------- diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index f0ea3f9802b0852413d9aee1f29491ed4fea5222..f2dcdbbc09b7996fd91d13e8f2e6d97319a3ed85 100644 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -3126,7 +3126,7 @@ class FormattedValueNode(ExprNode): self.format_spec = self.format_spec.analyse_types(env).coerce_to_pyobject(env) if self.c_format_spec is None: self.value = self.value.coerce_to_pyobject(env) - if not self.format_spec and not self.conversion_char: + if not self.format_spec and (not self.conversion_char or self.conversion_char == 's'): if self.value.type is unicode_type and not self.value.may_be_none(): # value is definitely a unicode string and we don't format it any special return self.value diff --git a/Cython/Compiler/Optimize.py b/Cython/Compiler/Optimize.py index 109c49794d3720f83e802559fc9fbac4cb332a32..479165f744f39694e5ed21047ad2e24c7dffad8a 100644 --- a/Cython/Compiler/Optimize.py +++ b/Cython/Compiler/Optimize.py @@ -3959,10 +3959,21 @@ class ConstantFolding(Visitor.VisitorTransform, SkipDeclarations): def visit_FormattedValueNode(self, node): self.visitchildren(node) + conversion_char = node.conversion_char or 's' if isinstance(node.format_spec, ExprNodes.UnicodeNode) and not node.format_spec.value: node.format_spec = None - if node.format_spec is None and node.conversion_char is None and isinstance(node.value, ExprNodes.UnicodeNode): - return node.value + if node.format_spec is None and isinstance(node.value, ExprNodes.IntNode): + value = EncodedString(node.value.value) + if value.isdigit(): + return ExprNodes.UnicodeNode(node.value.pos, value=value, constant_result=value) + if node.format_spec is None and conversion_char == 's': + value = None + if isinstance(node.value, ExprNodes.UnicodeNode): + value = node.value.value + elif isinstance(node.value, ExprNodes.StringNode): + value = node.value.unicode_value + if value is not None: + return ExprNodes.UnicodeNode(node.value.pos, value=value, constant_result=value) return node def visit_JoinedStrNode(self, node): @@ -3980,7 +3991,8 @@ class ConstantFolding(Visitor.VisitorTransform, SkipDeclarations): substrings = list(substrings) unode = substrings[0] if len(substrings) > 1: - unode.value = EncodedString(u''.join(value.value for value in substrings)) + value = EncodedString(u''.join(value.value for value in substrings)) + unode = ExprNodes.UnicodeNode(unode.pos, value=value, constant_result=value) # ignore empty Unicode strings if unode.value: values.append(unode) @@ -3988,7 +4000,8 @@ class ConstantFolding(Visitor.VisitorTransform, SkipDeclarations): values.extend(substrings) if not values: - node = ExprNodes.UnicodeNode(node.pos, value=EncodedString('')) + value = EncodedString('') + node = ExprNodes.UnicodeNode(node.pos, value=value, constant_result=value) elif len(values) == 1: node = values[0] elif len(values) == 2: diff --git a/tests/run/fstring.pyx b/tests/run/fstring.pyx index b01662f91552e285961c1bde54541705bb30eb63..09f5ec496c077d901b8a4a5f1b619d7489e2f321 100644 --- a/tests/run/fstring.pyx +++ b/tests/run/fstring.pyx @@ -5,6 +5,8 @@ # Cython specific PEP 498 tests in addition to test_fstring.pyx from CPython #### +cimport cython + import sys IS_PYPY = hasattr(sys, 'pypy_version_info') @@ -18,13 +20,39 @@ max_long = LONG_MAX min_long = LONG_MIN +@cython.test_fail_if_path_exists( + "//FormattedValueNode", + "//JoinedStrNode", + "//AddNode", +) def escaping(): """ >>> escaping() """ assert f'{{{{{"abc"}}}}}{{}}{{' == '{{abc}}{}{' + s = f'{{{{{"abc"}}}}}{{}}{{' + assert s == '{{abc}}{}{', s + assert f'\x7b}}' == '{}' + s = f'\x7b}}' + assert s == '{}', s + assert f'{"{{}}"}' == '{{}}' + s = f'{"{{}}"}' + assert s == '{{}}', s + + +@cython.test_fail_if_path_exists( + "//FormattedValueNode", + "//JoinedStrNode", + "//AddNode", +) +def nested_constant(): + """ + >>> nested_constant() + 'xyabc123321' + """ + return f"""{f'''xy{f"abc{123}{'321'}"!s}'''}""" def format2(ab, cd):