From a568f3ab178ad261b5f45db6ec2c73113afbd287 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 30 May 2023 01:46:28 +0200 Subject: [PATCH] Add foundation for TypeVar defaults (PEP 696) (#14872) Start implementing [PEP 696](https://peps.python.org/pep-0696/) TypeVar defaults. This PR * Adds a `default` parameter to `TypeVarLikeExpr` and `TypeVarLikeType`. * Updates most visitors to account for the new `default` parameter. * Update existing calls to add value for `default` => `AnyType(TypeOfAny.from_omitted_generics)`. A followup PR will update the semantic analyzer and add basic tests for `TypeVar`, `ParamSpec`, and `TypeVarTuple` calls with a `default` argument. -> #14873 Ref #14851 --- mypy/checker.py | 1 + mypy/checkexpr.py | 66 +++++++++++++++++++++++++++++++---- mypy/copytype.py | 8 +++-- mypy/expandtype.py | 9 +---- mypy/fixup.py | 10 ++++-- mypy/indirection.py | 6 ++-- mypy/nodes.py | 25 +++++++++++--- mypy/plugins/attrs.py | 7 +++- mypy/plugins/dataclasses.py | 7 +++- mypy/semanal.py | 67 ++++++++++++++++++++++++++++------- mypy/semanal_namedtuple.py | 7 +++- mypy/semanal_shared.py | 4 +++ mypy/server/astdiff.py | 18 ++++++++-- mypy/server/astmerge.py | 14 +++++++- mypy/server/deps.py | 10 ++++++ mypy/test/testtypes.py | 34 ++++++++++++++---- mypy/test/typefixture.py | 42 +++++++++++++++++++--- mypy/treetransform.py | 8 ++++- mypy/tvar_scope.py | 3 ++ mypy/type_visitor.py | 12 +++---- mypy/typeanal.py | 3 ++ mypy/types.py | 69 +++++++++++++++++++++++++------------ mypy/typevars.py | 10 +++++- 23 files changed, 356 insertions(+), 84 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index b7bd656ca87e..c1c31538b7de 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -7191,6 +7191,7 @@ def detach_callable(typ: CallableType) -> CallableType: id=var.id, values=var.values, upper_bound=var.upper_bound, + default=var.default, variance=var.variance, ) ) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index e58ddfc0799f..cd0ff1100183 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4189,7 +4189,14 @@ def check_lst_expr(self, e: ListExpr | SetExpr | TupleExpr, fullname: str, tag: # Used for list and set expressions, as well as for tuples # containing star expressions that don't refer to a # Tuple. (Note: "lst" stands for list-set-tuple. :-) - tv = TypeVarType("T", "T", id=-1, values=[], upper_bound=self.object_type()) + tv = TypeVarType( + "T", + "T", + id=-1, + values=[], + upper_bound=self.object_type(), + default=AnyType(TypeOfAny.from_omitted_generics), + ) constructor = CallableType( [tv], [nodes.ARG_STAR], @@ -4357,8 +4364,22 @@ def visit_dict_expr(self, e: DictExpr) -> Type: return dt # Define type variables (used in constructors below). - kt = TypeVarType("KT", "KT", id=-1, values=[], upper_bound=self.object_type()) - vt = TypeVarType("VT", "VT", id=-2, values=[], upper_bound=self.object_type()) + kt = TypeVarType( + "KT", + "KT", + id=-1, + values=[], + upper_bound=self.object_type(), + default=AnyType(TypeOfAny.from_omitted_generics), + ) + vt = TypeVarType( + "VT", + "VT", + id=-2, + values=[], + upper_bound=self.object_type(), + default=AnyType(TypeOfAny.from_omitted_generics), + ) # Collect function arguments, watching out for **expr. args: list[Expression] = [] @@ -4722,7 +4743,14 @@ def check_generator_or_comprehension( # Infer the type of the list comprehension by using a synthetic generic # callable type. - tv = TypeVarType("T", "T", id=-1, values=[], upper_bound=self.object_type()) + tv = TypeVarType( + "T", + "T", + id=-1, + values=[], + upper_bound=self.object_type(), + default=AnyType(TypeOfAny.from_omitted_generics), + ) tv_list: list[Type] = [tv] constructor = CallableType( tv_list, @@ -4742,8 +4770,22 @@ def visit_dictionary_comprehension(self, e: DictionaryComprehension) -> Type: # Infer the type of the list comprehension by using a synthetic generic # callable type. - ktdef = TypeVarType("KT", "KT", id=-1, values=[], upper_bound=self.object_type()) - vtdef = TypeVarType("VT", "VT", id=-2, values=[], upper_bound=self.object_type()) + ktdef = TypeVarType( + "KT", + "KT", + id=-1, + values=[], + upper_bound=self.object_type(), + default=AnyType(TypeOfAny.from_omitted_generics), + ) + vtdef = TypeVarType( + "VT", + "VT", + id=-2, + values=[], + upper_bound=self.object_type(), + default=AnyType(TypeOfAny.from_omitted_generics), + ) constructor = CallableType( [ktdef, vtdef], [nodes.ARG_POS, nodes.ARG_POS], @@ -5264,6 +5306,18 @@ def visit_callable_type(self, t: CallableType) -> bool: return False return super().visit_callable_type(t) + def visit_type_var(self, t: TypeVarType) -> bool: + default = [t.default] if t.has_default() else [] + return self.query_types([t.upper_bound, *default] + t.values) + + def visit_param_spec(self, t: ParamSpecType) -> bool: + default = [t.default] if t.has_default() else [] + return self.query_types([t.upper_bound, *default]) + + def visit_type_var_tuple(self, t: TypeVarTupleType) -> bool: + default = [t.default] if t.has_default() else [] + return self.query_types([t.upper_bound, *default]) + def has_coroutine_decorator(t: Type) -> bool: """Whether t came from a function decorated with `@coroutine`.""" diff --git a/mypy/copytype.py b/mypy/copytype.py index 46c307f3dd84..0b63c8e07ae8 100644 --- a/mypy/copytype.py +++ b/mypy/copytype.py @@ -72,7 +72,9 @@ def visit_type_var(self, t: TypeVarType) -> ProperType: return self.copy_common(t, t.copy_modified()) def visit_param_spec(self, t: ParamSpecType) -> ProperType: - dup = ParamSpecType(t.name, t.fullname, t.id, t.flavor, t.upper_bound, prefix=t.prefix) + dup = ParamSpecType( + t.name, t.fullname, t.id, t.flavor, t.upper_bound, t.default, prefix=t.prefix + ) return self.copy_common(t, dup) def visit_parameters(self, t: Parameters) -> ProperType: @@ -86,7 +88,9 @@ def visit_parameters(self, t: Parameters) -> ProperType: return self.copy_common(t, dup) def visit_type_var_tuple(self, t: TypeVarTupleType) -> ProperType: - dup = TypeVarTupleType(t.name, t.fullname, t.id, t.upper_bound, t.tuple_fallback) + dup = TypeVarTupleType( + t.name, t.fullname, t.id, t.upper_bound, t.tuple_fallback, t.default + ) return self.copy_common(t, dup) def visit_unpack_type(self, t: UnpackType) -> ProperType: diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 7d7af80ccb2b..5bbdd5311da7 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -130,14 +130,7 @@ def freshen_function_type_vars(callee: F) -> F: tvs = [] tvmap: dict[TypeVarId, Type] = {} for v in callee.variables: - if isinstance(v, TypeVarType): - tv: TypeVarLikeType = TypeVarType.new_unification_variable(v) - elif isinstance(v, TypeVarTupleType): - assert isinstance(v, TypeVarTupleType) - tv = TypeVarTupleType.new_unification_variable(v) - else: - assert isinstance(v, ParamSpecType) - tv = ParamSpecType.new_unification_variable(v) + tv = v.new_unification_variable(v) tvs.append(tv) tvmap[v.id] = tv fresh = expand_type(callee, tvmap).copy_modified(variables=tvs) diff --git a/mypy/fixup.py b/mypy/fixup.py index 01e4c0a716fc..15f4c13c20fa 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -171,17 +171,21 @@ def visit_class_def(self, c: ClassDef) -> None: for value in v.values: value.accept(self.type_fixer) v.upper_bound.accept(self.type_fixer) + v.default.accept(self.type_fixer) def visit_type_var_expr(self, tv: TypeVarExpr) -> None: for value in tv.values: value.accept(self.type_fixer) tv.upper_bound.accept(self.type_fixer) + tv.default.accept(self.type_fixer) def visit_paramspec_expr(self, p: ParamSpecExpr) -> None: p.upper_bound.accept(self.type_fixer) + p.default.accept(self.type_fixer) def visit_type_var_tuple_expr(self, tv: TypeVarTupleExpr) -> None: tv.upper_bound.accept(self.type_fixer) + tv.default.accept(self.type_fixer) def visit_var(self, v: Var) -> None: if self.current_info is not None: @@ -303,14 +307,16 @@ def visit_type_var(self, tvt: TypeVarType) -> None: if tvt.values: for vt in tvt.values: vt.accept(self) - if tvt.upper_bound is not None: - tvt.upper_bound.accept(self) + tvt.upper_bound.accept(self) + tvt.default.accept(self) def visit_param_spec(self, p: ParamSpecType) -> None: p.upper_bound.accept(self) + p.default.accept(self) def visit_type_var_tuple(self, t: TypeVarTupleType) -> None: t.upper_bound.accept(self) + t.default.accept(self) def visit_unpack_type(self, u: UnpackType) -> None: u.type.accept(self) diff --git a/mypy/indirection.py b/mypy/indirection.py index eef7601d6aae..00356d7a4ddb 100644 --- a/mypy/indirection.py +++ b/mypy/indirection.py @@ -64,13 +64,13 @@ def visit_deleted_type(self, t: types.DeletedType) -> set[str]: return set() def visit_type_var(self, t: types.TypeVarType) -> set[str]: - return self._visit(t.values) | self._visit(t.upper_bound) + return self._visit(t.values) | self._visit(t.upper_bound) | self._visit(t.default) def visit_param_spec(self, t: types.ParamSpecType) -> set[str]: - return set() + return self._visit(t.upper_bound) | self._visit(t.default) def visit_type_var_tuple(self, t: types.TypeVarTupleType) -> set[str]: - return self._visit(t.upper_bound) + return self._visit(t.upper_bound) | self._visit(t.default) def visit_unpack_type(self, t: types.UnpackType) -> set[str]: return t.type.accept(self) diff --git a/mypy/nodes.py b/mypy/nodes.py index 330e28b0fa2f..8457e39b6aa1 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2439,13 +2439,16 @@ class TypeVarLikeExpr(SymbolNode, Expression): Note that they are constructed by the semantic analyzer. """ - __slots__ = ("_name", "_fullname", "upper_bound", "variance") + __slots__ = ("_name", "_fullname", "upper_bound", "default", "variance") _name: str _fullname: str # Upper bound: only subtypes of upper_bound are valid as values. By default # this is 'object', meaning no restriction. upper_bound: mypy.types.Type + # Default: used to resolve the TypeVar if the default is not explicitly given. + # By default this is 'AnyType(TypeOfAny.from_omitted_generics)'. See PEP 696. + default: mypy.types.Type # Variance of the type variable. Invariant is the default. # TypeVar(..., covariant=True) defines a covariant type variable. # TypeVar(..., contravariant=True) defines a contravariant type @@ -2453,12 +2456,18 @@ class TypeVarLikeExpr(SymbolNode, Expression): variance: int def __init__( - self, name: str, fullname: str, upper_bound: mypy.types.Type, variance: int = INVARIANT + self, + name: str, + fullname: str, + upper_bound: mypy.types.Type, + default: mypy.types.Type, + variance: int = INVARIANT, ) -> None: super().__init__() self._name = name self._fullname = fullname self.upper_bound = upper_bound + self.default = default self.variance = variance @property @@ -2496,9 +2505,10 @@ def __init__( fullname: str, values: list[mypy.types.Type], upper_bound: mypy.types.Type, + default: mypy.types.Type, variance: int = INVARIANT, ) -> None: - super().__init__(name, fullname, upper_bound, variance) + super().__init__(name, fullname, upper_bound, default, variance) self.values = values def accept(self, visitor: ExpressionVisitor[T]) -> T: @@ -2511,6 +2521,7 @@ def serialize(self) -> JsonDict: "fullname": self._fullname, "values": [t.serialize() for t in self.values], "upper_bound": self.upper_bound.serialize(), + "default": self.default.serialize(), "variance": self.variance, } @@ -2522,6 +2533,7 @@ def deserialize(cls, data: JsonDict) -> TypeVarExpr: data["fullname"], [mypy.types.deserialize_type(v) for v in data["values"]], mypy.types.deserialize_type(data["upper_bound"]), + mypy.types.deserialize_type(data["default"]), data["variance"], ) @@ -2540,6 +2552,7 @@ def serialize(self) -> JsonDict: "name": self._name, "fullname": self._fullname, "upper_bound": self.upper_bound.serialize(), + "default": self.default.serialize(), "variance": self.variance, } @@ -2550,6 +2563,7 @@ def deserialize(cls, data: JsonDict) -> ParamSpecExpr: data["name"], data["fullname"], mypy.types.deserialize_type(data["upper_bound"]), + mypy.types.deserialize_type(data["default"]), data["variance"], ) @@ -2569,9 +2583,10 @@ def __init__( fullname: str, upper_bound: mypy.types.Type, tuple_fallback: mypy.types.Instance, + default: mypy.types.Type, variance: int = INVARIANT, ) -> None: - super().__init__(name, fullname, upper_bound, variance) + super().__init__(name, fullname, upper_bound, default, variance) self.tuple_fallback = tuple_fallback def accept(self, visitor: ExpressionVisitor[T]) -> T: @@ -2584,6 +2599,7 @@ def serialize(self) -> JsonDict: "fullname": self._fullname, "upper_bound": self.upper_bound.serialize(), "tuple_fallback": self.tuple_fallback.serialize(), + "default": self.default.serialize(), "variance": self.variance, } @@ -2595,6 +2611,7 @@ def deserialize(cls, data: JsonDict) -> TypeVarTupleExpr: data["fullname"], mypy.types.deserialize_type(data["upper_bound"]), mypy.types.Instance.deserialize(data["tuple_fallback"]), + mypy.types.deserialize_type(data["default"]), data["variance"], ) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index bd09eeb0264a..afd9423d6820 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -772,9 +772,14 @@ def _add_order(ctx: mypy.plugin.ClassDefContext, adder: MethodAdder) -> None: id=-1, values=[], upper_bound=object_type, + default=AnyType(TypeOfAny.from_omitted_generics), ) self_tvar_expr = TypeVarExpr( - SELF_TVAR_NAME, ctx.cls.info.fullname + "." + SELF_TVAR_NAME, [], object_type + SELF_TVAR_NAME, + ctx.cls.info.fullname + "." + SELF_TVAR_NAME, + [], + object_type, + AnyType(TypeOfAny.from_omitted_generics), ) ctx.cls.info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 0e195b9668d8..2fd903f2f8b9 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -254,7 +254,11 @@ def transform(self) -> bool: # Type variable for self types in generated methods. obj_type = self._api.named_type("builtins.object") self_tvar_expr = TypeVarExpr( - SELF_TVAR_NAME, info.fullname + "." + SELF_TVAR_NAME, [], obj_type + SELF_TVAR_NAME, + info.fullname + "." + SELF_TVAR_NAME, + [], + obj_type, + AnyType(TypeOfAny.from_omitted_generics), ) info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) @@ -273,6 +277,7 @@ def transform(self) -> bool: id=-1, values=[], upper_bound=obj_type, + default=AnyType(TypeOfAny.from_omitted_generics), ) order_return_type = self._api.named_type("builtins.bool") order_args = [ diff --git a/mypy/semanal.py b/mypy/semanal.py index 6bc8ff55d452..8934dc9321b7 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1076,7 +1076,12 @@ def setup_self_type(self) -> None: else: return info.self_type = TypeVarType( - "Self", f"{info.fullname}.Self", id=0, values=[], upper_bound=fill_typevars(info) + "Self", + f"{info.fullname}.Self", + id=0, + values=[], + upper_bound=fill_typevars(info), + default=AnyType(TypeOfAny.from_omitted_generics), ) def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: @@ -1607,6 +1612,11 @@ def analyze_class(self, defn: ClassDef) -> None: # Some type variable bounds or values are not ready, we need # to re-analyze this class. self.defer() + if has_placeholder(tvd.default): + # Placeholder values in TypeVarLikeTypes may get substituted in. + # Defer current target until they are ready. + self.mark_incomplete(defn.name, defn) + return self.analyze_class_keywords(defn) bases_result = self.analyze_base_classes(bases) @@ -3981,7 +3991,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: ) if res is None: return False - variance, upper_bound = res + variance, upper_bound, default = res existing = self.current_symbol_table().get(name) if existing and not ( @@ -4003,7 +4013,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: prefix = "Upper bound of type variable" self.msg.unimported_type_becomes_any(prefix, upper_bound, s) - for t in values + [upper_bound]: + for t in values + [upper_bound, default]: check_for_explicit_any( t, self.options, self.is_typeshed_stub_file, self.msg, context=s ) @@ -4016,19 +4026,28 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: # Yes, it's a valid type variable definition! Add it to the symbol table. if not call.analyzed: - type_var = TypeVarExpr(name, self.qualified_name(name), values, upper_bound, variance) + type_var = TypeVarExpr( + name, self.qualified_name(name), values, upper_bound, default, variance + ) type_var.line = call.line call.analyzed = type_var updated = True else: assert isinstance(call.analyzed, TypeVarExpr) - updated = values != call.analyzed.values or upper_bound != call.analyzed.upper_bound + updated = ( + values != call.analyzed.values + or upper_bound != call.analyzed.upper_bound + or default != call.analyzed.default + ) call.analyzed.upper_bound = upper_bound call.analyzed.values = values - if any(has_placeholder(v) for v in values) or has_placeholder(upper_bound): - self.process_placeholder( - None, f"TypeVar {'values' if values else 'upper bound'}", s, force_progress=updated - ) + call.analyzed.default = default + if any(has_placeholder(v) for v in values): + self.process_placeholder(None, "TypeVar values", s, force_progress=updated) + elif has_placeholder(upper_bound): + self.process_placeholder(None, "TypeVar upper bound", s, force_progress=updated) + elif has_placeholder(default): + self.process_placeholder(None, "TypeVar default", s, force_progress=updated) self.add_symbol(name, call.analyzed, s) return True @@ -4077,11 +4096,12 @@ def process_typevar_parameters( kinds: list[ArgKind], num_values: int, context: Context, - ) -> tuple[int, Type] | None: + ) -> tuple[int, Type, Type] | None: has_values = num_values > 0 covariant = False contravariant = False upper_bound: Type = self.object_type() + default: Type = AnyType(TypeOfAny.from_omitted_generics) for param_value, param_name, param_kind in zip(args, names, kinds): if not param_kind.is_named(): self.fail(message_registry.TYPEVAR_UNEXPECTED_ARGUMENT, context) @@ -4151,7 +4171,7 @@ def process_typevar_parameters( variance = CONTRAVARIANT else: variance = INVARIANT - return variance, upper_bound + return variance, upper_bound, default def extract_typevarlike_name(self, s: AssignmentStmt, call: CallExpr) -> str | None: if not call: @@ -4191,18 +4211,26 @@ def process_paramspec_declaration(self, s: AssignmentStmt) -> bool: if len(call.args) > 1: self.fail("Only the first argument to ParamSpec has defined semantics", s) + default: Type = AnyType(TypeOfAny.from_omitted_generics) + # PEP 612 reserves the right to define bound, covariant and contravariant arguments to # ParamSpec in a later PEP. If and when that happens, we should do something # on the lines of process_typevar_parameters if not call.analyzed: paramspec_var = ParamSpecExpr( - name, self.qualified_name(name), self.object_type(), INVARIANT + name, self.qualified_name(name), self.object_type(), default, INVARIANT ) paramspec_var.line = call.line call.analyzed = paramspec_var + updated = True else: assert isinstance(call.analyzed, ParamSpecExpr) + updated = default != call.analyzed.default + call.analyzed.default = default + if has_placeholder(default): + self.process_placeholder(None, "ParamSpec default", s, force_progress=updated) + self.add_symbol(name, call.analyzed, s) return True @@ -4220,6 +4248,8 @@ def process_typevartuple_declaration(self, s: AssignmentStmt) -> bool: if len(call.args) > 1: self.fail("Only the first argument to TypeVarTuple has defined semantics", s) + default: Type = AnyType(TypeOfAny.from_omitted_generics) + if not self.incomplete_feature_enabled(TYPE_VAR_TUPLE, s): return False @@ -4231,12 +4261,23 @@ def process_typevartuple_declaration(self, s: AssignmentStmt) -> bool: if not call.analyzed: tuple_fallback = self.named_type("builtins.tuple", [self.object_type()]) typevartuple_var = TypeVarTupleExpr( - name, self.qualified_name(name), self.object_type(), tuple_fallback, INVARIANT + name, + self.qualified_name(name), + self.object_type(), + tuple_fallback, + default, + INVARIANT, ) typevartuple_var.line = call.line call.analyzed = typevartuple_var + updated = True else: assert isinstance(call.analyzed, TypeVarTupleExpr) + updated = default != call.analyzed.default + call.analyzed.default = default + if has_placeholder(default): + self.process_placeholder(None, "TypeVarTuple default", s, force_progress=updated) + self.add_symbol(name, call.analyzed, s) return True diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index ab5a0f993b6a..c690d4ec6d20 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -549,6 +549,7 @@ def add_field( id=self.api.tvar_scope.new_unique_func_id(), values=[], upper_bound=info.tuple_type, + default=AnyType(TypeOfAny.from_omitted_generics), ) selftype = tvd @@ -617,7 +618,11 @@ def make_init_arg(var: Var) -> Argument: ) self_tvar_expr = TypeVarExpr( - SELF_TVAR_NAME, info.fullname + "." + SELF_TVAR_NAME, [], info.tuple_type + SELF_TVAR_NAME, + info.fullname + "." + SELF_TVAR_NAME, + [], + info.tuple_type, + AnyType(TypeOfAny.from_omitted_generics), ) info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) return info diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index c86ed828b2b9..d097e1fb08dc 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -32,6 +32,7 @@ from mypy.type_visitor import ANY_STRATEGY, BoolTypeQuery from mypy.types import ( TPDICT_FB_NAMES, + AnyType, FunctionLike, Instance, Parameters, @@ -41,6 +42,7 @@ ProperType, TupleType, Type, + TypeOfAny, TypeVarId, TypeVarLikeType, get_proper_type, @@ -308,6 +310,7 @@ def paramspec_args( id, flavor=ParamSpecFlavor.ARGS, upper_bound=named_type_func("builtins.tuple", [named_type_func("builtins.object")]), + default=AnyType(TypeOfAny.from_omitted_generics), line=line, column=column, prefix=prefix, @@ -332,6 +335,7 @@ def paramspec_kwargs( upper_bound=named_type_func( "builtins.dict", [named_type_func("builtins.str"), named_type_func("builtins.object")] ), + default=AnyType(TypeOfAny.from_omitted_generics), line=line, column=column, prefix=prefix, diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 83ae64fbc1a8..93f178dca35a 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -190,6 +190,7 @@ def snapshot_symbol_table(name_prefix: str, table: SymbolTable) -> dict[str, Sym node.variance, [snapshot_type(value) for value in node.values], snapshot_type(node.upper_bound), + snapshot_type(node.default), ) elif isinstance(node, TypeAlias): result[name] = ( @@ -200,9 +201,19 @@ def snapshot_symbol_table(name_prefix: str, table: SymbolTable) -> dict[str, Sym snapshot_optional_type(node.target), ) elif isinstance(node, ParamSpecExpr): - result[name] = ("ParamSpec", node.variance, snapshot_type(node.upper_bound)) + result[name] = ( + "ParamSpec", + node.variance, + snapshot_type(node.upper_bound), + snapshot_type(node.default), + ) elif isinstance(node, TypeVarTupleExpr): - result[name] = ("TypeVarTuple", node.variance, snapshot_type(node.upper_bound)) + result[name] = ( + "TypeVarTuple", + node.variance, + snapshot_type(node.upper_bound), + snapshot_type(node.default), + ) else: assert symbol.kind != UNBOUND_IMPORTED if node and get_prefix(node.fullname) != name_prefix: @@ -382,6 +393,7 @@ def visit_type_var(self, typ: TypeVarType) -> SnapshotItem: typ.id.meta_level, snapshot_types(typ.values), snapshot_type(typ.upper_bound), + snapshot_type(typ.default), typ.variance, ) @@ -392,6 +404,7 @@ def visit_param_spec(self, typ: ParamSpecType) -> SnapshotItem: typ.id.meta_level, typ.flavor, snapshot_type(typ.upper_bound), + snapshot_type(typ.default), ) def visit_type_var_tuple(self, typ: TypeVarTupleType) -> SnapshotItem: @@ -400,6 +413,7 @@ def visit_type_var_tuple(self, typ: TypeVarTupleType) -> SnapshotItem: typ.id.raw_id, typ.id.meta_level, snapshot_type(typ.upper_bound), + snapshot_type(typ.default), ) def visit_unpack_type(self, typ: UnpackType) -> SnapshotItem: diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index 0cc6377bfb0f..5e3759227c7b 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -250,6 +250,15 @@ def process_type_var_def(self, tv: TypeVarType) -> None: for value in tv.values: self.fixup_type(value) self.fixup_type(tv.upper_bound) + self.fixup_type(tv.default) + + def process_param_spec_def(self, tv: ParamSpecType) -> None: + self.fixup_type(tv.upper_bound) + self.fixup_type(tv.default) + + def process_type_var_tuple_def(self, tv: TypeVarTupleType) -> None: + self.fixup_type(tv.upper_bound) + self.fixup_type(tv.default) def visit_assignment_stmt(self, node: AssignmentStmt) -> None: self.fixup_type(node.type) @@ -478,14 +487,17 @@ def visit_type_type(self, typ: TypeType) -> None: def visit_type_var(self, typ: TypeVarType) -> None: typ.upper_bound.accept(self) + typ.default.accept(self) for value in typ.values: value.accept(self) def visit_param_spec(self, typ: ParamSpecType) -> None: - pass + typ.upper_bound.accept(self) + typ.default.accept(self) def visit_type_var_tuple(self, typ: TypeVarTupleType) -> None: typ.upper_bound.accept(self) + typ.default.accept(self) def visit_unpack_type(self, typ: UnpackType) -> None: typ.type.accept(self) diff --git a/mypy/server/deps.py b/mypy/server/deps.py index 2659f942817d..ed85b74f2206 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -1039,6 +1039,8 @@ def visit_type_var(self, typ: TypeVarType) -> list[str]: triggers.append(make_trigger(typ.fullname)) if typ.upper_bound: triggers.extend(self.get_type_triggers(typ.upper_bound)) + if typ.default: + triggers.extend(self.get_type_triggers(typ.default)) for val in typ.values: triggers.extend(self.get_type_triggers(val)) return triggers @@ -1047,6 +1049,10 @@ def visit_param_spec(self, typ: ParamSpecType) -> list[str]: triggers = [] if typ.fullname: triggers.append(make_trigger(typ.fullname)) + if typ.upper_bound: + triggers.extend(self.get_type_triggers(typ.upper_bound)) + if typ.default: + triggers.extend(self.get_type_triggers(typ.default)) triggers.extend(self.get_type_triggers(typ.upper_bound)) return triggers @@ -1054,6 +1060,10 @@ def visit_type_var_tuple(self, typ: TypeVarTupleType) -> list[str]: triggers = [] if typ.fullname: triggers.append(make_trigger(typ.fullname)) + if typ.upper_bound: + triggers.extend(self.get_type_triggers(typ.upper_bound)) + if typ.default: + triggers.extend(self.get_type_triggers(typ.default)) triggers.extend(self.get_type_triggers(typ.upper_bound)) return triggers diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index eb183a939161..5f6943de3199 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -141,8 +141,23 @@ def test_tuple_type(self) -> None: ) def test_type_variable_binding(self) -> None: - assert_equal(str(TypeVarType("X", "X", 1, [], self.fx.o)), "X`1") - assert_equal(str(TypeVarType("X", "X", 1, [self.x, self.y], self.fx.o)), "X`1") + assert_equal( + str(TypeVarType("X", "X", 1, [], self.fx.o, AnyType(TypeOfAny.from_omitted_generics))), + "X`1", + ) + assert_equal( + str( + TypeVarType( + "X", + "X", + 1, + [self.x, self.y], + self.fx.o, + AnyType(TypeOfAny.from_omitted_generics), + ) + ), + "X`1", + ) def test_generic_function_type(self) -> None: c = CallableType( @@ -152,11 +167,16 @@ def test_generic_function_type(self) -> None: self.y, self.function, name=None, - variables=[TypeVarType("X", "X", -1, [], self.fx.o)], + variables=[ + TypeVarType("X", "X", -1, [], self.fx.o, AnyType(TypeOfAny.from_omitted_generics)) + ], ) assert_equal(str(c), "def [X] (X?, Y?) -> Y?") - v = [TypeVarType("Y", "Y", -1, [], self.fx.o), TypeVarType("X", "X", -2, [], self.fx.o)] + v = [ + TypeVarType("Y", "Y", -1, [], self.fx.o, AnyType(TypeOfAny.from_omitted_generics)), + TypeVarType("X", "X", -2, [], self.fx.o, AnyType(TypeOfAny.from_omitted_generics)), + ] c2 = CallableType([], [], [], NoneType(), self.function, name=None, variables=v) assert_equal(str(c2), "def [Y, X] ()") @@ -183,7 +203,7 @@ def test_type_alias_expand_all(self) -> None: def test_recursive_nested_in_non_recursive(self) -> None: A, _ = self.fx.def_alias_1(self.fx.a) - T = TypeVarType("T", "T", -1, [], self.fx.o) + T = TypeVarType("T", "T", -1, [], self.fx.o, AnyType(TypeOfAny.from_omitted_generics)) NA = self.fx.non_rec_alias(Instance(self.fx.gi, [T]), [T], [A]) assert not NA.is_recursive assert has_recursive_types(NA) @@ -634,7 +654,9 @@ def callable(self, vars: list[str], *a: Type) -> CallableType: tv: list[TypeVarType] = [] n = -1 for v in vars: - tv.append(TypeVarType(v, v, n, [], self.fx.o)) + tv.append( + TypeVarType(v, v, n, [], self.fx.o, AnyType(TypeOfAny.from_omitted_generics)) + ) n -= 1 return CallableType( list(a[:-1]), diff --git a/mypy/test/typefixture.py b/mypy/test/typefixture.py index 1013b87c213f..bf1500a3cdec 100644 --- a/mypy/test/typefixture.py +++ b/mypy/test/typefixture.py @@ -54,7 +54,15 @@ def __init__(self, variance: int = COVARIANT) -> None: def make_type_var( name: str, id: int, values: list[Type], upper_bound: Type, variance: int ) -> TypeVarType: - return TypeVarType(name, name, id, values, upper_bound, variance) + return TypeVarType( + name, + name, + id, + values, + upper_bound, + AnyType(TypeOfAny.from_omitted_generics), + variance, + ) self.t = make_type_var("T", 1, [], self.o, variance) # T`1 (type variable) self.tf = make_type_var("T", -1, [], self.o, variance) # T`-1 (type variable) @@ -212,7 +220,14 @@ def make_type_var( self._add_bool_dunder(self.ai) def make_type_var_tuple(name: str, id: int, upper_bound: Type) -> TypeVarTupleType: - return TypeVarTupleType(name, name, id, upper_bound, self.std_tuple) + return TypeVarTupleType( + name, + name, + id, + upper_bound, + self.std_tuple, + AnyType(TypeOfAny.from_omitted_generics), + ) self.ts = make_type_var_tuple("Ts", 1, self.o) # Ts`1 (type var tuple) self.ss = make_type_var_tuple("Ss", 2, self.o) # Ss`2 (type var tuple) @@ -301,13 +316,32 @@ def make_type_info( v: list[TypeVarLikeType] = [] for id, n in enumerate(typevars, 1): if typevar_tuple_index is not None and id - 1 == typevar_tuple_index: - v.append(TypeVarTupleType(n, n, id, self.o, self.std_tuple)) + v.append( + TypeVarTupleType( + n, + n, + id, + self.o, + self.std_tuple, + AnyType(TypeOfAny.from_omitted_generics), + ) + ) else: if variances: variance = variances[id - 1] else: variance = COVARIANT - v.append(TypeVarType(n, n, id, [], self.o, variance=variance)) + v.append( + TypeVarType( + n, + n, + id, + [], + self.o, + AnyType(TypeOfAny.from_omitted_generics), + variance=variance, + ) + ) class_def.type_vars = v info = TypeInfo(SymbolTable(), class_def, module_name) diff --git a/mypy/treetransform.py b/mypy/treetransform.py index 424fa6b96415..bb34d8de2884 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -646,12 +646,17 @@ def visit_type_var_expr(self, node: TypeVarExpr) -> TypeVarExpr: node.fullname, self.types(node.values), self.type(node.upper_bound), + self.type(node.default), variance=node.variance, ) def visit_paramspec_expr(self, node: ParamSpecExpr) -> ParamSpecExpr: return ParamSpecExpr( - node.name, node.fullname, self.type(node.upper_bound), variance=node.variance + node.name, + node.fullname, + self.type(node.upper_bound), + self.type(node.default), + variance=node.variance, ) def visit_type_var_tuple_expr(self, node: TypeVarTupleExpr) -> TypeVarTupleExpr: @@ -660,6 +665,7 @@ def visit_type_var_tuple_expr(self, node: TypeVarTupleExpr) -> TypeVarTupleExpr: node.fullname, self.type(node.upper_bound), node.tuple_fallback, + self.type(node.default), variance=node.variance, ) diff --git a/mypy/tvar_scope.py b/mypy/tvar_scope.py index 1716df89aa39..c7a653a1552d 100644 --- a/mypy/tvar_scope.py +++ b/mypy/tvar_scope.py @@ -95,6 +95,7 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType: id=TypeVarId(i, namespace=namespace), values=tvar_expr.values, upper_bound=tvar_expr.upper_bound, + default=tvar_expr.default, variance=tvar_expr.variance, line=tvar_expr.line, column=tvar_expr.column, @@ -106,6 +107,7 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType: i, flavor=ParamSpecFlavor.BARE, upper_bound=tvar_expr.upper_bound, + default=tvar_expr.default, line=tvar_expr.line, column=tvar_expr.column, ) @@ -116,6 +118,7 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType: i, upper_bound=tvar_expr.upper_bound, tuple_fallback=tvar_expr.tuple_fallback, + default=tvar_expr.default, line=tvar_expr.line, column=tvar_expr.column, ) diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index 5a5643f35c01..2efae49e9e10 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -346,13 +346,13 @@ def visit_deleted_type(self, t: DeletedType) -> T: return self.strategy([]) def visit_type_var(self, t: TypeVarType) -> T: - return self.query_types([t.upper_bound] + t.values) + return self.query_types([t.upper_bound, t.default] + t.values) def visit_param_spec(self, t: ParamSpecType) -> T: - return self.strategy([]) + return self.query_types([t.upper_bound, t.default]) def visit_type_var_tuple(self, t: TypeVarTupleType) -> T: - return self.strategy([]) + return self.query_types([t.upper_bound, t.default]) def visit_unpack_type(self, t: UnpackType) -> T: return self.query_types([t.type]) @@ -480,13 +480,13 @@ def visit_deleted_type(self, t: DeletedType) -> bool: return self.default def visit_type_var(self, t: TypeVarType) -> bool: - return self.query_types([t.upper_bound] + t.values) + return self.query_types([t.upper_bound, t.default] + t.values) def visit_param_spec(self, t: ParamSpecType) -> bool: - return self.default + return self.query_types([t.upper_bound, t.default]) def visit_type_var_tuple(self, t: TypeVarTupleType) -> bool: - return self.default + return self.query_types([t.upper_bound, t.default]) def visit_unpack_type(self, t: UnpackType) -> bool: return self.query_types([t.type]) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index b5e7e9e7a3f9..d1e6e315b9e3 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -321,6 +321,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) tvar_def.id, tvar_def.flavor, tvar_def.upper_bound, + tvar_def.default, line=t.line, column=t.column, ) @@ -374,6 +375,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) tvar_def.id, tvar_def.upper_bound, sym.node.tuple_fallback, + tvar_def.default, line=t.line, column=t.column, ) @@ -1549,6 +1551,7 @@ def anal_var_def(self, var_def: TypeVarLikeType) -> TypeVarLikeType: id=var_def.id.raw_id, values=self.anal_array(var_def.values), upper_bound=var_def.upper_bound.accept(self), + default=var_def.default.accept(self), variance=var_def.variance, line=var_def.line, column=var_def.column, diff --git a/mypy/types.py b/mypy/types.py index ab4816bac6fd..53f21e8c0222 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -17,7 +17,7 @@ Union, cast, ) -from typing_extensions import Final, TypeAlias as _TypeAlias, TypeGuard, overload +from typing_extensions import Final, Self, TypeAlias as _TypeAlias, TypeGuard, overload import mypy.nodes from mypy.bogus_type import Bogus @@ -526,12 +526,13 @@ def is_meta_var(self) -> bool: class TypeVarLikeType(ProperType): - __slots__ = ("name", "fullname", "id", "upper_bound") + __slots__ = ("name", "fullname", "id", "upper_bound", "default") name: str # Name (may be qualified) fullname: str # Fully qualified name id: TypeVarId upper_bound: Type + default: Type def __init__( self, @@ -539,6 +540,7 @@ def __init__( fullname: str, id: TypeVarId | int, upper_bound: Type, + default: Type, line: int = -1, column: int = -1, ) -> None: @@ -549,6 +551,7 @@ def __init__( id = TypeVarId(id) self.id = id self.upper_bound = upper_bound + self.default = default def serialize(self) -> JsonDict: raise NotImplementedError @@ -557,6 +560,18 @@ def serialize(self) -> JsonDict: def deserialize(cls, data: JsonDict) -> TypeVarLikeType: raise NotImplementedError + def copy_modified(self, *, id: TypeVarId, **kwargs: Any) -> Self: + raise NotImplementedError + + @classmethod + def new_unification_variable(cls, old: Self) -> Self: + new_id = TypeVarId.new(meta_level=1) + return old.copy_modified(id=new_id) + + def has_default(self) -> bool: + t = get_proper_type(self.default) + return not (isinstance(t, AnyType) and t.type_of_any == TypeOfAny.from_omitted_generics) + class TypeVarType(TypeVarLikeType): """Type that refers to a type variable.""" @@ -573,27 +588,26 @@ def __init__( id: TypeVarId | int, values: list[Type], upper_bound: Type, + default: Type, variance: int = INVARIANT, line: int = -1, column: int = -1, ) -> None: - super().__init__(name, fullname, id, upper_bound, line, column) + super().__init__(name, fullname, id, upper_bound, default, line, column) assert values is not None, "No restrictions must be represented by empty list" self.values = values self.variance = variance - @staticmethod - def new_unification_variable(old: TypeVarType) -> TypeVarType: - new_id = TypeVarId.new(meta_level=1) - return old.copy_modified(id=new_id) - def copy_modified( self, + *, values: Bogus[list[Type]] = _dummy, upper_bound: Bogus[Type] = _dummy, + default: Bogus[Type] = _dummy, id: Bogus[TypeVarId | int] = _dummy, line: int = _dummy_int, column: int = _dummy_int, + **kwargs: Any, ) -> TypeVarType: return TypeVarType( name=self.name, @@ -601,6 +615,7 @@ def copy_modified( id=self.id if id is _dummy else id, values=self.values if values is _dummy else values, upper_bound=self.upper_bound if upper_bound is _dummy else upper_bound, + default=self.default if default is _dummy else default, variance=self.variance, line=self.line if line == _dummy_int else line, column=self.column if column == _dummy_int else column, @@ -631,6 +646,7 @@ def serialize(self) -> JsonDict: "namespace": self.id.namespace, "values": [v.serialize() for v in self.values], "upper_bound": self.upper_bound.serialize(), + "default": self.default.serialize(), "variance": self.variance, } @@ -643,6 +659,7 @@ def deserialize(cls, data: JsonDict) -> TypeVarType: id=TypeVarId(data["id"], namespace=data["namespace"]), values=[deserialize_type(v) for v in data["values"]], upper_bound=deserialize_type(data["upper_bound"]), + default=deserialize_type(data["default"]), variance=data["variance"], ) @@ -686,20 +703,16 @@ def __init__( id: TypeVarId | int, flavor: int, upper_bound: Type, + default: Type, *, line: int = -1, column: int = -1, prefix: Parameters | None = None, ) -> None: - super().__init__(name, fullname, id, upper_bound, line=line, column=column) + super().__init__(name, fullname, id, upper_bound, default, line=line, column=column) self.flavor = flavor self.prefix = prefix or Parameters([], [], []) - @staticmethod - def new_unification_variable(old: ParamSpecType) -> ParamSpecType: - new_id = TypeVarId.new(meta_level=1) - return old.copy_modified(id=new_id) - def with_flavor(self, flavor: int) -> ParamSpecType: return ParamSpecType( self.name, @@ -707,6 +720,7 @@ def with_flavor(self, flavor: int) -> ParamSpecType: self.id, flavor, upper_bound=self.upper_bound, + default=self.default, prefix=self.prefix, ) @@ -716,6 +730,8 @@ def copy_modified( id: Bogus[TypeVarId | int] = _dummy, flavor: int = _dummy_int, prefix: Bogus[Parameters] = _dummy, + default: Bogus[Type] = _dummy, + **kwargs: Any, ) -> ParamSpecType: return ParamSpecType( self.name, @@ -723,6 +739,7 @@ def copy_modified( id if id is not _dummy else self.id, flavor if flavor != _dummy_int else self.flavor, self.upper_bound, + default=default if default is not _dummy else self.default, line=self.line, column=self.column, prefix=prefix if prefix is not _dummy else self.prefix, @@ -757,6 +774,7 @@ def serialize(self) -> JsonDict: "id": self.id.raw_id, "flavor": self.flavor, "upper_bound": self.upper_bound.serialize(), + "default": self.default.serialize(), "prefix": self.prefix.serialize(), } @@ -769,6 +787,7 @@ def deserialize(cls, data: JsonDict) -> ParamSpecType: data["id"], data["flavor"], deserialize_type(data["upper_bound"]), + deserialize_type(data["default"]), prefix=Parameters.deserialize(data["prefix"]), ) @@ -786,11 +805,12 @@ def __init__( id: TypeVarId | int, upper_bound: Type, tuple_fallback: Instance, + default: Type, *, line: int = -1, column: int = -1, ) -> None: - super().__init__(name, fullname, id, upper_bound, line=line, column=column) + super().__init__(name, fullname, id, upper_bound, default, line=line, column=column) self.tuple_fallback = tuple_fallback def serialize(self) -> JsonDict: @@ -802,6 +822,7 @@ def serialize(self) -> JsonDict: "id": self.id.raw_id, "upper_bound": self.upper_bound.serialize(), "tuple_fallback": self.tuple_fallback.serialize(), + "default": self.default.serialize(), } @classmethod @@ -813,6 +834,7 @@ def deserialize(cls, data: JsonDict) -> TypeVarTupleType: data["id"], deserialize_type(data["upper_bound"]), Instance.deserialize(data["tuple_fallback"]), + deserialize_type(data["default"]), ) def accept(self, visitor: TypeVisitor[T]) -> T: @@ -826,18 +848,21 @@ def __eq__(self, other: object) -> bool: return NotImplemented return self.id == other.id - @staticmethod - def new_unification_variable(old: TypeVarTupleType) -> TypeVarTupleType: - new_id = TypeVarId.new(meta_level=1) - return old.copy_modified(id=new_id) - - def copy_modified(self, id: Bogus[TypeVarId | int] = _dummy) -> TypeVarTupleType: + def copy_modified( + self, + *, + id: Bogus[TypeVarId | int] = _dummy, + upper_bound: Bogus[Type] = _dummy, + default: Bogus[Type] = _dummy, + **kwargs: Any, + ) -> TypeVarTupleType: return TypeVarTupleType( self.name, self.fullname, self.id if id is _dummy else id, - self.upper_bound, + self.upper_bound if upper_bound is _dummy else upper_bound, self.tuple_fallback, + self.default if default is _dummy else default, line=self.line, column=self.column, ) diff --git a/mypy/typevars.py b/mypy/typevars.py index e43afe4f1374..027a8e3f7fc5 100644 --- a/mypy/typevars.py +++ b/mypy/typevars.py @@ -36,6 +36,7 @@ def fill_typevars(typ: TypeInfo) -> Instance | TupleType: tv.id, tv.upper_bound, tv.tuple_fallback, + tv.default, line=-1, column=-1, ) @@ -43,7 +44,14 @@ def fill_typevars(typ: TypeInfo) -> Instance | TupleType: else: assert isinstance(tv, ParamSpecType) tv = ParamSpecType( - tv.name, tv.fullname, tv.id, tv.flavor, tv.upper_bound, line=-1, column=-1 + tv.name, + tv.fullname, + tv.id, + tv.flavor, + tv.upper_bound, + tv.default, + line=-1, + column=-1, ) tvs.append(tv) inst = Instance(typ, tvs)