Skip to content

Commit a611b11

Browse files
ilevkivskyiJukkaL
authored andcommitted
Fix crashes and fails in forward references (#3952)
Forward references didn't work with anything apart from classes, for example this didn't work: ``` x: A A = NamedTuple('A', [('x', int)]) ``` The same situation was with `TypedDict`, `NewType`, and type aliases. The root problem is that these synthetic types are neither detected in first pass, nor fixed in third pass. In certain cases this can lead to crashes (first six issues below are various crash scenarios). This fixes these crashes by applying some additional patches after third pass. Here is the summary of the PR: * New simple wrapper type `ForwardRef` with only one field `link` is introduced (with updates to type visitors) * When an unknown type is found in second pass, the corresponding `UnboundType` is wrapped in `ForwardRef`, it is given a "second chance" in third pass. * After third pass I record the "suspicious" nodes, where forward references and synthetic types have been encountered and append patches (callbacks) to fix them after third pass. Patches use the new visitor `TypeReplacer` (which is the core of this PR). Fixes #3340 Fixes #3419 Fixes #3674 Fixes #3685 Fixes #3799 Fixes #3836 Fixes #3881 Fixes #867 Fixes #2241 Fixes #2399 Fixes #1701 Fixes #3016 Fixes #3054 Fixes #2762 Fixes #3575 Fixes #3990
1 parent b41632f commit a611b11

22 files changed

+1425
-85
lines changed

mypy/build.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ def __init__(self, data_dir: str,
499499
self.semantic_analyzer = SemanticAnalyzer(self.modules, self.missing_modules,
500500
lib_path, self.errors, self.plugin)
501501
self.modules = self.semantic_analyzer.modules
502-
self.semantic_analyzer_pass3 = ThirdPass(self.modules, self.errors)
502+
self.semantic_analyzer_pass3 = ThirdPass(self.modules, self.errors, self.semantic_analyzer)
503503
self.all_types = {} # type: Dict[Expression, Type]
504504
self.indirection_detector = TypeIndirectionVisitor()
505505
self.stale_modules = set() # type: Set[str]
@@ -1730,10 +1730,13 @@ def semantic_analysis(self) -> None:
17301730

17311731
def semantic_analysis_pass_three(self) -> None:
17321732
assert self.tree is not None, "Internal error: method must be called on parsed file only"
1733+
patches = [] # type: List[Callable[[], None]]
17331734
with self.wrap_context():
1734-
self.manager.semantic_analyzer_pass3.visit_file(self.tree, self.xpath, self.options)
1735+
self.manager.semantic_analyzer_pass3.visit_file(self.tree, self.xpath,
1736+
self.options, patches)
17351737
if self.options.dump_type_stats:
17361738
dump_type_stats(self.tree, self.xpath)
1739+
self.patches = patches + self.patches
17371740

17381741
def semantic_analysis_apply_patches(self) -> None:
17391742
for patch_func in self.patches:

mypy/indirection.py

+3
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,6 @@ def visit_ellipsis_type(self, t: types.EllipsisType) -> Set[str]:
101101

102102
def visit_type_type(self, t: types.TypeType) -> Set[str]:
103103
return self._visit(t.item)
104+
105+
def visit_forwardref_type(self, t: types.ForwardRef) -> Set[str]:
106+
return self._visit(t.link)

mypy/messages.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from mypy.types import (
2121
Type, CallableType, Instance, TypeVarType, TupleType, TypedDictType,
2222
UnionType, NoneTyp, AnyType, Overloaded, FunctionLike, DeletedType, TypeType,
23-
UninhabitedType, TypeOfAny
23+
UninhabitedType, TypeOfAny, ForwardRef, UnboundType
2424
)
2525
from mypy.nodes import (
2626
TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_type_aliases,
@@ -194,7 +194,7 @@ def quote_type_string(self, type_string: str) -> str:
194194
"""Quotes a type representation for use in messages."""
195195
no_quote_regex = r'^<(tuple|union): \d+ items>$'
196196
if (type_string in ['Module', 'overloaded function', '<nothing>', '<deleted>']
197-
or re.match(no_quote_regex, type_string) is not None):
197+
or re.match(no_quote_regex, type_string) is not None or type_string.endswith('?')):
198198
# Messages are easier to read if these aren't quoted. We use a
199199
# regex to match strings with variable contents.
200200
return type_string
@@ -309,6 +309,8 @@ def format_bare(self, typ: Type, verbosity: int = 0) -> str:
309309
return '<nothing>'
310310
elif isinstance(typ, TypeType):
311311
return 'Type[{}]'.format(self.format_bare(typ.item, verbosity))
312+
elif isinstance(typ, ForwardRef): # may appear in semanal.py
313+
return self.format_bare(typ.link, verbosity)
312314
elif isinstance(typ, FunctionLike):
313315
func = typ
314316
if func.is_type_obj():
@@ -350,6 +352,8 @@ def format_bare(self, typ: Type, verbosity: int = 0) -> str:
350352
# function types may result in long and difficult-to-read
351353
# error messages.
352354
return 'overloaded function'
355+
elif isinstance(typ, UnboundType):
356+
return str(typ)
353357
elif typ is None:
354358
raise RuntimeError('Type is None')
355359
else:

mypy/nodes.py

+6
Original file line numberDiff line numberDiff line change
@@ -1976,6 +1976,12 @@ class is generic then it will be a type constructor of higher kind.
19761976
# Is this a newtype type?
19771977
is_newtype = False
19781978

1979+
# If during analysis of ClassDef associated with this TypeInfo a syntethic
1980+
# type (NamedTuple or TypedDict) was generated, store the corresponding
1981+
# TypeInfo here. (This attribute does not need to be serialized, it is only
1982+
# needed during the semantic passes.)
1983+
replaced = None # type: TypeInfo
1984+
19791985
FLAGS = [
19801986
'is_abstract', 'is_enum', 'fallback_to_any', 'is_named_tuple',
19811987
'is_newtype', 'is_protocol', 'runtime_protocol'

mypy/semanal.py

+357-37
Large diffs are not rendered by default.

mypy/server/deps.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from mypy.types import (
1212
Type, Instance, AnyType, NoneTyp, TypeVisitor, CallableType, DeletedType, PartialType,
1313
TupleType, TypeType, TypeVarType, TypedDictType, UnboundType, UninhabitedType, UnionType,
14-
FunctionLike
14+
FunctionLike, ForwardRef
1515
)
1616
from mypy.server.trigger import make_trigger
1717

@@ -212,6 +212,9 @@ def visit_type_type(self, typ: TypeType) -> List[str]:
212212
# TODO: replace with actual implementation
213213
return []
214214

215+
def visit_forwardref_type(self, typ: ForwardRef) -> List[str]:
216+
return get_type_dependencies(typ.link)
217+
215218
def visit_type_var(self, typ: TypeVarType) -> List[str]:
216219
# TODO: replace with actual implementation
217220
return []

0 commit comments

Comments
 (0)