From 1c5c612ebcd323cfcc5afcf9eac74818330b2829 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sun, 24 Apr 2022 11:47:13 -0600 Subject: [PATCH 01/10] Use `tuple[Any, ...]` and ``dict[str, Any]` as fallbacks for `ParamSpec.(Kw)args` --- mypy/semanal.py | 2 +- mypy/tvar_scope.py | 9 ++++++--- mypy/typeanal.py | 20 ++++++++------------ mypy/types.py | 39 ++++++++++++++++++++++++++++----------- mypy/typevars.py | 2 +- 5 files changed, 44 insertions(+), 28 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 985d5217cc08..43116ad0074d 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1369,7 +1369,7 @@ class Foo(Bar, Generic[T]): ... del base_type_exprs[i] tvar_defs: List[TypeVarLikeType] = [] for name, tvar_expr in declared_tvars: - tvar_def = self.tvar_scope.bind_new(name, tvar_expr) + tvar_def = self.tvar_scope.bind_new(name, tvar_expr, named_type_func=self.named_type) tvar_defs.append(tvar_def) return base_type_exprs, tvar_defs, is_protocol diff --git a/mypy/tvar_scope.py b/mypy/tvar_scope.py index c1fe1cd6be35..c794435fbaf2 100644 --- a/mypy/tvar_scope.py +++ b/mypy/tvar_scope.py @@ -1,6 +1,7 @@ -from typing import Optional, Dict, Union +from typing import Optional, Dict, Union, Callable from mypy.types import ( TypeVarLikeType, TypeVarType, ParamSpecType, ParamSpecFlavor, TypeVarId, TypeVarTupleType, + Instance ) from mypy.nodes import ( ParamSpecExpr, TypeVarExpr, TypeVarLikeExpr, SymbolTableNode, TypeVarTupleExpr, @@ -61,7 +62,9 @@ def class_frame(self, namespace: str) -> 'TypeVarLikeScope': """A new scope frame for binding a class. Prohibits *this* class's tvars""" return TypeVarLikeScope(self.get_function_scope(), True, self, namespace=namespace) - def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType: + def bind_new( + self, name: str, tvar_expr: TypeVarLikeExpr, *, named_type_func: Callable[..., Instance] + ) -> TypeVarLikeType: if self.is_class_scope: self.class_id += 1 i = self.class_id @@ -88,7 +91,7 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType: tvar_expr.fullname, i, flavor=ParamSpecFlavor.BARE, - upper_bound=tvar_expr.upper_bound, + named_type_func=named_type_func, line=tvar_expr.line, column=tvar_expr.column ) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index eee8a43c25f3..c2b0913dae3a 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -222,7 +222,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) # Change the line number return ParamSpecType( tvar_def.name, tvar_def.fullname, tvar_def.id, tvar_def.flavor, - tvar_def.upper_bound, line=t.line, column=t.column, + named_type_func=self.named_type, line=t.line, column=t.column, ) if isinstance(sym.node, TypeVarExpr) and tvar_def is not None and self.defining_alias: self.fail('Can\'t use bound type variable "{}"' @@ -717,7 +717,7 @@ def anal_star_arg_type(self, t: Type, kind: ArgKind, nested: bool) -> Type: else: assert False, kind return ParamSpecType(tvar_def.name, tvar_def.fullname, tvar_def.id, flavor, - upper_bound=self.named_type('builtins.object'), + named_type_func=self.named_type, line=t.line, column=t.column) return self.anal_type(t, nested=nested) @@ -855,13 +855,11 @@ def analyze_callable_args_for_paramspec( if not isinstance(tvar_def, ParamSpecType): return None - # TODO: Use tuple[...] or Mapping[..] instead? - obj = self.named_type('builtins.object') return CallableType( [ParamSpecType(tvar_def.name, tvar_def.fullname, tvar_def.id, ParamSpecFlavor.ARGS, - upper_bound=obj), + named_type_func=self.named_type), ParamSpecType(tvar_def.name, tvar_def.fullname, tvar_def.id, ParamSpecFlavor.KWARGS, - upper_bound=obj)], + named_type_func=self.named_type)], [nodes.ARG_STAR, nodes.ARG_STAR2], [None, None], ret_type=ret_type, @@ -891,8 +889,6 @@ def analyze_callable_args_for_concatenate( if not isinstance(tvar_def, ParamSpecType): return None - # TODO: Use tuple[...] or Mapping[..] instead? - obj = self.named_type('builtins.object') # ick, CallableType should take ParamSpecType prefix = tvar_def.prefix # we don't set the prefix here as generic arguments will get updated at some point @@ -900,9 +896,9 @@ def analyze_callable_args_for_concatenate( return CallableType( [*prefix.arg_types, ParamSpecType(tvar_def.name, tvar_def.fullname, tvar_def.id, ParamSpecFlavor.ARGS, - upper_bound=obj), + named_type_func=self.named_type), ParamSpecType(tvar_def.name, tvar_def.fullname, tvar_def.id, ParamSpecFlavor.KWARGS, - upper_bound=obj)], + named_type_func=self.named_type)], [*prefix.arg_kinds, nodes.ARG_STAR, nodes.ARG_STAR2], [*prefix.arg_names, None, None], ret_type=ret_type, @@ -1134,7 +1130,7 @@ def bind_function_type_variables( assert var_node, "Binding for function type variable not found within function" var_expr = var_node.node assert isinstance(var_expr, TypeVarLikeExpr) - self.tvar_scope.bind_new(var.name, var_expr) + self.tvar_scope.bind_new(var.name, var_expr, named_type_func=self.named_type) return fun_type.variables typevars = self.infer_type_variables(fun_type) # Do not define a new type variable if already defined in scope. @@ -1144,7 +1140,7 @@ def bind_function_type_variables( for name, tvar in typevars: if not self.tvar_scope.allow_binding(tvar.fullname): self.fail('Type variable "{}" is bound by an outer class'.format(name), defn) - self.tvar_scope.bind_new(name, tvar) + self.tvar_scope.bind_new(name, tvar, named_type_func=self.named_type) binding = self.tvar_scope.get_binding(tvar.fullname) assert binding is not None defs.append(binding) diff --git a/mypy/types.py b/mypy/types.py index e43b73f093b8..b4e2df36ae8b 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -6,7 +6,7 @@ from typing import ( Any, TypeVar, Dict, List, Tuple, cast, Set, Optional, Union, Iterable, NamedTuple, - Sequence + Sequence, Callable ) from typing_extensions import ClassVar, Final, TYPE_CHECKING, overload, TypeAlias as _TypeAlias @@ -583,19 +583,36 @@ class ParamSpecType(TypeVarLikeType): prefix: 'Parameters' def __init__( - self, name: str, fullname: str, id: Union[TypeVarId, int], flavor: int, - upper_bound: Type, *, line: int = -1, column: int = -1, - prefix: Optional['Parameters'] = None - ) -> None: - super().__init__(name, fullname, id, upper_bound, line=line, column=column) + self, name: str, fullname: str, id: Union[TypeVarId, int], flavor: int, *, + upper_bound: Optional[Type] = None, + named_type_func: Optional[Callable[..., 'Instance']] = None, line: int = -1, + column: int = -1, prefix: Optional['Parameters'] = None) -> None: self.flavor = flavor + if upper_bound is None: + assert named_type_func is not None, ( + "Either 'upper_bound' or 'named_type_func' must be specified" + ) + upper_bound = self.get_fallback(named_type_func) + super().__init__(name, fullname, id, upper_bound=upper_bound, line=line, column=column) self.prefix = prefix or Parameters([], [], []) + def get_fallback(self, named_type_func: Callable[..., 'Instance']) -> 'Instance': + if self.flavor == ParamSpecFlavor.BARE: + return named_type_func('builtins.object') + elif self.flavor == ParamSpecFlavor.ARGS: + return named_type_func('builtins.tuple') + else: + return named_type_func( + 'builtins.dict', + [named_type_func('builtins.str'), AnyType(TypeOfAny.special_form)] + ) + @staticmethod def new_unification_variable(old: 'ParamSpecType') -> 'ParamSpecType': new_id = TypeVarId.new(meta_level=1) - return ParamSpecType(old.name, old.fullname, new_id, old.flavor, old.upper_bound, - line=old.line, column=old.column, prefix=old.prefix) + return ParamSpecType(old.name, old.fullname, new_id, old.flavor, + upper_bound=old.upper_bound, line=old.line, column=old.column, + prefix=old.prefix) def with_flavor(self, flavor: int) -> 'ParamSpecType': return ParamSpecType(self.name, self.fullname, self.id, flavor, @@ -610,7 +627,7 @@ def copy_modified(self, *, self.fullname, id if id is not _dummy else self.id, flavor if flavor is not _dummy else self.flavor, - self.upper_bound, + upper_bound=self.upper_bound, line=self.line, column=self.column, prefix=prefix if prefix is not _dummy else self.prefix, @@ -656,7 +673,7 @@ def deserialize(cls, data: JsonDict) -> 'ParamSpecType': data['fullname'], data['id'], data['flavor'], - deserialize_type(data['upper_bound']), + upper_bound=deserialize_type(data['upper_bound']), prefix=Parameters.deserialize(data['prefix']) ) @@ -1739,7 +1756,7 @@ def param_spec(self) -> Optional[ParamSpecType]: # TODO: confirm that all arg kinds are positional prefix = Parameters(self.arg_types[:-2], self.arg_kinds[:-2], self.arg_names[:-2]) return ParamSpecType(arg_type.name, arg_type.fullname, arg_type.id, ParamSpecFlavor.BARE, - arg_type.upper_bound, prefix=prefix) + upper_bound=arg_type.upper_bound, prefix=prefix) def expand_param_spec(self, c: Union['CallableType', Parameters], diff --git a/mypy/typevars.py b/mypy/typevars.py index b49194f342e0..cacd8db4a67a 100644 --- a/mypy/typevars.py +++ b/mypy/typevars.py @@ -23,7 +23,7 @@ def fill_typevars(typ: TypeInfo) -> Union[Instance, TupleType]: ) else: assert isinstance(tv, ParamSpecType) - tv = ParamSpecType(tv.name, tv.fullname, tv.id, tv.flavor, tv.upper_bound, + tv = ParamSpecType(tv.name, tv.fullname, tv.id, tv.flavor, upper_bound=tv.upper_bound, line=-1, column=-1) tvs.append(tv) inst = Instance(typ, tvs) From a8d623ee3c7dddf8436215fb2dfde8b6d0631075 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sun, 24 Apr 2022 12:16:55 -0600 Subject: [PATCH 02/10] Fix newly broken tests --- .../unit/check-parameter-specification.test | 52 +++++++-------- test-data/unit/fixtures/paramspec.pyi | 66 +++++++++++++++++++ 2 files changed, 92 insertions(+), 26 deletions(-) create mode 100644 test-data/unit/fixtures/paramspec.pyi diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 2242b79d4b64..0821c73bce74 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -11,7 +11,7 @@ P2 = ParamSpec("P2", contravariant=True) # E: Only the first argument to ParamS P3 = ParamSpec("P3", bound=int) # E: Only the first argument to ParamSpec has defined semantics P4 = ParamSpec("P4", int, str) # E: Only the first argument to ParamSpec has defined semantics P5 = ParamSpec("P5", covariant=True, bound=int) # E: Only the first argument to ParamSpec has defined semantics -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testParamSpecLocations] from typing import Callable, List @@ -35,7 +35,7 @@ def foo5(x: Callable[[int, str], P]) -> None: ... # E: Invalid location for Par def foo6(x: Callable[[P], int]) -> None: ... # E: Invalid location for ParamSpec "P" \ # N: You can use ParamSpec as the first argument to Callable, e.g., 'Callable[P, int]' -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testParamSpecContextManagerLike] from typing import Callable, List, Iterator, TypeVar @@ -51,7 +51,7 @@ def whatever(x: int) -> Iterator[int]: reveal_type(whatever) # N: Revealed type is "def (x: builtins.int) -> builtins.list[builtins.int]" reveal_type(whatever(217)) # N: Revealed type is "builtins.list[builtins.int]" -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testInvalidParamSpecType] # flags: --python-version 3.10 @@ -70,7 +70,7 @@ P = ParamSpec('P') def f(x: Callable[P, int]) -> None: ... reveal_type(f) # N: Revealed type is "def [P] (x: def (*P.args, **P.kwargs) -> builtins.int)" -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testParamSpecSimpleFunction] from typing import Callable, TypeVar @@ -83,7 +83,7 @@ def changes_return_type_to_str(x: Callable[P, int]) -> Callable[P, str]: ... def returns_int(a: str, b: bool) -> int: ... reveal_type(changes_return_type_to_str(returns_int)) # N: Revealed type is "def (a: builtins.str, b: builtins.bool) -> builtins.str" -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testParamSpecSimpleClass] from typing import Callable, TypeVar, Generic @@ -199,7 +199,7 @@ g: Any reveal_type(f(g)) # N: Revealed type is "def (*Any, **Any) -> builtins.str" f(g)(1, 3, x=1, y=2) -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testParamSpecDecoratorImplementation] from typing import Callable, Any, TypeVar, List @@ -556,7 +556,7 @@ a: Callable[[int, bytes], str] b: Callable[[str, bytes], str] reveal_type(f(a, b)) # N: Revealed type is "def (builtins.bytes) -> builtins.str" -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testParamSpecConcatenateInReturn] from typing_extensions import ParamSpec, Concatenate @@ -569,7 +569,7 @@ def f(i: Callable[Concatenate[int, P], str]) -> Callable[Concatenate[int, P], st n: Callable[[int, bytes], str] reveal_type(f(n)) # N: Revealed type is "def (builtins.int, builtins.bytes) -> builtins.str" -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testParamSpecConcatenateNamedArgs] # flags: --strict-concatenate @@ -592,7 +592,7 @@ def f2(c: Callable[P, R]) -> Callable[Concatenate[int, P], R]: # reason for rejection: f2(lambda x: 42)(42, x=42) -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [out] main:10: error: invalid syntax [out version>=3.8] @@ -619,7 +619,7 @@ def f2(c: Callable[P, R]) -> Callable[Concatenate[int, P], R]: # reason for rejection: f2(lambda x: 42)(42, x=42) -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [out] main:9: error: invalid syntax [out version>=3.8] @@ -640,7 +640,7 @@ n = f(a) reveal_type(n) # N: Revealed type is "def (builtins.int)" reveal_type(n(42)) # N: Revealed type is "None" -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testCallablesAsParameters] # credits to https://github.com/microsoft/pyright/issues/2705 @@ -658,7 +658,7 @@ def test(a: int, /, b: str) -> str: ... abc = Foo(test) reveal_type(abc) bar(abc) -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [out] main:11: error: invalid syntax [out version>=3.8] @@ -677,7 +677,7 @@ n: Foo[[int]] def f(x: int) -> None: ... n.foo(f) -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testParamSpecLiteralsTypeApplication] from typing_extensions import ParamSpec @@ -709,7 +709,7 @@ Z[bytes, str](lambda one: None) # E: Cannot infer type of lambda \ # E: Argument 1 to "Z" has incompatible type "Callable[[Any], None]"; expected "Callable[[bytes, str], None]" Z[bytes, str](f2) # E: Argument 1 to "Z" has incompatible type "Callable[[bytes, int], None]"; expected "Callable[[bytes, str], None]" -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testParamSpecLiteralEllipsis] from typing_extensions import ParamSpec @@ -740,7 +740,7 @@ n = Z(f1) n = Z(f2) n = Z(f3) -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testParamSpecApplyConcatenateTwice] from typing_extensions import ParamSpec, Concatenate @@ -770,7 +770,7 @@ def f(c: C[P]) -> None: reveal_type(p1) # N: Revealed type is "__main__.C[[builtins.str, **P`-1]]" p2 = p1.add_str() reveal_type(p2) # N: Revealed type is "__main__.C[[builtins.int, builtins.str, **P`-1]]" -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testParamSpecLiteralJoin] from typing import Generic, Callable, Union @@ -788,7 +788,7 @@ def func( ) -> None: job = action if isinstance(action, Job) else Job(action) reveal_type(job) # N: Revealed type is "__main__.Job[[builtins.int]]" -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testApplyParamSpecToParamSpecLiterals] from typing import TypeVar, Generic, Callable @@ -818,7 +818,7 @@ def func2(job: Job[..., None]) -> None: run_job(job, "Hello", 42) run_job(job, 42, msg="Hello") run_job(job, x=42, msg="Hello") -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testExpandNonBareParamSpecAgainstCallable] from typing import Callable, TypeVar, Any @@ -850,7 +850,7 @@ reveal_type(A().func(f, 42)) # N: Revealed type is "builtins.int" # TODO: this should reveal `int` reveal_type(A().func(lambda x: x + x, 42)) # N: Revealed type is "Any" -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testParamSpecConstraintOnOtherParamSpec] from typing import Callable, TypeVar, Any, Generic @@ -880,7 +880,7 @@ reveal_type(A().func(Job(lambda x: x))) # N: Revealed type is "__main__.Job[[x: def f(x: int, y: int) -> None: ... reveal_type(A().func(Job(f))) # N: Revealed type is "__main__.Job[[x: builtins.int, y: builtins.int], None]" -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testConstraintBetweenParamSpecFunctions1] from typing import Callable, TypeVar, Any, Generic @@ -898,7 +898,7 @@ def func(__action: Job[_P]) -> Callable[_P, None]: ... reveal_type(func) # N: Revealed type is "def [_P] (__main__.Job[_P`-1]) -> def (*_P.args, **_P.kwargs)" -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testConstraintBetweenParamSpecFunctions2] from typing import Callable, TypeVar, Any, Generic @@ -916,7 +916,7 @@ def func(__action: Job[_P]) -> Callable[_P, None]: ... reveal_type(func) # N: Revealed type is "def [_P] (__main__.Job[_P`-1]) -> def (*_P.args, **_P.kwargs)" -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testConstraintsBetweenConcatenatePrefixes] from typing import Any, Callable, Generic, TypeVar @@ -937,7 +937,7 @@ def adds_await() -> Callable[ ... return decorator # we want `_T` and `_P` to refer to the same things. -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testParamSpecVariance] from typing import Callable, Generic @@ -995,7 +995,7 @@ a3: Callable[[int], None] a3 = f3 # E: Incompatible types in assignment (expression has type "Callable[[bool], None]", variable has type "Callable[[int], None]") a3 = f2 a3 = f1 -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testDecoratingClassesThatUseParamSpec] from typing import Generic, TypeVar, Callable, Any @@ -1039,7 +1039,7 @@ reveal_type(j) # N: Revealed type is "__main__.Job[[x: _T`-1]]" jf = j.into_callable() reveal_type(jf) # N: Revealed type is "def [_T] (x: _T`-1)" reveal_type(jf(1)) # N: Revealed type is "None" -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testStackedConcatenateIsIllegal] from typing_extensions import Concatenate, ParamSpec @@ -1048,7 +1048,7 @@ from typing import Callable P = ParamSpec("P") def x(f: Callable[Concatenate[int, Concatenate[int, P]], None]) -> None: ... # E: Nested Concatenates are invalid -[builtins fixtures/tuple.pyi] +[builtins fixtures/paramspec.pyi] [case testPropagatedAnyConstraintsAreOK] from typing import Any, Callable, Generic, TypeVar diff --git a/test-data/unit/fixtures/paramspec.pyi b/test-data/unit/fixtures/paramspec.pyi new file mode 100644 index 000000000000..0177e93d1ae0 --- /dev/null +++ b/test-data/unit/fixtures/paramspec.pyi @@ -0,0 +1,66 @@ +# builtins stub for paramspec-related test cases + +from typing import ( + Sequence, Generic, TypeVar, Iterable, Iterator, Tuple, Mapping, Optional, Union, Type, overload +) + +T = TypeVar("T") +T_co = TypeVar('T_co', covariant=True) +KT = TypeVar("KT") +VT = TypeVar("VT") + +class object: + def __init__(self) -> None: pass + +class function: pass +class ellipsis: pass + +class type: + def __init__(self, *a: object) -> None: pass + def __call__(self, *a: object) -> object: pass + +class list(Sequence[T], Generic[T]): + @overload + def __getitem__(self, i: int) -> T: ... + @overload + def __getitem__(self, s: slice) -> list[T]: ... + def __contains__(self, item: object) -> bool: ... + def __iter__(self) -> Iterator[T]: ... + +# We need int and slice for indexing tuples. +class int: + def __neg__(self) -> 'int': pass + +class bool(int): pass +class float: pass +class slice: pass +class str: pass # for keyword argument key type +class bytes: pass + +class tuple(Sequence[T_co], Generic[T_co]): + def __new__(cls: Type[T], iterable: Iterable[T_co] = ...) -> T: pass + def __iter__(self) -> Iterator[T_co]: pass + def __contains__(self, item: object) -> bool: pass + def __getitem__(self, x: int) -> T_co: pass + def __mul__(self, n: int) -> Tuple[T_co, ...]: pass + def __rmul__(self, n: int) -> Tuple[T_co, ...]: pass + def __add__(self, x: Tuple[T_co, ...]) -> Tuple[T_co, ...]: pass + def count(self, obj: object) -> int: pass + +class dict(Mapping[KT, VT]): + @overload + def __init__(self, **kwargs: VT) -> None: pass + @overload + def __init__(self, arg: Iterable[Tuple[KT, VT]], **kwargs: VT) -> None: pass + def __getitem__(self, key: KT) -> VT: pass + def __setitem__(self, k: KT, v: VT) -> None: pass + def __iter__(self) -> Iterator[KT]: pass + def __contains__(self, item: object) -> int: pass + def update(self, a: Mapping[KT, VT]) -> None: pass + @overload + def get(self, k: KT) -> Optional[VT]: pass + @overload + def get(self, k: KT, default: Union[KT, T]) -> Union[VT, T]: pass + def __len__(self) -> int: pass + +def isinstance(x: object, t: type) -> bool: pass From 4616473ffdab4c36bcbea262c3c469f8e0b621f7 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sun, 24 Apr 2022 12:42:38 -0600 Subject: [PATCH 03/10] Add new test --- .../unit/check-parameter-specification.test | 21 +++++++++++++++++++ test-data/unit/fixtures/paramspec.pyi | 13 +++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 0821c73bce74..a0763697ec44 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -1063,3 +1063,24 @@ class Job(Generic[P]): ... @callback def run_job(job: Job[...]) -> T: ... [builtins fixtures/tuple.pyi] + +[case testTupleAndDictOperationsOnParamSpecArgsAndKwargs] +from typing import Callable +from typing_extensions import ParamSpec + +P = ParamSpec('P') + +def func(callback: Callable[P, str]) -> Callable[P, str]: + def inner(*args: P.args, **kwargs: P.kwargs) -> str: + for a in args: + pass + b = 'foo' in args + reveal_type(b) # N: Revealed type is "builtins.bool" + reveal_type(args.count(42)) # N: Revealed type is "builtins.int" + reveal_type(len(args)) # N: Revealed type is "builtins.int" + for c, d in kwargs.items(): + reveal_type(c) # N: Revealed type is "builtins.str" + kwargs.pop('bar') + return 'baz' + return inner +[builtins fixtures/paramspec.pyi] \ No newline at end of file diff --git a/test-data/unit/fixtures/paramspec.pyi b/test-data/unit/fixtures/paramspec.pyi index 0177e93d1ae0..ff86d621b321 100644 --- a/test-data/unit/fixtures/paramspec.pyi +++ b/test-data/unit/fixtures/paramspec.pyi @@ -1,7 +1,8 @@ # builtins stub for paramspec-related test cases from typing import ( - Sequence, Generic, TypeVar, Iterable, Iterator, Tuple, Mapping, Optional, Union, Type, overload + Sequence, Generic, TypeVar, Iterable, Iterator, Tuple, Mapping, Optional, Union, Type, overload, + Protocol ) T = TypeVar("T") @@ -45,8 +46,11 @@ class tuple(Sequence[T_co], Generic[T_co]): def __mul__(self, n: int) -> Tuple[T_co, ...]: pass def __rmul__(self, n: int) -> Tuple[T_co, ...]: pass def __add__(self, x: Tuple[T_co, ...]) -> Tuple[T_co, ...]: pass + def __len__(self) -> int: ... def count(self, obj: object) -> int: pass +class _ItemsView(Iterable[Tuple[KT, VT]]): pass + class dict(Mapping[KT, VT]): @overload def __init__(self, **kwargs: VT) -> None: pass @@ -62,5 +66,12 @@ class dict(Mapping[KT, VT]): @overload def get(self, k: KT, default: Union[KT, T]) -> Union[VT, T]: pass def __len__(self) -> int: pass + def pop(self, k: KT) -> VT: pass + def items(self) -> _ItemsView: pass def isinstance(x: object, t: type) -> bool: pass + +class _Sized(Protocol): + def __len__(self) -> int: pass + +def len(x: _Sized) -> int: pass From b9ddfcbcf3460c499856c8b91f2e26fa6adbc0d7 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sun, 24 Apr 2022 12:51:21 -0600 Subject: [PATCH 04/10] Make new test pass --- mypy/checkmember.py | 2 +- test-data/unit/fixtures/paramspec.pyi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 16bd0e074c3f..0480ed2a5468 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -164,7 +164,7 @@ def _analyze_member_access(name: str, return analyze_typeddict_access(name, typ, mx, override_info) elif isinstance(typ, NoneType): return analyze_none_member_access(name, typ, mx) - elif isinstance(typ, TypeVarType): + elif isinstance(typ, TypeVarLikeType): return _analyze_member_access(name, typ.upper_bound, mx, override_info) elif isinstance(typ, DeletedType): mx.msg.deleted_as_rvalue(typ, mx.context) diff --git a/test-data/unit/fixtures/paramspec.pyi b/test-data/unit/fixtures/paramspec.pyi index ff86d621b321..64382f7c88d0 100644 --- a/test-data/unit/fixtures/paramspec.pyi +++ b/test-data/unit/fixtures/paramspec.pyi @@ -67,7 +67,7 @@ class dict(Mapping[KT, VT]): def get(self, k: KT, default: Union[KT, T]) -> Union[VT, T]: pass def __len__(self) -> int: pass def pop(self, k: KT) -> VT: pass - def items(self) -> _ItemsView: pass + def items(self) -> _ItemsView[KT, VT]: pass def isinstance(x: object, t: type) -> bool: pass From b9b98b9ac4401c42bf40b822529b6224e51c9d6b Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sun, 24 Apr 2022 12:55:19 -0600 Subject: [PATCH 05/10] Add newline at end of file --- test-data/unit/check-parameter-specification.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index a0763697ec44..8c032a9b48a0 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -1083,4 +1083,4 @@ def func(callback: Callable[P, str]) -> Callable[P, str]: kwargs.pop('bar') return 'baz' return inner -[builtins fixtures/paramspec.pyi] \ No newline at end of file +[builtins fixtures/paramspec.pyi] From 5ff23097d48fcf338a4f5e157076e82692ad1866 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sun, 24 Apr 2022 13:36:59 -0600 Subject: [PATCH 06/10] Cleanup the diff a little bit --- mypy/types.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/mypy/types.py b/mypy/types.py index b4e2df36ae8b..5dcdb681ffa2 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -586,20 +586,22 @@ def __init__( self, name: str, fullname: str, id: Union[TypeVarId, int], flavor: int, *, upper_bound: Optional[Type] = None, named_type_func: Optional[Callable[..., 'Instance']] = None, line: int = -1, - column: int = -1, prefix: Optional['Parameters'] = None) -> None: - self.flavor = flavor + column: int = -1, prefix: Optional['Parameters'] = None + ) -> None: if upper_bound is None: assert named_type_func is not None, ( "Either 'upper_bound' or 'named_type_func' must be specified" ) - upper_bound = self.get_fallback(named_type_func) - super().__init__(name, fullname, id, upper_bound=upper_bound, line=line, column=column) + upper_bound = self.get_fallback(flavor, named_type_func) + super().__init__(name, fullname, id, upper_bound, line=line, column=column) + self.flavor = flavor self.prefix = prefix or Parameters([], [], []) - def get_fallback(self, named_type_func: Callable[..., 'Instance']) -> 'Instance': - if self.flavor == ParamSpecFlavor.BARE: + @staticmethod + def get_fallback(flavor: int, named_type_func: Callable[..., 'Instance']) -> 'Instance': + if flavor == ParamSpecFlavor.BARE: return named_type_func('builtins.object') - elif self.flavor == ParamSpecFlavor.ARGS: + elif flavor == ParamSpecFlavor.ARGS: return named_type_func('builtins.tuple') else: return named_type_func( From e1812ab4a66207d16e35101e02c0d5c2b0652167 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sun, 24 Apr 2022 15:05:23 -0600 Subject: [PATCH 07/10] Minor style cleanup for the new stub --- test-data/unit/fixtures/paramspec.pyi | 71 +++++++++++++-------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/test-data/unit/fixtures/paramspec.pyi b/test-data/unit/fixtures/paramspec.pyi index 64382f7c88d0..0686924aad6f 100644 --- a/test-data/unit/fixtures/paramspec.pyi +++ b/test-data/unit/fixtures/paramspec.pyi @@ -11,14 +11,14 @@ KT = TypeVar("KT") VT = TypeVar("VT") class object: - def __init__(self) -> None: pass + def __init__(self) -> None: ... -class function: pass -class ellipsis: pass +class function: ... +class ellipsis: ... class type: - def __init__(self, *a: object) -> None: pass - def __call__(self, *a: object) -> object: pass + def __init__(self, *a: object) -> None: ... + def __call__(self, *a: object) -> object: ... class list(Sequence[T], Generic[T]): @overload @@ -28,50 +28,49 @@ class list(Sequence[T], Generic[T]): def __contains__(self, item: object) -> bool: ... def __iter__(self) -> Iterator[T]: ... -# We need int and slice for indexing tuples. class int: - def __neg__(self) -> 'int': pass + def __neg__(self) -> 'int': ... -class bool(int): pass -class float: pass -class slice: pass -class str: pass # for keyword argument key type -class bytes: pass +class bool(int): ... +class float: ... +class slice: ... +class str: ... +class bytes: ... class tuple(Sequence[T_co], Generic[T_co]): - def __new__(cls: Type[T], iterable: Iterable[T_co] = ...) -> T: pass - def __iter__(self) -> Iterator[T_co]: pass - def __contains__(self, item: object) -> bool: pass - def __getitem__(self, x: int) -> T_co: pass - def __mul__(self, n: int) -> Tuple[T_co, ...]: pass - def __rmul__(self, n: int) -> Tuple[T_co, ...]: pass - def __add__(self, x: Tuple[T_co, ...]) -> Tuple[T_co, ...]: pass + def __new__(cls: Type[T], iterable: Iterable[T_co] = ...) -> T: ... + def __iter__(self) -> Iterator[T_co]: ... + def __contains__(self, item: object) -> bool: ... + def __getitem__(self, x: int) -> T_co: ... + def __mul__(self, n: int) -> Tuple[T_co, ...]: ... + def __rmul__(self, n: int) -> Tuple[T_co, ...]: ... + def __add__(self, x: Tuple[T_co, ...]) -> Tuple[T_co, ...]: ... def __len__(self) -> int: ... - def count(self, obj: object) -> int: pass + def count(self, obj: object) -> int: ... -class _ItemsView(Iterable[Tuple[KT, VT]]): pass +class _ItemsView(Iterable[Tuple[KT, VT]]): ... class dict(Mapping[KT, VT]): @overload - def __init__(self, **kwargs: VT) -> None: pass + def __init__(self, **kwargs: VT) -> None: ... @overload - def __init__(self, arg: Iterable[Tuple[KT, VT]], **kwargs: VT) -> None: pass - def __getitem__(self, key: KT) -> VT: pass - def __setitem__(self, k: KT, v: VT) -> None: pass - def __iter__(self) -> Iterator[KT]: pass - def __contains__(self, item: object) -> int: pass - def update(self, a: Mapping[KT, VT]) -> None: pass + def __init__(self, arg: Iterable[Tuple[KT, VT]], **kwargs: VT) -> None: ... + def __getitem__(self, key: KT) -> VT: ... + def __setitem__(self, k: KT, v: VT) -> None: ... + def __iter__(self) -> Iterator[KT]: ... + def __contains__(self, item: object) -> int: ... + def update(self, a: Mapping[KT, VT]) -> None: ... @overload - def get(self, k: KT) -> Optional[VT]: pass + def get(self, k: KT) -> Optional[VT]: ... @overload - def get(self, k: KT, default: Union[KT, T]) -> Union[VT, T]: pass - def __len__(self) -> int: pass - def pop(self, k: KT) -> VT: pass - def items(self) -> _ItemsView[KT, VT]: pass + def get(self, k: KT, default: Union[KT, T]) -> Union[VT, T]: ... + def __len__(self) -> int: ... + def pop(self, k: KT) -> VT: ... + def items(self) -> _ItemsView[KT, VT]: ... -def isinstance(x: object, t: type) -> bool: pass +def isinstance(x: object, t: type) -> bool: ... class _Sized(Protocol): - def __len__(self) -> int: pass + def __len__(self) -> int: ... -def len(x: _Sized) -> int: pass +def len(x: _Sized) -> int: ... From 7f372f90318e292618d28dec18ba1f88d9e596f7 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Tue, 26 Apr 2022 14:19:42 -0600 Subject: [PATCH 08/10] Use `object` instead of `Any` --- mypy/types.py | 7 ++++--- test-data/unit/check-parameter-specification.test | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mypy/types.py b/mypy/types.py index 5dcdb681ffa2..857fc2918e9a 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -599,14 +599,15 @@ def __init__( @staticmethod def get_fallback(flavor: int, named_type_func: Callable[..., 'Instance']) -> 'Instance': + builtins_object = named_type_func('builtins.object') if flavor == ParamSpecFlavor.BARE: - return named_type_func('builtins.object') + return builtins_object elif flavor == ParamSpecFlavor.ARGS: - return named_type_func('builtins.tuple') + return named_type_func('builtins.tuple', [builtins_object, EllipsisType()]) else: return named_type_func( 'builtins.dict', - [named_type_func('builtins.str'), AnyType(TypeOfAny.special_form)] + [named_type_func('builtins.str'), builtins_object] ) @staticmethod diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 8c032a9b48a0..ab4400e958db 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -1073,13 +1073,14 @@ P = ParamSpec('P') def func(callback: Callable[P, str]) -> Callable[P, str]: def inner(*args: P.args, **kwargs: P.kwargs) -> str: for a in args: - pass + reveal_type(a) # N: Revealed type is "builtins.object" b = 'foo' in args reveal_type(b) # N: Revealed type is "builtins.bool" reveal_type(args.count(42)) # N: Revealed type is "builtins.int" reveal_type(len(args)) # N: Revealed type is "builtins.int" for c, d in kwargs.items(): reveal_type(c) # N: Revealed type is "builtins.str" + reveal_type(d) # N: Revealed type is "builtins.object" kwargs.pop('bar') return 'baz' return inner From 0b71fca0cafe16d5e35dbd558c3a5df53fe23468 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Tue, 26 Apr 2022 14:26:51 -0600 Subject: [PATCH 09/10] Fix --- mypy/types.py | 2 +- test-data/unit/check-parameter-specification.test | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/types.py b/mypy/types.py index 857fc2918e9a..9a4836a996ea 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -603,7 +603,7 @@ def get_fallback(flavor: int, named_type_func: Callable[..., 'Instance']) -> 'In if flavor == ParamSpecFlavor.BARE: return builtins_object elif flavor == ParamSpecFlavor.ARGS: - return named_type_func('builtins.tuple', [builtins_object, EllipsisType()]) + return named_type_func('builtins.tuple', [builtins_object]) else: return named_type_func( 'builtins.dict', diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index ab4400e958db..28b08aa7122f 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -1072,6 +1072,7 @@ P = ParamSpec('P') def func(callback: Callable[P, str]) -> Callable[P, str]: def inner(*args: P.args, **kwargs: P.kwargs) -> str: + reveal_type(args[5]) # N: Revealed type is "builtins.object" for a in args: reveal_type(a) # N: Revealed type is "builtins.object" b = 'foo' in args From e57f9e5628ecf2f11bc13884a10abfebfdc56b20 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Wed, 27 Apr 2022 23:42:46 -0600 Subject: [PATCH 10/10] Address review --- mypy/semanal.py | 2 +- mypy/semanal_shared.py | 50 +++++++++++++++++++++++++++++++++++++++--- mypy/tvar_scope.py | 9 +++----- mypy/typeanal.py | 22 +++++++++---------- mypy/types.py | 38 ++++++++------------------------ mypy/typevars.py | 2 +- 6 files changed, 72 insertions(+), 51 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 43116ad0074d..985d5217cc08 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1369,7 +1369,7 @@ class Foo(Bar, Generic[T]): ... del base_type_exprs[i] tvar_defs: List[TypeVarLikeType] = [] for name, tvar_expr in declared_tvars: - tvar_def = self.tvar_scope.bind_new(name, tvar_expr, named_type_func=self.named_type) + tvar_def = self.tvar_scope.bind_new(name, tvar_expr) tvar_defs.append(tvar_def) return base_type_exprs, tvar_defs, is_protocol diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index 85a6779ac9f3..72a89150bb64 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -2,8 +2,8 @@ from abc import abstractmethod -from typing import Optional, List, Callable -from typing_extensions import Final +from typing import Optional, List, Callable, Union +from typing_extensions import Final, Protocol from mypy_extensions import trait from mypy.nodes import ( @@ -11,7 +11,8 @@ SymbolNode, SymbolTable ) from mypy.types import ( - Type, FunctionLike, Instance, TupleType, TPDICT_FB_NAMES, ProperType, get_proper_type + Type, FunctionLike, Instance, TupleType, TPDICT_FB_NAMES, ProperType, get_proper_type, + ParamSpecType, ParamSpecFlavor, Parameters, TypeVarId ) from mypy.tvar_scope import TypeVarLikeScope from mypy.errorcodes import ErrorCode @@ -212,3 +213,46 @@ def calculate_tuple_fallback(typ: TupleType) -> None: fallback = typ.partial_fallback assert fallback.type.fullname == 'builtins.tuple' fallback.args = (join.join_type_list(list(typ.items)),) + fallback.args[1:] + + +class _NamedTypeCallback(Protocol): + def __call__( + self, fully_qualified_name: str, args: Optional[List[Type]] = None + ) -> Instance: ... + + +def paramspec_args( + name: str, fullname: str, id: Union[TypeVarId, int], *, + named_type_func: _NamedTypeCallback, line: int = -1, column: int = -1, + prefix: Optional[Parameters] = None +) -> ParamSpecType: + return ParamSpecType( + name, + fullname, + id, + flavor=ParamSpecFlavor.ARGS, + upper_bound=named_type_func('builtins.tuple', [named_type_func('builtins.object')]), + line=line, + column=column, + prefix=prefix + ) + + +def paramspec_kwargs( + name: str, fullname: str, id: Union[TypeVarId, int], *, + named_type_func: _NamedTypeCallback, line: int = -1, column: int = -1, + prefix: Optional[Parameters] = None +) -> ParamSpecType: + return ParamSpecType( + name, + fullname, + id, + flavor=ParamSpecFlavor.KWARGS, + upper_bound=named_type_func( + 'builtins.dict', + [named_type_func('builtins.str'), named_type_func('builtins.object')] + ), + line=line, + column=column, + prefix=prefix + ) diff --git a/mypy/tvar_scope.py b/mypy/tvar_scope.py index c794435fbaf2..c1fe1cd6be35 100644 --- a/mypy/tvar_scope.py +++ b/mypy/tvar_scope.py @@ -1,7 +1,6 @@ -from typing import Optional, Dict, Union, Callable +from typing import Optional, Dict, Union from mypy.types import ( TypeVarLikeType, TypeVarType, ParamSpecType, ParamSpecFlavor, TypeVarId, TypeVarTupleType, - Instance ) from mypy.nodes import ( ParamSpecExpr, TypeVarExpr, TypeVarLikeExpr, SymbolTableNode, TypeVarTupleExpr, @@ -62,9 +61,7 @@ def class_frame(self, namespace: str) -> 'TypeVarLikeScope': """A new scope frame for binding a class. Prohibits *this* class's tvars""" return TypeVarLikeScope(self.get_function_scope(), True, self, namespace=namespace) - def bind_new( - self, name: str, tvar_expr: TypeVarLikeExpr, *, named_type_func: Callable[..., Instance] - ) -> TypeVarLikeType: + def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType: if self.is_class_scope: self.class_id += 1 i = self.class_id @@ -91,7 +88,7 @@ def bind_new( tvar_expr.fullname, i, flavor=ParamSpecFlavor.BARE, - named_type_func=named_type_func, + upper_bound=tvar_expr.upper_bound, line=tvar_expr.line, column=tvar_expr.column ) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index c2b0913dae3a..caf2ab8954c7 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -33,7 +33,7 @@ from mypy.tvar_scope import TypeVarLikeScope from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError from mypy.plugin import Plugin, TypeAnalyzerPluginInterface, AnalyzeTypeContext -from mypy.semanal_shared import SemanticAnalyzerCoreInterface +from mypy.semanal_shared import SemanticAnalyzerCoreInterface, paramspec_args, paramspec_kwargs from mypy.errorcodes import ErrorCode from mypy import nodes, message_registry, errorcodes as codes @@ -222,7 +222,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) # Change the line number return ParamSpecType( tvar_def.name, tvar_def.fullname, tvar_def.id, tvar_def.flavor, - named_type_func=self.named_type, line=t.line, column=t.column, + tvar_def.upper_bound, line=t.line, column=t.column, ) if isinstance(sym.node, TypeVarExpr) and tvar_def is not None and self.defining_alias: self.fail('Can\'t use bound type variable "{}"' @@ -711,12 +711,12 @@ def anal_star_arg_type(self, t: Type, kind: ArgKind, nested: bool) -> Type: tvar_def = self.tvar_scope.get_binding(sym) if isinstance(tvar_def, ParamSpecType): if kind == ARG_STAR: - flavor = ParamSpecFlavor.ARGS + make_paramspec = paramspec_args elif kind == ARG_STAR2: - flavor = ParamSpecFlavor.KWARGS + make_paramspec = paramspec_kwargs else: assert False, kind - return ParamSpecType(tvar_def.name, tvar_def.fullname, tvar_def.id, flavor, + return make_paramspec(tvar_def.name, tvar_def.fullname, tvar_def.id, named_type_func=self.named_type, line=t.line, column=t.column) return self.anal_type(t, nested=nested) @@ -856,9 +856,9 @@ def analyze_callable_args_for_paramspec( return None return CallableType( - [ParamSpecType(tvar_def.name, tvar_def.fullname, tvar_def.id, ParamSpecFlavor.ARGS, + [paramspec_args(tvar_def.name, tvar_def.fullname, tvar_def.id, named_type_func=self.named_type), - ParamSpecType(tvar_def.name, tvar_def.fullname, tvar_def.id, ParamSpecFlavor.KWARGS, + paramspec_kwargs(tvar_def.name, tvar_def.fullname, tvar_def.id, named_type_func=self.named_type)], [nodes.ARG_STAR, nodes.ARG_STAR2], [None, None], @@ -895,9 +895,9 @@ def analyze_callable_args_for_concatenate( # in the future. CallableType.param_spec() accounts for this. return CallableType( [*prefix.arg_types, - ParamSpecType(tvar_def.name, tvar_def.fullname, tvar_def.id, ParamSpecFlavor.ARGS, + paramspec_args(tvar_def.name, tvar_def.fullname, tvar_def.id, named_type_func=self.named_type), - ParamSpecType(tvar_def.name, tvar_def.fullname, tvar_def.id, ParamSpecFlavor.KWARGS, + paramspec_kwargs(tvar_def.name, tvar_def.fullname, tvar_def.id, named_type_func=self.named_type)], [*prefix.arg_kinds, nodes.ARG_STAR, nodes.ARG_STAR2], [*prefix.arg_names, None, None], @@ -1130,7 +1130,7 @@ def bind_function_type_variables( assert var_node, "Binding for function type variable not found within function" var_expr = var_node.node assert isinstance(var_expr, TypeVarLikeExpr) - self.tvar_scope.bind_new(var.name, var_expr, named_type_func=self.named_type) + self.tvar_scope.bind_new(var.name, var_expr) return fun_type.variables typevars = self.infer_type_variables(fun_type) # Do not define a new type variable if already defined in scope. @@ -1140,7 +1140,7 @@ def bind_function_type_variables( for name, tvar in typevars: if not self.tvar_scope.allow_binding(tvar.fullname): self.fail('Type variable "{}" is bound by an outer class'.format(name), defn) - self.tvar_scope.bind_new(name, tvar, named_type_func=self.named_type) + self.tvar_scope.bind_new(name, tvar) binding = self.tvar_scope.get_binding(tvar.fullname) assert binding is not None defs.append(binding) diff --git a/mypy/types.py b/mypy/types.py index 9a4836a996ea..e43b73f093b8 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -6,7 +6,7 @@ from typing import ( Any, TypeVar, Dict, List, Tuple, cast, Set, Optional, Union, Iterable, NamedTuple, - Sequence, Callable + Sequence ) from typing_extensions import ClassVar, Final, TYPE_CHECKING, overload, TypeAlias as _TypeAlias @@ -583,39 +583,19 @@ class ParamSpecType(TypeVarLikeType): prefix: 'Parameters' def __init__( - self, name: str, fullname: str, id: Union[TypeVarId, int], flavor: int, *, - upper_bound: Optional[Type] = None, - named_type_func: Optional[Callable[..., 'Instance']] = None, line: int = -1, - column: int = -1, prefix: Optional['Parameters'] = None + self, name: str, fullname: str, id: Union[TypeVarId, int], flavor: int, + upper_bound: Type, *, line: int = -1, column: int = -1, + prefix: Optional['Parameters'] = None ) -> None: - if upper_bound is None: - assert named_type_func is not None, ( - "Either 'upper_bound' or 'named_type_func' must be specified" - ) - upper_bound = self.get_fallback(flavor, named_type_func) super().__init__(name, fullname, id, upper_bound, line=line, column=column) self.flavor = flavor self.prefix = prefix or Parameters([], [], []) - @staticmethod - def get_fallback(flavor: int, named_type_func: Callable[..., 'Instance']) -> 'Instance': - builtins_object = named_type_func('builtins.object') - if flavor == ParamSpecFlavor.BARE: - return builtins_object - elif flavor == ParamSpecFlavor.ARGS: - return named_type_func('builtins.tuple', [builtins_object]) - else: - return named_type_func( - 'builtins.dict', - [named_type_func('builtins.str'), builtins_object] - ) - @staticmethod def new_unification_variable(old: 'ParamSpecType') -> 'ParamSpecType': new_id = TypeVarId.new(meta_level=1) - return ParamSpecType(old.name, old.fullname, new_id, old.flavor, - upper_bound=old.upper_bound, line=old.line, column=old.column, - prefix=old.prefix) + return ParamSpecType(old.name, old.fullname, new_id, old.flavor, old.upper_bound, + line=old.line, column=old.column, prefix=old.prefix) def with_flavor(self, flavor: int) -> 'ParamSpecType': return ParamSpecType(self.name, self.fullname, self.id, flavor, @@ -630,7 +610,7 @@ def copy_modified(self, *, self.fullname, id if id is not _dummy else self.id, flavor if flavor is not _dummy else self.flavor, - upper_bound=self.upper_bound, + self.upper_bound, line=self.line, column=self.column, prefix=prefix if prefix is not _dummy else self.prefix, @@ -676,7 +656,7 @@ def deserialize(cls, data: JsonDict) -> 'ParamSpecType': data['fullname'], data['id'], data['flavor'], - upper_bound=deserialize_type(data['upper_bound']), + deserialize_type(data['upper_bound']), prefix=Parameters.deserialize(data['prefix']) ) @@ -1759,7 +1739,7 @@ def param_spec(self) -> Optional[ParamSpecType]: # TODO: confirm that all arg kinds are positional prefix = Parameters(self.arg_types[:-2], self.arg_kinds[:-2], self.arg_names[:-2]) return ParamSpecType(arg_type.name, arg_type.fullname, arg_type.id, ParamSpecFlavor.BARE, - upper_bound=arg_type.upper_bound, prefix=prefix) + arg_type.upper_bound, prefix=prefix) def expand_param_spec(self, c: Union['CallableType', Parameters], diff --git a/mypy/typevars.py b/mypy/typevars.py index cacd8db4a67a..b49194f342e0 100644 --- a/mypy/typevars.py +++ b/mypy/typevars.py @@ -23,7 +23,7 @@ def fill_typevars(typ: TypeInfo) -> Union[Instance, TupleType]: ) else: assert isinstance(tv, ParamSpecType) - tv = ParamSpecType(tv.name, tv.fullname, tv.id, tv.flavor, upper_bound=tv.upper_bound, + tv = ParamSpecType(tv.name, tv.fullname, tv.id, tv.flavor, tv.upper_bound, line=-1, column=-1) tvs.append(tv) inst = Instance(typ, tvs)