Skip to content

Commit 74ec1b5

Browse files
ilevkivskyiJukkaL
authored andcommitted
Fix recent crashes in forward refs (#4120)
This adds processing of forward references in two nodes that were previously missing: `OverloadedFuncDef` and `NewTypeExpr.info`. (Note that some tests use `# type: ignore`, the original crash was discovered during serialization with errors ignored.) Fixes #4097.
1 parent 4e3d428 commit 74ec1b5

File tree

5 files changed

+157
-16
lines changed

5 files changed

+157
-16
lines changed

mypy/nodes.py

+1
Original file line numberDiff line numberDiff line change
@@ -1795,6 +1795,7 @@ class NewTypeExpr(Expression):
17951795
def __init__(self, name: str, old_type: 'Optional[mypy.types.Type]', line: int) -> None:
17961796
self.name = name
17971797
self.old_type = old_type
1798+
self.line = line
17981799

17991800
def accept(self, visitor: ExpressionVisitor[T]) -> T:
18001801
return visitor.visit_newtype_expr(self)

mypy/semanal_pass3.py

+37-15
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
Node, Expression, MypyFile, FuncDef, FuncItem, Decorator, RefExpr, Context, TypeInfo, ClassDef,
1818
Block, TypedDictExpr, NamedTupleExpr, AssignmentStmt, IndexExpr, TypeAliasExpr, NameExpr,
1919
CallExpr, NewTypeExpr, ForStmt, WithStmt, CastExpr, TypeVarExpr, TypeApplication, Lvalue,
20-
TupleExpr, RevealTypeExpr, SymbolTableNode, Var, ARG_POS
20+
TupleExpr, RevealTypeExpr, SymbolTableNode, Var, ARG_POS, OverloadedFuncDef
2121
)
2222
from mypy.types import (
2323
Type, Instance, AnyType, TypeOfAny, CallableType, TupleType, TypeVarType, TypedDictType,
@@ -85,6 +85,10 @@ def visit_func_def(self, fdef: FuncDef) -> None:
8585
super().visit_func_def(fdef)
8686
self.errors.pop_function()
8787

88+
def visit_overloaded_func_def(self, fdef: OverloadedFuncDef) -> None:
89+
self.analyze(fdef.type, fdef)
90+
super().visit_overloaded_func_def(fdef)
91+
8892
def visit_class_def(self, tdef: ClassDef) -> None:
8993
# NamedTuple base classes are validated in check_namedtuple_classdef; we don't have to
9094
# check them again here.
@@ -116,12 +120,7 @@ def visit_class_def(self, tdef: ClassDef) -> None:
116120
self.analyze(tdef.analyzed.info.typeddict_type, tdef.analyzed, warn=True)
117121
elif isinstance(tdef.analyzed, NamedTupleExpr):
118122
self.analyze(tdef.analyzed.info.tuple_type, tdef.analyzed, warn=True)
119-
for name in tdef.analyzed.info.names:
120-
sym = tdef.analyzed.info.names[name]
121-
if isinstance(sym.node, (FuncDef, Decorator)):
122-
self.accept(sym.node)
123-
if isinstance(sym.node, Var):
124-
self.analyze(sym.node.type, sym.node)
123+
self.analyze_info(tdef.analyzed.info)
125124
super().visit_class_def(tdef)
126125

127126
def visit_decorator(self, dec: Decorator) -> None:
@@ -189,6 +188,10 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
189188
analyzed = s.rvalue.analyzed
190189
if isinstance(analyzed, NewTypeExpr):
191190
self.analyze(analyzed.old_type, analyzed)
191+
if analyzed.info:
192+
# Currently NewTypes only have __init__, but to be future proof,
193+
# we analyze all symbols.
194+
self.analyze_info(analyzed.info)
192195
if analyzed.info and analyzed.info.mro:
193196
analyzed.info.mro = [] # Force recomputation
194197
mypy.semanal.calculate_class_mro(analyzed.info.defn, self.fail_blocker)
@@ -203,12 +206,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
203206
self.analyze(analyzed.info.typeddict_type, analyzed, warn=True)
204207
if isinstance(analyzed, NamedTupleExpr):
205208
self.analyze(analyzed.info.tuple_type, analyzed, warn=True)
206-
for name in analyzed.info.names:
207-
sym = analyzed.info.names[name]
208-
if isinstance(sym.node, (FuncDef, Decorator)):
209-
self.accept(sym.node)
210-
if isinstance(sym.node, Var):
211-
self.analyze(sym.node.type, sym.node)
209+
self.analyze_info(analyzed.info)
212210
# We need to pay additional attention to assignments that define a type alias.
213211
# The resulting type is also stored in the 'type_override' attribute of
214212
# the corresponding SymbolTableNode.
@@ -255,12 +253,27 @@ def perform_transform(self, node: Union[Node, SymbolTableNode],
255253
for n in node.target:
256254
if isinstance(n, NameExpr) and isinstance(n.node, Var) and n.node.type:
257255
n.node.type = transform(n.node.type)
258-
if isinstance(node, (FuncDef, CastExpr, AssignmentStmt, TypeAliasExpr, Var)):
256+
if isinstance(node, (FuncDef, OverloadedFuncDef, CastExpr, AssignmentStmt,
257+
TypeAliasExpr, Var)):
259258
assert node.type, "Scheduled patch for non-existent type"
260259
node.type = transform(node.type)
261260
if isinstance(node, NewTypeExpr):
262261
assert node.old_type, "Scheduled patch for non-existent type"
263262
node.old_type = transform(node.old_type)
263+
if node.info:
264+
new_bases = [] # type: List[Instance]
265+
for b in node.info.bases:
266+
new_b = transform(b)
267+
# TODO: this code can be combined with code in second pass.
268+
if isinstance(new_b, Instance):
269+
new_bases.append(new_b)
270+
elif isinstance(new_b, TupleType):
271+
new_bases.append(new_b.fallback)
272+
else:
273+
self.fail("Argument 2 to NewType(...) must be subclassable"
274+
" (got {})".format(new_b), node)
275+
new_bases.append(self.builtin_type('object'))
276+
node.info.bases = new_bases
264277
if isinstance(node, TypeVarExpr):
265278
if node.upper_bound:
266279
node.upper_bound = transform(node.upper_bound)
@@ -292,7 +305,7 @@ def perform_transform(self, node: Union[Node, SymbolTableNode],
292305
new_bases.append(new_base)
293306
else:
294307
# Don't fix the NamedTuple bases, they are Instance's intentionally.
295-
# Patch the 'args' just in case, although generic tuple type are
308+
# Patch the 'args' just in case, although generic tuple types are
296309
# not supported yet.
297310
alt_base = Instance(base.type, [transform(a) for a in base.args])
298311
new_bases.append(alt_base)
@@ -339,6 +352,15 @@ def patch() -> None:
339352
node, warn=False)))
340353
self.patches.append(patch)
341354

355+
def analyze_info(self, info: TypeInfo) -> None:
356+
# Similar to above but for nodes with synthetic TypeInfos (NamedTuple and NewType).
357+
for name in info.names:
358+
sym = info.names[name]
359+
if isinstance(sym.node, (FuncDef, Decorator)):
360+
self.accept(sym.node)
361+
if isinstance(sym.node, Var):
362+
self.analyze(sym.node.type, sym.node)
363+
342364
def make_type_analyzer(self, indicator: Dict[str, bool]) -> TypeAnalyserPass3:
343365
return TypeAnalyserPass3(self.sem.lookup_qualified,
344366
self.sem.lookup_fully_qualified,

mypy/typeanal.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
Type, UnboundType, TypeVarType, TupleType, TypedDictType, UnionType, Instance, AnyType,
1515
CallableType, NoneTyp, DeletedType, TypeList, TypeVarDef, TypeVisitor, SyntheticTypeVisitor,
1616
StarType, PartialType, EllipsisType, UninhabitedType, TypeType, get_typ_args, set_typ_args,
17-
CallableArgument, get_type_vars, TypeQuery, union_items, TypeOfAny, ForwardRef
17+
CallableArgument, get_type_vars, TypeQuery, union_items, TypeOfAny, ForwardRef, Overloaded
1818
)
1919

2020
from mypy.nodes import (
@@ -766,6 +766,10 @@ def visit_callable_type(self, t: CallableType) -> None:
766766
for arg_type in t.arg_types:
767767
arg_type.accept(self)
768768

769+
def visit_overloaded(self, t: Overloaded) -> None:
770+
for item in t.items():
771+
item.accept(self)
772+
769773
def visit_tuple_type(self, t: TupleType) -> None:
770774
for item in t.items:
771775
item.accept(self)

test-data/unit/check-classes.test

+49
Original file line numberDiff line numberDiff line change
@@ -3623,6 +3623,55 @@ def parse_ast(name_dict: NameDict) -> None:
36233623
[builtins fixtures/isinstancelist.pyi]
36243624
[out]
36253625

3626+
[case testNoCrashForwardRefToBrokenDoubleNewType]
3627+
from typing import Any, Dict, List, NewType
3628+
3629+
Foo = NewType('NotFoo', int) # E: String argument 1 'NotFoo' to NewType(...) does not match variable name 'Foo'
3630+
Foos = NewType('Foos', List[Foo]) # type: ignore
3631+
3632+
def frob(foos: Dict[Any, Foos]) -> None:
3633+
foo = foos.get(1)
3634+
dict(foo)
3635+
[builtins fixtures/dict.pyi]
3636+
[out]
3637+
3638+
[case testNoCrashForwardRefToBrokenDoubleNewTypeClass]
3639+
from typing import Any, Dict, List, NewType
3640+
3641+
Foo = NewType('NotFoo', int) # type: ignore
3642+
Foos = NewType('Foos', List[Foo]) # type: ignore
3643+
3644+
x: C
3645+
class C:
3646+
def frob(self, foos: Dict[Any, Foos]) -> None:
3647+
foo = foos.get(1)
3648+
dict(foo)
3649+
3650+
reveal_type(x.frob) # E: Revealed type is 'def (foos: builtins.dict[Any, __main__.Foos])'
3651+
[builtins fixtures/dict.pyi]
3652+
[out]
3653+
3654+
[case testNewTypeFromForwardNamedTuple]
3655+
from typing import NewType, NamedTuple, Tuple
3656+
3657+
NT = NewType('NT', N)
3658+
class N(NamedTuple):
3659+
x: int
3660+
3661+
x: NT = N(1) # E: Incompatible types in assignment (expression has type "N", variable has type "NT")
3662+
x = NT(N(1))
3663+
[out]
3664+
3665+
[case testNewTypeFromForwardTypedDict]
3666+
from typing import NewType, Tuple
3667+
from mypy_extensions import TypedDict
3668+
3669+
NT = NewType('NT', N) # E: Argument 2 to NewType(...) must be subclassable (got TypedDict('__main__.N', {'x': builtins.int}))
3670+
class N(TypedDict):
3671+
x: int
3672+
[builtins fixtures/dict.pyi]
3673+
[out]
3674+
36263675
[case testCorrectAttributeInForwardRefToNamedTuple]
36273676
from typing import NamedTuple
36283677
proc: Process

test-data/unit/check-incremental.test

+65
Original file line numberDiff line numberDiff line change
@@ -3046,6 +3046,71 @@ class Pair(NamedTuple):
30463046
Person(name=Pair(first="John", last="Doe"))
30473047
[out]
30483048

3049+
[case testNoCrashForwardRefToBrokenDoubleNewTypeIncremental]
3050+
from typing import Any, List, NewType
3051+
3052+
Foo = NewType('NotFoo', int) # type: ignore
3053+
Foos = NewType('Foos', List[Foo]) # type: ignore
3054+
3055+
def frob(foos: List[Foos]) -> None:
3056+
pass
3057+
[builtins fixtures/list.pyi]
3058+
[out]
3059+
3060+
[case testNoCrashForwardRefOverloadIncremental]
3061+
from typing import overload, List
3062+
3063+
@overload
3064+
def f(x: int) -> int: ...
3065+
@overload
3066+
def f(x: F) -> F: ...
3067+
def f(x):
3068+
pass
3069+
3070+
F = List[int]
3071+
[builtins fixtures/list.pyi]
3072+
[out]
3073+
3074+
[case testNoCrashForwardRefOverloadIncrementalClass]
3075+
from typing import overload, Tuple, NamedTuple
3076+
3077+
x: C
3078+
class C:
3079+
@overload
3080+
def f(self, x: str) -> N: pass
3081+
@overload
3082+
def f(self, x: int) -> int: pass
3083+
def f(self, x):
3084+
pass
3085+
3086+
class N(NamedTuple):
3087+
x: A
3088+
A = Tuple[int]
3089+
[builtins fixtures/tuple.pyi]
3090+
[out]
3091+
3092+
[case testNewTypeFromForwardNamedTupleIncremental]
3093+
from typing import NewType, NamedTuple, Tuple
3094+
3095+
NT = NewType('NT', N)
3096+
class N(NamedTuple):
3097+
x: int
3098+
3099+
x: NT = N(1) # type: ignore
3100+
x = NT(N(1))
3101+
[out]
3102+
3103+
[case testNewTypeFromForwardTypedDictIncremental]
3104+
from typing import NewType, Tuple, Dict
3105+
from mypy_extensions import TypedDict
3106+
3107+
NT = NewType('NT', N) # type: ignore
3108+
class N(TypedDict):
3109+
x: A
3110+
A = Dict[str, int]
3111+
[builtins fixtures/dict.pyi]
3112+
[out]
3113+
30493114
-- Some crazy selef-referential named tuples, types dicts, and aliases
30503115
-- to be sure that everything can be _serialized_ (i.e. ForwardRef's are removed).
30513116
-- For this reason errors are silenced (tests with # type: ignore have equivalents in other files)

0 commit comments

Comments
 (0)