Skip to content

Commit

Permalink
Fix crash on missing indirect dependencies (#13847)
Browse files Browse the repository at this point in the history
Fixes #13825

We add also types encountered in locally defined symbols, not just
expressions. I also added base-classes/metaclass etc for
`TypeInfo`s, as they also cause crashes.
  • Loading branch information
ilevkivskyi committed Oct 10, 2022
1 parent 2f08e40 commit 1eaf4c7
Show file tree
Hide file tree
Showing 2 changed files with 224 additions and 6 deletions.
26 changes: 20 additions & 6 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
from mypy.errors import CompileError, ErrorInfo, Errors, report_internal_error
from mypy.indirection import TypeIndirectionVisitor
from mypy.messages import MessageBuilder
from mypy.nodes import Import, ImportAll, ImportBase, ImportFrom, MypyFile, SymbolTable
from mypy.nodes import Import, ImportAll, ImportBase, ImportFrom, MypyFile, SymbolTable, TypeInfo
from mypy.partially_defined import PartiallyDefinedVariableVisitor
from mypy.semanal import SemanticAnalyzer
from mypy.semanal_pass1 import SemanticAnalyzerPreAnalysis
Expand Down Expand Up @@ -2363,7 +2363,24 @@ def finish_passes(self) -> None:

# We should always patch indirect dependencies, even in full (non-incremental) builds,
# because the cache still may be written, and it must be correct.
self._patch_indirect_dependencies(self.type_checker().module_refs, self.type_map())
# TODO: find a more robust way to traverse *all* relevant types?
expr_types = set(self.type_map().values())
symbol_types = set()
for _, sym, _ in self.tree.local_definitions():
if sym.type is not None:
symbol_types.add(sym.type)
if isinstance(sym.node, TypeInfo):
# TypeInfo symbols have some extra relevant types.
symbol_types.update(sym.node.bases)
if sym.node.metaclass_type:
symbol_types.add(sym.node.metaclass_type)
if sym.node.typeddict_type:
symbol_types.add(sym.node.typeddict_type)
if sym.node.tuple_type:
symbol_types.add(sym.node.tuple_type)
self._patch_indirect_dependencies(
self.type_checker().module_refs, expr_types | symbol_types
)

if self.options.dump_inference_stats:
dump_type_stats(
Expand All @@ -2386,10 +2403,7 @@ def free_state(self) -> None:
self._type_checker.reset()
self._type_checker = None

def _patch_indirect_dependencies(
self, module_refs: set[str], type_map: dict[Expression, Type]
) -> None:
types = set(type_map.values())
def _patch_indirect_dependencies(self, module_refs: set[str], types: set[Type]) -> None:
assert None not in types
valid = self.valid_references()

Expand Down
204 changes: 204 additions & 0 deletions test-data/unit/check-incremental.test
Original file line number Diff line number Diff line change
Expand Up @@ -6082,3 +6082,207 @@ class Base:
[out]
[out2]
main:6: error: Call to abstract method "meth" of "Base" with trivial body via super() is unsafe

[case testNoCrashDoubleReexportFunctionEmpty]
import m

[file m.py]
import f
[file m.py.3]
import f
# modify

[file f.py]
import c
def foo(arg: c.C) -> None: pass

[file c.py]
from types import C

[file types.py]
import pb1
C = pb1.C
[file types.py.2]
import pb1, pb2
C = pb2.C

[file pb1.py]
class C: ...
[file pb2.py.2]
class C: ...
[file pb1.py.2]
[out]
[out2]
[out3]

[case testNoCrashDoubleReexportBaseEmpty]
import m

[file m.py]
import f
[file m.py.3]
import f
# modify

[file f.py]
import c
class D(c.C): pass

[file c.py]
from types import C

[file types.py]
import pb1
C = pb1.C
[file types.py.2]
import pb1, pb2
C = pb2.C

[file pb1.py]
class C: ...
[file pb2.py.2]
class C: ...
[file pb1.py.2]
[out]
[out2]
[out3]

[case testNoCrashDoubleReexportMetaEmpty]
import m

[file m.py]
import f
[file m.py.3]
import f
# modify

[file f.py]
import c
class D(metaclass=c.C): pass

[file c.py]
from types import C

[file types.py]
import pb1
C = pb1.C
[file types.py.2]
import pb1, pb2
C = pb2.C

[file pb1.py]
class C(type): ...
[file pb2.py.2]
class C(type): ...
[file pb1.py.2]
[out]
[out2]
[out3]

[case testNoCrashDoubleReexportTypedDictEmpty]
import m

[file m.py]
import f
[file m.py.3]
import f
# modify

[file f.py]
from typing_extensions import TypedDict
import c
class D(TypedDict):
x: c.C

[file c.py]
from types import C

[file types.py]
import pb1
C = pb1.C
[file types.py.2]
import pb1, pb2
C = pb2.C

[file pb1.py]
class C: ...
[file pb2.py.2]
class C: ...
[file pb1.py.2]
[builtins fixtures/dict.pyi]
[out]
[out2]
[out3]

[case testNoCrashDoubleReexportTupleEmpty]
import m

[file m.py]
import f
[file m.py.3]
import f
# modify

[file f.py]
from typing import Tuple
import c
class D(Tuple[c.C, int]): pass

[file c.py]
from types import C

[file types.py]
import pb1
C = pb1.C
[file types.py.2]
import pb1, pb2
C = pb2.C

[file pb1.py]
class C: ...
[file pb2.py.2]
class C: ...
[file pb1.py.2]
[builtins fixtures/tuple.pyi]
[out]
[out2]
[out3]

[case testNoCrashDoubleReexportOverloadEmpty]
import m

[file m.py]
import f
[file m.py.3]
import f
# modify

[file f.py]
from typing import Any, overload
import c

@overload
def foo(arg: int) -> None: ...
@overload
def foo(arg: c.C) -> None: ...
def foo(arg: Any) -> None:
pass

[file c.py]
from types import C

[file types.py]
import pb1
C = pb1.C
[file types.py.2]
import pb1, pb2
C = pb2.C

[file pb1.py]
class C: ...
[file pb2.py.2]
class C: ...
[file pb1.py.2]
[out]
[out2]
[out3]

0 comments on commit 1eaf4c7

Please sign in to comment.