From 45c3f3966a6aafced11b5233426015e7f5a97d49 Mon Sep 17 00:00:00 2001 From: Anton Topchii Date: Mon, 19 Sep 2022 12:13:16 +0200 Subject: [PATCH 1/6] Fix: change pow and unary minus priority bug, and add test. --- src/jinja2/parser.py | 6 +++--- tests/test_lexnparse.py | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/jinja2/parser.py b/src/jinja2/parser.py index cefce2dfa..dbec1be2e 100644 --- a/src/jinja2/parser.py +++ b/src/jinja2/parser.py @@ -604,7 +604,7 @@ def parse_concat(self) -> nodes.Expr: def parse_math2(self) -> nodes.Expr: lineno = self.stream.current.lineno - left = self.parse_pow() + left = self.parse_unary() while self.stream.current.type in ("mul", "div", "floordiv", "mod"): cls = _math_nodes[self.stream.current.type] next(self.stream) @@ -615,7 +615,7 @@ def parse_math2(self) -> nodes.Expr: def parse_pow(self) -> nodes.Expr: lineno = self.stream.current.lineno - left = self.parse_unary() + left = self.parse_primary() while self.stream.current.type == "pow": next(self.stream) right = self.parse_unary() @@ -635,7 +635,7 @@ def parse_unary(self, with_filter: bool = True) -> nodes.Expr: next(self.stream) node = nodes.Pos(self.parse_unary(False), lineno=lineno) else: - node = self.parse_primary() + node = self.parse_pow() node = self.parse_postfix(node) if with_filter: node = self.parse_filter_expr(node) diff --git a/tests/test_lexnparse.py b/tests/test_lexnparse.py index c02adad5a..d862b7e5c 100644 --- a/tests/test_lexnparse.py +++ b/tests/test_lexnparse.py @@ -575,6 +575,10 @@ def test_parse_unary(self, env): tmpl = env.from_string('{{ -foo["bar"]|abs }}') assert tmpl.render(foo={"bar": 42}) == "42" + def test_negative_pow(self, env): + tmpl = env.from_string("{{ - 1 ** 2 }}") + assert tmpl.render() == "-1" + class TestLstripBlocks: def test_lstrip(self, env): From e1b8db8ef9ea29116bad8df21b9b26958923c3ef Mon Sep 17 00:00:00 2001 From: Anton Topchii Date: Mon, 19 Sep 2022 20:50:11 +0200 Subject: [PATCH 2/6] Add: test for pow evaluation order --- tests/test_lexnparse.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_lexnparse.py b/tests/test_lexnparse.py index d862b7e5c..38770941a 100644 --- a/tests/test_lexnparse.py +++ b/tests/test_lexnparse.py @@ -579,6 +579,10 @@ def test_negative_pow(self, env): tmpl = env.from_string("{{ - 1 ** 2 }}") assert tmpl.render() == "-1" + def test_pow_associative_from_left_to_right(self, env): + tmpl = env.from_string("{{ 2 ** 3 ** 2 }}") + assert tmpl.render() == "512" + class TestLstripBlocks: def test_lstrip(self, env): From 9ab3dc6abf2aa7c12603f1a3eedef475cec4706d Mon Sep 17 00:00:00 2001 From: Anton Topchii Date: Mon, 19 Sep 2022 21:00:35 +0200 Subject: [PATCH 3/6] Add: section to CHANGES.rst --- CHANGES.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index fede06ed3..207c863ab 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,10 @@ .. currentmodule:: jinja2 +Unreleased + +- Fix a bug with lexer evaluation order of exponent. :issue:`1720` :issue:`1722` + + Version 3.1.2 ------------- From 8a501e1f80ba788b845fedde491691491612ecc8 Mon Sep 17 00:00:00 2001 From: Anton Topchii Date: Fri, 25 Nov 2022 16:52:30 +0100 Subject: [PATCH 4/6] Add: more tests for parser. Wrap first arg of binop with additional brackets. --- src/jinja2/compiler.py | 3 ++- tests/test_lexnparse.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/jinja2/compiler.py b/src/jinja2/compiler.py index 3458095f5..5a8b8baf3 100644 --- a/src/jinja2/compiler.py +++ b/src/jinja2/compiler.py @@ -68,8 +68,9 @@ def visitor(self: "CodeGenerator", node: nodes.BinExpr, frame: Frame) -> None: self.write(", ") self.visit(node.right, frame) else: - self.write("(") + self.write("((") self.visit(node.left, frame) + self.write(")") self.write(f" {op} ") self.visit(node.right, frame) diff --git a/tests/test_lexnparse.py b/tests/test_lexnparse.py index 38770941a..bdee2e864 100644 --- a/tests/test_lexnparse.py +++ b/tests/test_lexnparse.py @@ -583,6 +583,18 @@ def test_pow_associative_from_left_to_right(self, env): tmpl = env.from_string("{{ 2 ** 3 ** 2 }}") assert tmpl.render() == "512" + def test_pow_negative_base(self, env): + tmpl = env.from_string("{{ (- 1) ** 2 }}") + assert tmpl.render() == "1" + + def test_pow_negative_base_variable_exponent(self, env): + tmpl = env.from_string("{{ (- 1) ** x }}", {"x": 2}) + assert tmpl.render() == "1" + + def test_negative_pow_variable_exponent(self, env): + tmpl = env.from_string("{{ - 1 ** x }}", {"x": 2}) + assert tmpl.render() == "-1" + class TestLstripBlocks: def test_lstrip(self, env): From c141c96dbfa22f2d80309db0f14a7cb0c49312b9 Mon Sep 17 00:00:00 2001 From: Anton Topchii Date: Fri, 25 Nov 2022 17:05:46 +0100 Subject: [PATCH 5/6] Change wording in changelog --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 207c863ab..7eca79c7d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,7 +2,7 @@ Unreleased -- Fix a bug with lexer evaluation order of exponent. :issue:`1720` :issue:`1722` +- Breaking change. The evaluation order of an exponent expression changed. Now it is more consistent with how it's going in pure python. :issue:`1720` :issue:`1722` Version 3.1.2 From 402d111f0ce31f7b837dcd29a110f76e7cfdaac2 Mon Sep 17 00:00:00 2001 From: Anton Topchii Date: Fri, 25 Nov 2022 17:20:37 +0100 Subject: [PATCH 6/6] Fix: add mention about new power evaluation order to documentation --- docs/templates.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/templates.rst b/docs/templates.rst index 2471cea39..d7f1fcf62 100644 --- a/docs/templates.rst +++ b/docs/templates.rst @@ -1369,7 +1369,10 @@ but exists for completeness' sake. The following operators are supported: ``**`` Raise the left operand to the power of the right operand. - ``{{ 2**3 }}`` would return ``8``. + ``{{ 2**3 }}`` would return ``8`` + + .. versionchanged:: 3.2 + From 3.2, this operator has the same evaluation order as in Python. ``{{ 2 ** 3 ** 2 }}`` will be evaluated as ``2 ** (3 ** 2)``. Unlike Python, chained pow is evaluated left to right. ``{{ 3**3**3 }}`` is evaluated as ``(3**3)**3`` in Jinja, but would