Skip to content

Commit d91efd4

Browse files
authored
Allow __getattr__ during semantic analysis (#5295)
Previously module `__getattr__` was used during type checking (runtime context). But it was only partially allowed in semantic analysis (after `from a import A`, `A` can be used in annotations/base classes). This PR allows the same support for `a.A`, i.e. it can be used in annotations and base classes (provided `__getattr__` has a suitable return type `Any`).
1 parent a12b6b8 commit d91efd4

File tree

2 files changed

+48
-17
lines changed

2 files changed

+48
-17
lines changed

mypy/semanal.py

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1405,23 +1405,14 @@ def visit_import_from(self, imp: ImportFrom) -> None:
14051405
# If it is still not resolved, check for a module level __getattr__
14061406
if (module and not node and (module.is_stub or self.options.python_version >= (3, 7))
14071407
and '__getattr__' in module.names):
1408-
getattr_defn = module.names['__getattr__']
1409-
if isinstance(getattr_defn.node, (FuncDef, Var)):
1410-
if isinstance(getattr_defn.node.type, CallableType):
1411-
typ = getattr_defn.node.type.ret_type
1412-
else:
1413-
typ = AnyType(TypeOfAny.from_error)
1414-
if as_id:
1415-
name = as_id
1416-
else:
1417-
name = id
1418-
ast_node = Var(name, type=typ)
1419-
if self.type:
1420-
ast_node._fullname = self.type.fullname() + "." + name
1421-
else:
1422-
ast_node._fullname = self.qualified_name(name)
1423-
symbol = SymbolTableNode(GDEF, ast_node)
1424-
self.add_symbol(name, symbol, imp)
1408+
name = as_id if as_id else id
1409+
if self.type:
1410+
fullname = self.type.fullname() + "." + name
1411+
else:
1412+
fullname = self.qualified_name(name)
1413+
gvar = self.create_getattr_var(module.names['__getattr__'], name, fullname)
1414+
if gvar:
1415+
self.add_symbol(name, gvar, imp)
14251416
continue
14261417
if node and node.kind != UNBOUND_IMPORTED and not node.module_hidden:
14271418
if not node:
@@ -3087,6 +3078,12 @@ def lookup_qualified(self, name: str, ctx: Context,
30873078
n = names.get(parts[i], None)
30883079
if n and isinstance(n.node, ImportedName):
30893080
n = self.dereference_module_cross_ref(n)
3081+
elif '__getattr__' in names:
3082+
gvar = self.create_getattr_var(names['__getattr__'],
3083+
parts[i], parts[i])
3084+
if gvar:
3085+
names[name] = gvar
3086+
n = gvar
30903087
# TODO: What if node is Var or FuncDef?
30913088
# Currently, missing these cases results in controversial behavior, when
30923089
# lookup_qualified(x.y.z) returns Var(x).
@@ -3102,6 +3099,22 @@ def lookup_qualified(self, name: str, ctx: Context,
31023099
return n
31033100
return None
31043101

3102+
def create_getattr_var(self, getattr_defn: SymbolTableNode,
3103+
name: str, fullname: str) -> Optional[SymbolTableNode]:
3104+
"""Create a dummy global symbol using __getattr__ return type.
3105+
3106+
If not possible, return None.
3107+
"""
3108+
if isinstance(getattr_defn.node, (FuncDef, Var)):
3109+
if isinstance(getattr_defn.node.type, CallableType):
3110+
typ = getattr_defn.node.type.ret_type
3111+
else:
3112+
typ = AnyType(TypeOfAny.from_error)
3113+
v = Var(name, type=typ)
3114+
v._fullname = fullname
3115+
return SymbolTableNode(GDEF, v)
3116+
return None
3117+
31053118
def rebind_symbol_table_node(self, n: SymbolTableNode) -> Optional[SymbolTableNode]:
31063119
"""If node refers to old version of module, return reference to new version.
31073120

test-data/unit/check-classes.test

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4451,3 +4451,21 @@ class C(B):
44514451
def meth(cls, x: int) -> int: ...
44524452
[builtins fixtures/classmethod.pyi]
44534453
[out]
4454+
4455+
[case testGetAttrImportAnnotation]
4456+
import a
4457+
x: a.A
4458+
[file a.pyi]
4459+
from typing import Any
4460+
def __getattr__(attr: str) -> Any: ...
4461+
[builtins fixtures/module.pyi]
4462+
[out]
4463+
4464+
[case testGetAttrImportBaseClass]
4465+
import a
4466+
class B(a.A): ...
4467+
[file a.pyi]
4468+
from typing import Any
4469+
def __getattr__(attr: str) -> Any: ...
4470+
[builtins fixtures/module.pyi]
4471+
[out]

0 commit comments

Comments
 (0)