Coverage for skema/program_analysis/CAST2FN/model/cast_to_air_model.py: 49%
354 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-30 17:15 +0000
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-30 17:15 +0000
1from skema.program_analysis.CAST2FN.model.cast import source_ref
2from skema.program_analysis.CAST2FN.model.cast.source_ref import SourceRef
3from typing import List, Dict, NoReturn, Set
4from enum import Enum
5from dataclasses import dataclass
6from datetime import datetime
8from skema.program_analysis.CAST2FN.model.cast import AstNode, var
9from skema.model_assembly.metadata import (
10 BaseMetadata,
11 MetadataType,
12 TypedMetadata,
13 VariableFromSource,
14)
17class C2ATypeError(TypeError):
18 """
19 Used to create exceptions during the CAST to AIR execution
21 Args:
22 Exception: An exception that occured during CAST to AIR execution
23 """
25 pass
28class C2ARuntimeError(Exception):
29 """
30 Used for any runtime errors that occur during CAST --> AIR processing
32 Args:
33 Exception: An exception that occured during CAST to AIR execution
34 """
36 pass
39class C2ANameError(NameError):
40 """
41 Used when name errors occur (such as a missing member variable for some
42 object) during CAST
44 Args:
45 Exception: An exception that occured during CAST to AIR execution
46 """
48 pass
51class C2AValueError(Exception):
52 """
53 Used when an operation cannot be performed for a given value during CAST
55 Args:
56 Exception: An exception that occured during CAST to AIR execution
57 """
59 pass
62class C2AException(Exception):
63 """
64 Used to create exceptions during the CAST to AIR execution
66 Args:
67 Exception: An exception that occured during CAST to AIR execution
68 """
70 pass
73class C2AIdentifierType(str, Enum):
74 UNKNOWN = "unknown"
75 VARIABLE = "variable"
76 CONTAINER = "container"
77 LAMBDA = "lambda"
78 DECISION = "decision"
79 PACK = "pack"
80 EXTRACT = "extract"
83@dataclass(repr=True, frozen=True)
84class C2AIdentifierInformation(object):
86 name: str
87 scope: List[str]
88 module: str
89 identifier_type: C2AIdentifierType
91 def build_identifier(self):
92 return f'@{self.identifier_type}::{self.module}::{".".join(self.scope)}::{self.name}'
95@dataclass(repr=True, frozen=True)
96class C2ASourceRef(object):
97 """
98 Represents a reference point of the containing object in the original source
99 code. If a field of the line/col reference information is missing, it will
100 hold the value -1.
101 """
103 file: str
104 line_begin: int
105 col_start: int
106 line_end: int
107 col_end: int
109 def to_AIR(self):
110 return self.__dict__
113class C2AVariable(object):
115 identifier_information: C2AIdentifierInformation
116 version: int
117 type_name: str
118 source_ref: C2ASourceRef
119 metadata: list
121 def __init__(
122 self,
123 identifier_information: C2AIdentifierInformation,
124 version: int,
125 type_name: str,
126 source_ref: C2ASourceRef,
127 ):
128 self.identifier_information = identifier_information
129 self.version = version
130 self.type_name = type_name
131 self.source_ref = source_ref
132 self.metadata = list()
134 def get_name(self):
135 return self.identifier_information.name
137 def build_identifier(self):
138 """
139 Builds the variable identifier which uses the identifier from identifier
140 information plus the variable version
142 Returns:
143 str: Unique variable identifier
144 """
145 return f"{self.identifier_information.build_identifier()}::{str(self.version)}"
147 def add_metadata(self, data: BaseMetadata):
148 self.metadata.append(data)
150 def to_AIR(self):
151 # TODO
152 domain = {
153 "type": "type", # TODO what is this field?
154 "mutable": False, # TODO probably only mutable if object/list/dict type
155 }
156 if self.type_name == "Number":
157 domain["name"] = "integer"
158 elif self.type_name.startswith("object$"):
159 name = self.type_name.split("object$")[-1]
160 domain.update(
161 {
162 "name": "object",
163 "object_name": name,
164 }
165 )
166 else:
167 domain["name"] = self.type_name
169 has_from_source_metadata = False
170 for m in self.metadata:
171 if m.type == MetadataType.FROM_SOURCE:
172 has_from_source_metadata = True
173 break
174 # If from_source does not exist already, then it wasnt handled by a special
175 # case where we added a variable during processing, so add True from_source
176 # metadata
177 if not has_from_source_metadata:
178 self.add_metadata(
179 TypedMetadata.from_data(
180 {
181 "type": "FROM_SOURCE",
182 "provenance": {
183 "method": "PROGRAM_ANALYSIS_PIPELINE",
184 "timestamp": datetime.now(),
185 },
186 "from_source": True,
187 "creation_reason": "UNKNOWN",
188 }
189 )
190 )
192 return {
193 "name": self.build_identifier(),
194 "source_refs": [self.source_ref.to_AIR()],
195 "domain": domain,
196 "domain_constraint": "(and (> v -infty) (< v infty))", # TODO
197 "metadata": [m.to_dict() for m in self.metadata],
198 }
201class C2ALambdaType(str, Enum):
202 UNKNOWN = "unknown"
203 ASSIGN = "assign"
204 CONDITION = "condition"
205 DECISION = "decision"
206 EXIT = "exit"
207 RETURN = "return"
208 CONTAINER = "container"
209 OPERATOR = "operator"
210 EXTRACT = "extract"
211 PACK = "pack"
214def build_unique_list_with_order(l, predicate):
215 new_list = []
216 for i in l:
217 res = predicate(i)
218 if res not in new_list:
219 new_list.append(res)
220 return new_list
223@dataclass(repr=True, frozen=False)
224class C2ALambda(object):
225 """
226 Represents an executable container/ function to transition between states in AIR
228 lambda, container, if-block, function
229 """
231 # Identifying information for lambda function
232 identifier_information: C2AIdentifierInformation
233 # Represents the variables coming into a lambda or container
234 input_variables: List[C2AVariable]
235 # Represents the new versions of variables that are created and output
236 output_variables: List[C2AVariable]
237 # Represents variables that were updated (typically list/dict/object with fields changed)
238 updated_variables: List[C2AVariable]
239 # The type of the container.
240 container_type: C2ALambdaType
241 # The reference to the source code that this lambda was derived from
242 source_ref: C2ASourceRef
244 def build_name(self):
245 var = None
246 # TODO how should we build the name if there is multiple updated/output vars?
247 # Will this situation be possible?
248 if len(self.output_variables) > 0:
249 var = self.output_variables[0]
250 elif len(self.updated_variables) > 0:
251 var = self.updated_variables[0]
252 else:
253 raise C2AException(f"No variables output or updated by lambda")
255 return (
256 f"{self.identifier_information.module}"
257 f"__{'.'.join(self.identifier_information.scope)}"
258 f"__{self.container_type}"
259 f"__{var.identifier_information.name}"
260 f"__{var.version}"
261 )
263 def to_AIR(self):
264 return self
267@dataclass(repr=True, frozen=False)
268class C2AExpressionLambda(C2ALambda):
269 """
270 A type of function within AIR that represents an executable lambda expression that transitions
271 between states of the data flow of the program
272 """
274 lambda_expr: str
275 cast: AstNode
277 def to_AIR(self):
278 return {
279 "function": {
280 "name": self.build_name(),
281 "type": "lambda",
282 "code": self.lambda_expr,
283 },
284 "input": build_unique_list_with_order(
285 self.input_variables, lambda v: v.build_identifier()
286 ),
287 "output": build_unique_list_with_order(
288 self.output_variables, lambda v: v.build_identifier()
289 ),
290 "updated": build_unique_list_with_order(
291 self.updated_variables, lambda v: v.build_identifier()
292 ),
293 "source_ref": self.source_ref.to_AIR(),
294 "metadata": [],
295 }
298@dataclass(repr=True, frozen=False)
299class C2AContainerCallLambda(C2ALambda):
300 """
301 Represents the call/passing to another container found in the body of a container definition
302 """
304 def build_name(self):
305 return self.identifier_information.build_identifier()
307 def to_AIR(self):
308 return {
309 "function": {
310 "name": self.identifier_information.build_identifier(),
311 "type": self.container_type.value,
312 },
313 # Note: Do not build a unique list because the same var could be
314 # passed in multiple times
315 "input": [v.build_identifier() for v in self.input_variables],
316 "output": build_unique_list_with_order(
317 self.output_variables, lambda v: v.build_identifier()
318 ),
319 "updated": build_unique_list_with_order(
320 self.updated_variables, lambda v: v.build_identifier()
321 ),
322 "source_ref": self.source_ref.to_AIR(),
323 "metadata": [],
324 }
327@dataclass(repr=True, frozen=False)
328class C2AContainerDef(object):
329 """
330 Represents a top level AIR container def. Has its arguments, outputs/ updates, and a body
332 lambda, container, if-block, function
333 """
335 # Name of the containrt
336 identifier_information: C2AIdentifierInformation
337 # Represents the variables coming into a lambda or container
338 arguments: List[C2AVariable]
339 # Represents the new versions of variables that are created and output
340 output_variables: List[C2AVariable]
341 # Represents variables that were updated (typically list/dict/object with fields changed)
342 updated_variables: List[C2AVariable]
343 # Represents the executable body statements
344 body: List[C2ALambda]
345 # Defines the span of code for this container body in the original source code
346 body_source_ref: C2ASourceRef
347 # Tracks what variables were added as arguments to container from previous scope
348 vars_from_previous_scope: List[C2AVariable]
350 def build_identifier(self):
351 return self.identifier_information.build_identifier()
353 def to_AIR(self):
354 return self
356 def add_arguments(self, arguments_to_add: List[C2AVariable]):
357 for v in arguments_to_add:
358 if v not in set(self.arguments):
359 self.arguments.append(v)
361 def add_outputs(self, output_variables_to_add: List[C2AVariable]):
362 # self.output_variables.update(set(output_variables_to_add))
363 for v in output_variables_to_add:
364 if v not in set(self.output_variables):
365 self.output_variables.append(v)
367 def add_updated(self, updated_variables_to_add: List[C2AVariable]):
368 # self.updated_variables.update(set(updated_variables_to_add))
369 for v in updated_variables_to_add:
370 if v not in set(self.updated_variables):
371 self.updated_variables.append(v)
373 def add_body_lambdas(self, body_to_add: List[C2ALambda]):
374 self.body.extend(body_to_add)
376 def add_body_source_ref(self, body_source_ref: SourceRef):
377 self.body_source_ref = body_source_ref
379 def add_var_used_from_previous_scope(self, var):
380 self.vars_from_previous_scope.append(var)
383@dataclass(repr=True, frozen=False)
384class C2AFunctionDefContainer(C2AContainerDef):
385 """
386 Represents a top level container definition. Input variables will represent the arguments to the funciton in the AIR. Also contains a body.
387 """
389 return_type_name: str
391 def to_AIR(self):
392 return {
393 # TODO
394 "name": self.identifier_information.build_identifier(),
395 "source_refs": [],
396 "type": "function",
397 "arguments": build_unique_list_with_order(
398 self.arguments, lambda v: v.build_identifier()
399 ),
400 "updated": build_unique_list_with_order(
401 self.updated_variables, lambda v: v.build_identifier()
402 ),
403 "return_value": build_unique_list_with_order(
404 self.output_variables, lambda v: v.build_identifier()
405 ),
406 "body": [i.to_AIR() for i in self.body],
407 "body_source_ref": self.body_source_ref.to_AIR(),
408 "metadata": [],
409 }
412@dataclass(repr=True, frozen=False)
413class C2ALoopContainer(C2AContainerDef):
414 """
415 Represents a top level container definition. Input variables will represent
416 the arguments that go through the loop interface. Also contains a body.
417 """
419 # Represents the reference to the source code where the conditional for this loop is
420 condition_source_ref: C2ASourceRef
422 def to_AIR(self):
423 return {
424 # TODO
425 "name": self.identifier_information.build_identifier(),
426 "source_refs": [],
427 "type": "loop",
428 "arguments": build_unique_list_with_order(
429 self.arguments, lambda v: v.build_identifier()
430 ),
431 "updated": build_unique_list_with_order(
432 self.updated_variables, lambda v: v.build_identifier()
433 ),
434 "return_value": build_unique_list_with_order(
435 self.output_variables, lambda v: v.build_identifier()
436 ),
437 "body": [i.to_AIR() for i in self.body],
438 "body_source_ref": self.body_source_ref.to_AIR(),
439 "condition_source_ref": self.condition_source_ref.to_AIR(),
440 "metadata": [],
441 }
444@dataclass(repr=True, frozen=False)
445class C2AIfContainer(C2AContainerDef):
446 """
447 Represents a top level container definition. Input variables will represent
448 the arguments that go through the if interface. Also contains a body.
449 """
451 # Represents the reference to the source code where the conditional for this if is
452 condition_source_ref: C2ASourceRef
453 # Output vars per each condition in the if block. Represent else condition
454 # as condition number -1.
455 output_per_condition: Dict[int, List[C2AVariable]]
457 def add_condition_outputs(self, condition_num, outputs):
458 if condition_num not in self.output_per_condition:
459 self.output_per_condition[condition_num] = []
460 self.output_per_condition[condition_num].extend(outputs)
462 def to_AIR(self):
464 return {
465 # TODO
466 "name": self.identifier_information.build_identifier(),
467 "source_refs": [],
468 "type": "if-block",
469 "arguments": build_unique_list_with_order(
470 self.arguments, lambda v: v.build_identifier()
471 ),
472 "updated": build_unique_list_with_order(
473 self.updated_variables, lambda v: v.build_identifier()
474 ),
475 "return_value": build_unique_list_with_order(
476 self.output_variables, lambda v: v.build_identifier()
477 ),
478 "body": [i.to_AIR() for i in self.body],
479 "body_source_ref": self.body_source_ref.to_AIR(),
480 "condition_source_ref": self.condition_source_ref.to_AIR(),
481 "metadata": [],
482 }
485@dataclass(repr=True, frozen=True)
486class C2ATypeDef(object):
487 class C2AType(str, Enum):
488 INTEGER = "integer"
489 FLOAT = "float"
490 STRING = "string"
491 LIST = "list"
492 DICT = "dict"
493 SET = "set"
494 OBJECT = "object"
496 name: str
497 given_type: C2AType
498 fields: Dict[str, C2AVariable]
499 function_identifiers: List[str]
500 source_ref: C2ASourceRef
502 def to_AIR(self):
503 air = self.__dict__
504 air.update({"metadata": [], "metatype": "composite"})
505 return air
508class C2AAttributeAccessState(object):
510 var_to_current_extract_node: Dict[str, C2ALambda]
511 var_to_current_pack_node: Dict[str, C2ALambda]
513 def __init__(self):
514 self.var_to_current_extract_node = {}
515 self.var_to_current_pack_node = {}
517 def need_attribute_extract(self, var, attr_var):
518 # Check if the attr_var name appears in either the extract node for the
519 # var or the current pack var. If it exists in either, we should not
520 # add the same attribute for extract.
521 vars_to_check = (
522 self.var_to_current_extract_node[var].output_variables
523 if var in self.var_to_current_extract_node
524 else []
525 ) + (
526 self.var_to_current_pack_node[var].input_variables
527 if var in self.var_to_current_pack_node
528 else []
529 )
531 return not any(
532 [
533 v.identifier_information.name
534 == attr_var.identifier_information.name
535 for v in vars_to_check
536 ]
537 )
539 def build_extract_lambda(self, extract_var, output_variables):
540 obj_var_name = extract_var.identifier_information.name
541 lambda_dict_keys = [
542 v.identifier_information.name.split("_", 1)[1]
543 for v in output_variables
544 ]
545 lambda_dict_accesses = ",".join(
546 [
547 f'{obj_var_name}["{v}"]'
548 for v in lambda_dict_keys
549 if obj_var_name != v
550 ]
551 )
552 lambda_expr = f"lambda {obj_var_name}: ({lambda_dict_accesses})"
553 return lambda_expr
555 def add_attribute_access(self, var, attr_var):
556 extract_lambda = self.var_to_current_extract_node.get(var, None)
557 if extract_lambda is None:
558 id = var.identifier_information
559 extract_lambda = C2AExpressionLambda(
560 C2AIdentifierInformation(
561 "EXTRACT", id.scope, id.module, C2AIdentifierType.CONTAINER
562 ),
563 [var],
564 [attr_var],
565 [],
566 C2ALambdaType.EXTRACT,
567 C2ASourceRef("", None, None, None, None),
568 self.build_extract_lambda(var, [attr_var]),
569 None,
570 )
571 self.var_to_current_extract_node[var] = extract_lambda
572 return extract_lambda
574 extract_lambda.output_variables.append(attr_var)
575 extract_lambda.lambda_expr = self.build_extract_lambda(
576 var, extract_lambda.output_variables
577 )
579 def add_attribute_to_pack(self, var, attr_var):
580 pack_lambda = self.var_to_current_pack_node.get(var, None)
581 if pack_lambda is None:
582 id = var.identifier_information
583 pack_lambda = C2AExpressionLambda(
584 C2AIdentifierInformation(
585 "PACK", id.scope, id.module, C2AIdentifierType.CONTAINER
586 ),
587 [var],
588 [],
589 [],
590 C2ALambdaType.PACK,
591 C2ASourceRef("", None, None, None, None),
592 "", # will be filled out when "get_outstandin_pack_node" is called
593 None,
594 )
596 for v in pack_lambda.input_variables:
597 if (
598 v.identifier_information.name
599 == attr_var.identifier_information.name
600 ):
601 pack_lambda.input_variables.remove(v)
602 pack_lambda.input_variables.append(attr_var)
604 self.var_to_current_pack_node[var] = pack_lambda
606 def has_outstanding_pack_nodes(self):
607 return bool(self.var_to_current_pack_node)
609 def get_outstanding_pack_node(self, var):
610 pack_lambda = self.var_to_current_pack_node.get(var)
611 # Add the updated version of the var after packing
612 new_var = C2AVariable(
613 var.identifier_information,
614 var.version + 1,
615 var.type_name,
616 var.source_ref,
617 )
618 pack_lambda.output_variables.append(new_var)
620 # Add the correct lambda now that we have all vars to pack
621 obj_var_name = var.identifier_information.name
622 lambda_inputs = [
623 v.identifier_information.name for v in pack_lambda.input_variables
624 ]
625 lambda_body_dict = ",".join(
626 [
627 f'"{v.split(f"{obj_var_name}_")[-1]}": ' + v
628 for v in lambda_inputs
629 if obj_var_name != v
630 ]
631 )
632 lambda_expr = (
633 f"lambda {','.join(lambda_inputs)}:"
634 f"{{ **{obj_var_name}, **{{ {lambda_body_dict} }} }}"
635 )
636 pack_lambda.lambda_expr = lambda_expr
638 # Delete from state map upon retrieval
639 del self.var_to_current_pack_node[var]
640 if var in self.var_to_current_extract_node:
641 del self.var_to_current_extract_node[var]
643 return pack_lambda
645 def get_outstanding_pack_nodes(self):
646 return [
647 self.get_outstanding_pack_node(k)
648 for k in self.var_to_current_pack_node.copy().keys()
649 ]
652class C2AVariableContext(Enum):
653 LOAD = 0
654 STORE = 1
655 ATTR_VALUE = 2
656 UNKNOWN = 3
659class C2AState(object):
660 containers: List[C2AContainerDef]
661 variables: List[C2AVariable]
662 types: List[C2ATypeDef]
663 scope_stack: List[str]
664 current_module: str
665 current_function: C2AFunctionDefContainer
666 current_conditional: int
667 attribute_access_state: C2AAttributeAccessState
668 current_context: C2AVariableContext
670 def __init__(self):
671 self.containers = list()
672 self.variables = list()
673 self.types = list()
674 self.scope_stack = []
675 self.current_module = "initial"
676 self.current_function = None
677 self.current_conditional = 0
678 self.current_context = C2AVariableContext.UNKNOWN
679 self.attribute_access_state = C2AAttributeAccessState()
681 def add_container(self, con: C2AContainerDef):
682 self.containers.append(con)
684 def add_variable(self, var: C2AVariable):
685 self.variables.append(var)
687 def add_type(self, type: C2ATypeDef):
688 self.types.append(type)
690 def get_scope_stack(self):
691 """
692 Returns the current scope of the CAST to AIR state
693 """
694 return self.scope_stack.copy()
696 def push_scope(self, scope):
697 """
698 Places the name scope level name onto the scope stack
699 """
700 self.scope_stack.append(scope)
702 def pop_scope(self):
703 """
704 Removes the last scope name from the stack and returns it
705 """
706 top = self.scope_stack[-1]
707 self.scope_stack = self.scope_stack[:-1]
708 return top
710 def is_var_identifier_in_variables(self, identifier):
711 for v in self.variables:
712 if v.build_identifier() == identifier:
713 return True
714 return False
716 def find_highest_version_var_in_scope(self, var_name, scope):
717 """
718 Given a variable name, finds the highest version defined
719 for that variable given a scope
720 """
721 # Check that the global/function_name are the same
722 # TODO define what needs to be checked here better
723 def share_scope(scope1, scope2):
724 return scope1 == scope2
726 instances = [
727 v
728 for v in self.variables
729 if v.identifier_information.name == var_name
730 and share_scope(scope, v.identifier_information.scope)
731 ]
732 return max(instances, key=lambda v: v.version, default=None)
734 def find_highest_version_var_in_previous_scopes(self, var_name):
735 """
736 Given a variable name, finds the highest version defined
737 for that variable along our current scope path
738 """
739 # Subtract one so we look at all scopes except "global"
740 i = len(self.scope_stack)
741 while i >= 0:
742 res = self.find_highest_version_var_in_scope(
743 var_name, self.scope_stack[:i]
744 )
745 if res is not None:
746 return res
747 i -= 1
749 return None
751 def find_highest_version_var_in_current_scope(self, var_name):
752 """
753 Given a variable name, finds the highest version defined
754 for that variable given the current scope
755 """
756 return self.find_highest_version_var_in_scope(
757 var_name, self.scope_stack
758 )
760 def find_next_var_version(self, var_name):
761 """
762 Determines the next version of a variable given its name and
763 variables in the current scope.
764 """
765 current_highest_ver = self.find_highest_version_var_in_current_scope(
766 var_name
767 )
768 return (
769 current_highest_ver.version + 1
770 if current_highest_ver is not None
771 else -1
772 )
774 def find_container(self, scope):
775 matching = [
776 c
777 for c in self.containers
778 if c.identifier_information.scope + [c.identifier_information.name]
779 == scope
780 ]
782 return matching[0] if matching else None
784 def find_root_level_containers(self):
785 called_containers = [
786 s.identifier_information.build_identifier()
787 for c in self.containers
788 for s in c.body
789 if isinstance(s, C2AContainerCallLambda)
790 ]
791 root_containers = [
792 c.identifier_information.name
793 for c in self.containers
794 if c.build_identifier() not in called_containers
795 ]
796 return root_containers
798 def get_next_conditional(self):
799 cur_cond = self.current_conditional
800 self.current_conditional += 1
801 return cur_cond
803 def reset_conditional_count(self):
804 self.current_conditional = 0
806 def reset_current_function(self):
807 self.current_function = None
809 def set_variable_context(self, context):
810 self.current_context = context
812 def to_AIR(self):
813 """
814 Translates the model used to translate CAST to AIR into the
815 final AIR structure.
816 """
817 container_air = [c.to_AIR() for c in self.containers]
818 var_air = [v.to_AIR() for v in self.variables]
819 types_air = [t.to_AIR() for t in self.types]
821 # Trim variables that are just defined and hanging. This seems like a
822 # bug BUT it is actually a remnant of how GCC gives variable definitions.
823 all_input_vars = {
824 v for c in container_air for l in c["body"] for v in l["input"]
825 }
826 all_return_vars = {v for c in container_air for v in c["return_value"]}
827 all_arg_vars = {v for c in container_air for v in c["arguments"]}
828 # all_vars_into_lambdbas = {*all_input_vars, *all_return_vars, *all_arg_vars}
830 all_vars_passed_through_lambdas = {
831 v_name
832 for c in container_air
833 for l in c["body"]
834 for v_name in l["input"] + l["output"]
835 if len(l["input"]) > 0
836 }
837 hanging_vars = [
838 v["name"]
839 for v in var_air
840 if v["name"] not in all_vars_passed_through_lambdas
841 ]
843 def is_hanging_lambda(l, c):
844 hanging_lambda = len(l["input"]) == 0 and all(
845 [
846 v not in {*all_input_vars, *all_return_vars, *all_arg_vars}
847 for v in l["output"]
848 ]
849 )
850 return hanging_lambda
852 for con in container_air:
853 hanging_lambda_vars = [
854 v
855 for l in con["body"]
856 if is_hanging_lambda(l, con)
857 for v in l["output"]
858 ]
859 # Trim variables
860 var_air = [
861 v for v in var_air if v["name"] not in hanging_lambda_vars
862 ]
863 if "return_value" in con:
864 hanging_ret_vars = {
865 v for v in con["return_value"] if v in hanging_vars
866 }
867 lambdas_calling = [
868 l
869 for c in container_air
870 for l in c["body"]
871 if l["function"]["type"] == "container"
872 and l["function"]["name"] == con["name"]
873 ]
875 if len(lambdas_calling) == 0:
876 con["return_value"] = [
877 v
878 for v in con["return_value"]
879 if v not in hanging_ret_vars
880 ]
881 all_return_vars.difference_update(hanging_ret_vars)
882 var_air = [
883 v for v in var_air if v["name"] not in hanging_ret_vars
884 ]
886 if "arguments" in con:
887 hanging_arg_vars = {
888 v for v in con["arguments"] if v in hanging_vars
889 }
890 con["arguments"] = [
891 v for v in con["arguments"] if v not in hanging_arg_vars
892 ]
893 all_arg_vars.difference_update(hanging_arg_vars)
894 var_air = [
895 v for v in var_air if v["name"] not in hanging_arg_vars
896 ]
898 con["body"] = [
899 l for l in con["body"] if not is_hanging_lambda(l, con)
900 ]
902 return {
903 "containers": container_air,
904 "variables": var_air,
905 "types": types_air,
906 }