From 24f283f4304854648f48019d3d0b48f9b701652c Mon Sep 17 00:00:00 2001 From: dnwpark Date: Tue, 4 Feb 2025 10:04:53 -0500 Subject: [PATCH] Fix inline functions substituting parameters from hidden nodes. (#8288) A function body may be a statement which contains references to a parent statement. Ensure that this parent's parameters are not substituted while inlining the function parameters. --- edb/common/ast/transformer.py | 12 +++++++++ edb/edgeql/compiler/func.py | 4 +++ tests/test_edgeql_functions_inline.py | 39 +++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/edb/common/ast/transformer.py b/edb/common/ast/transformer.py index 30d11671c79..c3e893d5beb 100644 --- a/edb/common/ast/transformer.py +++ b/edb/common/ast/transformer.py @@ -64,6 +64,12 @@ def generic_visit(self, node): changes = {} for field, old_value in base.iter_fields(node, include_meta=False): + field_spec = node._fields[field] + if self.skip_hidden and field_spec.hidden: + continue + if field in self.extra_skips: + continue + old_value = getattr(node, field, None) if typeutils.is_container(old_value): @@ -79,6 +85,12 @@ def generic_visit(self, node): else: for field, old_value in base.iter_fields(node, include_meta=False): + field_spec = node._fields[field] + if self.skip_hidden and field_spec.hidden: + continue + if field in self.extra_skips: + continue + old_value = getattr(node, field, None) if typeutils.is_container(old_value): diff --git a/edb/edgeql/compiler/func.py b/edb/edgeql/compiler/func.py index 736220333a2..19f46345346 100644 --- a/edb/edgeql/compiler/func.py +++ b/edb/edgeql/compiler/func.py @@ -479,6 +479,10 @@ def compile_FunctionCall( class ArgumentInliner(ast.NodeTransformer): + # Don't look through hidden nodes, they may contain references to nodes + # which should not be modified. For example, irast.Stmt.parent_stmt. + skip_hidden = True + mapped_args: dict[irast.PathId, irast.PathId] inlined_arg_keys: list[int | str] diff --git a/tests/test_edgeql_functions_inline.py b/tests/test_edgeql_functions_inline.py index 14f0bf13241..9487ca4a87f 100644 --- a/tests/test_edgeql_functions_inline.py +++ b/tests/test_edgeql_functions_inline.py @@ -3133,6 +3133,45 @@ async def test_edgeql_functions_inline_nested_basic_20(self): sort=True, ) + async def test_edgeql_functions_inline_nested_basic_21(self): + # Inner function body is a statement with a parent statement + # + # A function body may be a statement which contains references to a + # parent statement. Ensure that this parent's parameters are not + # substituted while inlining the function parameters. + # + # In this case the outer function's `for` contains the parameter `x` + # which is at risk of being substituted when the inner function + # inlines its parameters. + await self.con.execute(''' + create function inner(x: int64) -> int64 { + set is_inlined := true; + using (select x) + }; + create function foo(x: int64) -> set of int64 { + set is_inlined := true; + using (for y in {x, x + 1, x + 2} union (inner(y))); + }; + ''') + await self.assert_query_result( + 'select foo({})', + [], + ) + await self.assert_query_result( + 'select foo(10)', + [10, 11, 12], + ) + await self.assert_query_result( + 'select foo({10, 20, 30})', + [10, 11, 12, 20, 21, 22, 30, 31, 32], + sort=True, + ) + await self.assert_query_result( + 'for x in {10, 20, 30} union (select foo(x))', + [10, 11, 12, 20, 21, 22, 30, 31, 32], + sort=True, + ) + async def test_edgeql_functions_inline_nested_array_01(self): # Return array from inner function await self.con.execute('''