Skip to content

Commit 39d9fff

Browse files
authored
Make compiled output deterministic for tuple unpacking in set tag (#2022)
2 parents 3ef3ba8 + 4936e4d commit 39d9fff

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Unreleased
2020
async-aware filter. :issue:`1781`
2121
- ``|int`` filter handles ``OverflowError`` from scientific notation.
2222
:issue:`1921`
23+
- Make compiling deterministic for tuple unpacking in a ``{% set ... %}``
24+
call. :issue:`2021`
2325

2426

2527
Version 3.1.4

src/jinja2/compiler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ def pop_assign_tracking(self, frame: Frame) -> None:
811811
self.writeline("_block_vars.update({")
812812
else:
813813
self.writeline("context.vars.update({")
814-
for idx, name in enumerate(vars):
814+
for idx, name in enumerate(sorted(vars)):
815815
if idx:
816816
self.write(", ")
817817
ref = frame.symbols.ref(name)
@@ -821,7 +821,7 @@ def pop_assign_tracking(self, frame: Frame) -> None:
821821
if len(public_names) == 1:
822822
self.writeline(f"context.exported_vars.add({public_names[0]!r})")
823823
else:
824-
names_str = ", ".join(map(repr, public_names))
824+
names_str = ", ".join(map(repr, sorted(public_names)))
825825
self.writeline(f"context.exported_vars.update(({names_str}))")
826826

827827
# -- Statement Visitors

tests/test_compile.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,64 @@ def test_import_as_with_context_deterministic(tmp_path):
2626
expect = [f"'bar{i}': " for i in range(10)]
2727
found = re.findall(r"'bar\d': ", content)[:10]
2828
assert found == expect
29+
30+
31+
def test_top_level_set_vars_unpacking_deterministic(tmp_path):
32+
src = "\n".join(f"{{% set a{i}, b{i}, c{i} = tuple_var{i} %}}" for i in range(10))
33+
env = Environment(loader=DictLoader({"foo": src}))
34+
env.compile_templates(tmp_path, zip=None)
35+
name = os.listdir(tmp_path)[0]
36+
content = (tmp_path / name).read_text("utf8")
37+
expect = [
38+
f"context.vars.update({{'a{i}': l_0_a{i}, 'b{i}': l_0_b{i}, 'c{i}': l_0_c{i}}})"
39+
for i in range(10)
40+
]
41+
found = re.findall(
42+
r"context\.vars\.update\(\{'a\d': l_0_a\d, 'b\d': l_0_b\d, 'c\d': l_0_c\d\}\)",
43+
content,
44+
)[:10]
45+
assert found == expect
46+
expect = [
47+
f"context.exported_vars.update(('a{i}', 'b{i}', 'c{i}'))" for i in range(10)
48+
]
49+
found = re.findall(
50+
r"context\.exported_vars\.update\(\('a\d', 'b\d', 'c\d'\)\)",
51+
content,
52+
)[:10]
53+
assert found == expect
54+
55+
56+
def test_loop_set_vars_unpacking_deterministic(tmp_path):
57+
src = "\n".join(f" {{% set a{i}, b{i}, c{i} = tuple_var{i} %}}" for i in range(10))
58+
src = f"{{% for i in seq %}}\n{src}\n{{% endfor %}}"
59+
env = Environment(loader=DictLoader({"foo": src}))
60+
env.compile_templates(tmp_path, zip=None)
61+
name = os.listdir(tmp_path)[0]
62+
content = (tmp_path / name).read_text("utf8")
63+
expect = [
64+
f"_loop_vars.update({{'a{i}': l_1_a{i}, 'b{i}': l_1_b{i}, 'c{i}': l_1_c{i}}})"
65+
for i in range(10)
66+
]
67+
found = re.findall(
68+
r"_loop_vars\.update\(\{'a\d': l_1_a\d, 'b\d': l_1_b\d, 'c\d': l_1_c\d\}\)",
69+
content,
70+
)[:10]
71+
assert found == expect
72+
73+
74+
def test_block_set_vars_unpacking_deterministic(tmp_path):
75+
src = "\n".join(f" {{% set a{i}, b{i}, c{i} = tuple_var{i} %}}" for i in range(10))
76+
src = f"{{% block test %}}\n{src}\n{{% endblock test %}}"
77+
env = Environment(loader=DictLoader({"foo": src}))
78+
env.compile_templates(tmp_path, zip=None)
79+
name = os.listdir(tmp_path)[0]
80+
content = (tmp_path / name).read_text("utf8")
81+
expect = [
82+
f"_block_vars.update({{'a{i}': l_0_a{i}, 'b{i}': l_0_b{i}, 'c{i}': l_0_c{i}}})"
83+
for i in range(10)
84+
]
85+
found = re.findall(
86+
r"_block_vars\.update\(\{'a\d': l_0_a\d, 'b\d': l_0_b\d, 'c\d': l_0_c\d\}\)",
87+
content,
88+
)[:10]
89+
assert found == expect

0 commit comments

Comments
 (0)