def visit_FunctionDef(self, node): name = node.name returns = type_from_annotation(node.returns, f"{name} return", True) args = [] for arg in node.args.args: argname = arg.arg typed = type_from_annotation(arg.annotation, f"{name}: {argname}", False) args.append((argname, typed)) self.headers[name] = FunctionHeader(returns, args)
def visit_AnnAssign(self, node): if (isinstance(node.target, ast.Attribute) and isinstance(node.target.value, ast.Name) and node.target.value.id == "self"): # got a member variable definition name = node.target.attr typed = type_from_annotation(node.annotation, node.target, True) self.attributes[name] = typed
def visit_FunctionDef(self, node): name = node.name returns = type_from_annotation(node.returns, f"{name} return", True) args = [] for arg in node.args.args: argname = arg.arg typed = type_from_annotation(arg.annotation, f"{name}: {argname}", False) args.append((argname, typed)) self.methods[name] = FunctionHeader(returns, args) # If this is the __init__ method, any assignments to self.<something> # are effectively variable declarations. if name == "__init__": finder = InstanceAttributeFinder(dict(args)) for line in node.body: finder.visit(line) self.instance_attributes = finder.attributes
def visit_arg(self, node): if node.arg == "self": if not self.is_init: ref_type = "&mut " if node.arg in self.mutable_ref_vars else "&" print(f"{ref_type}self", end='') self.next_separator = ", " else: typed = type_from_annotation(node.annotation, node.arg, False) ref_type = "&mut " if node.arg in self.mutable_ref_vars else "" if ref_type: typed = dereference(typed) mutable = "mut " if node.arg in self.mutable_vars else "" print( f"{self.next_separator}{mutable}{node.arg}: {ref_type}{typed}", end='') self.variables.add(node.arg) self.next_separator = ", "
def visit_AnnAssign(self, node): """ Hinted variable assignment statement, such as x: int = 42 We do not yet handle non-simple assignments such as (x): int = 42 """ # treatment depends on whether it is the first time we # have seen this variable. (Do not use shadowing.) mutable, declared, _, _ = self.sex_variable(node.target) if declared: print(f"{self.pretty()}", end='') else: mut = "mut " if mutable else "" print(f"{self.pretty()}let {mut}", end='') self.visit(node.target) typed = type_from_annotation(node.annotation, node.target, True) print(f": {typed} = ", end='') self.precedence = 0 # don't need params around value self.visit_and_optionally_convert(node.value) print(";")
def visit_AnnAssign(self, node): self.visit(node.value) typed = type_from_annotation(node.annotation, node.target, True) self.handle_assignment(node.target, typed)
def visit_Call(self, node): """ Try to find the return type of the function we are calling. Also assign the right types to the args. """ # recurse through the arguments prev = self.current_type arg_types = [] for a in node.args: self.visit(a) arg_types.append(self.current_type) self.current_type = prev # a few functions are well-known (and in any case, they # do not behave properly with the below code) func_path = get_node_path(node.func) if func_path and len(func_path) == 1 and func_path[0] in STANDARD_FUNCTION_RETURNS: self.set_type(STANDARD_FUNCTION_RETURNS[func_path[0]](arg_types), node) return # Assume function names with no module are defined locally if len(func_path) == 1: if func_path[0] not in self.headers: print(f"Warning: cannot find function return for: {func_path[0]}", file = sys.stderr) else: self.set_type(self.headers[func_path[0]].returns, node) # If the first part of the path is a known variable, then this is # a method call on that variable. Ignore for now, apart from setting # the type of the variable if func_path[0] in self.vars: self.in_call = True self.visit(node.func) self.in_call = False typed = method_return_type(self.current_type, func_path[1]) self.set_type(typed, node) # for now, we assume that any method invoked on an object can # mutate that object self.vars[func_path[0]].mutable_ref = True return # We currently only handle module.func_name if len(func_path) != 2: return # Locate the function. In order to do this, we actually load # the module of interest into our own process space. Maybe # consider making this optional, as it is a bit of a # sledgehammer. module_name = func_path[0] func_name = func_path[1] namespace = load_and_import_module(module_name) func = getattr(namespace, func_name) # try to find the type of the function types = get_type_hints(func) if "return" in types: typed = type_from_annotation(types["return"],func_name, True) self.set_type(typed, node)
def visit_arg(self, node): typed = type_from_annotation(node.annotation, node.arg, False) if node.arg in self.vars: raise Exception(f"Repeated argument: {node.arg}") self.vars[node.arg] = VariableInfo(True, typed) self.type_by_node[node] = typed
def visit_FunctionDef(self, node): # Analyse the variables in this function to see which need # to be predeclared or marked as mutable analyser = VariableAnalyser(self.headers, self.class_headers, self.current_self) analyser.visit(node) self.type_by_node = analyser.get_type_by_node() # function name. Always public, as Python has no # private functions. Special handling for __init__, # which we always call "new". self.is_init = node.name == "__init__" name = "new" if self.is_init else node.name pub = "" if self.in_trait else "pub " print(f"{self.pretty()}{pub}fn {name}(", end='') # start with a clean set of variables # (do we need to worry about nested functions?) self.variables.clear() self.mutable_vars = analyser.get_mutable_vars() self.mutable_ref_vars = analyser.get_mutable_ref_vars() # function arg list self.next_separator = "" self.generic_visit(node.args) # return value if self.is_init: print(f") -> {self.current_self}", end='') elif node.returns is not None: typed = type_from_annotation(node.returns, "return", True) print(f") -> {typed}", end='') else: print(")", end='') # if all the function does is to pass, and we are in a trait, # just leave as a declaration. if (self.in_trait_definition and len(node.body) == 1 and isinstance(node.body[0], ast.Pass)): print(";") return print(" {") self.add_pretty(1) # start with any variable declarations for (var, typed, default) in analyser.get_predeclared_vars(): self.variables.add(var) print(f"{self.pretty()}let mut {var}: {typed} = {default};") # body of the function, but special handling for __init__ for expr in node.body: self.visit(expr) # in the case of __init__, we now want to return the object if self.is_init: print(f"{self.pretty()}{self.current_self} {OPEN_BRACE}") self.add_pretty(1) classdef = self.class_headers[self.current_self] for member, _ in classdef.instance_attributes.items(): print(f"{self.pretty()}{member}: tmp_{member},") self.add_pretty(-1) print(f"{self.pretty()}{CLOSE_BRACE}") self.add_pretty(-1) print(f"{self.pretty()}{CLOSE_BRACE}") print() # clean the set of variables. The names do not leak past here self.variables.clear()