Repository: iris-hep/func_adl

Issue:
Function simiplification should not return the ast as-is, but a copy for multiple references
When we do a dict reference twice, we should not return the same AST. This is because there are cases where we attach other information to the ast, and then we evaulate things in multiple contexts, which turns sour very vast in the xAOD backend.

Discussion hints:
codecov-commenter: ## [Codecov](https://app.codecov.io/gh/iris-hep/func_adl/pull/184?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iris-hep) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 97.43%. Comparing base [(`6f98f02`)](https://app.codecov.io/gh/iris-hep/func_adl/commit/6f98f02049ccb7207e35c032c614ec80e70b57e1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iris-hep) to head [(`222f5b7`)](https://app.codecov.io/gh/iris-hep/func_adl/commit/222f5b76584025cfae76dc122f59b25dc3d70856?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iris-hep).

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master     #184   +/-   ##
=======================================
  Coverage   97.43%   97.43%           
=======================================
  Files          15       15           
  Lines        1367     1367           
=======================================
  Hits         1332     1332           
  Misses         35       35           
```

| [Flag](https://app.codecov.io/gh/iris-hep/func_adl/pull/184/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iris-hep) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/iris-hep/func_adl/pull/184/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iris-hep) | `97.43% <100.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iris-hep#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/iris-hep/func_adl/pull/184?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iris-hep).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iris-hep).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Solution patch:
diff --git a/func_adl/ast/function_simplifier.py b/func_adl/ast/function_simplifier.py
index a86f704..9a409a7 100644
--- a/func_adl/ast/function_simplifier.py
+++ b/func_adl/ast/function_simplifier.py
@@ -453,7 +453,7 @@ class simplify_chained_calls(FuncADLNodeTransformer):
                 f" {len(v.elts)} values long."
             )
 
-        return v.elts[n]
+        return copy.deepcopy(v.elts[n])
 
     def visit_Subscript_List(self, v: ast.List, s: ast.Constant):
         """
@@ -470,7 +470,7 @@ class simplify_chained_calls(FuncADLNodeTransformer):
                 f" only {len(v.elts)} values long."
             )
 
-        return v.elts[n]
+        return copy.deepcopy(v.elts[n])
 
     def visit_Subscript_Dict(self, v: ast.Dict, s: ast.Constant):
         """
@@ -485,7 +485,7 @@ class simplify_chained_calls(FuncADLNodeTransformer):
         for index, value in enumerate(v.keys):
             assert isinstance(value, ast.Constant)
             if value.value == s:
-                return v.values[index]
+                return copy.deepcopy(v.values[index])
 
         return ast.Subscript(v, s, ast.Load())  # type: ignore
 


Test patch:
diff --git a/tests/ast/test_function_simplifier.py b/tests/ast/test_function_simplifier.py
index 5ac6e23..1c159e2 100644
--- a/tests/ast/test_function_simplifier.py
+++ b/tests/ast/test_function_simplifier.py
@@ -1,6 +1,9 @@
 import ast
+import copy
+from typing import cast
 
 from astunparse import unparse
+
 from func_adl.ast.function_simplifier import FuncADLIndexError, simplify_chained_calls
 from tests.util_debug_ast import normalize_ast
 
@@ -18,10 +21,14 @@ def util_process(ast_in, ast_out):
     a_updated_raw = simplify_chained_calls().visit(a_source)
 
     s_updated = ast.dump(
-        normalize_ast().visit(a_updated_raw), annotate_fields=False, include_attributes=False
+        normalize_ast().visit(copy.deepcopy(a_updated_raw)),
+        annotate_fields=False,
+        include_attributes=False,
     )
     s_expected = ast.dump(
-        normalize_ast().visit(a_expected), annotate_fields=False, include_attributes=False
+        normalize_ast().visit(copy.deepcopy(a_expected)),
+        annotate_fields=False,
+        include_attributes=False,
     )
 
     print(s_updated)
@@ -183,11 +190,27 @@ def test_tuple_select():
     util_process("(t1,t2)[0]", "t1")
 
 
+def test_tuple_select_copy_made():
+    # (t1, t2)[0] should be t1.
+    root = ast.parse("(t1,t2)[0]")
+    orig = root.body[0].value.value.elts[0]  # type: ignore
+    r1 = util_process(root, "t1")
+    assert r1.body[0].value != orig
+
+
 def test_list_select():
     # [t1, t2][0] should be t1.
     util_process("[t1,t2][0]", "t1")
 
 
+def test_list_select_copy_made():
+    # [t1, t2][0] should be t1.
+    root = ast.parse("[t1,t2][0]")
+    orig = root.body[0].value.value.elts[0]  # type: ignore
+    r1 = util_process(root, "t1")
+    assert r1.body[0].value != orig
+
+
 def test_tuple_select_past_end():
     # This should cause a crash!
     try:
@@ -260,3 +283,21 @@ def test_dict_around_first():
         'Select(events, lambda e: First(Select(e.jets, lambda j: {"j": j, "e": e})).j)',
         "Select(events, lambda e: First(e.jets))",
     )
+
+
+def test_dict_is_different_attrib():
+    "Make sure we are returning different ast's that have the same content"
+    root = cast(ast.expr, ast.parse('{"n1": t1, "n2": t2}.n1'))
+    orig_name = root.body[0].value.value.values[0]  # type: ignore
+    r1 = util_process(root, "t1")
+
+    assert r1.body[0].value != orig_name
+
+
+def test_dict_is_different():
+    "Make sure we are returning different ast's that have the same content"
+    root = cast(ast.expr, ast.parse('{"n1": t1, "n2": t2}["n1"]'))
+    orig_name = root.body[0].value.value.values[0]  # type: ignore
+    r1 = util_process(root, "t1")
+
+    assert r1.body[0].value != orig_name