From 1a1c8fb4433e4b938e7a12f6aa05e9c71823adf4 Mon Sep 17 00:00:00 2001 From: Stefan Behnel <stefan_ml@behnel.de> Date: Thu, 31 Aug 2017 22:43:45 +0200 Subject: [PATCH] Avoid f-string formatting of known unicode strings also for "!s" conversion. Evaluate and join more f-string constants at compile time, including unprefixed strings. Fix a case where f-string constants could have incorrect constant compile time result values, thus leading to incorrect comparisons and constant folding errors. --- CHANGES.rst | 3 +++ Cython/Compiler/ExprNodes.py | 2 +- Cython/Compiler/Optimize.py | 21 +++++++++++++++++---- tests/run/fstring.pyx | 28 ++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index bdf8c845b..c4e3043a3 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 f0ea3f980..f2dcdbbc0 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 109c49794..479165f74 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 b01662f91..09f5ec496 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): -- 2.30.9