Skip to content

Commit 2a8c180

Browse files
committed
WIP 3
1 parent 565e960 commit 2a8c180

File tree

5 files changed

+182
-8
lines changed

5 files changed

+182
-8
lines changed

mypy/build.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,7 +1420,8 @@ def type_check(self) -> None:
14201420
if manager.options.semantic_analysis_only:
14211421
return
14221422
with self.wrap_context():
1423-
manager.type_checker.type_map = {}
1423+
manager.type_checker.module_type_map = {}
1424+
manager.type_checker.module_refs = set()
14241425
manager.type_checker.visit_file(self.tree, self.xpath, set())
14251426
# encountered = manager.type_checker.get_encountered()
14261427
# print(manager.type_checker.type_map)
@@ -1431,15 +1432,25 @@ def type_check(self) -> None:
14311432
manager.report_file(self.tree)
14321433

14331434
def detect_indirection(self) -> None:
1435+
# print(self.id)
14341436
self.manager.indirection_detector.modules = set()
14351437
# self.manager.indirection_detector.visit_mypy_file(self.tree)
1436-
for item, val in self.manager.type_checker.type_map.items():
1437-
self.manager.indirection_detector._visit(item)
1438+
# count = 0
1439+
vals = self.manager.type_checker.module_type_map
1440+
self.manager.indirection_detector.type_visitor.count = 0
1441+
for item, val in self.manager.type_checker.module_type_map.items():
1442+
# self.manager.indirection_detector._visit(item)
14381443
self.manager.indirection_detector._visit_types(val)
14391444

1440-
encountered = self.manager.indirection_detector.modules
1445+
module_refs = self.manager.type_checker.module_refs
1446+
encountered = self.manager.indirection_detector.modules | module_refs
14411447
valid = self.valid_references()
14421448
extra = encountered - valid
1449+
# print(' found', encountered)
1450+
# print(' valid', valid)
1451+
# print(' extra', extra)
1452+
# print(' # checked', count)
1453+
# print()
14431454
for dep in extra:
14441455
self.dependencies.append(dep)
14451456
self.priorities[dep] = PRI_INDIRECT

mypy/checker.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class TypeChecker(NodeVisitor[Type]):
8686
msg = None # type: MessageBuilder
8787
# Types of type checked nodes
8888
type_map = None # type: Dict[Node, Type]
89+
module_type_map = None # type: Dict[Node, Type]
8990

9091
# Helper for managing conditional types
9192
binder = None # type: ConditionalTypeBinder
@@ -122,6 +123,8 @@ class TypeChecker(NodeVisitor[Type]):
122123
# not be modified.
123124
valid_module_references = None # type: Optional[Set[str]]
124125

126+
module_refs = None # type: Set[str]
127+
125128
def __init__(self, errors: Errors, modules: Dict[str, MypyFile], options: Options) -> None:
126129
"""Construct a type checker.
127130
@@ -132,6 +135,7 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile], options: Option
132135
self.options = options
133136
self.msg = MessageBuilder(errors, modules)
134137
self.type_map = {}
138+
self.module_type_map = {}
135139
self.binder = ConditionalTypeBinder()
136140
self.expr_checker = mypy.checkexpr.ExpressionChecker(self, self.msg)
137141
self.return_types = []
@@ -144,6 +148,7 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile], options: Option
144148
self.pass_num = 0
145149
self.current_node_deferred = False
146150
self.encountered = set() # type: Set[str]
151+
self.module_refs = set()
147152

148153
def get_encountered(self) -> Set[str]:
149154
return self.encountered
@@ -2197,6 +2202,7 @@ def check_type_equivalency(self, t1: Type, t2: Type, node: Context,
21972202
def store_type(self, node: Node, typ: Type) -> None:
21982203
"""Store the type of a node in the type map."""
21992204
self.type_map[node] = typ
2205+
self.module_type_map[node] = typ
22002206

22012207
def typing_mode_none(self) -> bool:
22022208
if self.is_dynamic_function() and not self.options.check_untyped_defs:

mypy/checkexpr.py

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Expression type checker. This file is conceptually part of TypeChecker."""
22

3-
from typing import cast, Dict, Set, List, Tuple, Callable, Union, Optional
3+
from typing import cast, Dict, Set, List, Iterable, Tuple, Callable, Union, Optional
44

55
from mypy.types import (
66
Type, AnyType, CallableType, Overloaded, NoneTyp, Void, TypeVarDef,
@@ -16,7 +16,7 @@
1616
ListComprehension, GeneratorExpr, SetExpr, MypyFile, Decorator,
1717
ConditionalExpr, ComparisonExpr, TempNode, SetComprehension,
1818
DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr,
19-
TypeAliasExpr, BackquoteExpr, ARG_POS, ARG_NAMED, ARG_STAR2, MODULE_REF,
19+
TypeAliasExpr, BackquoteExpr, ARG_POS, ARG_NAMED, ARG_STAR2, MODULE_REF, node_kinds,
2020
)
2121
from mypy.nodes import function_type
2222
from mypy import nodes
@@ -45,6 +45,45 @@
4545
None]
4646

4747

48+
def split_module_names(mod_name: str) -> Iterable[str]:
49+
yield mod_name
50+
while '.' in mod_name:
51+
mod_name = mod_name.rsplit('.', 1)[0]
52+
yield mod_name
53+
54+
55+
def extract_module_names(symbol_name: str) -> Iterable[str]:
56+
if symbol_name is None or '.' not in symbol_name:
57+
return
58+
# raise RuntimeError("{} is not a qualified symbol name".format(symbol_name))
59+
else:
60+
while '.' in symbol_name:
61+
symbol_name = symbol_name.rsplit('.', 1)[0]
62+
yield symbol_name
63+
64+
65+
def extract_refexpr_names(expr: RefExpr) -> Set[str]:
66+
output = set() # type: Set[str]
67+
while expr.kind == MODULE_REF or expr.fullname is not None:
68+
if expr.kind == MODULE_REF:
69+
output.add(expr.fullname)
70+
elif expr.fullname is not None and '.' in expr.fullname:
71+
output.add(expr.fullname.rsplit('.', 1)[0])
72+
73+
if isinstance(expr, NameExpr):
74+
if expr.info is not None:
75+
output.update(split_module_names(expr.info.module_name))
76+
break
77+
elif isinstance(expr, MemberExpr):
78+
if isinstance(expr.expr, RefExpr):
79+
expr = expr.expr
80+
else:
81+
break
82+
else:
83+
raise AssertionError("Unknown RefExpr subclass: {}".format(type(expr)))
84+
return output
85+
86+
4887
class Finished(Exception):
4988
"""Raised if we can terminate overload argument check early (no match)."""
5089

@@ -70,6 +109,12 @@ def __init__(self,
70109
self.msg = msg
71110
self.strfrm_checker = StringFormatterChecker(self, self.chk, self.msg)
72111

112+
def _visit_typeinfo(self, info: nodes.TypeInfo) -> None:
113+
if info is not None:
114+
# self._add_symbol(info.fullname())
115+
self.chk.module_refs.update(split_module_names(info.module_name))
116+
# self._visit_symboltable(info.names)
117+
73118
def visit_name_expr(self, e: NameExpr) -> Type:
74119
"""Type check a name expression.
75120
@@ -78,8 +123,17 @@ def visit_name_expr(self, e: NameExpr) -> Type:
78123
# if e.kind == MODULE_REF and e.fullname not in self.chk.valid_module_references:
79124
# self.chk.encountered.add(e.fullname)
80125
# self.chk.msg.indirect_import(e.fullname, e)
126+
# if e.kind == MODULE_REF:
127+
# self.chk.module_refs.add(e.fullname)
128+
# elif e.fullname is not None: # and '.' in expr.fullname:
129+
# self.chk.module_refs.update(extract_module_names(e.fullname))
130+
# self._visit_typeinfo(e.info)'''
81131
result = self.analyze_ref_expr(e)
82-
return self.chk.narrow_type_from_binder(e, result)
132+
out = self.chk.narrow_type_from_binder(e, result)
133+
134+
self.chk.module_refs.update(extract_refexpr_names(e))
135+
136+
return out
83137

84138
def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type:
85139
result = None # type: Type
@@ -867,8 +921,17 @@ def visit_member_expr(self, e: MemberExpr) -> Type:
867921
# if base.fullname not in self.chk.valid_module_references:
868922
# pass
869923
# self.chk.msg.indirect_import(base.fullname, e)
924+
# if isinstance(e, nodes.RefExpr) and e.kind == MODULE_REF:
925+
# print('A', e, e.fullname, e.name)
926+
# self.chk.module_refs.add(e.fullname)
927+
# else:
928+
# print('B', e, e.fullname, e.name)
929+
# self.chk.module_refs.update(extract_module_names(e.fullname))
870930
result = self.analyze_ordinary_member_access(e, False)
871931
out = self.chk.narrow_type_from_binder(e, result)
932+
933+
self.chk.module_refs.update(extract_refexpr_names(e))
934+
872935
return out
873936

874937
def analyze_ordinary_member_access(self, e: MemberExpr,

mypy/test/testcheck.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ def run_case(self, testcase: DataDrivenTestCase) -> None:
8888
# We briefly sleep to make sure file timestamps are distinct.
8989
self.clear_cache()
9090
self.run_case_once(testcase, 1)
91-
print('\n----\n')
9291
self.run_case_once(testcase, 2)
9392
elif optional:
9493
try:

test-data/unit/check-incremental.test

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,68 @@ const = 3
304304
main:1: note: In module imported here:
305305
tmp/mod1.py:3: error: "module" has no attribute "mod4"
306306

307+
[case testIncrementalLongBrokenCascade]
308+
import mod1
309+
310+
[file mod1.py]
311+
import mod2
312+
def accept_int(a: int) -> int: return a
313+
accept_int(mod2.mod3.mod4.mod5.mod6.mod7.const)
314+
315+
[file mod2.py]
316+
import mod3
317+
318+
[file mod3.py]
319+
import mod4
320+
321+
[file mod4.py]
322+
import mod5
323+
324+
[file mod5.py]
325+
import mod6
326+
327+
[file mod6.py]
328+
import mod7
329+
330+
[file mod7.py]
331+
const = 3
332+
333+
[file mod6.py.next]
334+
# Import to mod7 is gone!
335+
336+
[rechecked mod1, mod5, mod6]
337+
[stale mod6]
338+
[builtins fixtures/module.pyi]
339+
[out]
340+
main:1: note: In module imported here:
341+
tmp/mod1.py:3: error: "module" has no attribute "mod7"
342+
343+
[case testIncrementalNestedBrokenCascade]
344+
import mod1
345+
346+
[file mod1.py]
347+
import mod2
348+
def accept_int(a: int) -> int: return a
349+
accept_int(mod2.mod3.mod4.const)
350+
351+
[file mod2/__init__.py]
352+
import mod2.mod3 as mod3
353+
354+
[file mod2/mod3/__init__.py]
355+
import mod2.mod3.mod4 as mod4
356+
357+
[file mod2/mod3/mod4.py]
358+
const = 3
359+
360+
[file mod2/mod3/__init__.py.next]
361+
# Import is gone!
362+
363+
[rechecked mod1, mod2, mod2.mod3]
364+
[stale mod2.mod3]
365+
[out]
366+
main:1: note: In module imported here:
367+
tmp/mod1.py:3: error: "module" has no attribute "mod4"
368+
307369
[case testIncrementalRemoteChange]
308370
import mod1
309371

@@ -960,3 +1022,36 @@ foo(3)
9601022
[out]
9611023
main:1: note: In module imported here:
9621024
tmp/client.py:4: error: Argument 1 to "foo" has incompatible type "int"; expected "str"
1025+
1026+
[case testIncrementalChangingAlias]
1027+
import m1, m2, m3, m4, m5
1028+
1029+
[file m1.py]
1030+
from m2 import A
1031+
def accepts_int(x: int) -> None: pass
1032+
accepts_int(A())
1033+
1034+
[file m2.py]
1035+
from m3 import A
1036+
1037+
[file m3.py]
1038+
from m4 import B
1039+
A = B
1040+
1041+
[file m3.py.next]
1042+
from m5 import C
1043+
A = C
1044+
1045+
[file m4.py]
1046+
def B() -> int:
1047+
return 42
1048+
1049+
[file m5.py]
1050+
def C() -> str:
1051+
return "hello"
1052+
1053+
[rechecked m1, m2, m3]
1054+
[stale m3]
1055+
[out]
1056+
main:1: note: In module imported here:
1057+
tmp/m1.py:3: error: Argument 1 to "accepts_int" has incompatible type "str"; expected "int"

0 commit comments

Comments
 (0)