Skip to content

Commit 4fa4657

Browse files
authored
Fix type application for classes with generic constructors (#17354)
Fixes #17212 Note I removed the problematic asset after all. It is hard to maintain it, since this function may be called from both explicit application, and from type inference code paths. And these two cases may have different min/max type argument count (see tests and comments for examples).
1 parent 6427da6 commit 4fa4657

File tree

5 files changed

+80
-12
lines changed

5 files changed

+80
-12
lines changed

mypy/applytype.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,7 @@ def apply_generic_arguments(
101101
bound or constraints, instead of giving an error.
102102
"""
103103
tvars = callable.variables
104-
min_arg_count = sum(not tv.has_default() for tv in tvars)
105-
assert min_arg_count <= len(orig_types) <= len(tvars)
104+
assert len(orig_types) <= len(tvars)
106105
# Check that inferred type variable values are compatible with allowed
107106
# values and bounds. Also, promote subtype values to allowed values.
108107
# Create a map from type variable id to target type.
@@ -156,7 +155,7 @@ def apply_generic_arguments(
156155
type_is = None
157156

158157
# The callable may retain some type vars if only some were applied.
159-
# TODO: move apply_poly() logic from checkexpr.py here when new inference
158+
# TODO: move apply_poly() logic here when new inference
160159
# becomes universally used (i.e. in all passes + in unification).
161160
# With this new logic we can actually *add* some new free variables.
162161
remaining_tvars: list[TypeVarLikeType] = []

mypy/checkexpr.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4781,7 +4781,11 @@ class C(Generic[T, Unpack[Ts]]): ...
47814781
We simply group the arguments that need to go into Ts variable into a TupleType,
47824782
similar to how it is done in other places using split_with_prefix_and_suffix().
47834783
"""
4784-
vars = t.variables
4784+
if t.is_type_obj():
4785+
# Type arguments must map to class type variables, ignoring constructor vars.
4786+
vars = t.type_object().defn.type_vars
4787+
else:
4788+
vars = list(t.variables)
47854789
args = flatten_nested_tuples(args)
47864790

47874791
# TODO: this logic is duplicated with semanal_typeargs.
@@ -4799,6 +4803,7 @@ class C(Generic[T, Unpack[Ts]]): ...
47994803

48004804
if not vars or not any(isinstance(v, TypeVarTupleType) for v in vars):
48014805
return list(args)
4806+
# TODO: in future we may want to support type application to variadic functions.
48024807
assert t.is_type_obj()
48034808
info = t.type_object()
48044809
# We reuse the logic from semanal phase to reduce code duplication.
@@ -4832,10 +4837,23 @@ def apply_type_arguments_to_callable(
48324837
tp = get_proper_type(tp)
48334838

48344839
if isinstance(tp, CallableType):
4835-
min_arg_count = sum(not v.has_default() for v in tp.variables)
4836-
has_type_var_tuple = any(isinstance(v, TypeVarTupleType) for v in tp.variables)
4840+
if tp.is_type_obj():
4841+
# If we have a class object in runtime context, then the available type
4842+
# variables are those of the class, we don't include additional variables
4843+
# of the constructor. So that with
4844+
# class C(Generic[T]):
4845+
# def __init__(self, f: Callable[[S], T], x: S) -> None
4846+
# C[int] is valid
4847+
# C[int, str] is invalid (although C as a callable has 2 type variables)
4848+
# Note: various logic below and in applytype.py relies on the fact that
4849+
# class type variables appear *before* constructor variables.
4850+
type_vars = tp.type_object().defn.type_vars
4851+
else:
4852+
type_vars = list(tp.variables)
4853+
min_arg_count = sum(not v.has_default() for v in type_vars)
4854+
has_type_var_tuple = any(isinstance(v, TypeVarTupleType) for v in type_vars)
48374855
if (
4838-
len(args) < min_arg_count or len(args) > len(tp.variables)
4856+
len(args) < min_arg_count or len(args) > len(type_vars)
48394857
) and not has_type_var_tuple:
48404858
if tp.is_type_obj() and tp.type_object().fullname == "builtins.tuple":
48414859
# e.g. expression tuple[X, Y]
@@ -4854,19 +4872,24 @@ def apply_type_arguments_to_callable(
48544872
bound_args=tp.bound_args,
48554873
)
48564874
self.msg.incompatible_type_application(
4857-
min_arg_count, len(tp.variables), len(args), ctx
4875+
min_arg_count, len(type_vars), len(args), ctx
48584876
)
48594877
return AnyType(TypeOfAny.from_error)
48604878
return self.apply_generic_arguments(tp, self.split_for_callable(tp, args, ctx), ctx)
48614879
if isinstance(tp, Overloaded):
48624880
for it in tp.items:
4863-
min_arg_count = sum(not v.has_default() for v in it.variables)
4864-
has_type_var_tuple = any(isinstance(v, TypeVarTupleType) for v in it.variables)
4881+
if tp.is_type_obj():
4882+
# Same as above.
4883+
type_vars = tp.type_object().defn.type_vars
4884+
else:
4885+
type_vars = list(it.variables)
4886+
min_arg_count = sum(not v.has_default() for v in type_vars)
4887+
has_type_var_tuple = any(isinstance(v, TypeVarTupleType) for v in type_vars)
48654888
if (
4866-
len(args) < min_arg_count or len(args) > len(it.variables)
4889+
len(args) < min_arg_count or len(args) > len(type_vars)
48674890
) and not has_type_var_tuple:
48684891
self.msg.incompatible_type_application(
4869-
min_arg_count, len(it.variables), len(args), ctx
4892+
min_arg_count, len(type_vars), len(args), ctx
48704893
)
48714894
return AnyType(TypeOfAny.from_error)
48724895
return Overloaded(

mypy/typeanal.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2376,6 +2376,12 @@ def validate_instance(t: Instance, fail: MsgCallback, empty_tuple_index: bool) -
23762376
if not t.args:
23772377
if not (empty_tuple_index and len(t.type.type_vars) == 1):
23782378
# The Any arguments should be set by the caller.
2379+
if empty_tuple_index and min_tv_count:
2380+
fail(
2381+
f"At least {min_tv_count} type argument(s) expected, none given",
2382+
t,
2383+
code=codes.TYPE_ARG,
2384+
)
23792385
return False
23802386
elif not correct:
23812387
fail(

test-data/unit/check-generics.test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3443,6 +3443,19 @@ h: Callable[[Unpack[Us]], Foo[int]]
34433443
reveal_type(dec(h)) # N: Revealed type is "def (builtins.int) -> __main__.Foo[builtins.int]"
34443444
[builtins fixtures/list.pyi]
34453445

3446+
[case testTypeApplicationGenericConstructor]
3447+
from typing import Generic, TypeVar, Callable
3448+
3449+
T = TypeVar("T")
3450+
S = TypeVar("S")
3451+
class C(Generic[T]):
3452+
def __init__(self, f: Callable[[S], T], x: S) -> None:
3453+
self.x = f(x)
3454+
3455+
reveal_type(C[int]) # N: Revealed type is "def [S] (f: def (S`-1) -> builtins.int, x: S`-1) -> __main__.C[builtins.int]"
3456+
Alias = C[int]
3457+
C[int, str] # E: Type application has too many types (1 expected)
3458+
34463459
[case testHigherOrderGenericPartial]
34473460
from typing import TypeVar, Callable
34483461

test-data/unit/check-typevar-tuple.test

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2378,3 +2378,30 @@ def a2(x: Array[int, str]) -> None:
23782378
reveal_type(func(x, 2, "Hello", True)) # E: Cannot infer type argument 1 of "func" \
23792379
# N: Revealed type is "builtins.tuple[Any, ...]"
23802380
[builtins fixtures/tuple.pyi]
2381+
2382+
[case testTypeVarTupleTypeApplicationOverload]
2383+
from typing import Generic, TypeVar, TypeVarTuple, Unpack, overload, Callable
2384+
2385+
T = TypeVar("T")
2386+
T1 = TypeVar("T1")
2387+
T2 = TypeVar("T2")
2388+
T3 = TypeVar("T3")
2389+
Ts = TypeVarTuple("Ts")
2390+
2391+
class C(Generic[T, Unpack[Ts]]):
2392+
@overload
2393+
def __init__(self, f: Callable[[Unpack[Ts]], T]) -> None: ...
2394+
@overload
2395+
def __init__(self, f: Callable[[T1, T2, T3, Unpack[Ts]], T], a: T1, b: T2, c: T3) -> None: ...
2396+
def __init__(self, f, *args, **kwargs) -> None:
2397+
...
2398+
2399+
reveal_type(C[int, str]) # N: Revealed type is "Overload(def (f: def (builtins.str) -> builtins.int) -> __main__.C[builtins.int, builtins.str], def [T1, T2, T3] (f: def (T1`-1, T2`-2, T3`-3, builtins.str) -> builtins.int, a: T1`-1, b: T2`-2, c: T3`-3) -> __main__.C[builtins.int, builtins.str])"
2400+
Alias = C[int, str]
2401+
2402+
def f(x: int, y: int, z: int, t: int) -> str: ...
2403+
x = C(f, 0, 0, "hm") # E: Argument 1 to "C" has incompatible type "Callable[[int, int, int, int], str]"; expected "Callable[[int, int, str, int], str]"
2404+
reveal_type(x) # N: Revealed type is "__main__.C[builtins.str, builtins.int]"
2405+
reveal_type(C(f)) # N: Revealed type is "__main__.C[builtins.str, builtins.int, builtins.int, builtins.int, builtins.int]"
2406+
C[()] # E: At least 1 type argument(s) expected, none given
2407+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)