Skip to content

Commit

Permalink
Apply TypeVar defaults to callables (PEP 696) (#16842)
Browse files Browse the repository at this point in the history
Implement type application for callables with TypeVar defaults.
Similar to previous PRs, support for TypeVarTuples is still TODO.

Ref: #14851
  • Loading branch information
cdce8p authored Jan 31, 2024
1 parent e40935e commit 55247c4
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 19 deletions.
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

0 comments on commit 55247c4

Please sign in to comment.