Skip to content

Commit

Permalink
Tests for literals
Browse files Browse the repository at this point in the history
  • Loading branch information
A5rocks committed Dec 26, 2021
1 parent c507152 commit d202d1e
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 21 deletions.
34 changes: 13 additions & 21 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,18 +423,15 @@ def analyze_type_with_type_info(
if len(args) > 0 and info.fullname == 'builtins.tuple':
fallback = Instance(info, [AnyType(TypeOfAny.special_form)], ctx.line)
return TupleType(self.anal_array(args), fallback, ctx.line)
# Only allow ParamSpec literals if there's a ParamSpec arg type:
# This might not be necessary.
allow_param_spec_literal = any(isinstance(tvar, ParamSpecType) for tvar in info.defn.type_vars)

# Analyze arguments and (usually) construct Instance type. The
# number of type arguments and their values are
# checked only later, since we do not always know the
# valid count at this point. Thus we may construct an
# Instance with an invalid number of type arguments.
instance = Instance(info, self.anal_array(args, allow_param_spec=True,
allow_param_spec_literal=allow_param_spec_literal),
instance = Instance(info, self.anal_array(args, allow_param_spec=True),
ctx.line, ctx.column)

# Check type argument count.
if len(instance.args) != len(info.type_vars) and not self.defining_alias:
fix_instance(instance, self.fail, self.note,
Expand Down Expand Up @@ -566,9 +563,15 @@ def visit_deleted_type(self, t: DeletedType) -> Type:
return t

def visit_type_list(self, t: TypeList) -> Type:
self.fail('Bracketed expression "[...]" is not valid as a type', t)
self.note('Did you mean "List[...]"?', t)
return AnyType(TypeOfAny.from_error)
# paramspec literal (Z[[int, str, Whatever]])
# TODO: invalid usage restrictions
params = self.analyze_callable_args(t)
if params:
ts, kinds, names = params
# bind these types
return Parameters(self.anal_array(ts), kinds, names)
else:
return AnyType(TypeOfAny.from_error)

def visit_callable_argument(self, t: CallableArgument) -> Type:
self.fail('Invalid type', t)
Expand Down Expand Up @@ -1040,21 +1043,10 @@ def is_defined_type_var(self, tvar: str, context: Context) -> bool:
def anal_array(self,
a: Iterable[Type],
nested: bool = True, *,
allow_param_spec: bool = False,
allow_param_spec_literal: bool = False) -> List[Type]:
allow_param_spec: bool = False) -> List[Type]:
res: List[Type] = []
for t in a:
if allow_param_spec_literal and isinstance(t, TypeList):
# paramspec literal (Z[[int, str, Whatever]])
params = self.analyze_callable_args(t)
if params:
ts, kinds, names = params
# bind these types
res.append(Parameters(self.anal_array(ts), kinds, names))
else:
res.append(AnyType(TypeOfAny.from_error))
else:
res.append(self.anal_type(t, nested, allow_param_spec=allow_param_spec))
res.append(self.anal_type(t, nested, allow_param_spec=allow_param_spec))
return res

def anal_type(self, t: Type, nested: bool = True, *, allow_param_spec: bool = False) -> Type:
Expand Down
51 changes: 51 additions & 0 deletions test-data/unit/check-parameter-specification.test
Original file line number Diff line number Diff line change
Expand Up @@ -406,3 +406,54 @@ with f() as x:
pass
[builtins fixtures/dict.pyi]
[typing fixtures/typing-full.pyi]

[case testParamSpecLiterals]
from typing_extensions import ParamSpec, TypeAlias
from typing import Generic, TypeVar

P = ParamSpec("P")
T = TypeVar("T")

class Z(Generic[P]): ...

# literals can be applied
n: Z[[int]]

# type aliases too
nt1 = Z[[int]]
nt2: TypeAlias = Z[[int]]

unt1: nt1
unt2: nt2

# literals actually keep types
reveal_type(n) # N: Revealed type is "__main__.Z[[builtins.int]]"
reveal_type(unt1) # N: Revealed type is "__main__.Z[[builtins.int]]"
reveal_type(unt2) # N: Revealed type is "__main__.Z[[builtins.int]]"

# passing into a function keeps the type
def fT(a: T) -> T: ...
def fP(a: Z[P]) -> Z[P]: ...

reveal_type(fT(n)) # N: Revealed type is "__main__.Z*[[builtins.int]]"
reveal_type(fP(n)) # N: Revealed type is "__main__.Z[[builtins.int]]"

# literals can be in function args and return type
def k(a: Z[[int]]) -> Z[[str]]: ...

# functions work
reveal_type(k(n)) # N: Revealed type is "__main__.Z[[builtins.str]]"

# literals can be matched in arguments
def kb(a: Z[[bytes]]) -> Z[[str]]: ...

# TODO: return type is a bit weird, return Any
reveal_type(kb(n)) # N: Revealed type is "__main__.Z[[builtins.str]]" \
# E: Argument 1 to "kb" has incompatible type "Z[[int]]"; expected "Z[[bytes]]"


# TODO(PEP612): fancy "aesthetic" syntax defined in PEP
# n2: Z[bytes]
#
# reveal_type(kb(n2)) # N: Revealed type is "__main__.Z[[builtins.str]]"
[builtins fixtures/tuple.pyi]

0 comments on commit d202d1e

Please sign in to comment.