Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply TypeVar defaults to callables (PEP 696) #16842

Merged
merged 1 commit into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion mypy/applytype.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ def apply_generic_arguments(
bound or constraints, instead of giving an error.
"""
tvars = callable.variables
assert len(tvars) == len(orig_types)
min_arg_count = sum(not tv.has_default() for tv in tvars)
assert min_arg_count <= len(orig_types) <= len(tvars)
# Check that inferred type variable values are compatible with allowed
# values and bounds. Also, promote subtype values to allowed values.
# Create a map from type variable id to target type.
Expand Down
24 changes: 16 additions & 8 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4809,21 +4809,29 @@ def apply_type_arguments_to_callable(
tp = get_proper_type(tp)

if isinstance(tp, CallableType):
if len(tp.variables) != len(args) and not any(
isinstance(v, TypeVarTupleType) for v in tp.variables
):
min_arg_count = sum(not v.has_default() for v in tp.variables)
has_type_var_tuple = any(isinstance(v, TypeVarTupleType) for v in tp.variables)
if (
len(args) < min_arg_count or len(args) > len(tp.variables)
) and not has_type_var_tuple:
if tp.is_type_obj() and tp.type_object().fullname == "builtins.tuple":
# TODO: Specialize the callable for the type arguments
return tp
self.msg.incompatible_type_application(len(tp.variables), len(args), ctx)
self.msg.incompatible_type_application(
min_arg_count, len(tp.variables), len(args), ctx
)
return AnyType(TypeOfAny.from_error)
return self.apply_generic_arguments(tp, self.split_for_callable(tp, args, ctx), ctx)
if isinstance(tp, Overloaded):
for it in tp.items:
if len(it.variables) != len(args) and not any(
isinstance(v, TypeVarTupleType) for v in it.variables
):
self.msg.incompatible_type_application(len(it.variables), len(args), ctx)
min_arg_count = sum(not v.has_default() for v in it.variables)
has_type_var_tuple = any(isinstance(v, TypeVarTupleType) for v in it.variables)
if (
len(args) < min_arg_count or len(args) > len(it.variables)
) and not has_type_var_tuple:
self.msg.incompatible_type_application(
min_arg_count, len(it.variables), len(args), ctx
)
return AnyType(TypeOfAny.from_error)
return Overloaded(
[
Expand Down
21 changes: 12 additions & 9 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -1347,18 +1347,21 @@ def override_target(self, name: str, name_in_super: str, supertype: str) -> str:
return target

def incompatible_type_application(
self, expected_arg_count: int, actual_arg_count: int, context: Context
self, min_arg_count: int, max_arg_count: int, actual_arg_count: int, context: Context
) -> None:
if expected_arg_count == 0:
if max_arg_count == 0:
self.fail("Type application targets a non-generic function or class", context)
elif actual_arg_count > expected_arg_count:
self.fail(
f"Type application has too many types ({expected_arg_count} expected)", context
)
return

if min_arg_count == max_arg_count:
s = f"{max_arg_count} expected"
else:
self.fail(
f"Type application has too few types ({expected_arg_count} expected)", context
)
s = f"expected between {min_arg_count} and {max_arg_count}"

if actual_arg_count > max_arg_count:
self.fail(f"Type application has too many types ({s})", context)
else:
self.fail(f"Type application has too few types ({s})", context)

def could_not_infer_type_arguments(
self, callee_type: CallableType, n: int, context: Context
Expand Down
93 changes: 92 additions & 1 deletion test-data/unit/check-typevar-defaults.test
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def func_c1(x: Union[int, Callable[[Unpack[Ts1]], None]]) -> Tuple[Unpack[Ts1]]:
[builtins fixtures/tuple.pyi]

[case testTypeVarDefaultsClass1]
from typing import Generic, TypeVar
from typing import Generic, TypeVar, Union, overload

T1 = TypeVar("T1")
T2 = TypeVar("T2", default=int)
Expand All @@ -137,6 +137,15 @@ def func_a1(
reveal_type(c) # N: Revealed type is "__main__.ClassA1[builtins.float, builtins.float]"
reveal_type(d) # N: Revealed type is "__main__.ClassA1[builtins.int, builtins.str]"

k = ClassA1()
reveal_type(k) # N: Revealed type is "__main__.ClassA1[builtins.int, builtins.str]"
l = ClassA1[float]()
reveal_type(l) # N: Revealed type is "__main__.ClassA1[builtins.float, builtins.str]"
m = ClassA1[float, float]()
reveal_type(m) # N: Revealed type is "__main__.ClassA1[builtins.float, builtins.float]"
n = ClassA1[float, float, float]() # E: Type application has too many types (expected between 0 and 2)
reveal_type(n) # N: Revealed type is "Any"

class ClassA2(Generic[T1, T2, T3]): ...

def func_a2(
Expand All @@ -152,6 +161,44 @@ def func_a2(
reveal_type(d) # N: Revealed type is "__main__.ClassA2[builtins.float, builtins.float, builtins.float]"
reveal_type(e) # N: Revealed type is "__main__.ClassA2[Any, builtins.int, builtins.str]"

k = ClassA2() # E: Need type annotation for "k"
reveal_type(k) # N: Revealed type is "__main__.ClassA2[Any, builtins.int, builtins.str]"
l = ClassA2[float]()
reveal_type(l) # N: Revealed type is "__main__.ClassA2[builtins.float, builtins.int, builtins.str]"
m = ClassA2[float, float]()
reveal_type(m) # N: Revealed type is "__main__.ClassA2[builtins.float, builtins.float, builtins.str]"
n = ClassA2[float, float, float]()
reveal_type(n) # N: Revealed type is "__main__.ClassA2[builtins.float, builtins.float, builtins.float]"
o = ClassA2[float, float, float, float]() # E: Type application has too many types (expected between 1 and 3)
reveal_type(o) # N: Revealed type is "Any"

class ClassA3(Generic[T1, T2]):
@overload
def __init__(self) -> None: ...
@overload
def __init__(self, var: int) -> None: ...
def __init__(self, var: Union[int, None] = None) -> None: ...

def func_a3(
a: ClassA3,
b: ClassA3[float],
c: ClassA3[float, float],
d: ClassA3[float, float, float], # E: "ClassA3" expects between 1 and 2 type arguments, but 3 given
) -> None:
reveal_type(a) # N: Revealed type is "__main__.ClassA3[Any, builtins.int]"
reveal_type(b) # N: Revealed type is "__main__.ClassA3[builtins.float, builtins.int]"
reveal_type(c) # N: Revealed type is "__main__.ClassA3[builtins.float, builtins.float]"
reveal_type(d) # N: Revealed type is "__main__.ClassA3[Any, builtins.int]"

k = ClassA3() # E: Need type annotation for "k"
reveal_type(k) # N: Revealed type is "__main__.ClassA3[Any, builtins.int]"
l = ClassA3[float]()
reveal_type(l) # N: Revealed type is "__main__.ClassA3[builtins.float, builtins.int]"
m = ClassA3[float, float]()
reveal_type(m) # N: Revealed type is "__main__.ClassA3[builtins.float, builtins.float]"
n = ClassA3[float, float, float]() # E: Type application has too many types (expected between 1 and 2)
reveal_type(n) # N: Revealed type is "Any"

[case testTypeVarDefaultsClass2]
from typing import Generic, ParamSpec

Expand All @@ -172,6 +219,15 @@ def func_b1(
reveal_type(c) # N: Revealed type is "__main__.ClassB1[[builtins.float], [builtins.float]]"
reveal_type(d) # N: Revealed type is "__main__.ClassB1[[builtins.int, builtins.str], ...]"

k = ClassB1()
reveal_type(k) # N: Revealed type is "__main__.ClassB1[[builtins.int, builtins.str], [*Any, **Any]]"
l = ClassB1[[float]]()
reveal_type(l) # N: Revealed type is "__main__.ClassB1[[builtins.float], [*Any, **Any]]"
m = ClassB1[[float], [float]]()
reveal_type(m) # N: Revealed type is "__main__.ClassB1[[builtins.float], [builtins.float]]"
n = ClassB1[[float], [float], [float]]() # E: Type application has too many types (expected between 0 and 2)
reveal_type(n) # N: Revealed type is "Any"

class ClassB2(Generic[P1, P2]): ...

def func_b2(
Expand All @@ -185,6 +241,15 @@ def func_b2(
reveal_type(c) # N: Revealed type is "__main__.ClassB2[[builtins.float], [builtins.float]]"
reveal_type(d) # N: Revealed type is "__main__.ClassB2[Any, [builtins.int, builtins.str]]"

k = ClassB2() # E: Need type annotation for "k"
reveal_type(k) # N: Revealed type is "__main__.ClassB2[Any, [builtins.int, builtins.str]]"
l = ClassB2[[float]]()
reveal_type(l) # N: Revealed type is "__main__.ClassB2[[builtins.float], [builtins.int, builtins.str]]"
m = ClassB2[[float], [float]]()
reveal_type(m) # N: Revealed type is "__main__.ClassB2[[builtins.float], [builtins.float]]"
n = ClassB2[[float], [float], [float]]() # E: Type application has too many types (expected between 1 and 2)
reveal_type(n) # N: Revealed type is "Any"

[case testTypeVarDefaultsClass3]
from typing import Generic, Tuple, TypeVar
from typing_extensions import TypeVarTuple, Unpack
Expand All @@ -206,6 +271,11 @@ def func_c1(
# reveal_type(a) # Revealed type is "__main__.ClassC1[builtins.int, builtins.str]" # TODO
reveal_type(b) # N: Revealed type is "__main__.ClassC1[builtins.float]"

# k = ClassC1() # TODO
# reveal_type(k) # Revealed type is "__main__.ClassC1[builtins.int, builtins.str]" # TODO
l = ClassC1[float]()
reveal_type(l) # N: Revealed type is "__main__.ClassC1[builtins.float]"

class ClassC2(Generic[T3, Unpack[Ts3]]): ...

def func_c2(
Expand All @@ -217,6 +287,13 @@ def func_c2(
# reveal_type(b) # Revealed type is "__main__.ClassC2[builtins.int, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO
reveal_type(c) # N: Revealed type is "__main__.ClassC2[builtins.int]"

# k = ClassC2() # TODO
# reveal_type(k) # Revealed type is "__main__.ClassC2[builtins.str, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO
l = ClassC2[int]()
# reveal_type(l) # Revealed type is "__main__.ClassC2[builtins.int, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO
m = ClassC2[int, Unpack[Tuple[()]]]()
reveal_type(m) # N: Revealed type is "__main__.ClassC2[builtins.int]"

class ClassC3(Generic[T3, Unpack[Ts4]]): ...

def func_c3(
Expand All @@ -228,6 +305,13 @@ def func_c3(
reveal_type(b) # N: Revealed type is "__main__.ClassC3[builtins.int]"
reveal_type(c) # N: Revealed type is "__main__.ClassC3[builtins.int, builtins.float]"

# k = ClassC3() # TODO
# reveal_type(k) # Revealed type is "__main__.ClassC3[builtins.str]" # TODO
l = ClassC3[int]()
reveal_type(l) # N: Revealed type is "__main__.ClassC3[builtins.int]"
m = ClassC3[int, Unpack[Tuple[float]]]()
reveal_type(m) # N: Revealed type is "__main__.ClassC3[builtins.int, builtins.float]"

class ClassC4(Generic[T1, Unpack[Ts1], T3]): ...

def func_c4(
Expand All @@ -238,6 +322,13 @@ def func_c4(
reveal_type(a) # N: Revealed type is "__main__.ClassC4[Any, Unpack[builtins.tuple[Any, ...]], builtins.str]"
# reveal_type(b) # Revealed type is "__main__.ClassC4[builtins.int, builtins.str]" # TODO
reveal_type(c) # N: Revealed type is "__main__.ClassC4[builtins.int, builtins.float]"

k = ClassC4() # E: Need type annotation for "k"
reveal_type(k) # N: Revealed type is "__main__.ClassC4[Any, Unpack[builtins.tuple[Any, ...]], builtins.str]"
l = ClassC4[int]()
# reveal_type(l) # Revealed type is "__main__.ClassC4[builtins.int, builtins.str]" # TODO
m = ClassC4[int, float]()
reveal_type(m) # N: Revealed type is "__main__.ClassC4[builtins.int, builtins.float]"
[builtins fixtures/tuple.pyi]

[case testTypeVarDefaultsTypeAlias1]
Expand Down
Loading