]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
make tuple unpacking deterministic in compiler 2022/head
authorAnentropic <ego@anentropic.com>
Tue, 3 Sep 2024 21:16:33 +0000 (22:16 +0100)
committerDavid Lord <davidism@gmail.com>
Thu, 19 Dec 2024 16:02:33 +0000 (08:02 -0800)
CHANGES.rst
src/jinja2/compiler.py
tests/test_compile.py

index 6c998e62692ba6806ed8b44167f221c20d599b2e..aebb38b58083e15c9eb52faa23af2a797f7ddb2b 100644 (file)
@@ -20,6 +20,8 @@ Unreleased
     async-aware filter. :issue:`1781`
 -   ``|int`` filter handles ``OverflowError`` from scientific notation.
     :issue:`1921`
+-   Make compiling deterministic for tuple unpacking in a ``{% set ... %}``
+    call. :issue:`2021`
 
 
 Version 3.1.4
index 91720c5f97cdbb4017d38344a6dd0d4685a7d66c..074e9b1871bb9584f7038ebb632ef16ef418322a 100644 (file)
@@ -811,7 +811,7 @@ class CodeGenerator(NodeVisitor):
                 self.writeline("_block_vars.update({")
             else:
                 self.writeline("context.vars.update({")
-            for idx, name in enumerate(vars):
+            for idx, name in enumerate(sorted(vars)):
                 if idx:
                     self.write(", ")
                 ref = frame.symbols.ref(name)
@@ -821,7 +821,7 @@ class CodeGenerator(NodeVisitor):
             if len(public_names) == 1:
                 self.writeline(f"context.exported_vars.add({public_names[0]!r})")
             else:
-                names_str = ", ".join(map(repr, public_names))
+                names_str = ", ".join(map(repr, sorted(public_names)))
                 self.writeline(f"context.exported_vars.update(({names_str}))")
 
     # -- Statement Visitors
index 42a773f21cceff1695a5da1d7cb17abd0a86dd52..42efa59c0f02e1268ea71b479883c085973dc113 100644 (file)
@@ -26,3 +26,64 @@ def test_import_as_with_context_deterministic(tmp_path):
     expect = [f"'bar{i}': " for i in range(10)]
     found = re.findall(r"'bar\d': ", content)[:10]
     assert found == expect
+
+
+def test_top_level_set_vars_unpacking_deterministic(tmp_path):
+    src = "\n".join(f"{{% set a{i}, b{i}, c{i} = tuple_var{i} %}}" for i in range(10))
+    env = Environment(loader=DictLoader({"foo": src}))
+    env.compile_templates(tmp_path, zip=None)
+    name = os.listdir(tmp_path)[0]
+    content = (tmp_path / name).read_text("utf8")
+    expect = [
+        f"context.vars.update({{'a{i}': l_0_a{i}, 'b{i}': l_0_b{i}, 'c{i}': l_0_c{i}}})"
+        for i in range(10)
+    ]
+    found = re.findall(
+        r"context\.vars\.update\(\{'a\d': l_0_a\d, 'b\d': l_0_b\d, 'c\d': l_0_c\d\}\)",
+        content,
+    )[:10]
+    assert found == expect
+    expect = [
+        f"context.exported_vars.update(('a{i}', 'b{i}', 'c{i}'))" for i in range(10)
+    ]
+    found = re.findall(
+        r"context\.exported_vars\.update\(\('a\d', 'b\d', 'c\d'\)\)",
+        content,
+    )[:10]
+    assert found == expect
+
+
+def test_loop_set_vars_unpacking_deterministic(tmp_path):
+    src = "\n".join(f"  {{% set a{i}, b{i}, c{i} = tuple_var{i} %}}" for i in range(10))
+    src = f"{{% for i in seq %}}\n{src}\n{{% endfor %}}"
+    env = Environment(loader=DictLoader({"foo": src}))
+    env.compile_templates(tmp_path, zip=None)
+    name = os.listdir(tmp_path)[0]
+    content = (tmp_path / name).read_text("utf8")
+    expect = [
+        f"_loop_vars.update({{'a{i}': l_1_a{i}, 'b{i}': l_1_b{i}, 'c{i}': l_1_c{i}}})"
+        for i in range(10)
+    ]
+    found = re.findall(
+        r"_loop_vars\.update\(\{'a\d': l_1_a\d, 'b\d': l_1_b\d, 'c\d': l_1_c\d\}\)",
+        content,
+    )[:10]
+    assert found == expect
+
+
+def test_block_set_vars_unpacking_deterministic(tmp_path):
+    src = "\n".join(f"  {{% set a{i}, b{i}, c{i} = tuple_var{i} %}}" for i in range(10))
+    src = f"{{% block test %}}\n{src}\n{{% endblock test %}}"
+    env = Environment(loader=DictLoader({"foo": src}))
+    env.compile_templates(tmp_path, zip=None)
+    name = os.listdir(tmp_path)[0]
+    content = (tmp_path / name).read_text("utf8")
+    expect = [
+        f"_block_vars.update({{'a{i}': l_0_a{i}, 'b{i}': l_0_b{i}, 'c{i}': l_0_c{i}}})"
+        for i in range(10)
+    ]
+    found = re.findall(
+        r"_block_vars\.update\(\{'a\d': l_0_a\d, 'b\d': l_0_b\d, 'c\d': l_0_c\d\}\)",
+        content,
+    )[:10]
+    assert found == expect