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

Support TypeAliasType #16926

Merged
merged 18 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Fix unbound PS or TVT in Callable
  • Loading branch information
hamdanal committed Feb 21, 2024
commit aaf19dded630da0d189e04b33f2c5c8c595f4237
7 changes: 6 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3842,10 +3842,15 @@ def analyze_type_alias_type_params(self, rvalue: CallExpr) -> TypeVarLikeList:
base,
code=codes.TYPE_VAR,
)
sym = self.lookup_qualified(base.name, base)
if sym and sym.fullname in ("typing.Unpack", "typing_extensions.Unpack"):
self.note(
"Don't Unpack type variables in type_params", base, code=codes.TYPE_VAR
)
continue
if tvar in declared_tvars:
self.fail(
"Duplicate type variables in type_params argument to TypeAliasType",
f'Duplicate type variable "{tvar[0]}" in type_params argument to TypeAliasType',
base,
code=codes.TYPE_VAR,
)
Expand Down
27 changes: 25 additions & 2 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
if tvar_def is None:
if self.allow_unbound_tvars:
return t
if self.has_type_params:
if self.defining_alias and self.has_type_params:
msg = f'ParamSpec "{t.name}" is not included in type_params'
else:
msg = f'ParamSpec "{t.name}" is unbound'
Expand Down Expand Up @@ -386,7 +386,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
if tvar_def is None:
if self.allow_unbound_tvars:
return t
if self.has_type_params:
if self.defining_alias and self.has_type_params:
msg = f'TypeVarTuple "{t.name}" is not included in type_params'
else:
msg = f'TypeVarTuple "{t.name}" is unbound'
Expand Down Expand Up @@ -1258,6 +1258,19 @@ def analyze_callable_args_for_paramspec(
AnyType(TypeOfAny.explicit), ret_type=ret_type, fallback=fallback
)
return None
elif (
self.defining_alias
and self.has_type_params
and tvar_def not in self.allowed_alias_tvars
):
self.fail(
f'ParamSpec "{callable_args.name}" is not included in type_params',
callable_args,
code=codes.VALID_TYPE,
)
return callable_with_ellipsis(
AnyType(TypeOfAny.special_form), ret_type=ret_type, fallback=fallback
)

return CallableType(
[
Expand Down Expand Up @@ -1439,6 +1452,16 @@ def analyze_callable_args(
and self.refers_to_full_names(arg, ("typing_extensions.Unpack", "typing.Unpack"))
or isinstance(arg, UnpackType)
):
if self.defining_alias and self.has_type_params:
tvar_likes = self.find_type_var_likes(arg)
for name, tvar_expr in tvar_likes:
if (name, tvar_expr) not in self.allowed_alias_tvars:
self.fail(
f'Type variable "{name}" is not included in type_params',
arglist,
code=codes.VALID_TYPE,
)
return None
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this to handle Callable[[Unpack[Ts]], Ret] when Ts is not declared in the type parameters. It was causing a crash.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'll revert this part, I don't think it is the correct solution here.

Copy link
Collaborator Author

@hamdanal hamdanal Feb 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK so looks like this already crashes on master with old aliases https://mypy-play.net/?mypy=master&python=3.12&gist=744b559e656f6f35cd5762bddf979038. I'll open an issue to track it.

Edit: #16937

if seen_unpack:
# Multiple unpacks, preserve them, so we can give an error later.
if i == len(arglist.items) - 1 and not invalid_unpacks:
Expand Down
45 changes: 40 additions & 5 deletions test-data/unit/check-type-aliases.test
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,8 @@ Ts = TypeVarTuple("Ts")
Ts1 = TypeVarTuple("Ts1")
P = ParamSpec("P")

Ta0 = TypeAliasType("Ta0", int, type_params=(T, T)) # E: Duplicate type variable "T" in type_params argument to TypeAliasType

Ta1 = TypeAliasType("Ta1", int, type_params=K) # E: Tuple literal expected as the type_params argument to TypeAliasType

Ta2 = TypeAliasType("Ta2", int, type_params=(None,)) # E: Free type variable expected in type_params argument to TypeAliasType
Expand Down Expand Up @@ -1170,20 +1172,53 @@ unbound_ps_alias: Ta7[[int], str] # E: Bracketed expression "[...]" is not vali
# E: Bad number of arguments for type alias, expected 0, given 2
reveal_type(unbound_ps_alias) # N: Revealed type is "__main__.G[Any, Any]"

# TODO this does not work yet, it should report unbound P on the next line
# Ta8 = TypeAliasType("Ta8", Callable[P, int])
# unbound_ps_alias2: Ta8[int]
# reveal_type(unbound_ps_alias2)
Ta8 = TypeAliasType("Ta8", Callable[P, int]) # E: ParamSpec "P" is not included in type_params
unbound_ps_alias2: Ta8[int] # E: Bad number of arguments for type alias, expected 0, given 1
reveal_type(unbound_ps_alias2) # N: Revealed type is "def [P] (*Any, **Any) -> builtins.int"

Ta9 = TypeAliasType("Ta9", Callable[P, T]) # E: ParamSpec "P" is not included in type_params \
# E: Type variable "T" is not included in type_params
unbound_ps_alias3: Ta9[int, str] # E: Bad number of arguments for type alias, expected 0, given 2
reveal_type(unbound_ps_alias3) # N: Revealed type is "def [P] (*Any, **Any) -> Any"

Ta10 = TypeAliasType("Ta10", Callable[[Unpack[Ts]], str]) # E: Type variable "Ts" is not included in type_params
unbound_tvt_alias2: Ta10[int] # E: Bad number of arguments for type alias, expected 0, given 1
reveal_type(unbound_tvt_alias2) # N: Revealed type is "Any"

[builtins fixtures/dict.pyi]

[case testTypeAliasType312]
# flags: --python-version 3.12
from typing import Union, TypeAliasType
from typing import Callable, TypeAliasType, TypeVar, TypeVarTuple

T = TypeVar("T")
Ts = TypeVarTuple("Ts")

TestType = TypeAliasType("TestType", int | str)
x: TestType = 42
y: TestType = 'a'
z: TestType = object() # E: Incompatible types in assignment (expression has type "object", variable has type "Union[int, str]")

BadAlias1 = TypeAliasType("BadAlias1", tuple[*Ts]) # E: TypeVarTuple "Ts" is not included in type_params
ba1: BadAlias1[int] # E: Bad number of arguments for type alias, expected 0, given 1
reveal_type(ba1) # N: Revealed type is "builtins.tuple[Any, ...]"

BadAlias2 = TypeAliasType("BadAlias2", Callable[[*Ts], str]) # E: Type variable "Ts" is not included in type_params
ba2: BadAlias2[int] # E: Bad number of arguments for type alias, expected 0, given 1
reveal_type(ba2) # N: Revealed type is "Any"

[builtins fixtures/tuple.pyi]
[typing fixtures/typing-full.pyi]

[case testTypeAliasTypeNoUnpackInTypeParams311]
# flags: --python-version 3.11
from typing_extensions import TypeAliasType, TypeVar, TypeVarTuple, Unpack

T = TypeVar("T")
Ts = TypeVarTuple("Ts")

Ta1 = TypeAliasType("Ta1", None, type_params=(*Ts,)) # E: can't use starred expression here
Ta2 = TypeAliasType("Ta2", None, type_params=(Unpack[Ts],)) # E: Free type variable expected in type_params argument to TypeAliasType \
# N: Don't Unpack type variables in type_params

[builtins fixtures/tuple.pyi]
Loading