From ba350267d43af89dd2ad5b5c1adb22b80d3868a0 Mon Sep 17 00:00:00 2001 From: Richard Si Date: Mon, 24 Apr 2023 12:27:43 -0400 Subject: [PATCH] [mypyc] Switch to table-driven imports for smaller IR (#14917) Add CPyImport_ImportFromMany() which imports a module and a tuple of names, placing them in the globals dict in one go. Previously, each name would imported and placed in globals manually in IR, leading to some pretty verbose code. The other option to collect all from imports and perform them all at once in the helper would remove even more ops, however, it has some major downsides: - It wouldn't be able to be done in IRBuild directly, instead being handled in the prebuild visitor and codegen... which all sounds really involved. - It would cause from imports to be performed eagerly, potentially causing circular imports (especially in functions whose imports are probably there to avoid a circular import!). The latter is the nail in the coffin for this idea. --- Change how imports (not from imports!) are processed so they can be table-driven (tuple-driven, really) and compact. Here's how it works: Import nodes are divided in groups (in the prebuild visitor). Each group consists of consecutive Import nodes: import mod <| group 1 import mod2 | def foo() -> None: import mod3 <- group 2 (*) import mod4 <- group 3 Every time we encounter the first import of a group, build IR to call CPyImport_ImportMany() that will perform all of the group's imports in one go. (*) Imports in functions or classes are still transformed into the original, verbose IR as speed is more important than codesize. Previously, each module would imported and placed in globals manually in IR, leading to some pretty verbose code. The other option to collect all imports and perform them all at once in the helper would remove even more ops, however, it's problematic for the same reasons from the previous commit (spoiler: it's not safe). Implementation notes: - I had to add support for loading the address of a static directly, so I shoehorned in LoadStatic support for LoadAddress. - Unfortunately by replacing multiple import nodes with a single function call at the IR level, if any import within a group fails, the traceback line number is static and will be probably wrong (pointing to the first import node in my original impl.). To fix this, I had to make CPyImport_ImportMany() add the traceback entry itself on failure (instead of letting codegen handle it automatically). This is admittedly ugly. Overall, this doesn't speed up initialization. The only real speed impact is that back to back imports in a tight loop seems to be 10-20% slower. I believe that's acceptable given the code size reduction. --- **Other changes:** - Don't declare internal static for non-compiled modules It won't be read anywhere as the internal statics are only used to avoid runaway recursion with import cycles in our module init functions. - Wrap long RArray initializers and long annotations in codegen Table-driven imports can load some rather large RArrays and tuple literals so this was needed to keep the generated C readable. - Add LLBuilder helper for setting up a RArray Resolves mypyc/mypyc#591. --- mypyc/codegen/emit.py | 59 ++- mypyc/codegen/emitfunc.py | 43 +- mypyc/codegen/emitmodule.py | 56 +-- mypyc/ir/ops.py | 5 +- mypyc/ir/pprint.py | 5 + mypyc/irbuild/builder.py | 26 +- mypyc/irbuild/function.py | 2 +- mypyc/irbuild/ll_builder.py | 23 +- mypyc/irbuild/main.py | 2 +- mypyc/irbuild/prebuildvisitor.py | 32 +- mypyc/irbuild/statement.py | 152 +++++-- mypyc/lib-rt/CPy.h | 6 +- mypyc/lib-rt/misc_ops.c | 82 +++- mypyc/primitives/misc_ops.py | 33 +- mypyc/test-data/irbuild-basic.test | 654 ++++++++++++++++----------- mypyc/test-data/irbuild-classes.test | 297 +++++------- mypyc/test-data/run-imports.test | 71 +++ mypyc/test-data/run-multimodule.test | 8 +- mypyc/test/test_emit.py | 20 + 19 files changed, 954 insertions(+), 622 deletions(-) diff --git a/mypyc/codegen/emit.py b/mypyc/codegen/emit.py index 0f1f5ad071ad..56ce9637307b 100644 --- a/mypyc/codegen/emit.py +++ b/mypyc/codegen/emit.py @@ -2,7 +2,9 @@ from __future__ import annotations +import pprint import sys +import textwrap from typing import Callable from typing_extensions import Final @@ -191,10 +193,31 @@ def reg(self, reg: Value) -> str: def attr(self, name: str) -> str: return ATTR_PREFIX + name - def emit_line(self, line: str = "") -> None: + def object_annotation(self, obj: object, line: str) -> str: + """Build a C comment with an object's string represention. + + If the comment exceeds the line length limit, it's wrapped into a + multiline string (with the extra lines indented to be aligned with + the first line's comment). + + If it contains illegal characters, an empty string is returned.""" + line_width = self._indent + len(line) + formatted = pprint.pformat(obj, compact=True, width=max(90 - line_width, 20)) + if any(x in formatted for x in ("/*", "*/", "\0")): + return "" + + if "\n" in formatted: + first_line, rest = formatted.split("\n", maxsplit=1) + comment_continued = textwrap.indent(rest, (line_width + 3) * " ") + return f" /* {first_line}\n{comment_continued} */" + else: + return f" /* {formatted} */" + + def emit_line(self, line: str = "", *, ann: object = None) -> None: if line.startswith("}"): self.dedent() - self.fragments.append(self._indent * " " + line + "\n") + comment = self.object_annotation(ann, line) if ann is not None else "" + self.fragments.append(self._indent * " " + line + comment + "\n") if line.endswith("{"): self.indent() @@ -1119,3 +1142,35 @@ def _emit_traceback( self.emit_line(line) if DEBUG_ERRORS: self.emit_line('assert(PyErr_Occurred() != NULL && "failure w/o err!");') + + +def c_array_initializer(components: list[str], *, indented: bool = False) -> str: + """Construct an initializer for a C array variable. + + Components are C expressions valid in an initializer. + + For example, if components are ["1", "2"], the result + would be "{1, 2}", which can be used like this: + + int a[] = {1, 2}; + + If the result is long, split it into multiple lines. + """ + indent = " " * 4 if indented else "" + res = [] + current: list[str] = [] + cur_len = 0 + for c in components: + if not current or cur_len + 2 + len(indent) + len(c) < 70: + current.append(c) + cur_len += len(c) + 2 + else: + res.append(indent + ", ".join(current)) + current = [c] + cur_len = len(c) + if not res: + # Result fits on a single line + return "{%s}" % ", ".join(current) + # Multi-line result + res.append(indent + ", ".join(current)) + return "{\n " + ",\n ".join(res) + "\n" + indent + "}" diff --git a/mypyc/codegen/emitfunc.py b/mypyc/codegen/emitfunc.py index c6af1309550b..f2406ff1a257 100644 --- a/mypyc/codegen/emitfunc.py +++ b/mypyc/codegen/emitfunc.py @@ -5,7 +5,7 @@ from typing_extensions import Final from mypyc.analysis.blockfreq import frequently_executed_blocks -from mypyc.codegen.emit import DEBUG_ERRORS, Emitter, TracebackAndGotoHandler +from mypyc.codegen.emit import DEBUG_ERRORS, Emitter, TracebackAndGotoHandler, c_array_initializer from mypyc.common import MODULE_PREFIX, NATIVE_PREFIX, REG_PREFIX, STATIC_PREFIX, TYPE_PREFIX from mypyc.ir.class_ir import ClassIR from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD, FuncDecl, FuncIR, all_values @@ -262,12 +262,12 @@ def visit_assign_multi(self, op: AssignMulti) -> None: # RArray values can only be assigned to once, so we can always # declare them on initialization. self.emit_line( - "%s%s[%d] = {%s};" + "%s%s[%d] = %s;" % ( self.emitter.ctype_spaced(typ.item_type), dest, len(op.src), - ", ".join(self.reg(s) for s in op.src), + c_array_initializer([self.reg(s) for s in op.src], indented=True), ) ) @@ -282,15 +282,12 @@ def visit_load_error_value(self, op: LoadErrorValue) -> None: def visit_load_literal(self, op: LoadLiteral) -> None: index = self.literals.literal_index(op.value) - s = repr(op.value) - if not any(x in s for x in ("/*", "*/", "\0")): - ann = " /* %s */" % s - else: - ann = "" if not is_int_rprimitive(op.type): - self.emit_line("%s = CPyStatics[%d];%s" % (self.reg(op), index, ann)) + self.emit_line("%s = CPyStatics[%d];" % (self.reg(op), index), ann=op.value) else: - self.emit_line("%s = (CPyTagged)CPyStatics[%d] | 1;%s" % (self.reg(op), index, ann)) + self.emit_line( + "%s = (CPyTagged)CPyStatics[%d] | 1;" % (self.reg(op), index), ann=op.value + ) def get_attr_expr(self, obj: str, op: GetAttr | SetAttr, decl_cl: ClassIR) -> str: """Generate attribute accessor for normal (non-property) access. @@ -468,12 +465,7 @@ def visit_load_static(self, op: LoadStatic) -> None: name = self.emitter.static_name(op.identifier, op.module_name, prefix) if op.namespace == NAMESPACE_TYPE: name = "(PyObject *)%s" % name - ann = "" - if op.ann: - s = repr(op.ann) - if not any(x in s for x in ("/*", "*/", "\0")): - ann = " /* %s */" % s - self.emit_line(f"{dest} = {name};{ann}") + self.emit_line(f"{dest} = {name};", ann=op.ann) def visit_init_static(self, op: InitStatic) -> None: value = self.reg(op.value) @@ -636,12 +628,7 @@ def visit_extend(self, op: Extend) -> None: def visit_load_global(self, op: LoadGlobal) -> None: dest = self.reg(op) - ann = "" - if op.ann: - s = repr(op.ann) - if not any(x in s for x in ("/*", "*/", "\0")): - ann = " /* %s */" % s - self.emit_line(f"{dest} = {op.identifier};{ann}") + self.emit_line(f"{dest} = {op.identifier};", ann=op.ann) def visit_int_op(self, op: IntOp) -> None: dest = self.reg(op) @@ -727,7 +714,13 @@ def visit_get_element_ptr(self, op: GetElementPtr) -> None: def visit_load_address(self, op: LoadAddress) -> None: typ = op.type dest = self.reg(op) - src = self.reg(op.src) if isinstance(op.src, Register) else op.src + if isinstance(op.src, Register): + src = self.reg(op.src) + elif isinstance(op.src, LoadStatic): + prefix = self.PREFIX_MAP[op.src.namespace] + src = self.emitter.static_name(op.src.identifier, op.src.module_name, prefix) + else: + src = op.src self.emit_line(f"{dest} = ({typ._ctype})&{src};") def visit_keep_alive(self, op: KeepAlive) -> None: @@ -776,8 +769,8 @@ def c_error_value(self, rtype: RType) -> str: def c_undefined_value(self, rtype: RType) -> str: return self.emitter.c_undefined_value(rtype) - def emit_line(self, line: str) -> None: - self.emitter.emit_line(line) + def emit_line(self, line: str, *, ann: object = None) -> None: + self.emitter.emit_line(line, ann=ann) def emit_lines(self, *lines: str) -> None: self.emitter.emit_lines(*lines) diff --git a/mypyc/codegen/emitmodule.py b/mypyc/codegen/emitmodule.py index a8226314039d..0e80ff6da1f2 100644 --- a/mypyc/codegen/emitmodule.py +++ b/mypyc/codegen/emitmodule.py @@ -26,7 +26,7 @@ from mypy.plugin import Plugin, ReportConfigContext from mypy.util import hash_digest from mypyc.codegen.cstring import c_string_initializer -from mypyc.codegen.emit import Emitter, EmitterContext, HeaderDeclaration +from mypyc.codegen.emit import Emitter, EmitterContext, HeaderDeclaration, c_array_initializer from mypyc.codegen.emitclass import generate_class, generate_class_type_decl from mypyc.codegen.emitfunc import generate_native_function, native_function_header from mypyc.codegen.emitwrapper import ( @@ -296,11 +296,11 @@ def compile_ir_to_c( # compiled into a separate extension module. ctext: dict[str | None, list[tuple[str, str]]] = {} for group_sources, group_name in groups: - group_modules = [ - (source.module, modules[source.module]) + group_modules = { + source.module: modules[source.module] for source in group_sources if source.module in modules - ] + } if not group_modules: ctext[group_name] = [] continue @@ -465,7 +465,7 @@ def group_dir(group_name: str) -> str: class GroupGenerator: def __init__( self, - modules: list[tuple[str, ModuleIR]], + modules: dict[str, ModuleIR], source_paths: dict[str, str], group_name: str | None, group_map: dict[str, str | None], @@ -512,7 +512,7 @@ def generate_c_for_modules(self) -> list[tuple[str, str]]: multi_file = self.use_shared_lib and self.multi_file # Collect all literal refs in IR. - for _, module in self.modules: + for module in self.modules.values(): for fn in module.functions: collect_literals(fn, self.context.literals) @@ -528,7 +528,7 @@ def generate_c_for_modules(self) -> list[tuple[str, str]]: self.generate_literal_tables() - for module_name, module in self.modules: + for module_name, module in self.modules.items(): if multi_file: emitter = Emitter(self.context) emitter.emit_line(f'#include "__native{self.short_group_suffix}.h"') @@ -582,7 +582,7 @@ def generate_c_for_modules(self) -> list[tuple[str, str]]: declarations.emit_line("int CPyGlobalsInit(void);") declarations.emit_line() - for module_name, module in self.modules: + for module_name, module in self.modules.items(): self.declare_finals(module_name, module.final_names, declarations) for cl in module.classes: generate_class_type_decl(cl, emitter, ext_declarations, declarations) @@ -790,7 +790,7 @@ def generate_shared_lib_init(self, emitter: Emitter) -> None: "", ) - for mod, _ in self.modules: + for mod in self.modules: name = exported_name(mod) emitter.emit_lines( f"extern PyObject *CPyInit_{name}(void);", @@ -1023,12 +1023,13 @@ def module_internal_static_name(self, module_name: str, emitter: Emitter) -> str return emitter.static_name(module_name + "_internal", None, prefix=MODULE_PREFIX) def declare_module(self, module_name: str, emitter: Emitter) -> None: - # We declare two globals for each module: + # We declare two globals for each compiled module: # one used internally in the implementation of module init to cache results # and prevent infinite recursion in import cycles, and one used # by other modules to refer to it. - internal_static_name = self.module_internal_static_name(module_name, emitter) - self.declare_global("CPyModule *", internal_static_name, initializer="NULL") + if module_name in self.modules: + internal_static_name = self.module_internal_static_name(module_name, emitter) + self.declare_global("CPyModule *", internal_static_name, initializer="NULL") static_name = emitter.static_name(module_name, None, prefix=MODULE_PREFIX) self.declare_global("CPyModule *", static_name) self.simple_inits.append((static_name, "Py_None")) @@ -1126,37 +1127,6 @@ def collect_literals(fn: FuncIR, literals: Literals) -> None: literals.record_literal(op.value) -def c_array_initializer(components: list[str]) -> str: - """Construct an initializer for a C array variable. - - Components are C expressions valid in an initializer. - - For example, if components are ["1", "2"], the result - would be "{1, 2}", which can be used like this: - - int a[] = {1, 2}; - - If the result is long, split it into multiple lines. - """ - res = [] - current: list[str] = [] - cur_len = 0 - for c in components: - if not current or cur_len + 2 + len(c) < 70: - current.append(c) - cur_len += len(c) + 2 - else: - res.append(", ".join(current)) - current = [c] - cur_len = len(c) - if not res: - # Result fits on a single line - return "{%s}" % ", ".join(current) - # Multi-line result - res.append(", ".join(current)) - return "{\n " + ",\n ".join(res) + "\n}" - - def c_string_array_initializer(components: list[bytes]) -> str: result = [] result.append("{\n") diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index 229bdfb4a326..351f7c01efe2 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -1348,13 +1348,14 @@ class LoadAddress(RegisterOp): Attributes: type: Type of the loaded address(e.g. ptr/object_ptr) src: Source value (str for globals like 'PyList_Type', - Register for temporary values or locals) + Register for temporary values or locals, LoadStatic + for statics.) """ error_kind = ERR_NEVER is_borrowed = True - def __init__(self, type: RType, src: str | Register, line: int = -1) -> None: + def __init__(self, type: RType, src: str | Register | LoadStatic, line: int = -1) -> None: super().__init__(line) self.type = type self.src = src diff --git a/mypyc/ir/pprint.py b/mypyc/ir/pprint.py index 82e82913c9a6..4d10a91835ca 100644 --- a/mypyc/ir/pprint.py +++ b/mypyc/ir/pprint.py @@ -266,6 +266,11 @@ def visit_get_element_ptr(self, op: GetElementPtr) -> str: def visit_load_address(self, op: LoadAddress) -> str: if isinstance(op.src, Register): return self.format("%r = load_address %r", op, op.src) + elif isinstance(op.src, LoadStatic): + name = op.src.identifier + if op.src.module_name is not None: + name = f"{op.src.module_name}.{name}" + return self.format("%r = load_address %s :: %s", op, name, op.src.namespace) else: return self.format("%r = load_address %s", op, op.src) diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index 8bdc626bc9c5..f071cc20f6b6 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -90,7 +90,6 @@ RType, RUnion, bitmap_rprimitive, - c_int_rprimitive, c_pyssize_t_rprimitive, dict_rprimitive, int_rprimitive, @@ -127,12 +126,7 @@ from mypyc.primitives.dict_ops import dict_get_item_op, dict_set_item_op from mypyc.primitives.generic_ops import iter_op, next_op, py_setattr_op from mypyc.primitives.list_ops import list_get_item_unsafe_op, list_pop_last, to_list -from mypyc.primitives.misc_ops import ( - check_unpack_count_op, - get_module_dict_op, - import_extra_args_op, - import_op, -) +from mypyc.primitives.misc_ops import check_unpack_count_op, get_module_dict_op, import_op from mypyc.primitives.registry import CFunctionDescription, function_ops # These int binary operations can borrow their operands safely, since the @@ -194,6 +188,8 @@ def __init__( self.encapsulating_funcs = pbv.encapsulating_funcs self.nested_fitems = pbv.nested_funcs.keys() self.fdefs_to_decorators = pbv.funcs_to_decorators + self.module_import_groups = pbv.module_import_groups + self.singledispatch_impls = singledispatch_impls self.visitor = visitor @@ -395,22 +391,6 @@ def add_to_non_ext_dict( key_unicode = self.load_str(key) self.call_c(dict_set_item_op, [non_ext.dict, key_unicode, val], line) - def gen_import_from( - self, id: str, globals_dict: Value, imported: list[str], line: int - ) -> Value: - self.imports[id] = None - - null_dict = Integer(0, dict_rprimitive, line) - names_to_import = self.new_list_op([self.load_str(name) for name in imported], line) - zero_int = Integer(0, c_int_rprimitive, line) - value = self.call_c( - import_extra_args_op, - [self.load_str(id), globals_dict, null_dict, names_to_import, zero_int], - line, - ) - self.add(InitStatic(value, id, namespace=NAMESPACE_MODULE)) - return value - def gen_import(self, id: str, line: int) -> None: self.imports[id] = None diff --git a/mypyc/irbuild/function.py b/mypyc/irbuild/function.py index dd962d9184f5..822350ea829b 100644 --- a/mypyc/irbuild/function.py +++ b/mypyc/irbuild/function.py @@ -515,7 +515,7 @@ def gen_func_ns(builder: IRBuilder) -> str: return "_".join( info.name + ("" if not info.class_name else "_" + info.class_name) for info in builder.fn_infos - if info.name and info.name != "" + if info.name and info.name != "" ) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index d60ba917dae7..aa152d32a144 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -876,10 +876,8 @@ def _py_vector_call( ): if arg_values: # Create a C array containing all arguments as boxed values. - array = Register(RArray(object_rprimitive, len(arg_values))) coerced_args = [self.coerce(arg, object_rprimitive, line) for arg in arg_values] - self.add(AssignMulti(array, coerced_args)) - arg_ptr = self.add(LoadAddress(object_pointer_rprimitive, array)) + arg_ptr = self.setup_rarray(object_rprimitive, coerced_args, object_ptr=True) else: arg_ptr = Integer(0, object_pointer_rprimitive) num_pos = num_positional_args(arg_values, arg_kinds) @@ -953,13 +951,10 @@ def _py_vector_method_call( not kind.is_star() and not kind.is_optional() for kind in arg_kinds ): method_name_reg = self.load_str(method_name) - array = Register(RArray(object_rprimitive, len(arg_values) + 1)) - self_arg = self.coerce(obj, object_rprimitive, line) - coerced_args = [self_arg] + [ - self.coerce(arg, object_rprimitive, line) for arg in arg_values + coerced_args = [ + self.coerce(arg, object_rprimitive, line) for arg in [obj] + arg_values ] - self.add(AssignMulti(array, coerced_args)) - arg_ptr = self.add(LoadAddress(object_pointer_rprimitive, array)) + arg_ptr = self.setup_rarray(object_rprimitive, coerced_args, object_ptr=True) num_pos = num_positional_args(arg_values, arg_kinds) keywords = self._vectorcall_keywords(arg_names) value = self.call_c( @@ -1716,6 +1711,16 @@ def new_list_op(self, values: list[Value], line: int) -> Value: def new_set_op(self, values: list[Value], line: int) -> Value: return self.call_c(new_set_op, values, line) + def setup_rarray( + self, item_type: RType, values: Sequence[Value], *, object_ptr: bool = False + ) -> Value: + """Declare and initialize a new RArray, returning its address.""" + array = Register(RArray(item_type, len(values))) + self.add(AssignMulti(array, list(values))) + return self.add( + LoadAddress(object_pointer_rprimitive if object_ptr else c_pointer_rprimitive, array) + ) + def shortcircuit_helper( self, op: str, diff --git a/mypyc/irbuild/main.py b/mypyc/irbuild/main.py index 9bbb90aad207..85b905393af1 100644 --- a/mypyc/irbuild/main.py +++ b/mypyc/irbuild/main.py @@ -130,7 +130,7 @@ def transform_mypy_file(builder: IRBuilder, mypyfile: MypyFile) -> None: ir = builder.mapper.type_to_ir[cls.info] builder.classes.append(ir) - builder.enter("") + builder.enter("") # Make sure we have a builtins import builder.gen_import("builtins", -1) diff --git a/mypyc/irbuild/prebuildvisitor.py b/mypyc/irbuild/prebuildvisitor.py index d99453955002..519b3445e925 100644 --- a/mypyc/irbuild/prebuildvisitor.py +++ b/mypyc/irbuild/prebuildvisitor.py @@ -1,22 +1,25 @@ from __future__ import annotations from mypy.nodes import ( + Block, Decorator, Expression, FuncDef, FuncItem, + Import, LambdaExpr, MemberExpr, MypyFile, NameExpr, + Node, SymbolNode, Var, ) -from mypy.traverser import TraverserVisitor +from mypy.traverser import ExtendedTraverserVisitor from mypyc.errors import Errors -class PreBuildVisitor(TraverserVisitor): +class PreBuildVisitor(ExtendedTraverserVisitor): """Mypy file AST visitor run before building the IR. This collects various things, including: @@ -26,6 +29,7 @@ class PreBuildVisitor(TraverserVisitor): * Find non-local variables (free variables) * Find property setters * Find decorators of functions + * Find module import groups The main IR build pass uses this information. """ @@ -68,10 +72,26 @@ def __init__( # Map function to indices of decorators to remove self.decorators_to_remove: dict[FuncDef, list[int]] = decorators_to_remove + # A mapping of import groups (a series of Import nodes with + # nothing inbetween) where each group is keyed by its first + # import node. + self.module_import_groups: dict[Import, list[Import]] = {} + self._current_import_group: Import | None = None + self.errors: Errors = errors self.current_file: MypyFile = current_file + def visit(self, o: Node) -> bool: + if not isinstance(o, Import): + self._current_import_group = None + return True + + def visit_block(self, block: Block) -> None: + self._current_import_group = None + super().visit_block(block) + self._current_import_group = None + def visit_decorator(self, dec: Decorator) -> None: if dec.decorators: # Only add the function being decorated if there exist @@ -123,6 +143,14 @@ def visit_func(self, func: FuncItem) -> None: super().visit_func(func) self.funcs.pop() + def visit_import(self, imp: Import) -> None: + if self._current_import_group is not None: + self.module_import_groups[self._current_import_group].append(imp) + else: + self.module_import_groups[imp] = [imp] + self._current_import_group = imp + super().visit_import(imp) + def visit_name_expr(self, expr: NameExpr) -> None: if isinstance(expr.node, (Var, FuncDef)): self.visit_symbol_node(expr.node) diff --git a/mypyc/irbuild/statement.py b/mypyc/irbuild/statement.py index c7eace87a17c..63297618108c 100644 --- a/mypyc/irbuild/statement.py +++ b/mypyc/irbuild/statement.py @@ -43,13 +43,17 @@ YieldFromExpr, ) from mypyc.ir.ops import ( + NAMESPACE_MODULE, NO_TRACEBACK_LINE_NO, Assign, BasicBlock, Branch, + InitStatic, Integer, LoadAddress, LoadErrorValue, + LoadLiteral, + LoadStatic, MethodCall, RaiseStandardError, Register, @@ -60,6 +64,7 @@ ) from mypyc.ir.rtypes import ( RInstance, + c_pyssize_t_rprimitive, exc_rtuple, is_tagged, none_rprimitive, @@ -96,7 +101,8 @@ from mypyc.primitives.misc_ops import ( check_stop_op, coro_op, - import_from_op, + import_from_many_op, + import_many_op, send_op, type_op, yield_from_except_op, @@ -214,35 +220,93 @@ def transform_operator_assignment_stmt(builder: IRBuilder, stmt: OperatorAssignm builder.flush_keep_alives() +def import_globals_id_and_name(module_id: str, as_name: str | None) -> tuple[str, str]: + """Compute names for updating the globals dict with the appropriate module. + + * For 'import foo.bar as baz' we add 'foo.bar' with the name 'baz' + * For 'import foo.bar' we add 'foo' with the name 'foo' + + Typically we then ignore these entries and access things directly + via the module static, but we will use the globals version for + modules that mypy couldn't find, since it doesn't analyze module + references from those properly.""" + if as_name: + globals_id = module_id + globals_name = as_name + else: + globals_id = globals_name = module_id.split(".")[0] + + return globals_id, globals_name + + def transform_import(builder: IRBuilder, node: Import) -> None: if node.is_mypy_only: return - globals = builder.load_globals_dict() - for node_id, as_name in node.ids: - builder.gen_import(node_id, node.line) - - # Update the globals dict with the appropriate module: - # * For 'import foo.bar as baz' we add 'foo.bar' with the name 'baz' - # * For 'import foo.bar' we add 'foo' with the name 'foo' - # Typically we then ignore these entries and access things directly - # via the module static, but we will use the globals version for modules - # that mypy couldn't find, since it doesn't analyze module references - # from those properly. - - # TODO: Don't add local imports to the global namespace - - # Miscompiling imports inside of functions, like below in import from. - if as_name: - name = as_name - base = node_id - else: - base = name = node_id.split(".")[0] - obj = builder.get_module(base, node.line) + # Imports (not from imports!) are processed in an odd way so they can be + # table-driven and compact. Here's how it works: + # + # Import nodes are divided in groups (in the prebuild visitor). Each group + # consists of consecutive Import nodes: + # + # import mod <| group #1 + # import mod2 | + # + # def foo() -> None: + # import mod3 <- group #2 (*) + # + # import mod4 <| group #3 + # import mod5 | + # + # Every time we encounter the first import of a group, build IR to call a + # helper function that will perform all of the group's imports in one go. + if not node.is_top_level: + # (*) Unless the import is within a function. In that case, prioritize + # speed over codesize when generating IR. + globals = builder.load_globals_dict() + for mod_id, as_name in node.ids: + builder.gen_import(mod_id, node.line) + globals_id, globals_name = import_globals_id_and_name(mod_id, as_name) + builder.gen_method_call( + globals, + "__setitem__", + [builder.load_str(globals_name), builder.get_module(globals_id, node.line)], + result_type=None, + line=node.line, + ) + return - builder.gen_method_call( - globals, "__setitem__", [builder.load_str(name), obj], result_type=None, line=node.line - ) + if node not in builder.module_import_groups: + return + + modules = [] + static_ptrs = [] + # To show the right line number on failure, we have to add the traceback + # entry within the helper function (which is admittedly ugly). To drive + # this, we need the line number corresponding to each module. + mod_lines = [] + for import_node in builder.module_import_groups[node]: + for mod_id, as_name in import_node.ids: + builder.imports[mod_id] = None + modules.append((mod_id, *import_globals_id_and_name(mod_id, as_name))) + mod_static = LoadStatic(object_rprimitive, mod_id, namespace=NAMESPACE_MODULE) + static_ptrs.append(builder.add(LoadAddress(object_pointer_rprimitive, mod_static))) + mod_lines.append(Integer(import_node.line, c_pyssize_t_rprimitive)) + + static_array_ptr = builder.builder.setup_rarray(object_pointer_rprimitive, static_ptrs) + import_line_ptr = builder.builder.setup_rarray(c_pyssize_t_rprimitive, mod_lines) + builder.call_c( + import_many_op, + [ + builder.add(LoadLiteral(tuple(modules), object_rprimitive)), + static_array_ptr, + builder.load_globals_dict(), + builder.load_str(builder.module_path), + builder.load_str(builder.fn_info.name), + import_line_ptr, + ], + NO_TRACEBACK_LINE_NO, + ) def transform_import_from(builder: IRBuilder, node: ImportFrom) -> None: @@ -258,29 +322,25 @@ def transform_import_from(builder: IRBuilder, node: ImportFrom) -> None: module_package = "" id = importlib.util.resolve_name("." * node.relative + node.id, module_package) - - globals = builder.load_globals_dict() - imported_names = [name for name, _ in node.names] - module = builder.gen_import_from(id, globals, imported_names, node.line) - - # Copy everything into our module's dict. + builder.imports[id] = None + + names = [name for name, _ in node.names] + as_names = [as_name or name for name, as_name in node.names] + names_literal = builder.add(LoadLiteral(tuple(names), object_rprimitive)) + if as_names == names: + # Reuse names tuple to reduce verbosity. + as_names_literal = names_literal + else: + as_names_literal = builder.add(LoadLiteral(tuple(as_names), object_rprimitive)) # Note that we miscompile import from inside of functions here, - # since that case *shouldn't* load it into the globals dict. + # since that case *shouldn't* load everything into the globals dict. # This probably doesn't matter much and the code runs basically right. - for name, maybe_as_name in node.names: - as_name = maybe_as_name or name - obj = builder.call_c( - import_from_op, - [module, builder.load_str(id), builder.load_str(name), builder.load_str(as_name)], - node.line, - ) - builder.gen_method_call( - globals, - "__setitem__", - [builder.load_str(as_name), obj], - result_type=None, - line=node.line, - ) + module = builder.call_c( + import_from_many_op, + [builder.load_str(id), names_literal, as_names_literal, builder.load_globals_dict()], + node.line, + ) + builder.add(InitStatic(module, id, namespace=NAMESPACE_MODULE)) def transform_import_all(builder: IRBuilder, node: ImportAll) -> None: diff --git a/mypyc/lib-rt/CPy.h b/mypyc/lib-rt/CPy.h index e01acec4d2f0..7a3e16fe9d65 100644 --- a/mypyc/lib-rt/CPy.h +++ b/mypyc/lib-rt/CPy.h @@ -622,8 +622,10 @@ PyObject *CPy_Super(PyObject *builtins, PyObject *self); PyObject *CPy_CallReverseOpMethod(PyObject *left, PyObject *right, const char *op, _Py_Identifier *method); -PyObject *CPyImport_ImportFrom(PyObject *module, PyObject *package_name, - PyObject *import_name, PyObject *as_name); +bool CPyImport_ImportMany(PyObject *modules, CPyModule **statics[], PyObject *globals, + PyObject *tb_path, PyObject *tb_function, Py_ssize_t *tb_lines); +PyObject *CPyImport_ImportFromMany(PyObject *mod_id, PyObject *names, PyObject *as_names, + PyObject *globals); PyObject *CPySingledispatch_RegisterFunction(PyObject *singledispatch_func, PyObject *cls, PyObject *func); diff --git a/mypyc/lib-rt/misc_ops.c b/mypyc/lib-rt/misc_ops.c index 5fda78704bbc..88a76fb210d7 100644 --- a/mypyc/lib-rt/misc_ops.c +++ b/mypyc/lib-rt/misc_ops.c @@ -669,9 +669,62 @@ CPy_Super(PyObject *builtins, PyObject *self) { return result; } +static bool import_single(PyObject *mod_id, PyObject **mod_static, + PyObject *globals_id, PyObject *globals_name, PyObject *globals) { + if (*mod_static == Py_None) { + CPyModule *mod = PyImport_Import(mod_id); + if (mod == NULL) { + return false; + } + *mod_static = mod; + } + + PyObject *mod_dict = PyImport_GetModuleDict(); + CPyModule *globals_mod = CPyDict_GetItem(mod_dict, globals_id); + if (globals_mod == NULL) { + return false; + } + int ret = CPyDict_SetItem(globals, globals_name, globals_mod); + Py_DECREF(globals_mod); + if (ret < 0) { + return false; + } + + return true; +} + +// Table-driven import helper. See transform_import() in irbuild for the details. +bool CPyImport_ImportMany(PyObject *modules, CPyModule **statics[], PyObject *globals, + PyObject *tb_path, PyObject *tb_function, Py_ssize_t *tb_lines) { + for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(modules); i++) { + PyObject *module = PyTuple_GET_ITEM(modules, i); + PyObject *mod_id = PyTuple_GET_ITEM(module, 0); + PyObject *globals_id = PyTuple_GET_ITEM(module, 1); + PyObject *globals_name = PyTuple_GET_ITEM(module, 2); + + if (!import_single(mod_id, statics[i], globals_id, globals_name, globals)) { + assert(PyErr_Occurred() && "error indicator should be set on bad import!"); + PyObject *typ, *val, *tb; + PyErr_Fetch(&typ, &val, &tb); + const char *path = PyUnicode_AsUTF8(tb_path); + if (path == NULL) { + path = ""; + } + const char *function = PyUnicode_AsUTF8(tb_function); + if (function == NULL) { + function = ""; + } + PyErr_Restore(typ, val, tb); + CPy_AddTraceback(path, function, tb_lines[i], globals); + return false; + } + } + return true; +} + // This helper function is a simplification of cpython/ceval.c/import_from() -PyObject *CPyImport_ImportFrom(PyObject *module, PyObject *package_name, - PyObject *import_name, PyObject *as_name) { +static PyObject *CPyImport_ImportFrom(PyObject *module, PyObject *package_name, + PyObject *import_name, PyObject *as_name) { // check if the imported module has an attribute by that name PyObject *x = PyObject_GetAttr(module, import_name); if (x == NULL) { @@ -702,6 +755,31 @@ PyObject *CPyImport_ImportFrom(PyObject *module, PyObject *package_name, return NULL; } +PyObject *CPyImport_ImportFromMany(PyObject *mod_id, PyObject *names, PyObject *as_names, + PyObject *globals) { + PyObject *mod = PyImport_ImportModuleLevelObject(mod_id, globals, 0, names, 0); + if (mod == NULL) { + return NULL; + } + + for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(names); i++) { + PyObject *name = PyTuple_GET_ITEM(names, i); + PyObject *as_name = PyTuple_GET_ITEM(as_names, i); + PyObject *obj = CPyImport_ImportFrom(mod, mod_id, name, as_name); + if (obj == NULL) { + Py_DECREF(mod); + return NULL; + } + int ret = CPyDict_SetItem(globals, as_name, obj); + Py_DECREF(obj); + if (ret < 0) { + Py_DECREF(mod); + return NULL; + } + } + return mod; +} + // From CPython static PyObject * CPy_BinopTypeError(PyObject *left, PyObject *right, const char *op) { diff --git a/mypyc/primitives/misc_ops.py b/mypyc/primitives/misc_ops.py index 07df9c69714b..5a8cc111ebc2 100644 --- a/mypyc/primitives/misc_ops.py +++ b/mypyc/primitives/misc_ops.py @@ -7,10 +7,10 @@ bit_rprimitive, bool_rprimitive, c_int_rprimitive, + c_pointer_rprimitive, c_pyssize_t_rprimitive, dict_rprimitive, int_rprimitive, - list_rprimitive, object_pointer_rprimitive, object_rprimitive, str_rprimitive, @@ -112,7 +112,7 @@ is_borrowed=True, ) -# Import a module +# Import a module (plain) import_op = custom_op( arg_types=[str_rprimitive], return_type=object_rprimitive, @@ -120,25 +120,26 @@ error_kind=ERR_MAGIC, ) -# Import with extra arguments (used in from import handling) -import_extra_args_op = custom_op( +# Table-driven import op. +import_many_op = custom_op( arg_types=[ - str_rprimitive, - dict_rprimitive, - dict_rprimitive, - list_rprimitive, - c_int_rprimitive, + object_rprimitive, + c_pointer_rprimitive, + object_rprimitive, + object_rprimitive, + object_rprimitive, + c_pointer_rprimitive, ], - return_type=object_rprimitive, - c_function_name="PyImport_ImportModuleLevelObject", - error_kind=ERR_MAGIC, + return_type=bit_rprimitive, + c_function_name="CPyImport_ImportMany", + error_kind=ERR_FALSE, ) -# Import-from helper op -import_from_op = custom_op( - arg_types=[object_rprimitive, str_rprimitive, str_rprimitive, str_rprimitive], +# From import helper op +import_from_many_op = custom_op( + arg_types=[object_rprimitive, object_rprimitive, object_rprimitive, object_rprimitive], return_type=object_rprimitive, - c_function_name="CPyImport_ImportFrom", + c_function_name="CPyImport_ImportFromMany", error_kind=ERR_MAGIC, ) diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index e6426cdeea53..496eca77e090 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -682,14 +682,106 @@ L0: r5 = unbox(int, r4) return r5 -[case testFromImport] -from testmodule import g +[case testImport_toplevel] +import sys +import enum as enum2 +import collections.abc +import collections.abc as abc2 +_ = "filler" +import single +single.hello() + +[file single.py] +def hello() -> None: + print("hello, world") + +[out] +def __top_level__(): + r0, r1 :: object + r2 :: bit + r3 :: str + r4 :: object + r5, r6, r7, r8 :: object_ptr + r9 :: object_ptr[4] + r10 :: c_ptr + r11 :: native_int[4] + r12 :: c_ptr + r13 :: object + r14 :: dict + r15, r16 :: str + r17 :: bit + r18 :: str + r19 :: dict + r20 :: str + r21 :: int32 + r22 :: bit + r23 :: object_ptr + r24 :: object_ptr[1] + r25 :: c_ptr + r26 :: native_int[1] + r27 :: c_ptr + r28 :: object + r29 :: dict + r30, r31 :: str + r32 :: bit + r33 :: object + r34 :: str + r35, r36 :: object +L0: + r0 = builtins :: module + r1 = load_address _Py_NoneStruct + r2 = r0 != r1 + if r2 goto L2 else goto L1 :: bool +L1: + r3 = 'builtins' + r4 = PyImport_Import(r3) + builtins = r4 :: module +L2: + r5 = load_address sys :: module + r6 = load_address enum :: module + r7 = load_address collections.abc :: module + r8 = load_address collections.abc :: module + r9 = [r5, r6, r7, r8] + r10 = load_address r9 + r11 = [1, 2, 3, 4] + r12 = load_address r11 + r13 = (('sys', 'sys', 'sys'), ('enum', 'enum', 'enum2'), ('collections.abc', 'collections', 'collections'), ('collections.abc', 'collections.abc', 'abc2')) + r14 = __main__.globals :: static + r15 = 'main' + r16 = '' + r17 = CPyImport_ImportMany(r13, r10, r14, r15, r16, r12) + r18 = 'filler' + r19 = __main__.globals :: static + r20 = '_' + r21 = CPyDict_SetItem(r19, r20, r18) + r22 = r21 >= 0 :: signed + r23 = load_address single :: module + r24 = [r23] + r25 = load_address r24 + r26 = [6] + r27 = load_address r26 + r28 = (('single', 'single', 'single'),) + r29 = __main__.globals :: static + r30 = 'main' + r31 = '' + r32 = CPyImport_ImportMany(r28, r25, r29, r30, r31, r27) + r33 = single :: module + r34 = 'hello' + r35 = CPyObject_GetAttr(r33, r34) + r36 = PyObject_CallFunctionObjArgs(r35, 0) + return 1 + +[case testFromImport_toplevel] +from testmodule import g, h +from testmodule import h as two def f(x: int) -> int: - return g(x) + return g(x) + h() + two() [file testmodule.py] def g(x: int) -> int: return x + 1 +def h() -> int: + return 2 [out] def f(x): x :: int @@ -697,6 +789,14 @@ def f(x): r1 :: str r2, r3, r4 :: object r5 :: int + r6 :: dict + r7 :: str + r8, r9 :: object + r10, r11 :: int + r12 :: dict + r13 :: str + r14, r15 :: object + r16, r17 :: int L0: r0 = __main__.globals :: static r1 = 'g' @@ -704,7 +804,52 @@ L0: r3 = box(int, x) r4 = PyObject_CallFunctionObjArgs(r2, r3, 0) r5 = unbox(int, r4) - return r5 + r6 = __main__.globals :: static + r7 = 'h' + r8 = CPyDict_GetItem(r6, r7) + r9 = PyObject_CallFunctionObjArgs(r8, 0) + r10 = unbox(int, r9) + r11 = CPyTagged_Add(r5, r10) + r12 = __main__.globals :: static + r13 = 'two' + r14 = CPyDict_GetItem(r12, r13) + r15 = PyObject_CallFunctionObjArgs(r14, 0) + r16 = unbox(int, r15) + r17 = CPyTagged_Add(r11, r16) + return r17 +def __top_level__(): + r0, r1 :: object + r2 :: bit + r3 :: str + r4, r5 :: object + r6 :: str + r7 :: dict + r8, r9, r10 :: object + r11 :: str + r12 :: dict + r13 :: object +L0: + r0 = builtins :: module + r1 = load_address _Py_NoneStruct + r2 = r0 != r1 + if r2 goto L2 else goto L1 :: bool +L1: + r3 = 'builtins' + r4 = PyImport_Import(r3) + builtins = r4 :: module +L2: + r5 = ('g', 'h') + r6 = 'testmodule' + r7 = __main__.globals :: static + r8 = CPyImport_ImportFromMany(r6, r5, r5, r7) + testmodule = r8 :: module + r9 = ('h',) + r10 = ('two',) + r11 = 'testmodule' + r12 = __main__.globals :: static + r13 = CPyImport_ImportFromMany(r11, r9, r10, r12) + testmodule = r13 :: module + return 1 [case testPrintFullname] import builtins @@ -2263,79 +2408,61 @@ def __top_level__(): r0, r1 :: object r2 :: bit r3 :: str - r4 :: object - r5 :: dict - r6, r7, r8 :: str - r9 :: list - r10, r11, r12, r13 :: ptr + r4, r5 :: object + r6 :: str + r7 :: dict + r8 :: object + r9, r10 :: str + r11 :: object + r12 :: tuple[str, object] + r13 :: object r14 :: str r15 :: object - r16, r17, r18 :: str + r16 :: tuple[str, object] + r17 :: object + r18 :: tuple[object, object] r19 :: object - r20 :: str - r21 :: int32 - r22 :: bit - r23, r24, r25 :: str - r26 :: object - r27 :: str - r28 :: int32 - r29 :: bit - r30, r31, r32 :: str - r33 :: object - r34 :: str - r35 :: int32 - r36 :: bit - r37, r38 :: str - r39 :: object - r40 :: tuple[str, object] - r41 :: object - r42 :: str - r43 :: object - r44 :: tuple[str, object] - r45 :: object - r46 :: tuple[object, object] - r47 :: object - r48 :: dict - r49 :: str - r50, r51 :: object + r20 :: dict + r21 :: str + r22, r23 :: object + r24 :: dict + r25 :: str + r26 :: int32 + r27 :: bit + r28 :: str + r29 :: dict + r30 :: str + r31, r32, r33 :: object + r34 :: tuple + r35 :: dict + r36 :: str + r37 :: int32 + r38 :: bit + r39 :: dict + r40 :: str + r41, r42, r43 :: object + r44 :: dict + r45 :: str + r46 :: int32 + r47 :: bit + r48 :: str + r49 :: dict + r50 :: str + r51 :: object r52 :: dict r53 :: str - r54 :: int32 - r55 :: bit - r56 :: str - r57 :: dict - r58 :: str - r59, r60, r61 :: object - r62 :: tuple - r63 :: dict - r64 :: str - r65 :: int32 - r66 :: bit - r67 :: dict - r68 :: str - r69, r70, r71 :: object - r72 :: dict - r73 :: str - r74 :: int32 - r75 :: bit - r76 :: str - r77 :: dict - r78 :: str - r79 :: object - r80 :: dict - r81 :: str - r82, r83 :: object - r84 :: dict - r85 :: str - r86 :: int32 - r87 :: bit - r88 :: list - r89, r90, r91 :: object - r92, r93, r94, r95 :: ptr - r96 :: dict - r97 :: str - r98 :: int32 - r99 :: bit + r54, r55 :: object + r56 :: dict + r57 :: str + r58 :: int32 + r59 :: bit + r60 :: list + r61, r62, r63 :: object + r64, r65, r66, r67 :: ptr + r68 :: dict + r69 :: str + r70 :: int32 + r71 :: bit L0: r0 = builtins :: module r1 = load_address _Py_NoneStruct @@ -2346,110 +2473,78 @@ L1: r4 = PyImport_Import(r3) builtins = r4 :: module L2: - r5 = __main__.globals :: static - r6 = 'List' - r7 = 'NewType' - r8 = 'NamedTuple' - r9 = PyList_New(3) - r10 = get_element_ptr r9 ob_item :: PyListObject - r11 = load_mem r10 :: ptr* - set_mem r11, r6 :: builtins.object* - r12 = r11 + WORD_SIZE*1 - set_mem r12, r7 :: builtins.object* - r13 = r11 + WORD_SIZE*2 - set_mem r13, r8 :: builtins.object* - keep_alive r9 - r14 = 'typing' - r15 = PyImport_ImportModuleLevelObject(r14, r5, 0, r9, 0) - typing = r15 :: module - r16 = 'typing' - r17 = 'List' - r18 = 'List' - r19 = CPyImport_ImportFrom(r15, r16, r17, r18) - r20 = 'List' - r21 = CPyDict_SetItem(r5, r20, r19) - r22 = r21 >= 0 :: signed - r23 = 'typing' - r24 = 'NewType' - r25 = 'NewType' - r26 = CPyImport_ImportFrom(r15, r23, r24, r25) - r27 = 'NewType' - r28 = CPyDict_SetItem(r5, r27, r26) - r29 = r28 >= 0 :: signed - r30 = 'typing' - r31 = 'NamedTuple' - r32 = 'NamedTuple' - r33 = CPyImport_ImportFrom(r15, r30, r31, r32) - r34 = 'NamedTuple' - r35 = CPyDict_SetItem(r5, r34, r33) - r36 = r35 >= 0 :: signed - r37 = 'Lol' - r38 = 'a' - r39 = load_address PyLong_Type - r40 = (r38, r39) - r41 = box(tuple[str, object], r40) - r42 = 'b' - r43 = load_address PyUnicode_Type - r44 = (r42, r43) - r45 = box(tuple[str, object], r44) - r46 = (r41, r45) - r47 = box(tuple[object, object], r46) - r48 = __main__.globals :: static - r49 = 'NamedTuple' - r50 = CPyDict_GetItem(r48, r49) - r51 = PyObject_CallFunctionObjArgs(r50, r37, r47, 0) + r5 = ('List', 'NewType', 'NamedTuple') + r6 = 'typing' + r7 = __main__.globals :: static + r8 = CPyImport_ImportFromMany(r6, r5, r5, r7) + typing = r8 :: module + r9 = 'Lol' + r10 = 'a' + r11 = load_address PyLong_Type + r12 = (r10, r11) + r13 = box(tuple[str, object], r12) + r14 = 'b' + r15 = load_address PyUnicode_Type + r16 = (r14, r15) + r17 = box(tuple[str, object], r16) + r18 = (r13, r17) + r19 = box(tuple[object, object], r18) + r20 = __main__.globals :: static + r21 = 'NamedTuple' + r22 = CPyDict_GetItem(r20, r21) + r23 = PyObject_CallFunctionObjArgs(r22, r9, r19, 0) + r24 = __main__.globals :: static + r25 = 'Lol' + r26 = CPyDict_SetItem(r24, r25, r23) + r27 = r26 >= 0 :: signed + r28 = '' + r29 = __main__.globals :: static + r30 = 'Lol' + r31 = CPyDict_GetItem(r29, r30) + r32 = object 1 + r33 = PyObject_CallFunctionObjArgs(r31, r32, r28, 0) + r34 = cast(tuple, r33) + r35 = __main__.globals :: static + r36 = 'x' + r37 = CPyDict_SetItem(r35, r36, r34) + r38 = r37 >= 0 :: signed + r39 = __main__.globals :: static + r40 = 'List' + r41 = CPyDict_GetItem(r39, r40) + r42 = load_address PyLong_Type + r43 = PyObject_GetItem(r41, r42) + r44 = __main__.globals :: static + r45 = 'Foo' + r46 = CPyDict_SetItem(r44, r45, r43) + r47 = r46 >= 0 :: signed + r48 = 'Bar' + r49 = __main__.globals :: static + r50 = 'Foo' + r51 = CPyDict_GetItem(r49, r50) r52 = __main__.globals :: static - r53 = 'Lol' - r54 = CPyDict_SetItem(r52, r53, r51) - r55 = r54 >= 0 :: signed - r56 = '' - r57 = __main__.globals :: static - r58 = 'Lol' - r59 = CPyDict_GetItem(r57, r58) - r60 = object 1 - r61 = PyObject_CallFunctionObjArgs(r59, r60, r56, 0) - r62 = cast(tuple, r61) - r63 = __main__.globals :: static - r64 = 'x' - r65 = CPyDict_SetItem(r63, r64, r62) - r66 = r65 >= 0 :: signed - r67 = __main__.globals :: static - r68 = 'List' - r69 = CPyDict_GetItem(r67, r68) - r70 = load_address PyLong_Type - r71 = PyObject_GetItem(r69, r70) - r72 = __main__.globals :: static - r73 = 'Foo' - r74 = CPyDict_SetItem(r72, r73, r71) - r75 = r74 >= 0 :: signed - r76 = 'Bar' - r77 = __main__.globals :: static - r78 = 'Foo' - r79 = CPyDict_GetItem(r77, r78) - r80 = __main__.globals :: static - r81 = 'NewType' - r82 = CPyDict_GetItem(r80, r81) - r83 = PyObject_CallFunctionObjArgs(r82, r76, r79, 0) - r84 = __main__.globals :: static - r85 = 'Bar' - r86 = CPyDict_SetItem(r84, r85, r83) - r87 = r86 >= 0 :: signed - r88 = PyList_New(3) - r89 = object 1 - r90 = object 2 - r91 = object 3 - r92 = get_element_ptr r88 ob_item :: PyListObject - r93 = load_mem r92 :: ptr* - set_mem r93, r89 :: builtins.object* - r94 = r93 + WORD_SIZE*1 - set_mem r94, r90 :: builtins.object* - r95 = r93 + WORD_SIZE*2 - set_mem r95, r91 :: builtins.object* - keep_alive r88 - r96 = __main__.globals :: static - r97 = 'y' - r98 = CPyDict_SetItem(r96, r97, r88) - r99 = r98 >= 0 :: signed + r53 = 'NewType' + r54 = CPyDict_GetItem(r52, r53) + r55 = PyObject_CallFunctionObjArgs(r54, r48, r51, 0) + r56 = __main__.globals :: static + r57 = 'Bar' + r58 = CPyDict_SetItem(r56, r57, r55) + r59 = r58 >= 0 :: signed + r60 = PyList_New(3) + r61 = object 1 + r62 = object 2 + r63 = object 3 + r64 = get_element_ptr r60 ob_item :: PyListObject + r65 = load_mem r64 :: ptr* + set_mem r65, r61 :: builtins.object* + r66 = r65 + WORD_SIZE*1 + set_mem r66, r62 :: builtins.object* + r67 = r65 + WORD_SIZE*2 + set_mem r67, r63 :: builtins.object* + keep_alive r60 + r68 = __main__.globals :: static + r69 = 'y' + r70 = CPyDict_SetItem(r68, r69, r60) + r71 = r70 >= 0 :: signed return 1 [case testChainedConditional] @@ -2773,31 +2868,23 @@ def __top_level__(): r0, r1 :: object r2 :: bit r3 :: str - r4 :: object - r5 :: dict + r4, r5 :: object r6 :: str - r7 :: list - r8, r9 :: ptr + r7 :: dict + r8 :: object + r9 :: dict r10 :: str r11 :: object - r12, r13, r14 :: str - r15 :: object - r16 :: str - r17 :: int32 - r18 :: bit - r19 :: dict - r20 :: str - r21 :: object - r22 :: dict - r23 :: str - r24, r25 :: object - r26 :: dict - r27 :: str - r28, r29 :: object - r30 :: dict - r31 :: str - r32 :: int32 - r33 :: bit + r12 :: dict + r13 :: str + r14, r15 :: object + r16 :: dict + r17 :: str + r18, r19 :: object + r20 :: dict + r21 :: str + r22 :: int32 + r23 :: bit L0: r0 = builtins :: module r1 = load_address _Py_NoneStruct @@ -2808,38 +2895,26 @@ L1: r4 = PyImport_Import(r3) builtins = r4 :: module L2: - r5 = __main__.globals :: static - r6 = 'Callable' - r7 = PyList_New(1) - r8 = get_element_ptr r7 ob_item :: PyListObject - r9 = load_mem r8 :: ptr* - set_mem r9, r6 :: builtins.object* - keep_alive r7 - r10 = 'typing' - r11 = PyImport_ImportModuleLevelObject(r10, r5, 0, r7, 0) - typing = r11 :: module - r12 = 'typing' - r13 = 'Callable' - r14 = 'Callable' - r15 = CPyImport_ImportFrom(r11, r12, r13, r14) - r16 = 'Callable' - r17 = CPyDict_SetItem(r5, r16, r15) - r18 = r17 >= 0 :: signed - r19 = __main__.globals :: static - r20 = 'c' - r21 = CPyDict_GetItem(r19, r20) - r22 = __main__.globals :: static - r23 = 'b' - r24 = CPyDict_GetItem(r22, r23) - r25 = PyObject_CallFunctionObjArgs(r24, r21, 0) - r26 = __main__.globals :: static - r27 = 'a' - r28 = CPyDict_GetItem(r26, r27) - r29 = PyObject_CallFunctionObjArgs(r28, r25, 0) - r30 = __main__.globals :: static - r31 = 'c' - r32 = CPyDict_SetItem(r30, r31, r29) - r33 = r32 >= 0 :: signed + r5 = ('Callable',) + r6 = 'typing' + r7 = __main__.globals :: static + r8 = CPyImport_ImportFromMany(r6, r5, r5, r7) + typing = r8 :: module + r9 = __main__.globals :: static + r10 = 'c' + r11 = CPyDict_GetItem(r9, r10) + r12 = __main__.globals :: static + r13 = 'b' + r14 = CPyDict_GetItem(r12, r13) + r15 = PyObject_CallFunctionObjArgs(r14, r11, 0) + r16 = __main__.globals :: static + r17 = 'a' + r18 = CPyDict_GetItem(r16, r17) + r19 = PyObject_CallFunctionObjArgs(r18, r15, 0) + r20 = __main__.globals :: static + r21 = 'c' + r22 = CPyDict_SetItem(r20, r21, r19) + r23 = r22 >= 0 :: signed return 1 [case testDecoratorsSimple_toplevel] @@ -2914,18 +2989,10 @@ def __top_level__(): r0, r1 :: object r2 :: bit r3 :: str - r4 :: object - r5 :: dict + r4, r5 :: object r6 :: str - r7 :: list - r8, r9 :: ptr - r10 :: str - r11 :: object - r12, r13, r14 :: str - r15 :: object - r16 :: str - r17 :: int32 - r18 :: bit + r7 :: dict + r8 :: object L0: r0 = builtins :: module r1 = load_address _Py_NoneStruct @@ -2936,23 +3003,11 @@ L1: r4 = PyImport_Import(r3) builtins = r4 :: module L2: - r5 = __main__.globals :: static - r6 = 'Callable' - r7 = PyList_New(1) - r8 = get_element_ptr r7 ob_item :: PyListObject - r9 = load_mem r8 :: ptr* - set_mem r9, r6 :: builtins.object* - keep_alive r7 - r10 = 'typing' - r11 = PyImport_ImportModuleLevelObject(r10, r5, 0, r7, 0) - typing = r11 :: module - r12 = 'typing' - r13 = 'Callable' - r14 = 'Callable' - r15 = CPyImport_ImportFrom(r11, r12, r13, r14) - r16 = 'Callable' - r17 = CPyDict_SetItem(r5, r16, r15) - r18 = r17 >= 0 :: signed + r5 = ('Callable',) + r6 = 'typing' + r7 = __main__.globals :: static + r8 = CPyImport_ImportFromMany(r6, r5, r5, r7) + typing = r8 :: module return 1 [case testAnyAllG] @@ -3413,24 +3468,85 @@ L0: r2 = truncate r0: int32 to builtins.bool return r2 -[case testLocalImportSubmodule] -def f() -> int: +[case testLocalImports] +def root() -> None: + import dataclasses + import enum + +def submodule() -> int: import p.m return p.x [file p/__init__.py] x = 1 [file p/m.py] [out] -def f(): +def root(): r0 :: dict r1, r2 :: object r3 :: bit r4 :: str r5 :: object - r6 :: dict - r7 :: str - r8 :: object - r9 :: str + r6 :: str + r7 :: dict + r8 :: str + r9 :: object + r10 :: int32 + r11 :: bit + r12 :: dict + r13, r14 :: object + r15 :: bit + r16 :: str + r17 :: object + r18 :: str + r19 :: dict + r20 :: str + r21 :: object + r22 :: int32 + r23 :: bit +L0: + r0 = __main__.globals :: static + r1 = dataclasses :: module + r2 = load_address _Py_NoneStruct + r3 = r1 != r2 + if r3 goto L2 else goto L1 :: bool +L1: + r4 = 'dataclasses' + r5 = PyImport_Import(r4) + dataclasses = r5 :: module +L2: + r6 = 'dataclasses' + r7 = PyImport_GetModuleDict() + r8 = 'dataclasses' + r9 = CPyDict_GetItem(r7, r8) + r10 = CPyDict_SetItem(r0, r6, r9) + r11 = r10 >= 0 :: signed + r12 = __main__.globals :: static + r13 = enum :: module + r14 = load_address _Py_NoneStruct + r15 = r13 != r14 + if r15 goto L4 else goto L3 :: bool +L3: + r16 = 'enum' + r17 = PyImport_Import(r16) + enum = r17 :: module +L4: + r18 = 'enum' + r19 = PyImport_GetModuleDict() + r20 = 'enum' + r21 = CPyDict_GetItem(r19, r20) + r22 = CPyDict_SetItem(r12, r18, r21) + r23 = r22 >= 0 :: signed + return 1 +def submodule(): + r0 :: dict + r1, r2 :: object + r3 :: bit + r4 :: str + r5 :: object + r6 :: str + r7 :: dict + r8 :: str + r9 :: object r10 :: int32 r11 :: bit r12 :: dict @@ -3450,11 +3566,11 @@ L1: r5 = PyImport_Import(r4) p.m = r5 :: module L2: - r6 = PyImport_GetModuleDict() - r7 = 'p' - r8 = CPyDict_GetItem(r6, r7) - r9 = 'p' - r10 = CPyDict_SetItem(r0, r9, r8) + r6 = 'p' + r7 = PyImport_GetModuleDict() + r8 = 'p' + r9 = CPyDict_GetItem(r7, r8) + r10 = CPyDict_SetItem(r0, r6, r9) r11 = r10 >= 0 :: signed r12 = PyImport_GetModuleDict() r13 = 'p' diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index 0f98fc69e5f3..0a7076e5f0ad 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -200,84 +200,63 @@ def __top_level__(): r0, r1 :: object r2 :: bit r3 :: str - r4 :: object - r5 :: dict - r6, r7 :: str - r8 :: list - r9, r10, r11 :: ptr - r12 :: str - r13 :: object - r14, r15, r16 :: str - r17 :: object - r18 :: str - r19 :: int32 - r20 :: bit - r21, r22, r23 :: str - r24 :: object - r25 :: str - r26 :: int32 - r27 :: bit - r28 :: dict - r29 :: str - r30 :: list - r31, r32 :: ptr - r33 :: str - r34 :: object - r35, r36, r37 :: str - r38 :: object + r4, r5 :: object + r6 :: str + r7 :: dict + r8, r9 :: object + r10 :: str + r11 :: dict + r12 :: object + r13 :: str + r14 :: dict + r15 :: str + r16, r17 :: object + r18 :: dict + r19 :: str + r20 :: int32 + r21 :: bit + r22 :: object + r23 :: str + r24, r25 :: object + r26 :: bool + r27 :: str + r28 :: tuple + r29 :: int32 + r30 :: bit + r31 :: dict + r32 :: str + r33 :: int32 + r34 :: bit + r35 :: object + r36 :: str + r37, r38 :: object r39 :: str - r40 :: int32 - r41 :: bit - r42 :: str + r40 :: tuple + r41 :: int32 + r42 :: bit r43 :: dict r44 :: str - r45, r46 :: object - r47 :: dict - r48 :: str - r49 :: int32 - r50 :: bit + r45 :: int32 + r46 :: bit + r47, r48 :: object + r49 :: dict + r50 :: str r51 :: object - r52 :: str - r53, r54 :: object - r55 :: bool - r56 :: str - r57 :: tuple - r58 :: int32 - r59 :: bit - r60 :: dict - r61 :: str - r62 :: int32 - r63 :: bit - r64 :: object - r65 :: str - r66, r67 :: object - r68 :: str - r69 :: tuple - r70 :: int32 - r71 :: bit - r72 :: dict - r73 :: str - r74 :: int32 - r75 :: bit - r76, r77 :: object - r78 :: dict - r79 :: str - r80 :: object - r81 :: dict - r82 :: str - r83, r84 :: object - r85 :: tuple - r86 :: str - r87, r88 :: object - r89 :: bool - r90, r91 :: str - r92 :: tuple - r93 :: int32 - r94 :: bit - r95 :: dict - r96 :: str - r97 :: int32 - r98 :: bit + r52 :: dict + r53 :: str + r54, r55 :: object + r56 :: tuple + r57 :: str + r58, r59 :: object + r60 :: bool + r61, r62 :: str + r63 :: tuple + r64 :: int32 + r65 :: bit + r66 :: dict + r67 :: str + r68 :: int32 + r69 :: bit L0: r0 = builtins :: module r1 = load_address _Py_NoneStruct @@ -288,110 +267,76 @@ L1: r4 = PyImport_Import(r3) builtins = r4 :: module L2: - r5 = __main__.globals :: static - r6 = 'TypeVar' - r7 = 'Generic' - r8 = PyList_New(2) - r9 = get_element_ptr r8 ob_item :: PyListObject - r10 = load_mem r9 :: ptr* - set_mem r10, r6 :: builtins.object* - r11 = r10 + WORD_SIZE*1 - set_mem r11, r7 :: builtins.object* - keep_alive r8 - r12 = 'typing' - r13 = PyImport_ImportModuleLevelObject(r12, r5, 0, r8, 0) - typing = r13 :: module - r14 = 'typing' + r5 = ('TypeVar', 'Generic') + r6 = 'typing' + r7 = __main__.globals :: static + r8 = CPyImport_ImportFromMany(r6, r5, r5, r7) + typing = r8 :: module + r9 = ('trait',) + r10 = 'mypy_extensions' + r11 = __main__.globals :: static + r12 = CPyImport_ImportFromMany(r10, r9, r9, r11) + mypy_extensions = r12 :: module + r13 = 'T' + r14 = __main__.globals :: static r15 = 'TypeVar' - r16 = 'TypeVar' - r17 = CPyImport_ImportFrom(r13, r14, r15, r16) - r18 = 'TypeVar' - r19 = CPyDict_SetItem(r5, r18, r17) - r20 = r19 >= 0 :: signed - r21 = 'typing' - r22 = 'Generic' - r23 = 'Generic' - r24 = CPyImport_ImportFrom(r13, r21, r22, r23) - r25 = 'Generic' - r26 = CPyDict_SetItem(r5, r25, r24) - r27 = r26 >= 0 :: signed - r28 = __main__.globals :: static - r29 = 'trait' - r30 = PyList_New(1) - r31 = get_element_ptr r30 ob_item :: PyListObject - r32 = load_mem r31 :: ptr* - set_mem r32, r29 :: builtins.object* - keep_alive r30 - r33 = 'mypy_extensions' - r34 = PyImport_ImportModuleLevelObject(r33, r28, 0, r30, 0) - mypy_extensions = r34 :: module - r35 = 'mypy_extensions' - r36 = 'trait' - r37 = 'trait' - r38 = CPyImport_ImportFrom(r34, r35, r36, r37) - r39 = 'trait' - r40 = CPyDict_SetItem(r28, r39, r38) - r41 = r40 >= 0 :: signed - r42 = 'T' + r16 = CPyDict_GetItem(r14, r15) + r17 = PyObject_CallFunctionObjArgs(r16, r13, 0) + r18 = __main__.globals :: static + r19 = 'T' + r20 = CPyDict_SetItem(r18, r19, r17) + r21 = r20 >= 0 :: signed + r22 = :: object + r23 = '__main__' + r24 = __main__.C_template :: type + r25 = CPyType_FromTemplate(r24, r22, r23) + r26 = C_trait_vtable_setup() + r27 = '__mypyc_attrs__' + r28 = PyTuple_Pack(0) + r29 = PyObject_SetAttr(r25, r27, r28) + r30 = r29 >= 0 :: signed + __main__.C = r25 :: type + r31 = __main__.globals :: static + r32 = 'C' + r33 = CPyDict_SetItem(r31, r32, r25) + r34 = r33 >= 0 :: signed + r35 = :: object + r36 = '__main__' + r37 = __main__.S_template :: type + r38 = CPyType_FromTemplate(r37, r35, r36) + r39 = '__mypyc_attrs__' + r40 = PyTuple_Pack(0) + r41 = PyObject_SetAttr(r38, r39, r40) + r42 = r41 >= 0 :: signed + __main__.S = r38 :: type r43 = __main__.globals :: static - r44 = 'TypeVar' - r45 = CPyDict_GetItem(r43, r44) - r46 = PyObject_CallFunctionObjArgs(r45, r42, 0) - r47 = __main__.globals :: static - r48 = 'T' - r49 = CPyDict_SetItem(r47, r48, r46) - r50 = r49 >= 0 :: signed - r51 = :: object - r52 = '__main__' - r53 = __main__.C_template :: type - r54 = CPyType_FromTemplate(r53, r51, r52) - r55 = C_trait_vtable_setup() - r56 = '__mypyc_attrs__' - r57 = PyTuple_Pack(0) - r58 = PyObject_SetAttr(r54, r56, r57) - r59 = r58 >= 0 :: signed - __main__.C = r54 :: type - r60 = __main__.globals :: static - r61 = 'C' - r62 = CPyDict_SetItem(r60, r61, r54) - r63 = r62 >= 0 :: signed - r64 = :: object - r65 = '__main__' - r66 = __main__.S_template :: type - r67 = CPyType_FromTemplate(r66, r64, r65) - r68 = '__mypyc_attrs__' - r69 = PyTuple_Pack(0) - r70 = PyObject_SetAttr(r67, r68, r69) - r71 = r70 >= 0 :: signed - __main__.S = r67 :: type - r72 = __main__.globals :: static - r73 = 'S' - r74 = CPyDict_SetItem(r72, r73, r67) - r75 = r74 >= 0 :: signed - r76 = __main__.C :: type - r77 = __main__.S :: type - r78 = __main__.globals :: static - r79 = 'Generic' - r80 = CPyDict_GetItem(r78, r79) - r81 = __main__.globals :: static - r82 = 'T' - r83 = CPyDict_GetItem(r81, r82) - r84 = PyObject_GetItem(r80, r83) - r85 = PyTuple_Pack(3, r76, r77, r84) - r86 = '__main__' - r87 = __main__.D_template :: type - r88 = CPyType_FromTemplate(r87, r85, r86) - r89 = D_trait_vtable_setup() - r90 = '__mypyc_attrs__' - r91 = '__dict__' - r92 = PyTuple_Pack(1, r91) - r93 = PyObject_SetAttr(r88, r90, r92) - r94 = r93 >= 0 :: signed - __main__.D = r88 :: type - r95 = __main__.globals :: static - r96 = 'D' - r97 = CPyDict_SetItem(r95, r96, r88) - r98 = r97 >= 0 :: signed + r44 = 'S' + r45 = CPyDict_SetItem(r43, r44, r38) + r46 = r45 >= 0 :: signed + r47 = __main__.C :: type + r48 = __main__.S :: type + r49 = __main__.globals :: static + r50 = 'Generic' + r51 = CPyDict_GetItem(r49, r50) + r52 = __main__.globals :: static + r53 = 'T' + r54 = CPyDict_GetItem(r52, r53) + r55 = PyObject_GetItem(r51, r54) + r56 = PyTuple_Pack(3, r47, r48, r55) + r57 = '__main__' + r58 = __main__.D_template :: type + r59 = CPyType_FromTemplate(r58, r56, r57) + r60 = D_trait_vtable_setup() + r61 = '__mypyc_attrs__' + r62 = '__dict__' + r63 = PyTuple_Pack(1, r62) + r64 = PyObject_SetAttr(r59, r61, r63) + r65 = r64 >= 0 :: signed + __main__.D = r59 :: type + r66 = __main__.globals :: static + r67 = 'D' + r68 = CPyDict_SetItem(r66, r67, r59) + r69 = r68 >= 0 :: signed return 1 [case testIsInstance] diff --git a/mypyc/test-data/run-imports.test b/mypyc/test-data/run-imports.test index c6d5bdb3d864..c5839d57820e 100644 --- a/mypyc/test-data/run-imports.test +++ b/mypyc/test-data/run-imports.test @@ -2,6 +2,8 @@ [case testImports] import testmodule +import pkg2.mod +import pkg2.mod2 as mm2 def f(x: int) -> int: return testmodule.factorial(5) @@ -13,15 +15,21 @@ def g(x: int) -> int: def test_import_basics() -> None: assert f(5) == 120 assert g(5) == 5 + assert "pkg2.mod" not in globals(), "the root module should be in globals!" + assert pkg2.mod.x == 1 + assert "mod2" not in globals(), "pkg2.mod2 is aliased to mm2!" + assert mm2.y == 2 def test_import_submodule_within_function() -> None: import pkg.mod assert pkg.x == 1 assert pkg.mod.y == 2 + assert "pkg.mod" not in globals(), "the root module should be in globals!" def test_import_as_submodule_within_function() -> None: import pkg.mod as mm assert mm.y == 2 + assert "pkg.mod" not in globals(), "the root module should be in globals!" # TODO: Don't add local imports to globals() # @@ -57,6 +65,11 @@ def foo(x: int) -> int: x = 1 [file pkg/mod.py] y = 2 +[file pkg2/__init__.py] +[file pkg2/mod.py] +x = 1 +[file pkg2/mod2.py] +y = 2 [file nob.py] z = 3 @@ -192,3 +205,61 @@ a.x = 10 x = 20 [file driver.py] import native + +[case testLazyImport] +import shared + +def do_import() -> None: + import a + +assert shared.counter == 0 +do_import() +assert shared.counter == 1 + +[file a.py] +import shared +shared.counter += 1 + +[file shared.py] +counter = 0 + +[case testDelayedImport] +import a +print("inbetween") +import b + +[file a.py] +print("first") + +[file b.py] +print("last") + +[out] +first +inbetween +last + +[case testImportErrorLineNumber] +try: + import enum + import dataclasses, missing # type: ignore[import] +except ImportError as e: + line = e.__traceback__.tb_lineno # type: ignore[attr-defined] + assert line == 3, f"traceback's line number is {line}, expected 3" + +[case testImportGroupIsolation] +def func() -> None: + import second + +import first +func() + +[file first.py] +print("first") + +[file second.py] +print("second") + +[out] +first +second diff --git a/mypyc/test-data/run-multimodule.test b/mypyc/test-data/run-multimodule.test index 418af66ba060..70c73dc2088b 100644 --- a/mypyc/test-data/run-multimodule.test +++ b/mypyc/test-data/run-multimodule.test @@ -11,21 +11,23 @@ -- about how this is specified (e.g. .2 file name suffixes). [case testMultiModulePackage] -from p.other import g +from p.other import g, _i as i def f(x: int) -> int: from p.other import h - return h(g(x + 1)) + return i(h(g(x + 1))) [file p/__init__.py] [file p/other.py] def g(x: int) -> int: return x + 2 def h(x: int) -> int: return x + 1 +def _i(x: int) -> int: + return x + 3 [file driver.py] import native from native import f from p.other import g -assert f(3) == 7 +assert f(3) == 10 assert g(2) == 4 try: f(1.1) diff --git a/mypyc/test/test_emit.py b/mypyc/test/test_emit.py index 7351cd7fb13e..54bf4eef3c74 100644 --- a/mypyc/test/test_emit.py +++ b/mypyc/test/test_emit.py @@ -22,6 +22,16 @@ def test_reg(self) -> None: emitter = Emitter(self.context, names) assert emitter.reg(self.n) == "cpy_r_n" + def test_object_annotation(self) -> None: + emitter = Emitter(self.context, {}) + assert emitter.object_annotation("hello, world", "line;") == " /* 'hello, world' */" + assert ( + emitter.object_annotation(list(range(30)), "line;") + == """\ + /* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29] */""" + ) + def test_emit_line(self) -> None: emitter = Emitter(self.context, {}) emitter.emit_line("line;") @@ -29,3 +39,13 @@ def test_emit_line(self) -> None: emitter.emit_line("f();") emitter.emit_line("}") assert emitter.fragments == ["line;\n", "a {\n", " f();\n", "}\n"] + emitter = Emitter(self.context, {}) + emitter.emit_line("CPyStatics[0];", ann="hello, world") + emitter.emit_line("CPyStatics[1];", ann=list(range(30))) + assert emitter.fragments[0] == "CPyStatics[0]; /* 'hello, world' */\n" + assert ( + emitter.fragments[1] + == """\ +CPyStatics[1]; /* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29] */\n""" + )