diff --git a/src/sidewinder/analysis/transform/transformer.py b/src/sidewinder/analysis/transform/transformer.py index c4d39e8..1bdf592 100644 --- a/src/sidewinder/analysis/transform/transformer.py +++ b/src/sidewinder/analysis/transform/transformer.py @@ -90,7 +90,7 @@ def visit_Delete(self, node: ast.Delete) -> Any: return node - def visit_Assert(self, node: ast.Assert) -> ast.Expr: + def visit_Assert(self, node: ast.Assert) -> list[ast.stmt]: """ Transform assert statement to explicit sidewinder call. @@ -102,14 +102,20 @@ def visit_Assert(self, node: ast.Assert) -> ast.Expr: becomes: __sidewinder_assert__(expr, msg, __sidewinder_state=__sidewinder_state) """ - args = [self._visit_expr(node.test)] + lowered_test = self._visit_expr(node.test) + args = [lowered_test.expr] if node.msg is not None: - args.append(self._visit_expr(node.msg)) + lowered_msg = self._visit_expr(node.msg) + args.append(lowered_msg.expr) - return ast.Expr( + ret: list[ast.stmt] = [] + ret.extend(lowered_test.stmts) + ret.extend(lowered_msg.stmts) + ret.append(ast.Expr( self._emit_hook_call(SidewinderHookNames.SIDEWINDER_ASSERT, *args), lineno=0, col_offset=0, - ) + )) + return ret def visit_Import(self, node: ast.Import) -> Any: """Import statements pass through unchanged.""" @@ -127,13 +133,16 @@ def visit_Nonlocal(self, node: ast.Nonlocal) -> Any: """Nonlocal statements pass through unchanged.""" return node - def visit_Expr(self, node: ast.Expr) -> ast.Expr: + def visit_Expr(self, node: ast.Expr) -> list[ast.stmt]: """ Transform expression statement — a bare expression used as a statement. e.g. function calls whose return value is discarded: foo(), obj.method() """ - node.value = self._visit_expr(node.value) - return node + lowered_value = self._visit_expr(node.value) + ret = [] + ret.extend(lowered_value.stmts) + ret.append(ast.Expr(value=lowered_value.expr)) + return ret def visit_Pass(self, node: ast.Pass) -> Any: """Pass statements are unchanged.""" @@ -211,16 +220,27 @@ def visit_arguments(self, node: ast.arguments) -> Any: # Arguments are handled by FunctionDef/Lambda visitors return node - def visit_arg(self, node: ast.arg) -> Any: + def visit_arg(self, node: ast.arg) -> list[ast.AST]: """Transform function argument.""" + # TODO: This is not a expr or stmt, if theres a complex expression here it may :blows raspberry:. (Pydantic annotations could complain) if node.annotation: - node.annotation = self._visit_expr(node.annotation) - return node - - def visit_keyword(self, node: ast.keyword) -> Any: + lowered_annotation = self._visit_expr(node.annotation) + node.annotation = lowered_annotation.expr + ret = [] + ret.extend(lowered_annotation.stmts) + ret.append(node) + return ret + else: + return [node] + + def visit_keyword(self, node: ast.keyword) -> list[ast.stmt]: """Transform keyword argument.""" - node.value = self._visit_expr(node.value) - return node + lowered_keyword = self._visit_expr(node.value) + node.value = lowered_keyword.expr + ret = [] + ret.extend(lowered_keyword.stmts) + ret.append(node) + return ret def visit_alias(self, node: ast.alias) -> Any: """Import alias is unchanged.""" diff --git a/src/sidewinder/analysis/transform/transformer_assign.py b/src/sidewinder/analysis/transform/transformer_assign.py index c0b09b4..9b253e2 100644 --- a/src/sidewinder/analysis/transform/transformer_assign.py +++ b/src/sidewinder/analysis/transform/transformer_assign.py @@ -7,21 +7,27 @@ class SidewinderAssignTransformerMixin(SidewinderTransformerHelpers): def visit_Assign(self, node: ast.Assign) -> list[ast.stmt]: """Transform assignment statement - transform value.""" - transformed_value = self._visit_expr(node.value) + lowered_value = self._visit_expr(node.value) if len(node.targets) != 1: raise NotImplementedError(f"Sidewinder currently only supports a single target for assign statements like {ast.unparse(node)}") to_return = [] + to_return.extend(lowered_value.stmts) for target in node.targets: - to_return.extend(self._visit_target(target, transformed_value)) + to_return.extend(self._visit_target(target, lowered_value.expr)) return to_return def visit_AnnAssign(self, node: ast.AnnAssign) -> list[ast.stmt]: """Transform annotated assignment statement.""" # Transform the annotation (it's an expr) - node.annotation = self._visit_expr(node.annotation) + stmts = [] + lowered_annotation = self._visit_expr(node.annotation) + node.annotation = lowered_annotation.expr + stmts.extend(lowered_annotation.stmts) # Transform the value if it exists (can be None) if node.value is not None: - return self._visit_target(node.target, self._visit_expr(node.value)) + lowered_value = self._visit_expr(node.value) + stmts.append(lowered_value.stmts) + return stmts + self._visit_target(node.target, lowered_value.expr) return [] def visit_AugAssign(self, node: ast.AugAssign) -> list[ast.stmt]: @@ -47,15 +53,22 @@ def visit_AugAssign(self, node: ast.AugAssign) -> list[ast.stmt]: method_name = f'__sidewinder_i{op_name}__' + to_return = [] + read_target = copy.deepcopy(node.target) + lowered_read_target = self._visit_expr(read_target) + to_return.extend(lowered_read_target.stmts) read_target.ctx = ast.Load() + + lowered_value = self._visit_expr(node.value) + to_return.extend(lowered_value.stmts) rhs_value = ast.Call( func=ast.Attribute( - value=self._visit_expr(read_target), + value=lowered_read_target.expr, attr=method_name, ctx=ast.Load(), ), - args=[self._visit_expr(node.value)], + args=[lowered_value.expr], keywords=[ ast.keyword( arg='__sidewinder_state', @@ -65,7 +78,7 @@ def visit_AugAssign(self, node: ast.AugAssign) -> list[ast.stmt]: ) tmp = self._fresh_temp() - to_return = [] + to_return.append( ast.Assign( targets=[ast.Name(id=tmp, ctx=ast.Store())], diff --git a/src/sidewinder/analysis/transform/transformer_base.py b/src/sidewinder/analysis/transform/transformer_base.py index e8a4450..6869a40 100644 --- a/src/sidewinder/analysis/transform/transformer_base.py +++ b/src/sidewinder/analysis/transform/transformer_base.py @@ -34,3 +34,10 @@ def visit(self, node: ast.AST) -> ast.AST: ... def visit(self, node: ast.AST) -> Any: return super().visit(node) +class LoweredExpr: + stmts: list[ast.stmt] + expr: ast.expr + + def __init__(self, stmts, expr): + self.stmts = stmts + self.expr = expr \ No newline at end of file diff --git a/src/sidewinder/analysis/transform/transformer_classes.py b/src/sidewinder/analysis/transform/transformer_classes.py index 3cc7fa7..e2494d5 100644 --- a/src/sidewinder/analysis/transform/transformer_classes.py +++ b/src/sidewinder/analysis/transform/transformer_classes.py @@ -4,21 +4,46 @@ from sidewinder.analysis.transform.transformer_helpers import SidewinderTransformerHelpers class SidewinderClassTransformerMixin(SidewinderTransformerHelpers): - def visit_ClassDef(self, node: ast.ClassDef) -> Any: + def visit_ClassDef(self, node: ast.ClassDef) -> list[ast.stmt]: """Visit a class definition - transform all methods.""" self.current_scope.append(node.name) - + # Transform class body - with self.current_context.enter_context(node, "body") as c: + with self.current_context.enter_context(node, "body"): node.body = self._visit_list_of_stmts(node.body) - - # Transform decorators - node.decorator_list = [self._visit_expr(dec) for dec in node.decorator_list] + # Transform bases - node.bases = [self._visit_expr(bas) for bas in node.bases] + new_bases = [] + new_bases_stmts: list[ast.stmt] = [] + for base in node.bases: + lowered = self._visit_expr(base) + # TODO: handle base expression stmts if needed + new_bases.append(lowered.expr) + new_bases_stmts.extend(lowered.stmts) + + node.bases = new_bases + # Transform keywords for kw in node.keywords: - kw.value = self._visit_expr(kw.value) - + lowered = self._visit_expr(kw.value) + # TODO: handle keyword expression stmts if needed + kw.value = lowered.expr + self.current_scope.pop() - return node \ No newline at end of file + + # Lower decorators + decorator_pre, decorator_post = self._transform_decorators(node) + + ret: list[ast.stmt] = [] + ret.extend(new_bases_stmts) + + ret.extend(decorator_pre) + + # Emit class definition + ret.append(node) + + # Apply decorators after class exists + decorator_transforms = self._visit_list_of_stmts(decorator_post) + ret.extend(decorator_transforms) + + return ret \ No newline at end of file diff --git a/src/sidewinder/analysis/transform/transformer_context.py b/src/sidewinder/analysis/transform/transformer_context.py index adcfcc5..55465a7 100644 --- a/src/sidewinder/analysis/transform/transformer_context.py +++ b/src/sidewinder/analysis/transform/transformer_context.py @@ -18,7 +18,7 @@ class TransformerContext: def __init__(self): # Stack of (node, attr_name) tuples # e.g., (If node, 'body') or (If node, 'orelse') - self.context_stack: List[Tuple[ast.AST, str]] = [] + self.context_stack: List[Tuple[ast.AST, str, list]] = [] def push_context(self, node: ast.AST, attr_name: str) -> None: """ @@ -28,13 +28,14 @@ def push_context(self, node: ast.AST, attr_name: str) -> None: node: The AST node containing the statement list attr_name: The attribute name (e.g., 'body', 'orelse', 'finalbody') """ - self.context_stack.append((node, attr_name)) + self.context_stack.append((node, attr_name, [])) def pop_context(self) -> Tuple[ast.AST, str]: """Exit the current context.""" if not self.context_stack: raise RuntimeError("Cannot pop_context: context stack is empty") - return self.context_stack.pop() + # setattr(self.context_stack[-1][0], self.context_stack[-1][1], self.context_stack[-1][2]) + return self.context_stack.pop()[:-1] @contextmanager def enter_context(self, node: ast.AST, attr_name: str): @@ -54,10 +55,6 @@ def enter_context(self, node: ast.AST, attr_name: str): finally: self.pop_context() - def current_context(self) -> Optional[Tuple[ast.AST, str]]: - """Get the current (node, attr_name) context.""" - return self.context_stack[-1] if self.context_stack else None - def append_stmt(self, stmt: ast.stmt) -> None: """ Append a statement to the current context's statement list. @@ -67,16 +64,14 @@ def append_stmt(self, stmt: ast.stmt) -> None: if not self.context_stack: raise RuntimeError("No context available - cannot append statement") - node, attr_name = self.context_stack[-1] + node, attr_name, stmt_list = self.context_stack[-1] if not hasattr(node, attr_name): raise RuntimeError( f"Node {type(node).__name__} has no attribute '{attr_name}'" ) - - stmt_list = getattr(node, attr_name) - - if not isinstance(stmt_list, list): + + if not isinstance(getattr(node, attr_name), list): raise RuntimeError( f"Attribute {attr_name} of {type(node).__name__} is not a list" ) diff --git a/src/sidewinder/analysis/transform/transformer_control_flow_breaker.py b/src/sidewinder/analysis/transform/transformer_control_flow_breaker.py index 668e9bd..5ba9263 100644 --- a/src/sidewinder/analysis/transform/transformer_control_flow_breaker.py +++ b/src/sidewinder/analysis/transform/transformer_control_flow_breaker.py @@ -7,10 +7,14 @@ class SidewinderControlFlowBreakerTransformerMixin(SidewinderTransformerHelpers): def visit_Return(self, node: ast.Return) -> Any: """Transform return statement - transform the return value.""" - return ast.Expr(value=self._emit_hook_call( + lowered_value = self._visit_expr(node.value) if node.value else None + ret = [] + ret.extend(lowered_value.stmts) if lowered_value else None + ret.append(ast.Expr(value=self._emit_hook_call( SidewinderHookNames.SIDEWINDER_RETURN, - self._visit_expr(node.value) if node.value else ast.Constant(value=None) - ), lineno=0, col_offset=0) + lowered_value.expr if lowered_value else ast.Constant(value=None) + ), lineno=0, col_offset=0)) + return ret def visit_Break(self, node: ast.Break) -> ast.Expr: """ @@ -55,11 +59,16 @@ def visit_Raise(self, node: ast.Raise) -> ast.Expr: __sidewinder_raise__(__sidewinder_state=__sidewinder_state) """ + stmts = [] args = [] if node.exc is not None: - args = [self._visit_expr(node.exc)] + lowered_exc = self._visit_expr(node.exc) + args = [lowered_exc.expr] + stmts.extend(lowered_exc.stmts) if node.cause is not None: - args.append(self._visit_expr(node.cause)) + lowered_cause = self._visit_expr(node.cause) + args.append(lowered_cause.expr) + stmts.extend(lowered_cause.stmts) return ast.Expr( value=self._emit_hook_call(SidewinderHookNames.SIDEWINDER_RAISE, *args), diff --git a/src/sidewinder/analysis/transform/transformer_expr.py b/src/sidewinder/analysis/transform/transformer_expr.py index ecdee21..a8e5161 100644 --- a/src/sidewinder/analysis/transform/transformer_expr.py +++ b/src/sidewinder/analysis/transform/transformer_expr.py @@ -1,4 +1,5 @@ import ast +import copy from typing import Any from sidewinder.analysis.transform.errors import SidewinderIllegalStateError @@ -34,14 +35,17 @@ def visit_BinOp(self, node: ast.BinOp) -> tuple[list[ast.stmt], ast.expr]: return [], node method_name = f'__{op_name}__' + + lowered_left = self._visit_expr(node.left) + lowered_right = self._visit_expr(node.right) - return [], ast.Call( + return lowered_left.stmts + lowered_right.stmts, ast.Call( func=ast.Attribute( - value=self._visit_expr(node.left), + value=lowered_left.expr, attr=method_name, ctx=ast.Load() ), - args=[self._visit_expr(node.right), ast.Name(id='__sidewinder_state', ctx=ast.Load())], + args=[lowered_right.expr, ast.Name(id='__sidewinder_state', ctx=ast.Load())], keywords=[] ) @@ -66,13 +70,15 @@ def visit_UnaryOp(self, node: ast.UnaryOp) -> tuple[list[ast.stmt], ast.expr]: if op_name == 'not': # TODO: 'not x' should become something like: not x.__sidewinder_bool__(__sidewinder_state) # For now, just transform the operand - return [], ast.UnaryOp(op=node.op, operand=self._visit_expr(node.operand)) + lowered_operand = self._visit_expr(node.operand) + return lowered_operand.stmts, ast.UnaryOp(op=node.op, operand=lowered_operand.expr) method_name = f'__sidewinder_{op_name}__' - - return [], ast.Call( + + lowered_operand = self._visit_expr(node.operand) + return lowered_operand.stmts, ast.Call( func=ast.Attribute( - value=self._visit_expr(node.operand), + value=lowered_operand.expr, attr=method_name, ctx=ast.Load() ), @@ -119,20 +125,24 @@ def visit_Compare(self, node: ast.Compare) -> tuple[list[ast.stmt], ast.expr]: if op_name in ['is', 'is_not']: # 'is' and 'is not' are identity checks, not method calls # Transform operands but keep the operator - node.left = self._visit_expr(node.left) - node.comparators = [self._visit_expr(comparator)] - return [], node + lowered_left = self._visit_expr(node.left) + lowered_comparator = self._visit_expr(comparator) + node.left = lowered_left.expr + node.comparators = [lowered_comparator.expr] + return lowered_left.stmts + lowered_comparator.stmts, node if op_name in ['contains', 'not_contains']: # 'in' is reversed: a in b -> b.__contains__(a) method_name = "__contains__" + lowered_left = self._visit_expr(node.left) + lowered_comparator = self._visit_expr(comparator) result = ast.Call( func=ast.Attribute( - value=self._visit_expr(comparator), + value=lowered_comparator.expr, attr=method_name, ctx=ast.Load() ), - args=[self._visit_expr(node.left)], + args=[lowered_left.expr], keywords=[self._sidewinder_state_keyword()], ) @@ -140,17 +150,19 @@ def visit_Compare(self, node: ast.Compare) -> tuple[list[ast.stmt], ast.expr]: # Negate the result result = ast.UnaryOp(op=ast.Not(), operand=result) - return [], result + return lowered_left.stmts + lowered_comparator.stmts, result if op_name: method_name = f'__{op_name}__' - return [], ast.Call( + lowered_left = self._visit_expr(node.left) + lowered_comparator = self._visit_expr(comparator) + return lowered_left.stmts + lowered_comparator.stmts, ast.Call( func=ast.Attribute( - value=self._visit_expr(node.left), + value=lowered_left.expr, attr=method_name, ctx=ast.Load() ), - args=[self._visit_expr(comparator)], + args=[lowered_comparator.expr], keywords=[self._sidewinder_state_keyword()], ) else: @@ -171,24 +183,39 @@ def visit_Call(self, node: ast.Call) -> tuple[list[ast.stmt], ast.expr]: func(a, x=b) -> func(a, __sidewinder_state=__sidewinder_state, x=b) [if func has **kwargs] """ # Transform the function expression + context_stmts = [] - transformed_func = self._visit_expr(node.func) + lowered_func = self._visit_expr(node.func) + context_stmts.extend(lowered_func.stmts) # Transform arguments - transformed_args = [self._visit_expr(arg) for arg in node.args] - transformed_keywords = [ast.keyword(kw.arg, self._visit_expr(kw.value)) for kw in node.keywords] + transformed_args = [] + for arg in node.args: + lowered_arg = self._visit_expr(arg) + transformed_args.append(lowered_arg.expr) + context_stmts.extend(lowered_arg.stmts) - transformed_keywords.insert(0, self._sidewinder_state_keyword()) - - return [], ast.Call( - func=transformed_func, - args=transformed_args, - keywords=transformed_keywords, - lineno=node.lineno, - col_offset=node.col_offset, - end_lineno=node.end_lineno, - end_col_offset=node.end_col_offset + transformed_keywords: dict[str | None, ast.expr] = {} + for kw in node.keywords: + lowered_kw_value = self._visit_expr(kw.value) + transformed_keywords[kw.arg] = lowered_kw_value.expr + context_stmts.extend(lowered_kw_value.stmts) + + temp_variable = self._fresh_temp() + + call_node = self._emit_hook_call( + SidewinderHookNames.SIDEWINDER_CALL, + *[lowered_func.expr, *transformed_args], + extra_kwargs=transformed_keywords ) + + context_stmts.append(ast.Assign( + targets=[ast.Name(id=temp_variable, ctx=ast.Store())], + value=call_node, + lineno=0, col_offset=0, + )) + + return context_stmts, ast.Name(id=temp_variable, ctx=ast.Load()) def visit_Attribute(self, node: ast.Attribute) -> tuple[list[ast.stmt], ast.expr]: """ @@ -202,21 +229,21 @@ def visit_Attribute(self, node: ast.Attribute) -> tuple[list[ast.stmt], ast.expr __t2 = __t1.__sidewinder_getattr__("c", __sidewinder_state__=__sidewinder_state__) """ # recursively transform the object being accessed - visited_obj = self._visit_expr(node.value) + lowered_visited_obj = self._visit_expr(node.value) # store result in temp temp = self._fresh_temp("__sidewinder_attr") stmt = ast.Assign( targets=[ast.Name(id=temp, ctx=ast.Store())], value=self._emit_method_hook_call( - visited_obj, + lowered_visited_obj.expr, SidewinderHookNames.SIDEWINDER_GETATTR, ast.Constant(value=node.attr), ), lineno=0, col_offset=0, ) - return [stmt], ast.Name(id=temp, ctx=ast.Load()) + return lowered_visited_obj.stmts + [stmt], ast.Name(id=temp, ctx=ast.Load()) def visit_Subscript(self, node: ast.Subscript) -> tuple[list[ast.stmt], ast.expr]: """ @@ -225,34 +252,43 @@ def visit_Subscript(self, node: ast.Subscript) -> tuple[list[ast.stmt], ast.expr a[b] -> __t1 = a.__sidewinder_getitem__(b,__sidewinder_state__=__sidewinder_state__) """ - visited_obj = self._visit_expr(node.value) - visited_slice = self._visit_expr(node.slice) + lowered_visited_obj = self._visit_expr(node.value) + lowered_visited_slice = self._visit_expr(node.slice) temp = self._fresh_temp("__sidewinder_subscript") stmt = ast.Assign( targets=[ast.Name(id=temp, ctx=ast.Store())], value=self._emit_method_hook_call( - visited_obj, + lowered_visited_obj.expr, SidewinderHookNames.SIDEWINDER_GETITEM, - visited_slice, + lowered_visited_slice.expr, ), lineno=0, col_offset=0, ) - return [stmt], ast.Name(id=temp, ctx=ast.Load()) + return lowered_visited_obj.stmts + lowered_visited_slice.stmts + [stmt], ast.Name(id=temp, ctx=ast.Load()) def visit_Slice(self, node: ast.Slice) -> Any: """Transform slice - convert to slice object.""" # a[1:5:2] -> slice(1, 5, 2) - return [], ast.Call( + lowered_lower = self._visit_expr(node.lower) if node.lower else None + lowered_upper = self._visit_expr(node.upper) if node.upper else None + lowered_step = self._visit_expr(node.step) if node.step else None + args = [] + args.append(lowered_lower.expr if lowered_lower else ast.Constant(value=None)) + args.append(lowered_upper.expr if lowered_upper else ast.Constant(value=None)) + args.append(lowered_step.expr if lowered_step else ast.Constant(value=None)) + + context_stmts = [] + context_stmts.extend(lowered_lower.stmts) if lowered_lower else None + context_stmts.extend(lowered_upper.stmts) if lowered_upper else None + context_stmts.extend(lowered_step.stmts) if lowered_step else None + + return context_stmts, ast.Call( func=ast.Name(id='slice', ctx=ast.Load()), - args=[ - self._visit_expr(node.lower) if node.lower else ast.Constant(value=None), - self._visit_expr(node.upper) if node.upper else ast.Constant(value=None), - self._visit_expr(node.step) if node.step else ast.Constant(value=None), - ], + args=args, keywords=[] ) @@ -357,9 +393,10 @@ def visit_Lambda(self, node: ast.Lambda) -> Any: node.args.args.append(state_param) # Transform body - node.body = self._visit_expr(node.body) + lowered_body = self._visit_expr(node.body) + node.body = lowered_body.expr - return [], node + return lowered_body.stmts, node def visit_IfExp(self, node: ast.IfExp) -> tuple[list[ast.stmt], ast.expr]: result = self._fresh_temp("__sidewinder_ifexp") @@ -399,26 +436,68 @@ def visit_IfExp(self, node: ast.IfExp) -> tuple[list[ast.stmt], ast.expr]: return self._visit_list_of_stmts(stmts), ast.Name(id=result, ctx=ast.Load()) def visit_Dict(self, node: ast.Dict) -> Any: - """Transform dictionary literal.""" - assert (k is not None for k in node.keys), "Unpacking dict is not supported yet" - node.keys = [self._visit_expr(k) if k else None for k in node.keys] - node.values = [self._visit_expr(v) for v in node.values] - return [], node + stmts = [] + new_keys = [] + new_values = [] + + for key, value in zip(node.keys, node.values): + if key is None: + raise NotImplementedError("Dict unpacking not supported") + + lowered = self._visit_expr(key) + stmts.extend(lowered.stmts) + new_keys.append(lowered.expr) + + lowered = self._visit_expr(value) + stmts.extend(lowered.stmts) + new_values.append(lowered.expr) + + node.keys = new_keys + node.values = new_values + + return stmts, node def visit_Set(self, node: ast.Set) -> Any: """Transform set literal.""" - node.elts = [self._visit_expr(elt) for elt in node.elts] - return [], node + stmts = [] + new_elts = [] + + for elt in node.elts: + lowered = self._visit_expr(elt) + stmts.extend(lowered.stmts) + new_elts.append(lowered.expr) + + node.elts = new_elts + + return stmts, node def visit_List(self, node: ast.List) -> Any: """Transform list literal.""" - node.elts = [self._visit_expr(elt) for elt in node.elts] - return [], node + stmts = [] + new_elts = [] + + for elt in node.elts: + lowered = self._visit_expr(elt) + stmts.extend(lowered.stmts) + new_elts.append(lowered.expr) + + node.elts = new_elts + + return stmts, node def visit_Tuple(self, node: ast.Tuple) -> Any: """Transform tuple literal.""" - node.elts = [self._visit_expr(elt) for elt in node.elts] - return [], node + stmts = [] + new_elts = [] + + for elt in node.elts: + lowered = self._visit_expr(elt) + stmts.extend(lowered.stmts) + new_elts.append(lowered.expr) + + node.elts = new_elts + + return stmts, node def visit_Await(self, node: ast.Await) -> Any: """Transform await expression.""" @@ -426,20 +505,42 @@ def visit_Await(self, node: ast.Await) -> Any: def visit_FormattedValue(self, node: ast.FormattedValue) -> Any: """Transform formatted value in f-string.""" - node.value = self._visit_expr(node.value) + stmts = [] + + lowered = self._visit_expr(node.value) + stmts.extend(lowered.stmts) + node.value = lowered.expr + if node.format_spec: - node.format_spec = self._visit_expr(node.format_spec) - return [], node + lowered = self._visit_expr(node.format_spec) + stmts.extend(lowered.stmts) + node.format_spec = lowered.expr + + return stmts, node def visit_JoinedStr(self, node: ast.JoinedStr) -> Any: """Transform f-string.""" - node.values = [self._visit_expr(val) for val in node.values] - return [], node + stmts = [] + new_values = [] + + for value in node.values: + lowered = self._visit_expr(value) + stmts.extend(lowered.stmts) + new_values.append(lowered.expr) + + node.values = new_values + + return stmts, node def visit_Starred(self, node: ast.Starred) -> Any: """Transform starred expression.""" - node.value = self._visit_expr(node.value) - return [], node + stmts = [] + + lowered = self._visit_expr(node.value) + stmts.extend(lowered.stmts) + node.value = lowered.expr + + return stmts, node def visit_Name(self, node: ast.Name) -> Any: """Name nodes are unchanged.""" diff --git a/src/sidewinder/analysis/transform/transformer_for.py b/src/sidewinder/analysis/transform/transformer_for.py index bf65200..399894f 100644 --- a/src/sidewinder/analysis/transform/transformer_for.py +++ b/src/sidewinder/analysis/transform/transformer_for.py @@ -58,11 +58,13 @@ def _transform_for( iter_tmp = self._fresh_temp("__aiter" if is_async else "__iter") # _iter = iterable.__sidewinder_iter__(__sidewinder_state) + lowered_iter_expr = self._visit_expr(iter_expr) + iter_assign = ast.Assign( targets=[ast.Name(id=iter_tmp, ctx=ast.Store())], value=ast.Call( func=ast.Attribute( - value=self._visit_expr(iter_expr), + value=lowered_iter_expr.expr, attr=iter_method, ctx=ast.Load(), ), @@ -121,4 +123,7 @@ def _transform_for( lineno=0, col_offset=0, ) - return [iter_assign, while_loop] \ No newline at end of file + ret = [] + ret.extend(lowered_iter_expr.stmts) + ret.extend([iter_assign, while_loop]) + return ret \ No newline at end of file diff --git a/src/sidewinder/analysis/transform/transformer_functions.py b/src/sidewinder/analysis/transform/transformer_functions.py index 6b38ed1..27ab274 100644 --- a/src/sidewinder/analysis/transform/transformer_functions.py +++ b/src/sidewinder/analysis/transform/transformer_functions.py @@ -44,11 +44,23 @@ def _transform_function_def(self, node: Union[ast.FunctionDef, ast.AsyncFunction # TODO: Fixed point statement must be added with self.current_context.enter_context(node, "body") as c: node.body = self._visit_list_of_stmts(node.body) - - # Transform decorators - node.decorator_list = [self._visit_expr(dec) for dec in node.decorator_list] - - return node + + # Lower decorators + decorator_pre, decorator_post = self._transform_decorators(node) + + ret = [] + + # Evaluate decorator expressions before definition + ret.extend(decorator_pre) + + # Emit function definition + ret.append(node) + + # Apply decorators after definition + decorator_transforms = self._visit_list_of_stmts(decorator_post) + ret.extend(decorator_transforms) + + return ret def visit_FunctionDef(self, node: ast.FunctionDef) -> Any: """Visit a function definition.""" diff --git a/src/sidewinder/analysis/transform/transformer_helpers.py b/src/sidewinder/analysis/transform/transformer_helpers.py index 4690127..f60431e 100644 --- a/src/sidewinder/analysis/transform/transformer_helpers.py +++ b/src/sidewinder/analysis/transform/transformer_helpers.py @@ -3,7 +3,7 @@ from collections import defaultdict import copy -from sidewinder.analysis.transform.transformer_base import SidewinderTransformerBase, T +from sidewinder.analysis.transform.transformer_base import SidewinderTransformerBase, T, LoweredExpr from sidewinder.analysis.transform.transformer_context import TransformerContext from sidewinder.analysis.transform.errors import SidewinderIllegalStateError from sidewinder.analysis.symbolic.hook import SidewinderHookNames @@ -34,12 +34,12 @@ def _emit_hook_call( self, hook: SidewinderHookNames, *args: ast.expr, - extra_kwargs: Optional[Dict[str, ast.expr]] = None + extra_kwargs: Optional[Dict[str | None, ast.expr]] = None ) -> ast.Call: """Create a sidewinder hook call with __sidewinder_state__ threaded through.""" - keywords = [ + keywords = [self._sidewinder_state_keyword()] + [ ast.keyword(arg=k, value=v) for k, v in (extra_kwargs or {}).items() - ] + [self._sidewinder_state_keyword()] + ] return ast.Call( func=ast.Name(id=f"__{hook.name.lower()}__", ctx=ast.Load()), args=list(args), @@ -84,11 +84,9 @@ def _visit_list_of_stmts(self, stmts: List[ast.stmt]) -> List[ast.stmt]: result.append(visited) return result - def _visit_expr(self, expr: T) -> ast.expr: + def _visit_expr(self, expr: T) -> LoweredExpr: generated_stmts, final_expr = self.visit(expr) - for stmt in generated_stmts: - self.current_context.append_stmt(stmt) - return final_expr + return LoweredExpr(stmts=generated_stmts, expr=final_expr) def _visit_target(self, target: ast.expr | ast.Tuple | ast.List, visited_rhs: ast.expr) -> List[ast.stmt]: """ @@ -108,26 +106,33 @@ def _visit_target(self, target: ast.expr | ast.Tuple | ast.List, visited_rhs: as case ast.Attribute(): # y.attr = rhs → _visit_expr(y).__setattr__("attr", rhs) - obj = self._visit_expr(target.value) - return [ + lowered_obj = self._visit_expr(target.value) + ret = [] + ret.extend(lowered_obj.stmts) + ret.append([ ast.Expr(value=ast.Call( - func=ast.Attribute(value=obj, attr="__sidewinder_setattr__", ctx=ast.Load()), + func=ast.Attribute(value=lowered_obj.expr, attr="__sidewinder_setattr__", ctx=ast.Load()), args=[ast.Constant(value = target.attr), visited_rhs], keywords=[], ), lineno=0, col_offset=0) - ] + ]) + return ret case ast.Subscript(): # y[i] = rhs → _visit_expr(y).__setitem__(_visit_expr(i), rhs) - obj = self._visit_expr(target.value) - idx = self._visit_expr(target.slice) - return [ + lowered_obj = self._visit_expr(target.value) + lowered_idx = self._visit_expr(target.slice) + ret = [] + ret.extend(lowered_obj.stmts) + ret.extend(lowered_idx.stmts) + ret.append([ ast.Expr(value=ast.Call( - func=ast.Attribute(value=obj, attr="__sidewinder_setitem__", ctx=ast.Load()), - args=[idx, visited_rhs], + func=ast.Attribute(value=lowered_obj.expr, attr="__sidewinder_setitem__", ctx=ast.Load()), + args=[lowered_idx.expr, visited_rhs], keywords=[], ), lineno=0, col_offset=0) - ] + ]) + return ret case ast.Tuple() | ast.List(): to_return = [] @@ -184,3 +189,65 @@ def _visit_target(self, target: ast.expr | ast.Tuple | ast.List, visited_rhs: as case _: # anything else: func() = rhs, (x + y) = rhs, literal = rhs etc. raise NotImplementedError(f"unsupported assignment target: {ast.dump(target)}") + + def _transform_decorators( + self, + node: Union[ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef], + ) -> tuple[list[ast.stmt], list[ast.stmt]]: + """ + Lower decorators. + + For: + + @dec1 + @dec2 + def/class Foo: + ... + + Produces: + + + + + Foo = dec2(Foo) + Foo = dec1(Foo) + + Returns: + (pre_stmts, post_stmts) + + pre_stmts: + statements needed to evaluate decorator expressions + + post_stmts: + decorator application assignments + """ + + pre_stmts = [] + dec_expressions: list[ast.expr] = [] + + for dec in node.decorator_list: + lowered_dec = self._visit_expr(dec) + pre_stmts.extend(lowered_dec.stmts) + dec_expressions.append(lowered_dec.expr) + + node.decorator_list = [] + + post_stmts = [] + + for dec_expr in reversed(dec_expressions): + post_stmts.append( + ast.Assign( + targets=[ + ast.Name(id=node.name, ctx=ast.Store()) + ], + value=ast.Call( + func=dec_expr, + args=[ + ast.Name(id=node.name, ctx=ast.Load()) + ], + keywords=[] + ) + ) + ) + + return pre_stmts, post_stmts \ No newline at end of file diff --git a/src/sidewinder/analysis/transform/transformer_if.py b/src/sidewinder/analysis/transform/transformer_if.py index ba0a858..56f09b2 100644 --- a/src/sidewinder/analysis/transform/transformer_if.py +++ b/src/sidewinder/analysis/transform/transformer_if.py @@ -9,10 +9,11 @@ def visit_If(self, node: ast.If) -> list[ast.stmt]: # t = temp = self._fresh_temp("__sidewinder_cond") - transformed_test = self._visit_expr(node.test) + lowered_transformed_test = self._visit_expr(node.test) + result.extend(lowered_transformed_test.stmts) result.append(ast.Assign( targets=[ast.Name(id=temp, ctx=ast.Store())], - value=transformed_test, + value=lowered_transformed_test.expr, lineno=0, col_offset=0, )) diff --git a/src/sidewinder/analysis/transform/transformer_modules.py b/src/sidewinder/analysis/transform/transformer_modules.py index 7d46aaf..90f832e 100644 --- a/src/sidewinder/analysis/transform/transformer_modules.py +++ b/src/sidewinder/analysis/transform/transformer_modules.py @@ -6,9 +6,9 @@ class SidewinderModuleTransformerMixin(SidewinderTransformerHelpers): def visit_Module(self, node: ast.Module) -> Any: """Visit a module node - add import for SidewinderState.""" - # Add import: from sidewinder.analysis.symbolic.state import SidewinderState + # Add import: from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState import_node = ast.ImportFrom( - module="sidewinder.analysis.symbolic.state", + module="sidewinder.analysis.symbolic.runtime.memory.state", names=[ast.alias(name="SidewinderState", asname=None)], level=0 ) diff --git a/src/sidewinder/analysis/transform/transformer_try.py b/src/sidewinder/analysis/transform/transformer_try.py index 49fbd65..22850a1 100644 --- a/src/sidewinder/analysis/transform/transformer_try.py +++ b/src/sidewinder/analysis/transform/transformer_try.py @@ -79,9 +79,12 @@ def visit_Try(self, node: ast.Try) -> list[ast.stmt]: # Evaluate exception type expression symbolically if exc_type is not None: exc_type_temp = self._fresh_temp("__sidewinder_exc_type") + lowered_exc_type = self._visit_expr(exc_type) + + result.extend(lowered_exc_type.stmts) result.append(ast.Assign( targets=[ast.Name(id=exc_type_temp, ctx=ast.Store())], - value=self._visit_expr(exc_type), + value=lowered_exc_type.expr, lineno=0, col_offset=0, )) exc_type_arg = ast.Name(id=exc_type_temp, ctx=ast.Load()) diff --git a/src/sidewinder/analysis/transform/transformer_while.py b/src/sidewinder/analysis/transform/transformer_while.py index 3b10a7c..e4f5c94 100644 --- a/src/sidewinder/analysis/transform/transformer_while.py +++ b/src/sidewinder/analysis/transform/transformer_while.py @@ -28,9 +28,13 @@ def visit_While(self, node: ast.While) -> list[ast.stmt]: # __sidewinder_condN__ = cond_temp = self._fresh_temp("__sidewinder_cond") + + lowered_test = self._visit_expr(node.test) + result.extend(lowered_test.stmts) + result.append(ast.Assign( targets=[ast.Name(id=cond_temp, ctx=ast.Store())], - value=self._visit_expr(node.test), + value=lowered_test.expr, lineno=0, col_offset=0, )) @@ -52,9 +56,9 @@ def visit_While(self, node: ast.While) -> list[ast.stmt]: lineno=0, col_offset=0, ) - with self.current_context.enter_context(while_node, "body") as c: + with self.current_context.enter_context(while_node, "body"): # push true condition - c.append_stmt( + while_node.body.append( ast.Expr(value=self._emit_hook_call( SidewinderHookNames.SIDEWINDER_CONDITION_TRUE, ast.Name(id=cond_temp, ctx=ast.Load()) @@ -65,20 +69,23 @@ def visit_While(self, node: ast.While) -> list[ast.stmt]: while_node.body.extend(self._visit_list_of_stmts(node.body)) # union update condition - c.append_stmt( + lowered_second_test = self._visit_expr(node.test) + for stmt in lowered_second_test.stmts: + while_node.body.append(stmt) + while_node.body.append( ast.Assign( targets=[ast.Name(id=cond_temp, ctx=ast.Store())], value=self._emit_hook_call( SidewinderHookNames.SIDEWINDER_UNION, ast.Name(id=cond_temp, ctx=ast.Load()), - self._visit_expr(node.test), + lowered_second_test.expr, ), lineno=0, col_offset=0, ) ) # fixed point check - c.append_stmt( + while_node.body.append( ast.Assign( targets=[ast.Name(id=fixed_point_temp, ctx=ast.Store())], value=self._emit_hook_call(SidewinderHookNames.SIDEWINDER_FIXED_POINT), @@ -86,7 +93,7 @@ def visit_While(self, node: ast.While) -> list[ast.stmt]: ) ) - c.append_stmt( + while_node.body.append( ast.Expr(value=self._emit_hook_call( SidewinderHookNames.SIDEWINDER_POP_CONDITION, ), lineno=0, col_offset=0), diff --git a/src/sidewinder/analysis/transform/transformer_with.py b/src/sidewinder/analysis/transform/transformer_with.py index 0766857..05a4d15 100644 --- a/src/sidewinder/analysis/transform/transformer_with.py +++ b/src/sidewinder/analysis/transform/transformer_with.py @@ -5,46 +5,39 @@ class SidewinderWithTransformerMixin(SidewinderTransformerHelpers): def visit_With(self, node: ast.With) -> list[ast.stmt]: """ - Transform with statement to explicit __enter__/__exit__ calls. - - with expr as var: - body - - Becomes: - __ctx = expr.__sidewinder_enter__(__sidewinder_state) - try: - var = __ctx - body - finally: - __ctx.__sidewinder_exit__(__sidewinder_state) - - Multiple context managers are desugared recursively: - with a as x, b as y: - body - - Becomes: - with a as x: - with b as y: - body + Normalize a `with` statement into simpler AST constructs before the + Sidewinder lowering pass. + + TODO(sidewinder): This is a simplified desugaring and does NOT exactly + match Python's context manager semantics. + + In particular: + - __exit__ is not passed exception information. + - Exception suppression is not modeled. + - The exact protocol defined by the language reference is intentionally + simplified. + + Multiple context managers are recursively desugared. """ - return self._transform_with(node.items, node.body, node.lineno, node.col_offset, is_async=False) + return self._transform_with( + node.items, + node.body, + node.lineno, + node.col_offset, + is_async=False, + ) + def visit_AsyncWith(self, node: ast.AsyncWith) -> list[ast.stmt]: - """ - Transform async with statement to explicit __aenter__/__aexit__ calls. - - async with expr as var: - body - - Becomes: - __ctx = await expr.__sidewinder_aenter__(__sidewinder_state) - try: - var = __ctx - body - finally: - await __ctx.__sidewinder_aexit__(__sidewinder_state) - """ - return self._transform_with(node.items, node.body, node.lineno, node.col_offset, is_async=True) + """See visit_With().""" + return self._transform_with( + node.items, + node.body, + node.lineno, + node.col_offset, + is_async=True, + ) + def _transform_with( self, @@ -54,74 +47,109 @@ def _transform_with( col_offset: int, is_async: bool, ) -> list[ast.stmt]: - + # Desugar multiple context managers recursively. if len(items) > 1: - inner = ast.With( - items=items[1:], - body=body, - lineno=lineno, col_offset=col_offset, - ) if not is_async else ast.AsyncWith( - items=items[1:], - body=body, - lineno=lineno, col_offset=col_offset, + inner = ( + ast.AsyncWith( + items=items[1:], + body=body, + lineno=lineno, + col_offset=col_offset, + ) + if is_async + else ast.With( + items=items[1:], + body=body, + lineno=lineno, + col_offset=col_offset, + ) + ) + return self._transform_with( + [items[0]], + [inner], + lineno, + col_offset, + is_async, ) - # Extract the first context variable, and the constructed with the reamining variables processed separately - return self._transform_with([items[0]], [inner], lineno, col_offset, is_async) - - enter_method = '__sidewinder_aenter__' if is_async else '__sidewinder_enter__' - exit_method = '__sidewinder_aexit__' if is_async else '__sidewinder_exit__' item = items[0] - ctx_tmp = self._fresh_temp("__ctx") + mgr_name = self._fresh_temp("__mgr") + + # __mgr = expr + mgr_assign = ast.Assign( + targets=[ast.Name(id=mgr_name, ctx=ast.Store())], + value=item.context_expr, + lineno=lineno, + col_offset=col_offset, + ) + + enter_name = "__enter__" if not is_async else "__aenter__" + exit_name = "__exit__" if not is_async else "__aexit__" enter_call = ast.Call( func=ast.Attribute( - value=self._visit_expr(item.context_expr), - attr=enter_method, + value=ast.Name(id=mgr_name, ctx=ast.Load()), + attr=enter_name, ctx=ast.Load(), ), args=[], - keywords=[ast.keyword(arg='__sidewinder_state', value=ast.Name(id='__sidewinder_state', ctx=ast.Load()))], + keywords=[], ) - # __ctx = await expr.__sidewinder_aenter__(...) or expr.__sidewinder_enter__(...) - ctx_assign = ast.Assign( - targets=[ast.Name(id=ctx_tmp, ctx=ast.Store())], - value=ast.Await(value=enter_call) if is_async else enter_call, - lineno=0, col_offset=0, + enter_value: ast.expr = ( + ast.Await(value=enter_call) if is_async else enter_call ) - try_block = ast.Try( - body=[], - handlers=[], - orelse=[], - finalbody=[], - ) + try_body: list[ast.stmt] = [] - with self.current_context.enter_context(try_block, "body"): - if item.optional_vars is not None: - self._visit_target( - item.optional_vars, - ast.Name(id=ctx_tmp, ctx=ast.Load()), + if item.optional_vars is not None: + try_body.append( + ast.Assign( + targets=[item.optional_vars], + value=enter_value, + lineno=lineno, + col_offset=col_offset, + ) + ) + else: + try_body.append( + ast.Expr( + value=enter_value, + lineno=lineno, + col_offset=col_offset, ) - transformed_body = self._visit_list_of_stmts(body) + ) - try_block.body.extend(transformed_body) + try_body.extend(body) exit_call = ast.Call( func=ast.Attribute( - value=ast.Name(id=ctx_tmp, ctx=ast.Load()), - attr=exit_method, + value=ast.Name(id=mgr_name, ctx=ast.Load()), + attr=exit_name, ctx=ast.Load(), ), args=[], - keywords=[ast.keyword(arg='__sidewinder_state', value=ast.Name(id='__sidewinder_state', ctx=ast.Load()))], + keywords=[], + ) + + exit_expr: ast.expr = ( + ast.Await(value=exit_call) if is_async else exit_call ) - # In the transformed version we will only have a single name being used to call the __exit__ method (roughly __ctx.__exit__(....) so this should work) - try_block.finalbody = [ast.Expr( - value=ast.Await(value=exit_call) if is_async else exit_call, - lineno=0, col_offset=0, - )] + try_stmt = ast.Try( + body=try_body, + handlers=[], + orelse=[], + finalbody=[ + ast.Expr( + value=exit_expr, + lineno=lineno, + col_offset=col_offset, + ) + ], + lineno=lineno, + col_offset=col_offset, + ) - return [ctx_assign, try_block] + # Push the normalized AST back through the transformer. + return self._visit_list_of_stmts([mgr_assign, try_stmt]) \ No newline at end of file diff --git a/test/analysis/transform/inputs/big_one.py b/test/analysis/transform/inputs/big_one.py new file mode 100644 index 0000000..107a8f6 --- /dev/null +++ b/test/analysis/transform/inputs/big_one.py @@ -0,0 +1,15 @@ +def source(filename): + def decorator(fn): + def wrapper(): + with open(filename, "r") as f: + return fn(f.read()) + return wrapper + return decorator + + +@source("users.csv") +def process_users(users): + return users.upper() + + +process_users() \ No newline at end of file diff --git a/test/analysis/transform/outputs/big_one_expected.py b/test/analysis/transform/outputs/big_one_expected.py new file mode 100644 index 0000000..f8d245d --- /dev/null +++ b/test/analysis/transform/outputs/big_one_expected.py @@ -0,0 +1,31 @@ +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState + +def source(filename, *, __sidewinder_state: SidewinderState): + + def decorator(fn, *, __sidewinder_state: SidewinderState): + + def wrapper(*, __sidewinder_state: SidewinderState): + __t1 = __sidewinder_call__(open, filename, 'r', __sidewinder_state=__sidewinder_state) + __mgr0 = __t1 + __sidewinder_attr2 = __mgr0.__sidewinder_getattr__('__enter__', __sidewinder_state=__sidewinder_state) + __t3 = __sidewinder_call__(__sidewinder_attr2, __sidewinder_state=__sidewinder_state) + f = __t3 + __sidewinder_attr4 = f.__sidewinder_getattr__('read', __sidewinder_state=__sidewinder_state) + __t5 = __sidewinder_call__(__sidewinder_attr4, __sidewinder_state=__sidewinder_state) + __t6 = __sidewinder_call__(fn, __t5, __sidewinder_state=__sidewinder_state) + __sidewinder_return__(__t6, __sidewinder_state=__sidewinder_state) + __sidewinder_attr7 = __mgr0.__sidewinder_getattr__('__exit__', __sidewinder_state=__sidewinder_state) + __t8 = __sidewinder_call__(__sidewinder_attr7, __sidewinder_state=__sidewinder_state) + __t8 + __sidewinder_return__(wrapper, __sidewinder_state=__sidewinder_state) + __sidewinder_return__(decorator, __sidewinder_state=__sidewinder_state) +__t11 = __sidewinder_call__(source, 'users.csv', __sidewinder_state=__sidewinder_state) + +def process_users(users, *, __sidewinder_state: SidewinderState): + __sidewinder_attr9 = users.__sidewinder_getattr__('upper', __sidewinder_state=__sidewinder_state) + __t10 = __sidewinder_call__(__sidewinder_attr9, __sidewinder_state=__sidewinder_state) + __sidewinder_return__(__t10, __sidewinder_state=__sidewinder_state) +__t12 = __sidewinder_call__(__t11, process_users, __sidewinder_state=__sidewinder_state) +process_users = __t12 +__t13 = __sidewinder_call__(process_users, __sidewinder_state=__sidewinder_state) +__t13 \ No newline at end of file diff --git a/test/analysis/transform/outputs/call/basic_args_kwargs_expected.py b/test/analysis/transform/outputs/call/basic_args_kwargs_expected.py index 9068695..63d1e23 100644 --- a/test/analysis/transform/outputs/call/basic_args_kwargs_expected.py +++ b/test/analysis/transform/outputs/call/basic_args_kwargs_expected.py @@ -1,2 +1,3 @@ -from sidewinder.analysis.symbolic.state import SidewinderState -f(x, *y, __sidewinder_state=__sidewinder_state, **z) \ No newline at end of file +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState +__t0 = __sidewinder_call__(f, x, *y, __sidewinder_state=__sidewinder_state, **z) +__t0 \ No newline at end of file diff --git a/test/analysis/transform/outputs/call/basic_expected.py b/test/analysis/transform/outputs/call/basic_expected.py index 6ab5b0d..7d1f898 100644 --- a/test/analysis/transform/outputs/call/basic_expected.py +++ b/test/analysis/transform/outputs/call/basic_expected.py @@ -1,2 +1,3 @@ -from sidewinder.analysis.symbolic.state import SidewinderState -f(x, __sidewinder_state=__sidewinder_state) \ No newline at end of file +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState +__t0 = __sidewinder_call__(f, x, __sidewinder_state=__sidewinder_state) +__t0 \ No newline at end of file diff --git a/test/analysis/transform/outputs/call/basic_kw_expected.py b/test/analysis/transform/outputs/call/basic_kw_expected.py index ad3aabe..40dfbe3 100644 --- a/test/analysis/transform/outputs/call/basic_kw_expected.py +++ b/test/analysis/transform/outputs/call/basic_kw_expected.py @@ -1,2 +1,3 @@ -from sidewinder.analysis.symbolic.state import SidewinderState -f(__sidewinder_state=__sidewinder_state, x=y) \ No newline at end of file +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState +__t0 = __sidewinder_call__(f, __sidewinder_state=__sidewinder_state, x=y) +__t0 \ No newline at end of file diff --git a/test/analysis/transform/outputs/call/basic_kwargs_expected.py b/test/analysis/transform/outputs/call/basic_kwargs_expected.py index a907b7e..2598428 100644 --- a/test/analysis/transform/outputs/call/basic_kwargs_expected.py +++ b/test/analysis/transform/outputs/call/basic_kwargs_expected.py @@ -1,2 +1,3 @@ -from sidewinder.analysis.symbolic.state import SidewinderState -f(x, __sidewinder_state=__sidewinder_state, **y) \ No newline at end of file +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState +__t0 = __sidewinder_call__(f, x, __sidewinder_state=__sidewinder_state, **y) +__t0 \ No newline at end of file diff --git a/test/analysis/transform/outputs/call/basic_positional_kw_expected.py b/test/analysis/transform/outputs/call/basic_positional_kw_expected.py index 091ed1e..e600495 100644 --- a/test/analysis/transform/outputs/call/basic_positional_kw_expected.py +++ b/test/analysis/transform/outputs/call/basic_positional_kw_expected.py @@ -1,2 +1,3 @@ -from sidewinder.analysis.symbolic.state import SidewinderState -f(x, __sidewinder_state=__sidewinder_state, y=z) \ No newline at end of file +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState +__t0 = __sidewinder_call__(f, x, __sidewinder_state=__sidewinder_state, y=z) +__t0 \ No newline at end of file diff --git a/test/analysis/transform/outputs/call/basic_varargs_expected.py b/test/analysis/transform/outputs/call/basic_varargs_expected.py index 211d193..69f0954 100644 --- a/test/analysis/transform/outputs/call/basic_varargs_expected.py +++ b/test/analysis/transform/outputs/call/basic_varargs_expected.py @@ -1,2 +1,3 @@ -from sidewinder.analysis.symbolic.state import SidewinderState -f(x, *y, __sidewinder_state=__sidewinder_state) \ No newline at end of file +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState +__t0 = __sidewinder_call__(f, x, *y, __sidewinder_state=__sidewinder_state) +__t0 \ No newline at end of file diff --git a/test/analysis/transform/outputs/if/if_elif_expected.py b/test/analysis/transform/outputs/if/if_elif_expected.py index c60151d..c44db42 100644 --- a/test/analysis/transform/outputs/if/if_elif_expected.py +++ b/test/analysis/transform/outputs/if/if_elif_expected.py @@ -1,4 +1,4 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState __sidewinder_cond0 = x __sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) diff --git a/test/analysis/transform/outputs/if/if_elif_no_else_expected.py b/test/analysis/transform/outputs/if/if_elif_no_else_expected.py index e44afe0..58fa117 100644 --- a/test/analysis/transform/outputs/if/if_elif_no_else_expected.py +++ b/test/analysis/transform/outputs/if/if_elif_no_else_expected.py @@ -1,4 +1,4 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState __sidewinder_cond0 = x __sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) diff --git a/test/analysis/transform/outputs/if/if_multiple_elif_expected.py b/test/analysis/transform/outputs/if/if_multiple_elif_expected.py index 380df62..672ed03 100644 --- a/test/analysis/transform/outputs/if/if_multiple_elif_expected.py +++ b/test/analysis/transform/outputs/if/if_multiple_elif_expected.py @@ -1,4 +1,4 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState __sidewinder_cond0 = x __sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) diff --git a/test/analysis/transform/outputs/if/if_nested_else_expected.py b/test/analysis/transform/outputs/if/if_nested_else_expected.py index 2c2a7cd..6ac561f 100644 --- a/test/analysis/transform/outputs/if/if_nested_else_expected.py +++ b/test/analysis/transform/outputs/if/if_nested_else_expected.py @@ -1,4 +1,4 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState __sidewinder_cond0 = x __sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) diff --git a/test/analysis/transform/outputs/if/if_nested_if_expected.py b/test/analysis/transform/outputs/if/if_nested_if_expected.py index 11b415a..1b71f58 100644 --- a/test/analysis/transform/outputs/if/if_nested_if_expected.py +++ b/test/analysis/transform/outputs/if/if_nested_if_expected.py @@ -1,4 +1,4 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState __sidewinder_cond0 = x __sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) diff --git a/test/analysis/transform/outputs/if/if_simple_expected.py b/test/analysis/transform/outputs/if/if_simple_expected.py index b990c84..b625431 100644 --- a/test/analysis/transform/outputs/if/if_simple_expected.py +++ b/test/analysis/transform/outputs/if/if_simple_expected.py @@ -1,4 +1,4 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState __sidewinder_cond0 = x __sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) diff --git a/test/analysis/transform/outputs/if/if_without_else_expected.py b/test/analysis/transform/outputs/if/if_without_else_expected.py index 19322cc..dafc964 100644 --- a/test/analysis/transform/outputs/if/if_without_else_expected.py +++ b/test/analysis/transform/outputs/if/if_without_else_expected.py @@ -1,4 +1,4 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState __sidewinder_cond0 = x __sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) diff --git a/test/analysis/transform/outputs/try/try_bare_except_expected.py b/test/analysis/transform/outputs/try/try_bare_except_expected.py index b0c1a0d..576a43c 100644 --- a/test/analysis/transform/outputs/try/try_bare_except_expected.py +++ b/test/analysis/transform/outputs/try/try_bare_except_expected.py @@ -1,11 +1,11 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState x = 1 __sidewinder_exc_type0 = ValueError -__sidewinder_cond1, __sidewinder_discard2 = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type0, already_handled=[], __sidewinder_state=__sidewinder_state) +__sidewinder_cond1, __sidewinder_discard2 = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type0, __sidewinder_state=__sidewinder_state, already_handled=[]) __sidewinder_condition_true__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) x = 2 __sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) -__sidewinder_cond3, __sidewinder_discard4 = __sidewinder_exception_condition_and_object__(None, already_handled=[__sidewinder_cond1], __sidewinder_state=__sidewinder_state) +__sidewinder_cond3, __sidewinder_discard4 = __sidewinder_exception_condition_and_object__(None, __sidewinder_state=__sidewinder_state, already_handled=[__sidewinder_cond1]) __sidewinder_condition_true__(__sidewinder_cond3, __sidewinder_state=__sidewinder_state) x = 3 __sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) \ No newline at end of file diff --git a/test/analysis/transform/outputs/try/try_except_as_expected.py b/test/analysis/transform/outputs/try/try_except_as_expected.py index f416e0d..4bf2dca 100644 --- a/test/analysis/transform/outputs/try/try_except_as_expected.py +++ b/test/analysis/transform/outputs/try/try_except_as_expected.py @@ -1,7 +1,7 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState x = 1 __sidewinder_exc_type0 = ValueError -__sidewinder_cond1, e = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type0, already_handled=[], __sidewinder_state=__sidewinder_state) +__sidewinder_cond1, e = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type0, __sidewinder_state=__sidewinder_state, already_handled=[]) __sidewinder_condition_true__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) x = e __sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) \ No newline at end of file diff --git a/test/analysis/transform/outputs/try/try_full_expected.py b/test/analysis/transform/outputs/try/try_full_expected.py index 25800fd..7090e59 100644 --- a/test/analysis/transform/outputs/try/try_full_expected.py +++ b/test/analysis/transform/outputs/try/try_full_expected.py @@ -1,16 +1,16 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState x = 1 __sidewinder_exc_type0 = ValueError -__sidewinder_cond1, e = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type0, already_handled=[], __sidewinder_state=__sidewinder_state) +__sidewinder_cond1, e = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type0, __sidewinder_state=__sidewinder_state, already_handled=[]) __sidewinder_condition_true__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) x = 2 __sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) __sidewinder_exc_type3 = TypeError -__sidewinder_cond4, __sidewinder_discard5 = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type3, already_handled=[__sidewinder_cond1], __sidewinder_state=__sidewinder_state) +__sidewinder_cond4, __sidewinder_discard5 = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type3, __sidewinder_state=__sidewinder_state, already_handled=[__sidewinder_cond1]) __sidewinder_condition_true__(__sidewinder_cond4, __sidewinder_state=__sidewinder_state) x = 3 __sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) -__sidewinder_cond6, __sidewinder_discard7 = __sidewinder_exception_condition_and_object__(None, already_handled=[__sidewinder_cond1, __sidewinder_cond4], __sidewinder_state=__sidewinder_state) +__sidewinder_cond6, __sidewinder_discard7 = __sidewinder_exception_condition_and_object__(None, __sidewinder_state=__sidewinder_state, already_handled=[__sidewinder_cond1, __sidewinder_cond4]) __sidewinder_condition_true__(__sidewinder_cond6, __sidewinder_state=__sidewinder_state) x = 4 __sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) diff --git a/test/analysis/transform/outputs/try/try_multiple_except_expected.py b/test/analysis/transform/outputs/try/try_multiple_except_expected.py index 8e681e5..2d56acf 100644 --- a/test/analysis/transform/outputs/try/try_multiple_except_expected.py +++ b/test/analysis/transform/outputs/try/try_multiple_except_expected.py @@ -1,12 +1,12 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState x = 1 __sidewinder_exc_type0 = ValueError -__sidewinder_cond1, __sidewinder_discard2 = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type0, already_handled=[], __sidewinder_state=__sidewinder_state) +__sidewinder_cond1, __sidewinder_discard2 = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type0, __sidewinder_state=__sidewinder_state, already_handled=[]) __sidewinder_condition_true__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) x = 2 __sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) __sidewinder_exc_type3 = TypeError -__sidewinder_cond4, __sidewinder_discard5 = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type3, already_handled=[__sidewinder_cond1], __sidewinder_state=__sidewinder_state) +__sidewinder_cond4, __sidewinder_discard5 = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type3, __sidewinder_state=__sidewinder_state, already_handled=[__sidewinder_cond1]) __sidewinder_condition_true__(__sidewinder_cond4, __sidewinder_state=__sidewinder_state) x = 3 __sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) \ No newline at end of file diff --git a/test/analysis/transform/outputs/try/try_simple_expected.py b/test/analysis/transform/outputs/try/try_simple_expected.py index 9e87651..d223c3f 100644 --- a/test/analysis/transform/outputs/try/try_simple_expected.py +++ b/test/analysis/transform/outputs/try/try_simple_expected.py @@ -1,7 +1,7 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState x1 __sidewinder_exc_type0 = ValueError -__sidewinder_cond1, __sidewinder_discard2 = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type0, already_handled=[], __sidewinder_state=__sidewinder_state) +__sidewinder_cond1, __sidewinder_discard2 = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type0, __sidewinder_state=__sidewinder_state, already_handled=[]) __sidewinder_condition_true__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) x2 __sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) \ No newline at end of file diff --git a/test/analysis/transform/outputs/try/try_with_else_expected.py b/test/analysis/transform/outputs/try/try_with_else_expected.py index 8ca9866..ace4e2f 100644 --- a/test/analysis/transform/outputs/try/try_with_else_expected.py +++ b/test/analysis/transform/outputs/try/try_with_else_expected.py @@ -1,7 +1,7 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState x = 1 __sidewinder_exc_type0 = ValueError -__sidewinder_cond1, __sidewinder_discard2 = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type0, already_handled=[], __sidewinder_state=__sidewinder_state) +__sidewinder_cond1, __sidewinder_discard2 = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type0, __sidewinder_state=__sidewinder_state, already_handled=[]) __sidewinder_condition_true__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) x = 2 __sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) diff --git a/test/analysis/transform/outputs/try/try_with_finally_expected.py b/test/analysis/transform/outputs/try/try_with_finally_expected.py index 743e76c..b090c34 100644 --- a/test/analysis/transform/outputs/try/try_with_finally_expected.py +++ b/test/analysis/transform/outputs/try/try_with_finally_expected.py @@ -1,7 +1,7 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState x = 1 __sidewinder_exc_type0 = ValueError -__sidewinder_cond1, __sidewinder_discard2 = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type0, already_handled=[], __sidewinder_state=__sidewinder_state) +__sidewinder_cond1, __sidewinder_discard2 = __sidewinder_exception_condition_and_object__(__sidewinder_exc_type0, __sidewinder_state=__sidewinder_state, already_handled=[]) __sidewinder_condition_true__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) x = 2 __sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) diff --git a/test/analysis/transform/outputs/while/while_nested_expected.py b/test/analysis/transform/outputs/while/while_nested_expected.py index bb006c9..58f3e0a 100644 --- a/test/analysis/transform/outputs/while/while_nested_expected.py +++ b/test/analysis/transform/outputs/while/while_nested_expected.py @@ -1,4 +1,4 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState __sidewinder_cond0 = x __sidewinder_fixed_point1 = False diff --git a/test/analysis/transform/outputs/while/while_pass_expected.py b/test/analysis/transform/outputs/while/while_pass_expected.py index 55cd876..fb82474 100644 --- a/test/analysis/transform/outputs/while/while_pass_expected.py +++ b/test/analysis/transform/outputs/while/while_pass_expected.py @@ -1,4 +1,4 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState __sidewinder_cond0 = x __sidewinder_fixed_point1 = False diff --git a/test/analysis/transform/outputs/while/while_simple_expected.py b/test/analysis/transform/outputs/while/while_simple_expected.py index 1ff116a..a108612 100644 --- a/test/analysis/transform/outputs/while/while_simple_expected.py +++ b/test/analysis/transform/outputs/while/while_simple_expected.py @@ -1,4 +1,4 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState __sidewinder_cond0 = x __sidewinder_fixed_point1 = False diff --git a/test/analysis/transform/outputs/while/while_with_if_expected.py b/test/analysis/transform/outputs/while/while_with_if_expected.py index e1d2103..728cec2 100644 --- a/test/analysis/transform/outputs/while/while_with_if_expected.py +++ b/test/analysis/transform/outputs/while/while_with_if_expected.py @@ -1,4 +1,4 @@ -from sidewinder.analysis.symbolic.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState __sidewinder_cond0 = x __sidewinder_fixed_point1 = False diff --git a/test/analysis/transform/test_transform.py b/test/analysis/transform/test_transform.py index 0a7df60..69a0d01 100644 --- a/test/analysis/transform/test_transform.py +++ b/test/analysis/transform/test_transform.py @@ -66,7 +66,9 @@ def _diff(expected: ast.AST, actual: ast.AST) -> str: ids=[f"{input_file.parent.stem}::{input_file.stem}" for input_file, _ in test_cases] ) def test_transformer(input_file: Path, expected_file: Path) -> None: - # parse input + # # parse input + # if "if_without_else" not in input_file.__str__(): + # pytest.skip() input_tree = _parse_file(input_file) # transform