diff --git a/mypy/checker.py b/mypy/checker.py index 2b865f645330e..11f156a028b89 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -7093,6 +7093,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 c0c4e18d8f1f5..8f61ccdbc2531 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4138,7 +4138,9 @@ 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", -1, [], self.object_type()) + tv = TypeVarType( + "T", "T", -1, [], self.object_type(), AnyType(TypeOfAny.from_omitted_generics) + ) constructor = CallableType( [tv], [nodes.ARG_STAR], @@ -4321,8 +4323,12 @@ def visit_dict_expr(self, e: DictExpr) -> Type: tup.column = value.column args.append(tup) # Define type variables (used in constructors below). - kt = TypeVarType("KT", "KT", -1, [], self.object_type()) - vt = TypeVarType("VT", "VT", -2, [], self.object_type()) + kt = TypeVarType( + "KT", "KT", -1, [], self.object_type(), AnyType(TypeOfAny.from_omitted_generics) + ) + vt = TypeVarType( + "VT", "VT", -2, [], self.object_type(), AnyType(TypeOfAny.from_omitted_generics) + ) rv = None # Call dict(*args), unless it's empty and stargs is not. if args or not stargs: @@ -4693,7 +4699,9 @@ def check_generator_or_comprehension( # Infer the type of the list comprehension by using a synthetic generic # callable type. - tv = TypeVarType("T", "T", -1, [], self.object_type()) + tv = TypeVarType( + "T", "T", -1, [], self.object_type(), AnyType(TypeOfAny.from_omitted_generics) + ) tv_list: list[Type] = [tv] constructor = CallableType( tv_list, @@ -4713,8 +4721,12 @@ 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", -1, [], self.object_type()) - vtdef = TypeVarType("VT", "VT", -2, [], self.object_type()) + ktdef = TypeVarType( + "KT", "KT", -1, [], self.object_type(), AnyType(TypeOfAny.from_omitted_generics) + ) + vtdef = TypeVarType( + "VT", "VT", -2, [], self.object_type(), AnyType(TypeOfAny.from_omitted_generics) + ) constructor = CallableType( [ktdef, vtdef], [nodes.ARG_POS, nodes.ARG_POS], @@ -5242,6 +5254,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 6024e527705bb..1454dd4b96254 100644 --- a/mypy/copytype.py +++ b/mypy/copytype.py @@ -75,12 +75,15 @@ def visit_type_var(self, t: TypeVarType) -> ProperType: t.id, values=t.values, upper_bound=t.upper_bound, + default=t.default, variance=t.variance, ) return self.copy_common(t, dup) 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: @@ -94,7 +97,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 21c3a592669ec..fdaa3a111946b 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -27,7 +27,6 @@ TypedDictType, TypeType, TypeVarId, - TypeVarLikeType, TypeVarTupleType, TypeVarType, TypeVisitor, @@ -135,14 +134,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 01e4c0a716fc3..15f4c13c20fa2 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 eef7601d6aae7..00356d7a4ddbe 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 83d8d319f725d..63f4b79241e84 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2427,13 +2427,14 @@ 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: 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 @@ -2441,12 +2442,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 @@ -2484,9 +2491,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: @@ -2499,6 +2507,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, } @@ -2510,6 +2519,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"], ) @@ -2528,6 +2538,7 @@ def serialize(self) -> JsonDict: "name": self._name, "fullname": self._fullname, "upper_bound": self.upper_bound.serialize(), + "default": self.default.serialize(), "variance": self.variance, } @@ -2538,6 +2549,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"], ) @@ -2557,9 +2569,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: @@ -2572,6 +2585,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, } @@ -2583,6 +2597,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 80c2ff3d33259..1bd39341cf12c 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -762,10 +762,19 @@ def _add_order(ctx: mypy.plugin.ClassDefContext, adder: MethodAdder) -> None: # def __lt__(self: AT, other: AT) -> bool # This way comparisons with subclasses will work correctly. tvd = TypeVarType( - SELF_TVAR_NAME, ctx.cls.info.fullname + "." + SELF_TVAR_NAME, -1, [], object_type + SELF_TVAR_NAME, + ctx.cls.info.fullname + "." + SELF_TVAR_NAME, + -1, + [], + object_type, + 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 a577784217aa9..025a86f4dd8f1 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) @@ -268,7 +272,12 @@ def transform(self) -> bool: # the self type. obj_type = self._api.named_type("builtins.object") order_tvar_def = TypeVarType( - SELF_TVAR_NAME, info.fullname + "." + SELF_TVAR_NAME, -1, [], obj_type + SELF_TVAR_NAME, + info.fullname + "." + SELF_TVAR_NAME, + -1, + [], + obj_type, + 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 547bf4863edde..a2d051dd6b1ac 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1064,17 +1064,26 @@ def setup_self_type(self) -> None: assert self.type is not None info = self.type if info.self_type is not None: - if has_placeholder(info.self_type.upper_bound): + if has_placeholder(info.self_type.upper_bound) or has_placeholder( + info.self_type.default + ): # Similar to regular (user defined) type variables. self.process_placeholder( None, - "Self upper bound", + "Self upper bound or default", info, force_progress=info.self_type.upper_bound != fill_typevars(info), ) else: return - info.self_type = TypeVarType("Self", f"{info.fullname}.Self", 0, [], fill_typevars(info)) + info.self_type = TypeVarType( + "Self", + f"{info.fullname}.Self", + 0, + [], + fill_typevars(info), + AnyType(TypeOfAny.from_omitted_generics), + ) def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: self.statement = defn @@ -1595,6 +1604,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) @@ -3951,7 +3965,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 ( @@ -3973,7 +3987,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 ) @@ -3986,18 +4000,29 @@ 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): + call.analyzed.default = default + if ( + any(has_placeholder(v) for v in values) + or has_placeholder(upper_bound) + or has_placeholder(default) + ): self.process_placeholder( - None, f"TypeVar {'values' if values else 'upper bound'}", s, force_progress=updated + None, "TypeVar values, upper bound, or default", s, force_progress=updated ) self.add_symbol(name, call.analyzed, s) @@ -4047,11 +4072,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) @@ -4121,7 +4147,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: @@ -4161,13 +4187,15 @@ 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 @@ -4190,6 +4218,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 @@ -4201,7 +4231,12 @@ 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 diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index a9f12ceae5c2c..0fc75a3c947a0 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -549,6 +549,7 @@ def add_field( self.api.tvar_scope.new_unique_func_id(), [], info.tuple_type, + 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 c86ed828b2b9f..d097e1fb08dc8 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 83ae64fbc1a8d..93f178dca35a2 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 0cc6377bfb0f7..5e3759227c7b3 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 50b66b70b8aaf..30464b02d7e4b 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 6fe65675554bd..1edcd396f632f 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -132,8 +132,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( @@ -143,11 +158,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] ()") @@ -174,7 +194,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) @@ -628,7 +648,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 1013b87c213f2..bf1500a3cdec7 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 535f50d5cf5e1..94350c7d814bb 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -643,12 +643,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: @@ -657,6 +662,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 9b432d8e68ec5..236a2a5644ec7 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: 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 5a5643f35c017..2efae49e9e103 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 f3329af6207af..7b1a6947d8415 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, ) @@ -349,6 +350,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) tvar_def.id, tvar_def.values, tvar_def.upper_bound, + tvar_def.default, tvar_def.variance, line=t.line, column=t.column, @@ -381,6 +383,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, ) @@ -1539,6 +1542,7 @@ def anal_var_def(self, var_def: TypeVarLikeType) -> TypeVarLikeType: var_def.id.raw_id, self.anal_array(var_def.values), var_def.upper_bound.accept(self), + var_def.default.accept(self), var_def.variance, var_def.line, ) diff --git a/mypy/types.py b/mypy/types.py index e78209be058fe..803067a4688b4 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -18,7 +18,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 @@ -505,12 +505,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, @@ -518,6 +519,7 @@ def __init__( fullname: str, id: TypeVarId | int, upper_bound: Type, + default: Type, line: int = -1, column: int = -1, ) -> None: @@ -528,6 +530,7 @@ def __init__( id = TypeVarId(id) self.id = id self.upper_bound = upper_bound + self.default = default def serialize(self) -> JsonDict: raise NotImplementedError @@ -536,6 +539,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.""" @@ -552,27 +567,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( self.name, @@ -580,6 +594,7 @@ def copy_modified( self.id if id is _dummy else id, self.values if values is _dummy else values, self.upper_bound if upper_bound is _dummy else upper_bound, + self.default if default is _dummy else default, self.variance, self.line if line == _dummy_int else line, self.column if column == _dummy_int else column, @@ -610,6 +625,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, } @@ -622,6 +638,7 @@ def deserialize(cls, data: JsonDict) -> TypeVarType: TypeVarId(data["id"], namespace=data["namespace"]), [deserialize_type(v) for v in data["values"]], deserialize_type(data["upper_bound"]), + deserialize_type(data["default"]), data["variance"], ) @@ -665,20 +682,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, @@ -686,6 +699,7 @@ def with_flavor(self, flavor: int) -> ParamSpecType: self.id, flavor, upper_bound=self.upper_bound, + default=self.default, prefix=self.prefix, ) @@ -695,6 +709,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, @@ -702,6 +718,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, @@ -736,6 +753,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(), } @@ -748,6 +766,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"]), ) @@ -765,11 +784,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: @@ -781,6 +801,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 @@ -792,6 +813,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: @@ -805,18 +827,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 69c2eed37fa4f..b54ccd16b8ab6 100644 --- a/mypy/typevars.py +++ b/mypy/typevars.py @@ -33,6 +33,7 @@ def fill_typevars(typ: TypeInfo) -> Instance | TupleType: tv.id, tv.values, tv.upper_bound, + tv.default, tv.variance, line=-1, column=-1, @@ -45,6 +46,7 @@ def fill_typevars(typ: TypeInfo) -> Instance | TupleType: tv.id, tv.upper_bound, tv.tuple_fallback, + tv.default, line=-1, column=-1, ) @@ -52,7 +54,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)