def visit_Compare(self, node): if self._language in {"dart", "kotlin", "nim", "python"}: return node if isinstance(node.ops[0], ast.In): left = node.left right = node.comparators[0] left_type = get_id(get_inferred_type(left)) right_type = get_id(get_inferred_type(right)) if left_type == "str" and right_type == "str": if self._language == "julia": ret = ast.parse("findfirst(a, b) != Nothing").body[0].value ret.left.args[0] = left ret.left.args[1] = right elif self._language == "go": # To be rewritten to strings.Contains via plugins ret = ast.parse("StringsContains(a, b)").body[0].value ret.args[0] = right ret.args[1] = left elif self._language == "cpp": ret = ast.parse("a.find(b) != string.npos").body[0].value ret.left.func.value = right ret.left.args[0] = left else: # rust and c++23 ret = ast.parse("a.contains(b)").body[0].value ret.func.value = right ret.args[0] = left ret.lineno = node.lineno ast.fix_missing_locations(ret) return ret return node
def _visit_AssignOne(self, node, target): if isinstance(target, ast.Tuple): elts = [self.visit(e) for e in target.elts] value = self.visit(node.value) return "{0} = {1}".format(", ".join(elts), value) if isinstance(node.scopes[-1], ast.If): outer_if = node.scopes[-1] target_id = self.visit(target) if target_id in outer_if.common_vars: value = self.visit(node.value) return "{0} = {1}".format(target_id, value) if isinstance(target, ast.Subscript) or isinstance( target, ast.Attribute): target = self.visit(target) value = self.visit(node.value) if value == None: value = "None" return "{0} = {1}".format(target, value) definition = node.scopes.parent_scopes.find(get_id(target)) if definition is None: definition = node.scopes.find(get_id(target)) if isinstance(target, ast.Name) and defined_before(definition, node): target_str = self.visit(target) value = self.visit(node.value) return f"{target_str} = {value};" else: target = self.visit(target) value = self.visit(node.value) return f"{target} = {value}"
def _visit_AssignOne(self, node, target) -> str: kw = "var" if is_mutable(node.scopes, get_id(target)) else "let" if isinstance(target, ast.Tuple): elts = [self.visit(e) for e in target.elts] elts_str = ", ".join(elts) value = self.visit(node.value) return f"{kw} ({elts_str}) = {value}" if isinstance(node.scopes[-1], ast.If): outer_if = node.scopes[-1] target_id = self.visit(target) if target_id in outer_if.common_vars: value = self.visit(node.value) return f"{kw} {target_id} = {value}" if isinstance(target, ast.Subscript) or isinstance( target, ast.Attribute): target = self.visit(target) value = self.visit(node.value) return f"{target} = {value}" definition = node.scopes.parent_scopes.find(get_id(target)) if definition is None: definition = node.scopes.find(get_id(target)) if isinstance(target, ast.Name) and defined_before(definition, node): target = self.visit(target) value = self.visit(node.value) return f"{target} = {value}" else: target = self.visit(target) value = self.visit(node.value) return f"{kw} {target} = {value}"
def visit_Call(self, node): needs_assign = False fname = node.func if isinstance(fname, ast.Attribute): if is_list(node.func.value) and fname.attr == "append": needs_assign = True value_id = get_id(fname.value) if value_id not in IGNORED_MODULE_SET: if value_id: node0 = ast.Name(id=get_id(fname.value), lineno=node.lineno) else: node0 = fname.value node.args = [node0] + node.args node.func = ast.Name(id=fname.attr, lineno=node.lineno, ctx=fname.ctx) if needs_assign: ret = ast.Assign( targets=[ast.Name(id=fname.value.id, lineno=node.lineno)], value=node, lineno=node.lineno, scopes=node.scopes, ) return ret return node
def _typename_from_type_node(self, node) -> Union[List, str, None]: if isinstance(node, ast.Name): return self._map_type( get_id(node), getattr(node, "lifetime", LifeTime.UNKNOWN) ) elif isinstance(node, ast.ClassDef): return get_id(node) elif isinstance(node, ast.Tuple): return [self._typename_from_type_node(e) for e in node.elts] elif isinstance(node, ast.Attribute): node_id = get_id(node) if node_id.startswith("typing."): node_id = node_id.split(".")[1] return node_id elif isinstance(node, ast.Subscript): # Store a tuple like (List, int) or (Dict, (str, int)) for container types # in node.container_type # And return a target specific type slice_value = self._slice_value(node) (value_type, index_type) = tuple( map(self._typename_from_type_node, (node.value, slice_value)) ) value_type = self._map_container_type(value_type) node.container_type = (value_type, index_type) return self._combine_value_index(value_type, index_type) return self._default_type
def visit_Call(self, node): if get_id(node.func) == "print": if len(node.args) == 1: anno = getattr(node.args[0], "annotation", None) if get_id(anno) == "bool": if self._language == "go": return self._do_go_rewrite(node) else: return self._do_other_rewrite(node) return node
def visit_Name(self, node): if node.id == "True" or node.id == "False": return CLikeTranspiler().visit(node) var = node.scopes.find(node.id) if not var: return get_id(node) if defined_before(var, node): return get_id(node) else: return self.visit(var.assigned_from.value)
def visit_BinOp(self, node): self.generic_visit(node) if isinstance(node.op, ast.Div): left_type = get_id(get_inferred_type(node.left)) right_type = get_id(get_inferred_type(node.right)) if set([left_type, right_type]) == {"int"}: # This attribute should technically be on node.op # But python seems to use the same AST node for other # division operations? node.use_integer_div = True return node
def visit_Set(self, node): self.generic_visit(node) if len(node.elts) > 0: elements = [self.visit(e) for e in node.elts] elt_types = set([get_id(get_inferred_type(e)) for e in elements]) if len(elt_types) == 1: elt_type = get_id(elements[0].annotation) self._annotate(node, f"Set[{elt_type}]") else: if not hasattr(node, "annotation"): node.annotation = ast.Name(id="Set") return node
def visit_Call(self, node): fname = get_id(node.func) fndef = node.scopes.find(fname) if fndef and hasattr(fndef, "args"): for fnarg, node_arg in zip(fndef.args.args, node.args): if hasattr(fndef, "mutable_vars") and fnarg.arg in fndef.mutable_vars: self.increase_use_count(get_id(node_arg)) if hasattr(node.func, "attr"): if node.func.attr == "append": self.increase_use_count(get_id(node.func.value)) self.generic_visit(node) return node
def visit_If(self, node): body_vars = {get_id(v): v for v in node.scopes[-1].body_vars} orelse_vars = {get_id(v): v for v in node.scopes[-1].orelse_vars} node.common_vars = set(body_vars.keys()).intersection(set(orelse_vars.keys())) types = [self._typename_from_annotation(body_vars[v]) for v in node.common_vars] common_vars = "\n".join( [f"var {v} {t}" for v, t in zip(node.common_vars, types)] ) if common_vars: return common_vars + "\n" + super().visit_If(node) else: return super().visit_If(node)
def _visit_AssignOne(self, node, target): if isinstance(target, ast.Tuple): elts = [self.visit(e) for e in target.elts] value = self.visit(node.value) return "var {0} = {1}".format(", ".join(elts), value) if isinstance(node.scopes[-1], ast.If): outer_if = node.scopes[-1] target_id = self.visit(target) if target_id in outer_if.common_vars: value = self.visit(node.value) return "{0} = {1}".format(target_id, value) if isinstance(target, ast.Subscript) or isinstance( target, ast.Attribute): target = self.visit(target) value = self.visit(node.value) if value == None: value = "None" return "{0} = {1}".format(target, value) typename = self._typename_from_annotation(target) needs_cast = self._needs_cast(target, node.value) target_str = self.visit(target) value = self.visit(node.value) if needs_cast: left_annotation = target.annotation right_annotation = getattr(node.value, "annotation", None) if right_annotation is None: right_annotation = ast.Name( id=get_inferred_go_type(node.value)) value = self._assign_cast(value, typename, left_annotation, right_annotation) definition = node.scopes.parent_scopes.find(get_id(target)) if definition is None: definition = node.scopes.find(get_id(target)) if isinstance(target, ast.Name) and defined_before(definition, node): return f"{target_str} = {value}" else: if typename is not None: # Dummy assign to silence the compiler on unused vars maybe_tail = "" if target_str.startswith("_") and target_str != "_": maybe_tail = f"\n_ = {target_str}" if target_str == "_": return f"{target_str} = {value}" return f"var {target_str} {typename} = {value}{maybe_tail}" if is_global(node): return f"var {target_str} = {value}" return f"{target_str} := {value}"
def get_inferred_rust_type(node): if hasattr(node, "rust_annotation"): return node.rust_annotation if isinstance(node, ast.Name): if not hasattr(node, "scopes"): return None definition = node.scopes.find(get_id(node)) # Prevent infinite recursion if definition != node: return get_inferred_rust_type(definition) python_type = get_inferred_type(node) ret = map_type(get_id(python_type)) node.rust_annotation = ret return ret
def visit_If(self, node): body_vars = set([get_id(v) for v in node.scopes[-1].body_vars]) orelse_vars = set([get_id(v) for v in node.scopes[-1].orelse_vars]) node.common_vars = body_vars.intersection(orelse_vars) var_definitions = [] for cv in node.common_vars: definition = node.scopes.find(cv) var_type = self._typename_from_annotation(definition) if var_type == self._default_type: var_type = decltype(definition) var_definitions.append("{0} {1};\n".format(var_type, cv)) return "".join(var_definitions) + super().visit_If(node)
def visit_Name(self, node): var = node.scopes.find(get_id(node)) if not var: # TODO why no scopes found for node id? return get_id(node) if isinstance(var.assigned_from, ast.For): it = var.assigned_from.iter return "std::declval<typename decltype({0})::value_type>()".format( self.visit(it)) elif isinstance(var.assigned_from, ast.FunctionDef): return get_id(var) else: return self.visit(var.assigned_from.value)
def visit_Assign(self, node: ast.Assign) -> str: assign: List[str] = [] use_temp: bool = len(node.targets) > 1 and isinstance( node.value, ast.Call) if use_temp: assign.append(f"mut tmp := {self.visit(node.value)}") for target in node.targets: kw: str = "mut " if is_mutable(node.scopes, get_id(target)) else "" if use_temp: value: str = "tmp" else: value: str = self.visit(node.value) if isinstance(target, (ast.Tuple, ast.List)): value = value[1:-1] subtargets: List[str] = [] op: str = ":=" for subtarget in target.elts: subkw: str = ("mut " if is_mutable( node.scopes, get_id(subtarget)) else "") subtargets.append(f"{subkw}{self.visit(subtarget)}") definition: Optional[ ast.AST] = node.scopes.parent_scopes.find( get_id(subtarget)) or node.scopes.find( get_id(subtarget)) if definition is not None and defined_before( definition, subtarget): op = "=" elif op == "=": raise AstNotImplementedError( "Mixing declarations and assignment in the same statement is unsupported.", node, ) assign.append(f"{', '.join(subtargets)} {op} {value}") elif isinstance(target, (ast.Subscript, ast.Attribute)): target: str = self.visit(target) assign.append(f"{target} = {value}") elif isinstance(target, ast.Name) and defined_before( node.scopes.parent_scopes.find(target.id) or node.scopes.find(target.id), node, ): target: str = self.visit(target) assign.append(f"{target} = {value}") else: target: str = self.visit(target) assign.append(f"{kw}{target} := {value}") return "\n".join(assign)
def is_dict(node: ast.AST) -> bool: if isinstance(node, (ast.Dict, ast.DictComp)): return True elif isinstance(node, ast.Call) and get_id(node.func) == "dict": return True elif isinstance(node, ast.Assign): return is_dict(node.value) elif isinstance(node, ast.Name): var: ast.AST = node.scopes.find(get_id(node)) return (hasattr(var, "assigned_from") and not isinstance(var.assigned_from, ast.FunctionDef) and not isinstance(var.assigned_from, ast.For) and is_dict(var.assigned_from.value)) else: return False
def visit_sealed_class(self, node): variants = [] accessors = [] tag_checkers = [] camel_node_name = camel_case(node.name) for member, var in node.class_assignments.items(): member_id = get_id(member) camel_member_id = camel_case(member_id) lower_member_id = member_id.lower() typename = node.declarations_with_defaults.get(member_id, None) if typename == None: variants.append(f"{member_id},") else: typename, _ = typename variants.append(f"{member_id}({typename}),") tag_check = textwrap.dedent(f""" fn is_{lower_member_id}(&self) -> bool {{ matches!(*self, {camel_node_name}::{camel_member_id}(_)) }}""") tag_checkers.append(tag_check) accessor = textwrap.dedent(f""" fn {lower_member_id}(&self) -> Option<&{typename}>{{ if let {camel_node_name}::{camel_member_id}(val) = self {{ Some(val) }} else {{ None }} }}""") accessors.append(accessor) body_str = "\n".join(variants) impl_str = "\n".join(accessors) + "\n" + "\n".join(tag_checkers) return f"enum {camel_node_name} {{ {body_str} }}\n\n impl {camel_node_name} {{ {impl_str} }}\n\n"
def get_inferred_kotlin_type(node): if isinstance(node, ast.Call): fname = get_id(node.func) if fname in {"max", "min", "floor"}: return "float64" if isinstance(node, ast.Name): if not hasattr(node, "scopes"): return None definition = node.scopes.find(get_id(node)) # Prevent infinite recursion if definition != node: return get_inferred_kotlin_type(definition) if hasattr(node, "kotlin_annotation"): return node.kotlin_annotation python_type = get_inferred_type(node) return map_type(get_id(python_type))
def visit_Call(self, node): fname = get_id(node.func) if fname is not None: # Handle methods calls by looking up the method name # without the prefix # TODO: use remove suffix if fname.startswith("self."): fname = fname.split(".", 1)[1] fn = node.scopes.find(fname) if isinstance(fn, ast.ClassDef): self._annotate(node, fn.name) elif isinstance(fn, ast.FunctionDef): return_type = (fn.returns if hasattr(fn, "returns") and fn.returns else None) if return_type is not None: node.annotation = return_type lifetime = getattr(fn.returns, "lifetime", None) if lifetime is not None: node.annotation.lifetime = lifetime elif fname in {"max", "min"}: return_type = get_inferred_type(node.args[0]) if return_type is not None: node.annotation = return_type elif fname in self.TYPE_DICT.values(): node.annotation = ast.Name(id=fname) self.generic_visit(node) return node
def visit_Name(self, node): if hasattr(node, "scopes") and is_global(node): old_name = get_id(node) new_name = camel_case(old_name) if old_name != new_name: rename(node.scopes[-1], old_name, new_name) return node
def visit_ClassDef(self, node): extractor = DeclarationExtractor(JuliaTranspiler()) extractor.visit(node) declarations = node.declarations = extractor.get_declarations() node.class_assignments = extractor.class_assignments ret = super().visit_ClassDef(node) if ret is not None: return ret decorators = [get_id(d) for d in node.decorator_list] decorators = [ class_for_typename(t, None, self._imported_names) for t in decorators ] for d in decorators: if d in CLASS_DISPATCH_TABLE: ret = CLASS_DISPATCH_TABLE[d](self, node) if ret is not None: return ret fields = [] index = 0 for declaration, typename in declarations.items(): if typename == None: typename = "ST{0}".format(index) index += 1 fields.append(f"{declaration}::{typename}") fields = "\n".join(fields) struct_def = f"struct {node.name}\n{fields}\nend\n" for b in node.body: if isinstance(b, ast.FunctionDef): b.self_type = node.name body = "\n".join([self.visit(b) for b in node.body]) return f"{struct_def}\n{body}"
def visit_Call(self, node): fname = node.func if isinstance(fname, ast.Attribute): if is_list(node.func.value) and fname.attr == "append": new_func_name = "push!" else: new_func_name = fname.attr if get_id(fname.value): node0 = ast.Name(id=get_id(fname.value), lineno=node.lineno) else: node0 = fname.value node.args = [node0] + node.args node.func = ast.Name(id=new_func_name, lineno=node.lineno, ctx=fname.ctx) return node
def visit_AnnAssign(self, node: ast.AnnAssign) -> ast.AST: self.generic_visit(node) node.target.annotation = node.annotation if get_id(node.annotation) in self.FIXED_WIDTH_INTS_NAME: self.has_fixed_width_ints = True return node
def visit_Assign(self, node): if self._disable: return node target = node.targets[0] if isinstance(target, ast.Tuple) and isinstance(node.value, ast.Tuple): names = [ get_id(elt) for elt in target.elts if isinstance(elt, ast.Name) ] has_ignored = "_" in names if self._unpack and has_ignored: return self._visit_assign_unpack_all(node) if not has_ignored: return node body = [node] to_eval = [] for i in range(len(target.elts)): if names[i] == "_": del target.elts[i] to_eval.append(node.value.elts[i]) del node.value.elts[i] # TODO: Evaluation order - we may have to split the tuple assignment to get # it right. For now, keep it simple body = [ast.Expr(value=e) for e in to_eval] + body return create_ast_block(body=body, at_node=node) return node
def visit_Attribute(self, node): attr = node.attr value_id = get_id(node.value) if is_builtin_import(value_id): return "py14::" + value_id + "::" + attr elif value_id == "math": if node.attr == "asin": return "std::asin" elif node.attr == "atan": return "std::atan" elif node.attr == "acos": return "std::acos" if is_list(node.value): if node.attr == "append": attr = "push_back" if is_enum(value_id, node.scopes): return f"{value_id}::{attr}" if is_class_or_module(value_id, node.scopes): return f"{value_id}.{attr}" if is_self_arg(value_id, node.scopes): return f"this->{attr}" return f"{value_id}.{attr}"
def visit_ClassDef(self, node): extractor = DeclarationExtractor(KotlinTranspiler()) extractor.visit(node) declarations = node.declarations = extractor.get_declarations() node.class_assignments = extractor.class_assignments ret = super().visit_ClassDef(node) if ret is not None: return ret fields = [] index = 0 for declaration, typename in declarations.items(): if typename == None: typename = "ST{0}".format(index) index += 1 mut = is_mutable(node.scopes, get_id(declaration)) mut = "var" if mut else "val" fields.append(f"{mut} {declaration}: {typename}") for b in node.body: if isinstance(b, ast.FunctionDef): b.self_type = node.name if node.is_dataclass: fields = ", ".join(fields) body = [self.visit(b) for b in node.body] body = "\n".join(body) return f"data class {node.name}({fields}) {{\n{body}\n}}\n" else: fields = "\n".join(fields) body = [self.visit(b) for b in node.body] body = "\n".join(body) return f"class {node.name} {{\n{fields}\n\n {body}\n}}\n"
def visit_If(self, node): body_vars = set([get_id(v) for v in node.scopes[-1].body_vars]) orelse_vars = set([get_id(v) for v in node.scopes[-1].orelse_vars]) node.common_vars = body_vars.intersection(orelse_vars) var_definitions = [] for cv in node.common_vars: typename = "var" if hasattr(cv, "annotation"): typename = get_id(cv.annotation) var_definitions.append((typename, cv)) decls = "\n".join( [f"{typename} {cv};" for typename, cv in var_definitions]) return decls + "\n\n" + super().visit_If(node)
def is_enum(name, scopes): entry = _lookup_class_or_module(name, scopes) if entry: bases = set([get_id(base) for base in entry.bases]) enum_bases = {"Enum", "IntEnum", "IntFlag"} return bases & enum_bases return False
def _visit_AssignOne(self, node, target): kw = "var" if is_mutable(node.scopes, get_id(target)) else "final" if isinstance(target, ast.Tuple): self._usings.add("package:tuple/tuple.dart") elts = [self.visit(e) for e in target.elts] value = self.visit(node.value) value_types = "int, int" count = len(elts) tmp_var = self._get_temp() buf = [f"{kw} {tmp_var} = Tuple{count}<{value_types}>{value};"] for i, elt in enumerate(elts): buf.extend([f"{elt} = {tmp_var}.item{i+1};"]) return "\n".join(buf) if isinstance(node.scopes[-1], ast.If): outer_if = node.scopes[-1] target_id = self.visit(target) if target_id in outer_if.common_vars: value = self.visit(node.value) return f"{kw} {target_id} = {value};" if isinstance(target, ast.Subscript) or isinstance( target, ast.Attribute): target = self.visit(target) value = self.visit(node.value) return f"{target} = {value};" definition = node.scopes.parent_scopes.find(get_id(target)) if definition is None: definition = node.scopes.find(get_id(target)) if isinstance(target, ast.Name) and defined_before(definition, node): target = self.visit(target) value = self.visit(node.value) return f"{target} = {value};" else: typename = self._typename_from_annotation(target) target = self.visit(target) value = self.visit(node.value) if typename != self._default_type: if kw == self._default_type: return f"{typename} {target} = {value};" else: return f"{kw} {target} = {value};" return f"{kw} {typename} {target} = {value};"