|
29 | 29 | TypeInfo, |
30 | 30 | Var, |
31 | 31 | ) |
32 | | -from mypy.types import CallableType, get_proper_type |
| 32 | +from mypy.types import CallableType, Type, UnboundType, get_proper_type |
33 | 33 | from mypyc.common import LAMBDA_NAME, PROPSET_PREFIX, SELF_NAME |
34 | 34 | from mypyc.ir.class_ir import ClassIR, NonExtClassInfo |
35 | 35 | from mypyc.ir.func_ir import ( |
@@ -802,15 +802,37 @@ def get_func_target(builder: IRBuilder, fdef: FuncDef) -> AssignmentTarget: |
802 | 802 | return builder.add_local_reg(fdef, object_rprimitive) |
803 | 803 |
|
804 | 804 |
|
805 | | -def load_type(builder: IRBuilder, typ: TypeInfo, line: int) -> Value: |
| 805 | +def load_type(builder: IRBuilder, typ: TypeInfo, unbounded_type: Type | None, line: int) -> Value: |
| 806 | + # typ.fullname contains the module where the class object was defined. However, it is possible that the class |
| 807 | + # object's module was not imported in the file currently being compiled. So, we use unbounded_type.name (if provided |
| 808 | + # by caller) to load the class object through one of the imported modules. |
| 809 | + # Example: for `json.JSONDecoder`, typ.fullname is `json.decoder.JSONDecoder` but the Python file may import `json` |
| 810 | + # not `json.decoder`. |
| 811 | + # Another corner case: The Python file being compiled imports mod1 and has a type hint `mod1.OuterClass.InnerClass`. |
| 812 | + # But, mod1/__init__.py might import OuterClass like this: `from mod2.mod3 import OuterClass`. In this case, |
| 813 | + # typ.fullname is `mod2.mod3.OuterClass.InnerClass` and `unbounded_type.name` is `mod1.OuterClass.InnerClass`. So, |
| 814 | + # we must use unbounded_type.name to load the class object. |
| 815 | + # See issue mypy/mypy#1087. |
| 816 | + load_attr_path = ( |
| 817 | + unbounded_type.name if isinstance(unbounded_type, UnboundType) else typ.fullname |
| 818 | + ).removesuffix(f".{typ.name}") |
806 | 819 | if typ in builder.mapper.type_to_ir: |
807 | 820 | class_ir = builder.mapper.type_to_ir[typ] |
808 | 821 | class_obj = builder.builder.get_native_type(class_ir) |
809 | 822 | elif typ.fullname in builtin_names: |
810 | 823 | builtin_addr_type, src = builtin_names[typ.fullname] |
811 | 824 | class_obj = builder.add(LoadAddress(builtin_addr_type, src, line)) |
812 | | - elif typ.module_name in builder.imports: |
813 | | - loaded_module = builder.load_module(typ.module_name) |
| 825 | + # This elif-condition finds the longest import that matches the load_attr_path. |
| 826 | + elif module_name := max((i for i in builder.imports if load_attr_path.startswith(i)), key=len): |
| 827 | + # Load the imported module. |
| 828 | + loaded_module = builder.load_module(module_name) |
| 829 | + # Recursively load attributes of the imported module. These may be submodules, classes or any other object. |
| 830 | + for attr in ( |
| 831 | + load_attr_path.removeprefix(f"{module_name}.").split(".") |
| 832 | + if load_attr_path != module_name |
| 833 | + else [] |
| 834 | + ): |
| 835 | + loaded_module = builder.py_get_attr(loaded_module, attr, line) |
814 | 836 | class_obj = builder.builder.get_attr( |
815 | 837 | loaded_module, typ.name, object_rprimitive, line, borrow=False |
816 | 838 | ) |
@@ -1039,7 +1061,7 @@ def maybe_insert_into_registry_dict(builder: IRBuilder, fitem: FuncDef) -> None: |
1039 | 1061 | ) |
1040 | 1062 | registry = load_singledispatch_registry(builder, dispatch_func_obj, line) |
1041 | 1063 | for typ in types: |
1042 | | - loaded_type = load_type(builder, typ, line) |
| 1064 | + loaded_type = load_type(builder, typ, None, line) |
1043 | 1065 | builder.primitive_op(dict_set_item_op, [registry, loaded_type, to_insert], line) |
1044 | 1066 | dispatch_cache = builder.builder.get_attr( |
1045 | 1067 | dispatch_func_obj, "dispatch_cache", dict_rprimitive, line |
|
0 commit comments