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

Fix minor issues with keywords Unpack #13854

Merged
merged 1 commit into from
Oct 9, 2022
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
11 changes: 11 additions & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -2704,6 +2704,17 @@ def for_function(callee: CallableType) -> str:
return ""


def wrong_type_arg_count(n: int, act: str, name: str) -> str:
s = f"{n} type arguments"
if n == 0:
s = "no type arguments"
elif n == 1:
s = "1 type argument"
if act == "0":
act = "none"
return f'"{name}" expects {s}, but {act} given'


def find_defining_module(modules: dict[str, MypyFile], typ: CallableType) -> MypyFile | None:
if not typ.definition:
return None
Expand Down
47 changes: 33 additions & 14 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from mypy import errorcodes as codes, message_registry, nodes
from mypy.errorcodes import ErrorCode
from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type
from mypy.messages import MessageBuilder, format_type_bare, quote_type_string
from mypy.messages import MessageBuilder, format_type_bare, quote_type_string, wrong_type_arg_count
from mypy.nodes import (
ARG_NAMED,
ARG_NAMED_OPT,
Expand Down Expand Up @@ -571,6 +571,9 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ
elif fullname in ("typing.Unpack", "typing_extensions.Unpack"):
if not self.api.incomplete_feature_enabled(UNPACK, t):
return AnyType(TypeOfAny.from_error)
if len(t.args) != 1:
self.fail("Unpack[...] requires exactly one type argument", t)
return AnyType(TypeOfAny.from_error)
return UnpackType(self.anal_type(t.args[0]), line=t.line, column=t.column)
return None

Expand Down Expand Up @@ -644,14 +647,28 @@ def analyze_type_with_type_info(
# The class has a Tuple[...] base class so it will be
# represented as a tuple type.
if info.special_alias:
return TypeAliasType(info.special_alias, self.anal_array(args))
return expand_type_alias(
info.special_alias,
self.anal_array(args),
self.fail,
False,
ctx,
use_standard_error=True,
)
return tup.copy_modified(items=self.anal_array(tup.items), fallback=instance)
td = info.typeddict_type
if td is not None:
# The class has a TypedDict[...] base class so it will be
# represented as a typeddict type.
if info.special_alias:
return TypeAliasType(info.special_alias, self.anal_array(args))
return expand_type_alias(
info.special_alias,
self.anal_array(args),
self.fail,
False,
ctx,
use_standard_error=True,
)
# Create a named TypedDictType
return td.copy_modified(
item_types=self.anal_array(list(td.items.values())), fallback=instance
Expand Down Expand Up @@ -1535,16 +1552,11 @@ def fix_instance(
t.args = (any_type,) * len(t.type.type_vars)
return
# Invalid number of type parameters.
n = len(t.type.type_vars)
s = f"{n} type arguments"
if n == 0:
s = "no type arguments"
elif n == 1:
s = "1 type argument"
act = str(len(t.args))
if act == "0":
act = "none"
fail(f'"{t.type.name}" expects {s}, but {act} given', t, code=codes.TYPE_ARG)
fail(
wrong_type_arg_count(len(t.type.type_vars), str(len(t.args)), t.type.name),
t,
code=codes.TYPE_ARG,
)
# Construct the correct number of type arguments, as
# otherwise the type checker may crash as it expects
# things to be right.
Expand All @@ -1561,6 +1573,7 @@ def expand_type_alias(
*,
unexpanded_type: Type | None = None,
disallow_any: bool = False,
use_standard_error: bool = False,
) -> Type:
"""Expand a (generic) type alias target following the rules outlined in TypeAlias docstring.

Expand Down Expand Up @@ -1602,7 +1615,13 @@ def expand_type_alias(
tp.column = ctx.column
return tp
if act_len != exp_len:
fail(f"Bad number of arguments for type alias, expected: {exp_len}, given: {act_len}", ctx)
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, str(act_len), node.name)
else:
msg = f"Bad number of arguments for type alias, expected: {exp_len}, given: {act_len}"
fail(msg, ctx, code=codes.TYPE_ARG)
return set_any_tvars(node, ctx.line, ctx.column, from_error=True)
typ = TypeAliasType(node, args, ctx.line, ctx.column)
assert typ.alias is not None
Expand Down
38 changes: 38 additions & 0 deletions test-data/unit/check-varargs.test
Original file line number Diff line number Diff line change
Expand Up @@ -1043,3 +1043,41 @@ def g(**kwargs: Unpack[Person]) -> int: ...

reveal_type(g) # N: Revealed type is "def (*, name: builtins.str, age: builtins.int) -> builtins.list[builtins.int]"
[builtins fixtures/dict.pyi]

[case testUnpackGenericTypedDictImplicitAnyEnabled]
from typing import Generic, TypeVar
from typing_extensions import Unpack, TypedDict

T = TypeVar("T")
class TD(TypedDict, Generic[T]):
key: str
value: T

def foo(**kwds: Unpack[TD]) -> None: ... # Same as `TD[Any]`
foo(key="yes", value=42)
foo(key="yes", value="ok")
[builtins fixtures/dict.pyi]

[case testUnpackGenericTypedDictImplicitAnyDisabled]
# flags: --disallow-any-generics
from typing import Generic, TypeVar
from typing_extensions import Unpack, TypedDict

T = TypeVar("T")
class TD(TypedDict, Generic[T]):
key: str
value: T

def foo(**kwds: Unpack[TD]) -> None: ... # E: Missing type parameters for generic type "TD"
foo(key="yes", value=42)
foo(key="yes", value="ok")
[builtins fixtures/dict.pyi]

[case testUnpackNoCrashOnEmpty]
from typing_extensions import Unpack

class C:
def __init__(self, **kwds: Unpack) -> None: ... # E: Unpack[...] requires exactly one type argument
class D:
def __init__(self, **kwds: Unpack[int, str]) -> None: ... # E: Unpack[...] requires exactly one type argument
[builtins fixtures/dict.pyi]