Skip to content

Commit 9f69bea

Browse files
Fix crash on lambda in generic context with generic method in body (#15155)
Fixes #15060 The example in the issue contains another case where an erased type may legitimately appear in a generic function. Namely, when we have a lambda in a generic context, and its body contains a call to a generic _method_. First, since we infer the type of lambda in erased context, some of lambda parameters may get assigned a type containing erased components. Then, when accessing a generic method on such type we may get a callable that is both generic and has erased components, thus causing the crash (actually there are two very similar crashes depending on the details of the generic method). Provided that we now have two legitimate cases for erased type appearing in `expand_type()`, and special-casing (enabling) them would be tricky (a lot of functions will need to have `allow_erased_callables`), I propose to simply remove the check. Co-authored-by: Shantanu <[email protected]>
1 parent 6f28cc3 commit 9f69bea

File tree

4 files changed

+28
-36
lines changed

4 files changed

+28
-36
lines changed

mypy/applytype.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ def apply_generic_arguments(
7575
report_incompatible_typevar_value: Callable[[CallableType, Type, str, Context], None],
7676
context: Context,
7777
skip_unsatisfied: bool = False,
78-
allow_erased_callables: bool = False,
7978
) -> CallableType:
8079
"""Apply generic type arguments to a callable type.
8180
@@ -119,15 +118,9 @@ def apply_generic_arguments(
119118
star_index = callable.arg_kinds.index(ARG_STAR)
120119
callable = callable.copy_modified(
121120
arg_types=(
122-
[
123-
expand_type(at, id_to_type, allow_erased_callables)
124-
for at in callable.arg_types[:star_index]
125-
]
121+
[expand_type(at, id_to_type) for at in callable.arg_types[:star_index]]
126122
+ [callable.arg_types[star_index]]
127-
+ [
128-
expand_type(at, id_to_type, allow_erased_callables)
129-
for at in callable.arg_types[star_index + 1 :]
130-
]
123+
+ [expand_type(at, id_to_type) for at in callable.arg_types[star_index + 1 :]]
131124
)
132125
)
133126

@@ -163,22 +156,20 @@ def apply_generic_arguments(
163156
assert False, "mypy bug: unhandled case applying unpack"
164157
else:
165158
callable = callable.copy_modified(
166-
arg_types=[
167-
expand_type(at, id_to_type, allow_erased_callables) for at in callable.arg_types
168-
]
159+
arg_types=[expand_type(at, id_to_type) for at in callable.arg_types]
169160
)
170161

171162
# Apply arguments to TypeGuard if any.
172163
if callable.type_guard is not None:
173-
type_guard = expand_type(callable.type_guard, id_to_type, allow_erased_callables)
164+
type_guard = expand_type(callable.type_guard, id_to_type)
174165
else:
175166
type_guard = None
176167

177168
# The callable may retain some type vars if only some were applied.
178169
remaining_tvars = [tv for tv in tvars if tv.id not in id_to_type]
179170

180171
return callable.copy_modified(
181-
ret_type=expand_type(callable.ret_type, id_to_type, allow_erased_callables),
172+
ret_type=expand_type(callable.ret_type, id_to_type),
182173
variables=remaining_tvars,
183174
type_guard=type_guard,
184175
)

mypy/expandtype.py

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,33 +48,25 @@
4848

4949

5050
@overload
51-
def expand_type(
52-
typ: CallableType, env: Mapping[TypeVarId, Type], allow_erased_callables: bool = ...
53-
) -> CallableType:
51+
def expand_type(typ: CallableType, env: Mapping[TypeVarId, Type]) -> CallableType:
5452
...
5553

5654

5755
@overload
58-
def expand_type(
59-
typ: ProperType, env: Mapping[TypeVarId, Type], allow_erased_callables: bool = ...
60-
) -> ProperType:
56+
def expand_type(typ: ProperType, env: Mapping[TypeVarId, Type]) -> ProperType:
6157
...
6258

6359

6460
@overload
65-
def expand_type(
66-
typ: Type, env: Mapping[TypeVarId, Type], allow_erased_callables: bool = ...
67-
) -> Type:
61+
def expand_type(typ: Type, env: Mapping[TypeVarId, Type]) -> Type:
6862
...
6963

7064

71-
def expand_type(
72-
typ: Type, env: Mapping[TypeVarId, Type], allow_erased_callables: bool = False
73-
) -> Type:
65+
def expand_type(typ: Type, env: Mapping[TypeVarId, Type]) -> Type:
7466
"""Substitute any type variable references in a type given by a type
7567
environment.
7668
"""
77-
return typ.accept(ExpandTypeVisitor(env, allow_erased_callables))
69+
return typ.accept(ExpandTypeVisitor(env))
7870

7971

8072
@overload
@@ -195,11 +187,8 @@ class ExpandTypeVisitor(TypeVisitor[Type]):
195187

196188
variables: Mapping[TypeVarId, Type] # TypeVar id -> TypeVar value
197189

198-
def __init__(
199-
self, variables: Mapping[TypeVarId, Type], allow_erased_callables: bool = False
200-
) -> None:
190+
def __init__(self, variables: Mapping[TypeVarId, Type]) -> None:
201191
self.variables = variables
202-
self.allow_erased_callables = allow_erased_callables
203192

204193
def visit_unbound_type(self, t: UnboundType) -> Type:
205194
return t
@@ -217,13 +206,12 @@ def visit_deleted_type(self, t: DeletedType) -> Type:
217206
return t
218207

219208
def visit_erased_type(self, t: ErasedType) -> Type:
220-
if not self.allow_erased_callables:
221-
raise RuntimeError()
222209
# This may happen during type inference if some function argument
223210
# type is a generic callable, and its erased form will appear in inferred
224211
# constraints, then solver may check subtyping between them, which will trigger
225-
# unify_generic_callables(), this is why we can get here. In all other cases it
226-
# is a sign of a bug, since <Erased> should never appear in any stored types.
212+
# unify_generic_callables(), this is why we can get here. Another example is
213+
# when inferring type of lambda in generic context, the lambda body contains
214+
# a generic method in generic class.
227215
return t
228216

229217
def visit_instance(self, t: Instance) -> Type:

mypy/subtypes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1714,7 +1714,7 @@ def report(*args: Any) -> None:
17141714
# (probably also because solver needs subtyping). See also comment in
17151715
# ExpandTypeVisitor.visit_erased_type().
17161716
applied = mypy.applytype.apply_generic_arguments(
1717-
type, non_none_inferred_vars, report, context=target, allow_erased_callables=True
1717+
type, non_none_inferred_vars, report, context=target
17181718
)
17191719
if had_errors:
17201720
return None

test-data/unit/check-generics.test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2698,3 +2698,16 @@ def func(var: T) -> T:
26982698
reveal_type(func(1)) # N: Revealed type is "builtins.int"
26992699

27002700
[builtins fixtures/tuple.pyi]
2701+
2702+
[case testGenericLambdaGenericMethodNoCrash]
2703+
from typing import TypeVar, Union, Callable, Generic
2704+
2705+
S = TypeVar("S")
2706+
T = TypeVar("T")
2707+
2708+
def f(x: Callable[[G[T]], int]) -> T: ...
2709+
2710+
class G(Generic[T]):
2711+
def g(self, x: S) -> Union[S, T]: ...
2712+
2713+
f(lambda x: x.g(0)) # E: Cannot infer type argument 1 of "f"

0 commit comments

Comments
 (0)