diff --git a/mypy/semanal.py b/mypy/semanal.py index d6a025ea84ce..d7847ca30118 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1405,23 +1405,14 @@ def visit_import_from(self, imp: ImportFrom) -> None: # If it is still not resolved, check for a module level __getattr__ if (module and not node and (module.is_stub or self.options.python_version >= (3, 7)) and '__getattr__' in module.names): - getattr_defn = module.names['__getattr__'] - if isinstance(getattr_defn.node, (FuncDef, Var)): - if isinstance(getattr_defn.node.type, CallableType): - typ = getattr_defn.node.type.ret_type - else: - typ = AnyType(TypeOfAny.from_error) - if as_id: - name = as_id - else: - name = id - ast_node = Var(name, type=typ) - if self.type: - ast_node._fullname = self.type.fullname() + "." + name - else: - ast_node._fullname = self.qualified_name(name) - symbol = SymbolTableNode(GDEF, ast_node) - self.add_symbol(name, symbol, imp) + name = as_id if as_id else id + if self.type: + fullname = self.type.fullname() + "." + name + else: + fullname = self.qualified_name(name) + gvar = self.create_getattr_var(module.names['__getattr__'], name, fullname) + if gvar: + self.add_symbol(name, gvar, imp) continue if node and node.kind != UNBOUND_IMPORTED and not node.module_hidden: if not node: @@ -3087,6 +3078,12 @@ def lookup_qualified(self, name: str, ctx: Context, n = names.get(parts[i], None) if n and isinstance(n.node, ImportedName): n = self.dereference_module_cross_ref(n) + elif '__getattr__' in names: + gvar = self.create_getattr_var(names['__getattr__'], + parts[i], parts[i]) + if gvar: + names[name] = gvar + n = gvar # TODO: What if node is Var or FuncDef? # Currently, missing these cases results in controversial behavior, when # lookup_qualified(x.y.z) returns Var(x). @@ -3102,6 +3099,22 @@ def lookup_qualified(self, name: str, ctx: Context, return n return None + def create_getattr_var(self, getattr_defn: SymbolTableNode, + name: str, fullname: str) -> Optional[SymbolTableNode]: + """Create a dummy global symbol using __getattr__ return type. + + If not possible, return None. + """ + if isinstance(getattr_defn.node, (FuncDef, Var)): + if isinstance(getattr_defn.node.type, CallableType): + typ = getattr_defn.node.type.ret_type + else: + typ = AnyType(TypeOfAny.from_error) + v = Var(name, type=typ) + v._fullname = fullname + return SymbolTableNode(GDEF, v) + return None + def rebind_symbol_table_node(self, n: SymbolTableNode) -> Optional[SymbolTableNode]: """If node refers to old version of module, return reference to new version. diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 7ae23b2f86f0..58bdae746cb2 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4451,3 +4451,21 @@ class C(B): def meth(cls, x: int) -> int: ... [builtins fixtures/classmethod.pyi] [out] + +[case testGetAttrImportAnnotation] +import a +x: a.A +[file a.pyi] +from typing import Any +def __getattr__(attr: str) -> Any: ... +[builtins fixtures/module.pyi] +[out] + +[case testGetAttrImportBaseClass] +import a +class B(a.A): ... +[file a.pyi] +from typing import Any +def __getattr__(attr: str) -> Any: ... +[builtins fixtures/module.pyi] +[out]