classiq 0.58.1__py3-none-any.whl → 0.60.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. classiq/_internals/api_wrapper.py +91 -20
  2. classiq/_internals/client.py +48 -11
  3. classiq/_internals/jobs.py +47 -40
  4. classiq/execution/execution_session.py +62 -22
  5. classiq/execution/jobs.py +64 -23
  6. classiq/execution/qaoa.py +17 -15
  7. classiq/execution/qnn.py +17 -18
  8. classiq/executor.py +2 -1
  9. classiq/interface/_version.py +1 -1
  10. classiq/interface/generator/arith/arithmetic_operations.py +1 -0
  11. classiq/interface/generator/register_role.py +8 -0
  12. classiq/interface/model/handle_binding.py +22 -3
  13. classiq/model_expansions/capturing/captured_vars.py +316 -0
  14. classiq/model_expansions/capturing/mangling_utils.py +18 -9
  15. classiq/model_expansions/closure.py +29 -74
  16. classiq/model_expansions/function_builder.py +51 -66
  17. classiq/model_expansions/interpreter.py +4 -7
  18. classiq/model_expansions/quantum_operations/bind.py +1 -3
  19. classiq/model_expansions/quantum_operations/call_emitter.py +46 -11
  20. classiq/model_expansions/quantum_operations/classicalif.py +2 -5
  21. classiq/model_expansions/quantum_operations/control.py +13 -16
  22. classiq/model_expansions/quantum_operations/emitter.py +36 -8
  23. classiq/model_expansions/quantum_operations/expression_operation.py +9 -19
  24. classiq/model_expansions/quantum_operations/inplace_binary_operation.py +4 -6
  25. classiq/model_expansions/quantum_operations/invert.py +5 -8
  26. classiq/model_expansions/quantum_operations/power.py +5 -10
  27. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +1 -3
  28. classiq/model_expansions/quantum_operations/quantum_function_call.py +1 -3
  29. classiq/model_expansions/quantum_operations/repeat.py +3 -3
  30. classiq/model_expansions/quantum_operations/variable_decleration.py +1 -1
  31. classiq/model_expansions/quantum_operations/within_apply.py +1 -5
  32. classiq/model_expansions/scope.py +2 -2
  33. classiq/model_expansions/transformers/var_splitter.py +32 -19
  34. classiq/model_expansions/utils/handles_collector.py +33 -0
  35. classiq/model_expansions/visitors/variable_references.py +18 -2
  36. classiq/qmod/qfunc.py +9 -13
  37. classiq/qmod/quantum_expandable.py +1 -21
  38. classiq/qmod/quantum_function.py +16 -0
  39. {classiq-0.58.1.dist-info → classiq-0.60.0.dist-info}/METADATA +1 -1
  40. {classiq-0.58.1.dist-info → classiq-0.60.0.dist-info}/RECORD +41 -42
  41. classiq/interface/executor/aws_execution_cost.py +0 -90
  42. classiq/model_expansions/capturing/captured_var_manager.py +0 -48
  43. classiq/model_expansions/capturing/propagated_var_stack.py +0 -194
  44. {classiq-0.58.1.dist-info → classiq-0.60.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,316 @@
1
+ import dataclasses
2
+ from collections.abc import Iterator, Sequence
3
+ from contextlib import contextmanager
4
+ from dataclasses import dataclass, field
5
+ from typing import TYPE_CHECKING
6
+
7
+ from classiq.interface.enum_utils import StrEnum
8
+ from classiq.interface.exceptions import (
9
+ ClassiqExpansionError,
10
+ ClassiqInternalExpansionError,
11
+ )
12
+ from classiq.interface.generator.functions.port_declaration import (
13
+ PortDeclarationDirection,
14
+ )
15
+ from classiq.interface.model.handle_binding import HandleBinding, NestedHandleBinding
16
+ from classiq.interface.model.port_declaration import PortDeclaration
17
+ from classiq.interface.model.quantum_function_call import ArgValue
18
+ from classiq.interface.model.quantum_type import QuantumType
19
+
20
+ from classiq.model_expansions.capturing.mangling_utils import (
21
+ demangle_handle,
22
+ mangle_captured_var_name,
23
+ )
24
+ from classiq.model_expansions.scope import QuantumSymbol
25
+ from classiq.model_expansions.transformers.var_splitter import SymbolPart, SymbolParts
26
+
27
+ if TYPE_CHECKING:
28
+ from classiq.model_expansions.closure import FunctionClosure
29
+
30
+
31
+ class PortDirection(StrEnum):
32
+ Input = "input"
33
+ Inout = "inout"
34
+ Output = "output"
35
+ Outin = "outin"
36
+
37
+ def negate(self) -> "PortDirection":
38
+ if self == PortDirection.Input:
39
+ return PortDirection.Output
40
+ if self == PortDirection.Output:
41
+ return PortDirection.Input
42
+ return self
43
+
44
+ @staticmethod
45
+ def load(direction: PortDeclarationDirection) -> "PortDirection":
46
+ if direction == PortDeclarationDirection.Input:
47
+ return PortDirection.Input
48
+ if direction == PortDeclarationDirection.Output:
49
+ return PortDirection.Output
50
+ if direction == PortDeclarationDirection.Inout:
51
+ return PortDirection.Inout
52
+ raise ClassiqInternalExpansionError
53
+
54
+ def dump(self) -> PortDeclarationDirection:
55
+ if self == PortDirection.Input:
56
+ return PortDeclarationDirection.Input
57
+ if self == PortDirection.Output:
58
+ return PortDeclarationDirection.Output
59
+ if self == PortDirection.Inout:
60
+ return PortDeclarationDirection.Inout
61
+ raise ClassiqInternalExpansionError
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class _CapturedHandle:
66
+ handle: HandleBinding
67
+ quantum_type: QuantumType
68
+ defining_function: "FunctionClosure"
69
+ direction: PortDirection
70
+ is_propagated: bool
71
+
72
+ @property
73
+ def mangled_name(self) -> str:
74
+ return mangle_captured_var_name(self.handle.identifier, self.defining_function)
75
+
76
+ @property
77
+ def port(self) -> PortDeclaration:
78
+ return PortDeclaration(
79
+ name=self.mangled_name,
80
+ quantum_type=self.quantum_type,
81
+ direction=self.direction.dump(),
82
+ )
83
+
84
+ def is_same_var(self, other: "_CapturedHandle") -> bool:
85
+ return self.handle.name == other.handle.name and _same_closure(
86
+ self.defining_function, other.defining_function
87
+ )
88
+
89
+ def change_direction(self, new_direction: PortDirection) -> "_CapturedHandle":
90
+ return dataclasses.replace(self, direction=new_direction)
91
+
92
+ def set_propagated(self) -> "_CapturedHandle":
93
+ return dataclasses.replace(self, is_propagated=True)
94
+
95
+ def update_propagation(
96
+ self, other_captured_handle: "_CapturedHandle"
97
+ ) -> "_CapturedHandle":
98
+ if self.is_propagated and not other_captured_handle.is_propagated:
99
+ return dataclasses.replace(self, is_propagated=False)
100
+ return self
101
+
102
+
103
+ @dataclass
104
+ class CapturedVars:
105
+ _captured_handles: list[_CapturedHandle] = field(default_factory=list)
106
+
107
+ def capture_handle(
108
+ self,
109
+ handle: HandleBinding,
110
+ quantum_type: QuantumType,
111
+ defining_function: "FunctionClosure",
112
+ direction: PortDeclarationDirection,
113
+ ) -> None:
114
+ self._capture_handle(
115
+ _CapturedHandle(
116
+ handle=handle,
117
+ quantum_type=quantum_type,
118
+ defining_function=defining_function,
119
+ direction=PortDirection.load(direction),
120
+ is_propagated=False,
121
+ )
122
+ )
123
+
124
+ def _capture_handle(self, captured_handle: _CapturedHandle) -> None:
125
+ if (
126
+ isinstance(captured_handle.handle, NestedHandleBinding)
127
+ and captured_handle.direction != PortDirection.Inout
128
+ ):
129
+ raise ClassiqInternalExpansionError("Captured nested handles must be inout")
130
+
131
+ new_captured_handles = []
132
+ for existing_captured_handle in self._captured_handles:
133
+ if not existing_captured_handle.is_same_var(captured_handle):
134
+ new_captured_handles.append(existing_captured_handle)
135
+ continue
136
+ captured_handle = captured_handle.update_propagation(
137
+ existing_captured_handle
138
+ )
139
+ if existing_captured_handle.handle == captured_handle.handle:
140
+ captured_handle = self._conjugate_direction(
141
+ existing_captured_handle, captured_handle
142
+ )
143
+ elif captured_handle.handle in existing_captured_handle.handle:
144
+ if existing_captured_handle.direction in (
145
+ PortDirection.Input,
146
+ PortDirection.Outin,
147
+ ):
148
+ raise ClassiqInternalExpansionError(
149
+ "Captured handle is already freed"
150
+ )
151
+ captured_handle = existing_captured_handle
152
+ elif existing_captured_handle.handle in captured_handle.handle:
153
+ if captured_handle.direction in (
154
+ PortDirection.Output,
155
+ PortDirection.Outin,
156
+ ):
157
+ raise ClassiqInternalExpansionError(
158
+ "Captured handle is already allocated"
159
+ )
160
+ else:
161
+ new_captured_handles.append(existing_captured_handle)
162
+ new_captured_handles.append(captured_handle)
163
+ self._captured_handles = new_captured_handles
164
+
165
+ def _conjugate_direction(
166
+ self,
167
+ existing_captured_handle: _CapturedHandle,
168
+ captured_handle: _CapturedHandle,
169
+ ) -> _CapturedHandle:
170
+ if existing_captured_handle.direction == PortDirection.Input:
171
+ if captured_handle.direction == PortDirection.Output:
172
+ return captured_handle.change_direction(PortDirection.Inout)
173
+ if captured_handle.direction == PortDirection.Outin:
174
+ return captured_handle.change_direction(PortDirection.Input)
175
+ raise ClassiqInternalExpansionError("Captured handle is already freed")
176
+ if existing_captured_handle.direction == PortDirection.Output:
177
+ if captured_handle.direction == PortDirection.Input:
178
+ return captured_handle.change_direction(PortDirection.Outin)
179
+ if captured_handle.direction in (
180
+ PortDirection.Output,
181
+ PortDirection.Outin,
182
+ ):
183
+ raise ClassiqInternalExpansionError(
184
+ "Captured handle is already allocated"
185
+ )
186
+ return captured_handle.change_direction(PortDirection.Output)
187
+ if existing_captured_handle.direction == PortDirection.Inout:
188
+ if captured_handle.direction in (
189
+ PortDirection.Output,
190
+ PortDirection.Outin,
191
+ ):
192
+ raise ClassiqInternalExpansionError(
193
+ "Captured handle is already allocated"
194
+ )
195
+ elif captured_handle.direction in (
196
+ PortDirection.Input,
197
+ PortDirection.Inout,
198
+ ):
199
+ raise ClassiqInternalExpansionError("Captured handle is already freed")
200
+ return captured_handle
201
+
202
+ def update(self, other_captured_vars: "CapturedVars") -> None:
203
+ for captured_handle in other_captured_vars._captured_handles:
204
+ self._capture_handle(captured_handle)
205
+
206
+ def negate(self) -> "CapturedVars":
207
+ return CapturedVars(
208
+ _captured_handles=[
209
+ captured_handle.change_direction(captured_handle.direction.negate())
210
+ for captured_handle in self._captured_handles
211
+ ]
212
+ )
213
+
214
+ def filter(self, current_function: "FunctionClosure") -> "CapturedVars":
215
+ return CapturedVars(
216
+ _captured_handles=[
217
+ captured_handle
218
+ for captured_handle in self._captured_handles
219
+ if not _same_closure(
220
+ captured_handle.defining_function, current_function
221
+ )
222
+ ]
223
+ )
224
+
225
+ def set_propagated(self) -> "CapturedVars":
226
+ return CapturedVars(
227
+ _captured_handles=[
228
+ captured_handle.set_propagated()
229
+ for captured_handle in self._captured_handles
230
+ ]
231
+ )
232
+
233
+ def get_captured_ports(self) -> list[PortDeclaration]:
234
+ return [captured_handle.port for captured_handle in self._captured_handles]
235
+
236
+ def get_captured_args(
237
+ self, current_function: "FunctionClosure"
238
+ ) -> list[HandleBinding]:
239
+ return [
240
+ (
241
+ captured_handle.handle
242
+ if _same_closure(current_function, captured_handle.defining_function)
243
+ else HandleBinding(name=captured_handle.mangled_name)
244
+ )
245
+ for captured_handle in self._captured_handles
246
+ ]
247
+
248
+ def get_captured_mapping(self) -> SymbolParts:
249
+ return {
250
+ QuantumSymbol(
251
+ handle=captured_handle.handle,
252
+ quantum_type=captured_handle.quantum_type,
253
+ ): [
254
+ SymbolPart(
255
+ source_handle=captured_handle.handle,
256
+ target_var_name=captured_handle.mangled_name,
257
+ target_var_type=captured_handle.quantum_type,
258
+ )
259
+ ]
260
+ for captured_handle in self._captured_handles
261
+ if not captured_handle.is_propagated
262
+ }
263
+
264
+ @contextmanager
265
+ def freeze(self) -> Iterator[None]:
266
+ previous = self._captured_handles
267
+ yield
268
+ self._captured_handles = previous
269
+
270
+
271
+ def _same_closure(closure_1: "FunctionClosure", closure_2: "FunctionClosure") -> bool:
272
+ return closure_1.depth == closure_2.depth
273
+
274
+
275
+ def validate_args_are_not_propagated(
276
+ args: Sequence[ArgValue], captured_vars: Sequence[HandleBinding]
277
+ ) -> None:
278
+ if not captured_vars:
279
+ return
280
+ captured_handles = {demangle_handle(handle) for handle in captured_vars}
281
+ arg_handles = {
282
+ demangle_handle(arg) for arg in args if isinstance(arg, HandleBinding)
283
+ }
284
+ if any(
285
+ arg_handle.overlaps(captured_handle)
286
+ for arg_handle in arg_handles
287
+ for captured_handle in captured_handles
288
+ ):
289
+ captured_handles_str = {str(handle) for handle in captured_handles}
290
+ arg_handles_str = {str(handle) for handle in arg_handles}
291
+ vars_msg = f"Explicitly passed variables: {arg_handles_str}, captured variables: {captured_handles_str}"
292
+ raise ClassiqExpansionError(
293
+ f"Cannot capture variables that are explicitly passed as arguments. "
294
+ f"{vars_msg}"
295
+ )
296
+
297
+
298
+ def validate_captured_directions(captured_vars: CapturedVars) -> None:
299
+ captured_inputs = [
300
+ captured_handle.handle.name
301
+ for captured_handle in captured_vars._captured_handles
302
+ if captured_handle.direction == PortDirection.Input
303
+ ]
304
+ captured_outputs = [
305
+ captured_handle.handle.name
306
+ for captured_handle in captured_vars._captured_handles
307
+ if captured_handle.direction == PortDirection.Output
308
+ ]
309
+ if len(captured_inputs) > 0:
310
+ raise ClassiqExpansionError(
311
+ f"Captured quantum variables {captured_inputs!r} cannot be used as inputs"
312
+ )
313
+ if len(captured_outputs) > 0:
314
+ raise ClassiqExpansionError(
315
+ f"Captured quantum variables {captured_outputs!r} cannot be used as outputs"
316
+ )
@@ -1,17 +1,25 @@
1
1
  import re
2
+ from typing import TYPE_CHECKING
2
3
 
3
4
  from classiq.interface.generator.compiler_keywords import CAPTURE_SUFFIX
4
5
  from classiq.interface.model.handle_binding import HANDLE_ID_SEPARATOR, HandleBinding
5
6
 
7
+ if TYPE_CHECKING:
8
+ from classiq.model_expansions.closure import FunctionClosure
9
+
6
10
  IDENTIFIER_PATTERN = r"[a-zA-Z_][a-zA-Z0-9_]*"
7
11
  CAPTURE_PATTERN = re.compile(
8
- rf"({IDENTIFIER_PATTERN}){CAPTURE_SUFFIX}{IDENTIFIER_PATTERN}__"
12
+ rf"({IDENTIFIER_PATTERN}){CAPTURE_SUFFIX}{IDENTIFIER_PATTERN}__\d*"
9
13
  )
10
14
  ARRAY_CAST_SUFFIX = HANDLE_ID_SEPARATOR + "array_cast"
11
15
 
12
16
 
13
- def mangle_captured_var_name(var_name: str, defining_function: str) -> str:
14
- return f"{var_name}{CAPTURE_SUFFIX}{defining_function}__"
17
+ def mangle_captured_var_name(
18
+ var_name: str, defining_function: "FunctionClosure"
19
+ ) -> str:
20
+ return (
21
+ f"{var_name}{CAPTURE_SUFFIX}{defining_function.name}__{defining_function.depth}"
22
+ )
15
23
 
16
24
 
17
25
  def demangle_name(name: str) -> str:
@@ -25,15 +33,16 @@ def demangle_handle(handle: HandleBinding) -> HandleBinding:
25
33
  return handle
26
34
  if ARRAY_CAST_SUFFIX in name:
27
35
  return HandleBinding(name=name.split(ARRAY_CAST_SUFFIX)[0])
28
- name = re.sub(r"_\d+$", "", name)
36
+ name = re.sub(r"([^_])_\d+$", r"\1", name)
29
37
  name_parts = name.split(HANDLE_ID_SEPARATOR)
30
- new_name = name_parts[0]
38
+ new_name_parts = [name_parts[0]]
31
39
  for part in name_parts[1:]:
32
40
  if re.fullmatch(r"\d+", part):
33
- new_name += f"[{part}]"
41
+ new_name_parts.append(f"[{part}]")
34
42
  elif re.fullmatch(r"\d+_\d+", part):
35
43
  part_left, part_right = part.split("_")
36
- new_name += f"[{part_left}:{part_right}]"
44
+ new_name_parts.append(f"[{part_left}:{part_right}]")
37
45
  else:
38
- new_name += f".{part}"
39
- return handle.rename(new_name)
46
+ new_name_parts.append(f".{part}")
47
+ new_name_parts = list(map(demangle_name, new_name_parts))
48
+ return handle.rename("".join(new_name_parts))
@@ -1,34 +1,29 @@
1
+ import dataclasses
1
2
  import json
2
3
  import uuid
3
- from collections import defaultdict
4
- from collections.abc import Collection, Sequence
4
+ from collections.abc import Collection, Iterator, Sequence
5
+ from contextlib import contextmanager
5
6
  from dataclasses import dataclass, field
6
- from functools import cached_property, singledispatch
7
+ from functools import singledispatch
7
8
  from symtable import Symbol
8
- from typing import Any, Optional, Union
9
+ from typing import Any, Optional
9
10
 
10
11
  from typing_extensions import Self
11
12
 
12
- from classiq.interface.exceptions import (
13
- ClassiqInternalExpansionError,
14
- )
13
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
15
14
  from classiq.interface.generator.functions.builtins.internal_operators import (
16
15
  All_BUILTINS_OPERATORS,
17
16
  )
18
- from classiq.interface.generator.visitor import Visitor
19
17
  from classiq.interface.model.port_declaration import PortDeclaration
20
- from classiq.interface.model.quantum_function_call import QuantumFunctionCall
21
18
  from classiq.interface.model.quantum_function_declaration import (
22
19
  NamedParamsQuantumFunctionDeclaration,
23
20
  PositionalArg,
24
21
  QuantumOperandDeclaration,
25
22
  )
26
23
  from classiq.interface.model.quantum_statement import QuantumStatement
27
- from classiq.interface.model.variable_declaration_statement import (
28
- VariableDeclarationStatement,
29
- )
30
24
 
31
25
  from classiq import ClassicalParameterDeclaration
26
+ from classiq.model_expansions.capturing.captured_vars import CapturedVars
32
27
  from classiq.model_expansions.expression_renamer import ExpressionRenamer
33
28
  from classiq.model_expansions.scope import (
34
29
  Evaluated,
@@ -46,6 +41,7 @@ class Closure:
46
41
  blocks: dict[str, Sequence[QuantumStatement]]
47
42
  scope: Scope
48
43
  positional_arg_declarations: Sequence[PositionalArg] = tuple()
44
+ captured_vars: CapturedVars = field(default_factory=CapturedVars)
49
45
 
50
46
  @property
51
47
  def port_declarations(self) -> dict[str, PortDeclaration]:
@@ -55,6 +51,11 @@ class Closure:
55
51
  if isinstance(param, PortDeclaration)
56
52
  }
57
53
 
54
+ @contextmanager
55
+ def freeze(self) -> Iterator[None]:
56
+ with self.scope.freeze(), self.captured_vars.freeze():
57
+ yield
58
+
58
59
 
59
60
  @dataclass(frozen=True)
60
61
  class GenerativeClosure(Closure):
@@ -66,6 +67,13 @@ class FunctionClosure(Closure):
66
67
  is_lambda: bool = False
67
68
  is_atomic: bool = False
68
69
  signature_scope: Scope = field(default_factory=Scope)
70
+ _depth: Optional[int] = None
71
+
72
+ @property
73
+ def depth(self) -> int:
74
+ if self._depth is None:
75
+ raise ClassiqInternalExpansionError
76
+ return self._depth
69
77
 
70
78
  # creates a unique id for the function closure based on the arguments values.
71
79
  # The closure is changing across the interpreter flow so it's closure_id may change
@@ -89,12 +97,6 @@ class FunctionClosure(Closure):
89
97
  return []
90
98
  return self.blocks["body"]
91
99
 
92
- @cached_property
93
- def colliding_variables(self) -> set[str]:
94
- # Note that this has to be accessed after adding the parameters from the signature and not during
95
- # initialization
96
- return VariableCollector(self.scope).get_colliding_variables(self.body)
97
-
98
100
  @classmethod
99
101
  def create(
100
102
  cls,
@@ -122,6 +124,7 @@ class FunctionClosure(Closure):
122
124
  blocks,
123
125
  scope,
124
126
  positional_arg_declarations,
127
+ CapturedVars(),
125
128
  is_lambda,
126
129
  is_atomic,
127
130
  **kwargs,
@@ -134,68 +137,20 @@ class FunctionClosure(Closure):
134
137
  "name": declaration.name,
135
138
  "positional_arg_declarations": declaration.positional_arg_declarations,
136
139
  }
137
- fields.pop("colliding_variables", 0)
138
140
  return type(self)(**fields)
139
141
 
142
+ def set_depth(self, depth: int) -> Self:
143
+ return dataclasses.replace(self, _depth=depth)
140
144
 
141
- @dataclass(frozen=True)
142
- class GenerativeFunctionClosure(GenerativeClosure, FunctionClosure):
143
- pass
144
-
145
-
146
- NestedFunctionClosureT = Union[FunctionClosure, list["NestedFunctionClosureT"]]
147
-
148
-
149
- class VariableCollector(Visitor):
150
- def __init__(self, function_scope: Scope) -> None:
151
- self._function_scope = function_scope
152
- self._variables: defaultdict[str, set[Optional[str]]] = defaultdict(set)
153
- for var in self._function_scope.data:
154
- defining_function = self._function_scope[var].defining_function
155
- if defining_function is not None:
156
- self._variables[var].add(defining_function.name)
157
-
158
- def get_colliding_variables(self, body: Sequence[QuantumStatement]) -> set[str]:
159
- self.visit(body)
160
- return {
161
- var
162
- for var, defining_functions in self._variables.items()
163
- if len(defining_functions) > 1
164
- }
165
-
166
- def visit_VariableDeclarationStatement(
167
- self, node: VariableDeclarationStatement
168
- ) -> None:
169
- self._variables[node.name].add(None)
170
-
171
- def visit_QuantumFunctionCall(self, node: QuantumFunctionCall) -> None:
172
- # The else case corresponds to operand identifiers. In case of operand identifiers, we scan
173
- # the whole list of operands because we can't evaluate the index yet.
174
- identifier = (
175
- node.function if isinstance(node.function, str) else node.function.name
145
+ def copy_scope(self) -> Self: # Remove when scoping is normal (CAD-24980)
146
+ return dataclasses.replace(
147
+ self, scope=Scope(self.scope.data, parent=self.scope.parent)
176
148
  )
177
- self._add_variables(self._function_scope[identifier].value)
178
-
179
- def _add_variables(self, evaluated: NestedFunctionClosureT) -> None:
180
- if isinstance(evaluated, list):
181
- for elem in evaluated:
182
- self._add_variables(elem)
183
- return
184
- if not isinstance(evaluated, FunctionClosure):
185
- raise ClassiqInternalExpansionError
186
- self._add_variables_from_closure(evaluated)
187
149
 
188
- def _add_variables_from_closure(self, closure: FunctionClosure) -> None:
189
- if not closure.is_lambda:
190
- return
191
- lambda_environment = closure.scope.parent
192
- if lambda_environment is None:
193
- raise ClassiqInternalExpansionError
194
150
 
195
- for var in lambda_environment.iter_without_top_level():
196
- defining_function = lambda_environment[var].defining_function
197
- if defining_function is not None:
198
- self._variables[var].add(defining_function.name)
151
+ @dataclass(frozen=True)
152
+ class GenerativeFunctionClosure(GenerativeClosure, FunctionClosure):
153
+ pass
199
154
 
200
155
 
201
156
  def _generate_closure_id(