Skip to content

Commit

Permalink
Use TypeVar defaults instead of Any when fixing TypeAlias types (PEP …
Browse files Browse the repository at this point in the history
…696) (python#16825)

This PR applies the TypeVar defaults to `TypeAlias` types instead of
using `Any` exclusively, similar to
python#16812.
Again `TypeVarTuple` defaults aren't handled correctly yet.

Ref: python#14851
  • Loading branch information
cdce8p authored Jan 28, 2024
1 parent 717a263 commit 09490c8
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 34 deletions.
1 change: 1 addition & 0 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4711,6 +4711,7 @@ class LongName(Generic[T]): ...
item = get_proper_type(
set_any_tvars(
alias,
[],
ctx.line,
ctx.column,
self.chk.options,
Expand Down
104 changes: 70 additions & 34 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1927,34 +1927,35 @@ def instantiate_type_alias(
if any(unknown_unpack(a) for a in args):
# This type is not ready to be validated, because of unknown total count.
# Note that we keep the kind of Any for consistency.
return set_any_tvars(node, ctx.line, ctx.column, options, special_form=True)
return set_any_tvars(node, [], ctx.line, ctx.column, options, special_form=True)

exp_len = len(node.alias_tvars)
max_tv_count = len(node.alias_tvars)
act_len = len(args)
if (
exp_len > 0
max_tv_count > 0
and act_len == 0
and not (empty_tuple_index and node.tvar_tuple_index is not None)
):
# Interpret bare Alias same as normal generic, i.e., Alias[Any, Any, ...]
return set_any_tvars(
node,
args,
ctx.line,
ctx.column,
options,
disallow_any=disallow_any,
fail=fail,
unexpanded_type=unexpanded_type,
)
if exp_len == 0 and act_len == 0:
if max_tv_count == 0 and act_len == 0:
if no_args:
assert isinstance(node.target, Instance) # type: ignore[misc]
# Note: this is the only case where we use an eager expansion. See more info about
# no_args aliases like L = List in the docstring for TypeAlias class.
return Instance(node.target.type, [], line=ctx.line, column=ctx.column)
return TypeAliasType(node, [], line=ctx.line, column=ctx.column)
if (
exp_len == 0
max_tv_count == 0
and act_len > 0
and isinstance(node.target, Instance) # type: ignore[misc]
and no_args
Expand All @@ -1967,32 +1968,48 @@ def instantiate_type_alias(
if any(isinstance(a, UnpackType) for a in args):
# A variadic unpack in fixed size alias (fixed unpacks must be flattened by the caller)
fail(message_registry.INVALID_UNPACK_POSITION, ctx, code=codes.VALID_TYPE)
return set_any_tvars(node, ctx.line, ctx.column, options, from_error=True)
correct = act_len == exp_len
return set_any_tvars(node, [], ctx.line, ctx.column, options, from_error=True)
min_tv_count = sum(not tv.has_default() for tv in node.alias_tvars)
fill_typevars = act_len != max_tv_count
correct = min_tv_count <= act_len <= max_tv_count
else:
correct = act_len >= exp_len - 1
min_tv_count = sum(
not tv.has_default() and not isinstance(tv, TypeVarTupleType)
for tv in node.alias_tvars
)
correct = act_len >= min_tv_count
for a in args:
if isinstance(a, UnpackType):
unpacked = get_proper_type(a.type)
if isinstance(unpacked, Instance) and unpacked.type.fullname == "builtins.tuple":
# Variadic tuple is always correct.
correct = True
if not correct:
if use_standard_error:
# This is used if type alias is an internal representation of another type,
# for example a generic TypedDict or NamedTuple.
msg = wrong_type_arg_count(exp_len, exp_len, str(act_len), node.name)
else:
if node.tvar_tuple_index is not None:
exp_len_str = f"at least {exp_len - 1}"
fill_typevars = not correct
if fill_typevars:
if not correct:
if use_standard_error:
# This is used if type alias is an internal representation of another type,
# for example a generic TypedDict or NamedTuple.
msg = wrong_type_arg_count(max_tv_count, max_tv_count, str(act_len), node.name)
else:
exp_len_str = str(exp_len)
msg = (
"Bad number of arguments for type alias,"
f" expected: {exp_len_str}, given: {act_len}"
)
fail(msg, ctx, code=codes.TYPE_ARG)
return set_any_tvars(node, ctx.line, ctx.column, options, from_error=True)
if node.tvar_tuple_index is not None:
msg = (
"Bad number of arguments for type alias,"
f" expected: at least {min_tv_count}, given: {act_len}"
)
elif min_tv_count != max_tv_count:
msg = (
"Bad number of arguments for type alias,"
f" expected between {min_tv_count} and {max_tv_count}, given: {act_len}"
)
else:
msg = (
"Bad number of arguments for type alias,"
f" expected: {min_tv_count}, given: {act_len}"
)
fail(msg, ctx, code=codes.TYPE_ARG)
args = []
return set_any_tvars(node, args, ctx.line, ctx.column, options, from_error=True)
elif node.tvar_tuple_index is not None:
# We also need to check if we are not performing a type variable tuple split.
unpack = find_unpack_in_list(args)
Expand All @@ -2006,7 +2023,7 @@ def instantiate_type_alias(
act_suffix = len(args) - unpack - 1
if act_prefix < exp_prefix or act_suffix < exp_suffix:
fail("TypeVarTuple cannot be split", ctx, code=codes.TYPE_ARG)
return set_any_tvars(node, ctx.line, ctx.column, options, from_error=True)
return set_any_tvars(node, [], ctx.line, ctx.column, options, from_error=True)
# TODO: we need to check args validity w.r.t alias.alias_tvars.
# Otherwise invalid instantiations will be allowed in runtime context.
# Note: in type context, these will be still caught by semanal_typeargs.
Expand All @@ -2025,6 +2042,7 @@ def instantiate_type_alias(

def set_any_tvars(
node: TypeAlias,
args: list[Type],
newline: int,
newcolumn: int,
options: Options,
Expand All @@ -2041,7 +2059,33 @@ def set_any_tvars(
type_of_any = TypeOfAny.special_form
else:
type_of_any = TypeOfAny.from_omitted_generics
if disallow_any and node.alias_tvars:
any_type = AnyType(type_of_any, line=newline, column=newcolumn)

env: dict[TypeVarId, Type] = {}
used_any_type = False
has_type_var_tuple_type = False
for tv, arg in itertools.zip_longest(node.alias_tvars, args, fillvalue=None):
if tv is None:
continue
if arg is None:
if tv.has_default():
arg = tv.default
else:
arg = any_type
used_any_type = True
if isinstance(tv, TypeVarTupleType):
# TODO Handle TypeVarTuple defaults
has_type_var_tuple_type = True
arg = UnpackType(Instance(tv.tuple_fallback.type, [any_type]))
args.append(arg)
env[tv.id] = arg
t = TypeAliasType(node, args, newline, newcolumn)
if not has_type_var_tuple_type:
fixed = expand_type(t, env)
assert isinstance(fixed, TypeAliasType)
t.args = fixed.args

if used_any_type and disallow_any and node.alias_tvars:
assert fail is not None
if unexpanded_type:
type_str = (
Expand All @@ -2057,15 +2101,7 @@ def set_any_tvars(
Context(newline, newcolumn),
code=codes.TYPE_ARG,
)
any_type = AnyType(type_of_any, line=newline, column=newcolumn)

args: list[Type] = []
for tv in node.alias_tvars:
if isinstance(tv, TypeVarTupleType):
args.append(UnpackType(Instance(tv.tuple_fallback.type, [any_type])))
else:
args.append(any_type)
return TypeAliasType(node, args, newline, newcolumn)
return t


def flatten_tvars(lists: list[list[T]]) -> list[T]:
Expand Down
161 changes: 161 additions & 0 deletions test-data/unit/check-typevar-defaults.test
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,164 @@ def func_c4(
# 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]"
[builtins fixtures/tuple.pyi]

[case testTypeVarDefaultsTypeAlias1]
# flags: --disallow-any-generics
from typing import Any, Dict, List, Tuple, TypeVar, Union

T1 = TypeVar("T1")
T2 = TypeVar("T2", default=int)
T3 = TypeVar("T3", default=str)
T4 = TypeVar("T4")

TA1 = Dict[T2, T3]

def func_a1(
a: TA1,
b: TA1[float],
c: TA1[float, float],
d: TA1[float, float, float], # E: Bad number of arguments for type alias, expected between 0 and 2, given: 3
) -> None:
reveal_type(a) # N: Revealed type is "builtins.dict[builtins.int, builtins.str]"
reveal_type(b) # N: Revealed type is "builtins.dict[builtins.float, builtins.str]"
reveal_type(c) # N: Revealed type is "builtins.dict[builtins.float, builtins.float]"
reveal_type(d) # N: Revealed type is "builtins.dict[builtins.int, builtins.str]"

TA2 = Tuple[T1, T2, T3]

def func_a2(
a: TA2, # E: Missing type parameters for generic type "TA2"
b: TA2[float],
c: TA2[float, float],
d: TA2[float, float, float],
e: TA2[float, float, float, float], # E: Bad number of arguments for type alias, expected between 1 and 3, given: 4
) -> None:
reveal_type(a) # N: Revealed type is "Tuple[Any, builtins.int, builtins.str]"
reveal_type(b) # N: Revealed type is "Tuple[builtins.float, builtins.int, builtins.str]"
reveal_type(c) # N: Revealed type is "Tuple[builtins.float, builtins.float, builtins.str]"
reveal_type(d) # N: Revealed type is "Tuple[builtins.float, builtins.float, builtins.float]"
reveal_type(e) # N: Revealed type is "Tuple[Any, builtins.int, builtins.str]"

TA3 = Union[Dict[T1, T2], List[T3]]

def func_a3(
a: TA3, # E: Missing type parameters for generic type "TA3"
b: TA3[float],
c: TA3[float, float],
d: TA3[float, float, float],
e: TA3[float, float, float, float], # E: Bad number of arguments for type alias, expected between 1 and 3, given: 4
) -> None:
reveal_type(a) # N: Revealed type is "Union[builtins.dict[Any, builtins.int], builtins.list[builtins.str]]"
reveal_type(b) # N: Revealed type is "Union[builtins.dict[builtins.float, builtins.int], builtins.list[builtins.str]]"
reveal_type(c) # N: Revealed type is "Union[builtins.dict[builtins.float, builtins.float], builtins.list[builtins.str]]"
reveal_type(d) # N: Revealed type is "Union[builtins.dict[builtins.float, builtins.float], builtins.list[builtins.float]]"
reveal_type(e) # N: Revealed type is "Union[builtins.dict[Any, builtins.int], builtins.list[builtins.str]]"

TA4 = Tuple[T1, T4, T2]

def func_a4(
a: TA4, # E: Missing type parameters for generic type "TA4"
b: TA4[float], # E: Bad number of arguments for type alias, expected between 2 and 3, given: 1
c: TA4[float, float],
d: TA4[float, float, float],
e: TA4[float, float, float, float], # E: Bad number of arguments for type alias, expected between 2 and 3, given: 4
) -> None:
reveal_type(a) # N: Revealed type is "Tuple[Any, Any, builtins.int]"
reveal_type(b) # N: Revealed type is "Tuple[Any, Any, builtins.int]"
reveal_type(c) # N: Revealed type is "Tuple[builtins.float, builtins.float, builtins.int]"
reveal_type(d) # N: Revealed type is "Tuple[builtins.float, builtins.float, builtins.float]"
reveal_type(e) # N: Revealed type is "Tuple[Any, Any, builtins.int]"
[builtins fixtures/dict.pyi]

[case testTypeVarDefaultsTypeAlias2]
# flags: --disallow-any-generics
from typing import Any, Generic, ParamSpec

P1 = ParamSpec("P1")
P2 = ParamSpec("P2", default=[int, str])
P3 = ParamSpec("P3", default=...)

class ClassB1(Generic[P2, P3]): ...
TB1 = ClassB1[P2, P3]

def func_b1(
a: TB1,
b: TB1[[float]],
c: TB1[[float], [float]],
d: TB1[[float], [float], [float]], # E: Bad number of arguments for type alias, expected between 0 and 2, given: 3
) -> None:
reveal_type(a) # N: Revealed type is "__main__.ClassB1[[builtins.int, builtins.str], [*Any, **Any]]"
reveal_type(b) # N: Revealed type is "__main__.ClassB1[[builtins.float], [*Any, **Any]]"
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], [*Any, **Any]]"

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

def func_b2(
a: TB2, # E: Missing type parameters for generic type "TB2"
b: TB2[[float]],
c: TB2[[float], [float]],
d: TB2[[float], [float], [float]], # E: Bad number of arguments for type alias, expected between 1 and 2, given: 3
) -> None:
reveal_type(a) # N: Revealed type is "__main__.ClassB2[Any, [builtins.int, builtins.str]]"
reveal_type(b) # N: Revealed type is "__main__.ClassB2[[builtins.float], [builtins.int, builtins.str]]"
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]]"
[builtins fixtures/tuple.pyi]

[case testTypeVarDefaultsTypeAlias3]
# flags: --disallow-any-generics
from typing import Tuple, TypeVar
from typing_extensions import TypeVarTuple, Unpack

T1 = TypeVar("T1")
T3 = TypeVar("T3", default=str)

Ts1 = TypeVarTuple("Ts1")
Ts2 = TypeVarTuple("Ts2", default=Unpack[Tuple[int, str]])
Ts3 = TypeVarTuple("Ts3", default=Unpack[Tuple[float, ...]])
Ts4 = TypeVarTuple("Ts4", default=Unpack[Tuple[()]])

TC1 = Tuple[Unpack[Ts2]]

def func_c1(
a: TC1,
b: TC1[float],
) -> None:
# reveal_type(a) # Revealed type is "Tuple[builtins.int, builtins.str]" # TODO
reveal_type(b) # N: Revealed type is "Tuple[builtins.float]"

TC2 = Tuple[T3, Unpack[Ts3]]

def func_c2(
a: TC2,
b: TC2[int],
c: TC2[int, Unpack[Tuple[()]]],
) -> None:
# reveal_type(a) # Revealed type is "Tuple[builtins.str, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO
# reveal_type(b) # Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO
reveal_type(c) # N: Revealed type is "Tuple[builtins.int]"

TC3 = Tuple[T3, Unpack[Ts4]]

def func_c3(
a: TC3,
b: TC3[int],
c: TC3[int, Unpack[Tuple[float]]],
) -> None:
# reveal_type(a) # Revealed type is "Tuple[builtins.str]" # TODO
reveal_type(b) # N: Revealed type is "Tuple[builtins.int]"
reveal_type(c) # N: Revealed type is "Tuple[builtins.int, builtins.float]"

TC4 = Tuple[T1, Unpack[Ts1], T3]

def func_c4(
a: TC4, # E: Missing type parameters for generic type "TC4"
b: TC4[int],
c: TC4[int, float],
) -> None:
reveal_type(a) # N: Revealed type is "Tuple[Any, Unpack[builtins.tuple[Any, ...]], builtins.str]"
# reveal_type(b) # Revealed type is "Tuple[builtins.int, builtins.str]" # TODO
reveal_type(c) # N: Revealed type is "Tuple[builtins.int, builtins.float]"
[builtins fixtures/tuple.pyi]

0 comments on commit 09490c8

Please sign in to comment.