Skip to content

Commit 45a787b

Browse files
committed
Use TypeVar defaults to fix instances
1 parent 11c984d commit 45a787b

File tree

3 files changed

+105
-26
lines changed

3 files changed

+105
-26
lines changed

mypy/messages.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2910,12 +2910,15 @@ def for_function(callee: CallableType) -> str:
29102910
return ""
29112911

29122912

2913-
def wrong_type_arg_count(n: int, act: str, name: str) -> str:
2914-
s = f"{n} type arguments"
2915-
if n == 0:
2916-
s = "no type arguments"
2917-
elif n == 1:
2918-
s = "1 type argument"
2913+
def wrong_type_arg_count(low: int, high: int, act: str, name: str) -> str:
2914+
if low == high:
2915+
s = f"{low} type arguments"
2916+
if low == 0:
2917+
s = "no type arguments"
2918+
elif low == 1:
2919+
s = "1 type argument"
2920+
else:
2921+
s = f"between {low} and {high} type arguments"
29192922
if act == "0":
29202923
act = "none"
29212924
return f'"{name}" expects {s}, but {act} given'

mypy/typeanal.py

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from mypy import errorcodes as codes, message_registry, nodes
1111
from mypy.errorcodes import ErrorCode
12+
from mypy.expandtype import expand_type
1213
from mypy.messages import MessageBuilder, format_type_bare, quote_type_string, wrong_type_arg_count
1314
from mypy.nodes import (
1415
ARG_NAMED,
@@ -75,6 +76,7 @@
7576
TypeOfTypeList,
7677
TypeQuery,
7778
TypeType,
79+
TypeVarId,
7880
TypeVarLikeType,
7981
TypeVarTupleType,
8082
TypeVarType,
@@ -1679,25 +1681,39 @@ def fix_instance(
16791681
16801682
Also emit a suitable error if this is not due to implicit Any's.
16811683
"""
1682-
if len(t.args) == 0:
1683-
if use_generic_error:
1684-
fullname: str | None = None
1685-
else:
1686-
fullname = t.type.fullname
1687-
any_type = get_omitted_any(disallow_any, fail, note, t, options, fullname, unexpanded_type)
1688-
t.args = (any_type,) * len(t.type.type_vars)
1689-
return
1690-
# Invalid number of type parameters.
1691-
fail(
1692-
wrong_type_arg_count(len(t.type.type_vars), str(len(t.args)), t.type.name),
1693-
t,
1694-
code=codes.TYPE_ARG,
1695-
)
1696-
# Construct the correct number of type arguments, as
1697-
# otherwise the type checker may crash as it expects
1698-
# things to be right.
1699-
t.args = tuple(AnyType(TypeOfAny.from_error) for _ in t.type.type_vars)
1700-
t.invalid = True
1684+
arg_count = len(t.args)
1685+
max_tv_count = len(t.type.type_vars)
1686+
args: list[Type] = [*(t.args[:max_tv_count])]
1687+
any_type: AnyType | None = None
1688+
env: dict[TypeVarId, Type] = {}
1689+
for tv, arg in itertools.zip_longest(t.type.defn.type_vars, t.args, fillvalue=None):
1690+
if tv is None:
1691+
continue
1692+
if arg is None:
1693+
if tv.has_default():
1694+
arg = tv.default
1695+
else:
1696+
if any_type is None:
1697+
fullname = None if use_generic_error else t.type.fullname
1698+
any_type = get_omitted_any(
1699+
disallow_any, fail, note, t, options, fullname, unexpanded_type
1700+
)
1701+
arg = any_type
1702+
args.append(arg)
1703+
env[tv.id] = arg
1704+
t.args = tuple(args)
1705+
fixed = expand_type(t, env)
1706+
assert isinstance(fixed, Instance)
1707+
t.args = fixed.args
1708+
1709+
min_tv_count = sum(tv.has_default() is False for tv in t.type.defn.type_vars)
1710+
if arg_count != 0 and not (min_tv_count <= arg_count <= max_tv_count):
1711+
fail(
1712+
wrong_type_arg_count(min_tv_count, max_tv_count, str(arg_count), t.type.name),
1713+
t,
1714+
code=codes.TYPE_ARG,
1715+
)
1716+
t.invalid = True
17011717

17021718

17031719
def expand_type_alias(
@@ -1756,7 +1772,7 @@ def expand_type_alias(
17561772
if use_standard_error:
17571773
# This is used if type alias is an internal representation of another type,
17581774
# for example a generic TypedDict or NamedTuple.
1759-
msg = wrong_type_arg_count(exp_len, str(act_len), node.name)
1775+
msg = wrong_type_arg_count(exp_len, exp_len, str(act_len), node.name)
17601776
else:
17611777
msg = f"Bad number of arguments for type alias, expected: {exp_len}, given: {act_len}"
17621778
fail(msg, ctx, code=codes.TYPE_ARG)

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,63 @@ def func_c1(x: Union[int, Callable[[Unpack[Ts1]], None]]) -> Tuple[Unpack[Ts1]]:
115115
# reveal_type(func_c1(callback1)) # Revealed type is "builtins.tuple[str]" # TODO
116116
# reveal_type(func_c1(2)) # Revealed type is "builtins.tuple[builtins.int, builtins.str]" # TODO
117117
[builtins fixtures/tuple.pyi]
118+
119+
[case testTypeVarDefaultsClass1]
120+
from typing import Generic, TypeVar
121+
122+
T1 = TypeVar("T1")
123+
T2 = TypeVar("T2", default=int)
124+
T3 = TypeVar("T3", default=str)
125+
126+
class ClassA1(Generic[T2, T3]): ...
127+
128+
def func_a1(
129+
a: ClassA1,
130+
b: ClassA1[float],
131+
c: ClassA1[float, float],
132+
) -> None:
133+
reveal_type(a) # N: Revealed type is "__main__.ClassA1[builtins.int, builtins.str]"
134+
reveal_type(b) # N: Revealed type is "__main__.ClassA1[builtins.float, builtins.str]"
135+
reveal_type(c) # N: Revealed type is "__main__.ClassA1[builtins.float, builtins.float]"
136+
137+
class ClassA2(Generic[T1, T2, T3]): ...
138+
139+
def func_a2(
140+
a: ClassA2,
141+
b: ClassA2[float],
142+
c: ClassA2[float, float],
143+
d: ClassA2[float, float, float],
144+
) -> None:
145+
reveal_type(a) # N: Revealed type is "__main__.ClassA2[Any, builtins.int, builtins.str]"
146+
reveal_type(b) # N: Revealed type is "__main__.ClassA2[builtins.float, builtins.int, builtins.str]"
147+
reveal_type(c) # N: Revealed type is "__main__.ClassA2[builtins.float, builtins.float, builtins.str]"
148+
reveal_type(d) # N: Revealed type is "__main__.ClassA2[builtins.float, builtins.float, builtins.float]"
149+
150+
[case testTypeVarDefaultsClass2]
151+
from typing import Generic, ParamSpec
152+
153+
P1 = ParamSpec("P1")
154+
P2 = ParamSpec("P2", default=(int, str))
155+
P3 = ParamSpec("P3", default=...)
156+
157+
class ClassB1(Generic[P2, P3]): ...
158+
159+
def func_b1(
160+
a: ClassB1,
161+
b: ClassB1[[float]],
162+
c: ClassB1[[float], [float]],
163+
) -> None:
164+
reveal_type(a) # N: Revealed type is "__main__.ClassB1[[builtins.int, builtins.str], ...]"
165+
reveal_type(b) # N: Revealed type is "__main__.ClassB1[[builtins.float], ...]"
166+
reveal_type(c) # N: Revealed type is "__main__.ClassB1[[builtins.float], [builtins.float]]"
167+
168+
class ClassB2(Generic[P1, P2]): ...
169+
170+
def func_b2(
171+
a: ClassB2,
172+
b: ClassB2[[float]],
173+
c: ClassB2[[float], [float]],
174+
) -> None:
175+
reveal_type(a) # N: Revealed type is "__main__.ClassB2[Any, [builtins.int, builtins.str]]"
176+
reveal_type(b) # N: Revealed type is "__main__.ClassB2[[builtins.float], [builtins.int, builtins.str]]"
177+
reveal_type(c) # N: Revealed type is "__main__.ClassB2[[builtins.float], [builtins.float]]"

0 commit comments

Comments
 (0)