Skip to content

Commit 245108c

Browse files
gvanrossumddfisher
authored andcommitted
Support for import inside class (#2077)
Tentatively fix #1839.
1 parent 5e822a8 commit 245108c

File tree

3 files changed

+32
-1
lines changed

3 files changed

+32
-1
lines changed

mypy/checkmember.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Overloaded, TypeVarType, TypeTranslator, UnionType, PartialType,
88
DeletedType, NoneTyp, TypeType
99
)
10-
from mypy.nodes import TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context
10+
from mypy.nodes import TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context, MypyFile
1111
from mypy.nodes import ARG_POS, ARG_STAR, ARG_STAR2, OpExpr, ComparisonExpr
1212
from mypy.nodes import function_type, Decorator, OverloadedFuncDef
1313
from mypy.messages import MessageBuilder
@@ -351,6 +351,10 @@ def analyze_class_attribute_access(itype: Instance,
351351
if isinstance(node.node, TypeInfo):
352352
return type_object_type(node.node, builtin_type)
353353

354+
if isinstance(node.node, MypyFile):
355+
# Reference to a module object.
356+
return builtin_type('builtins.module')
357+
354358
if is_decorated:
355359
# TODO: Return type of decorated function. This is quick hack to work around #998.
356360
return AnyType()

mypy/semanal.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,6 +2133,9 @@ def visit_member_expr(self, expr: MemberExpr) -> None:
21332133
base.accept(self)
21342134
# Bind references to module attributes.
21352135
if isinstance(base, RefExpr) and base.kind == MODULE_REF:
2136+
# This branch handles the case foo.bar where foo is a module.
2137+
# In this case base.node is the module's MypyFile and we look up
2138+
# bar in its namespace. This must be done for all types of bar.
21362139
file = cast(MypyFile, base.node)
21372140
n = file.names.get(expr.name, None) if file is not None else None
21382141
if n:
@@ -2154,6 +2157,21 @@ def visit_member_expr(self, expr: MemberExpr) -> None:
21542157
if full_name in obsolete_name_mapping:
21552158
self.fail("Module has no attribute %r (it's now called %r)" % (
21562159
expr.name, obsolete_name_mapping[full_name]), expr)
2160+
elif isinstance(base, RefExpr) and isinstance(base.node, TypeInfo):
2161+
# This branch handles the case C.bar where C is a class
2162+
# and bar is a module resulting from `import bar` inside
2163+
# class C. Here base.node is a TypeInfo, and again we
2164+
# look up the name in its namespace. This is done only
2165+
# when bar is a module; other things (e.g. methods)
2166+
# are handled by other code in checkmember.
2167+
n = base.node.names.get(expr.name)
2168+
if n is not None and n.kind == MODULE_REF:
2169+
n = self.normalize_type_alias(n, expr)
2170+
if not n:
2171+
return
2172+
expr.kind = n.kind
2173+
expr.fullname = n.fullname
2174+
expr.node = n.node
21572175

21582176
def visit_op_expr(self, expr: OpExpr) -> None:
21592177
expr.left.accept(self)

test-data/unit/check-modules.test

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,15 @@ from a import x
974974
x = 0
975975
[out]
976976

977+
[case testImportInClass]
978+
class C:
979+
import foo
980+
reveal_type(C.foo.bar) # E: Revealed type is 'builtins.int'
981+
[file foo.py]
982+
bar = 0
983+
[builtins fixtures/module.pyi]
984+
[out]
985+
977986

978987
-- Test stability under import cycles
979988
-- ----------------------------------

0 commit comments

Comments
 (0)