From 5fb8d6262f2ade83234e46334eb3fb8a4bbaedc0 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 17 May 2024 11:23:20 +0100 Subject: [PATCH] [PEP 695] Partial support for new type parameter syntax in Python 3.12 (#17233) Add basic support for most features of PEP 695. It's still not generally useful, but it should be enough for experimentation and testing. I will continue working on follow-up PRs after this has been merged. This is currently behind a feature flag: `--enable-incomplete-feature=NewGenericSyntax` These features, among other things, are unimplemented (or at least untested): * Recursive type aliases * Checking for various errors * Inference of variance in complex cases * Dealing with unknown variance consistently * Scoping * Mypy daemon * Compilation using mypyc The trickiest remaining thing is probably variance inference in cases where some types aren't ready (i.e. not inferred) when we need variance. I have some ideas about how to tackle this, but it might need significant work. Currently the idea is to infer variance on demand when we need it, but we may need to defer if variance can't be calculated, for example if a type of an attribute is not yet ready. The current approach is to fall back to covariance in some cases, which is not ideal. Work on #15238. --- mypy/checker.py | 11 +- mypy/fastparse.py | 106 +++- mypy/join.py | 4 +- mypy/nodes.py | 54 +- mypy/options.py | 3 +- mypy/partially_defined.py | 5 + mypy/semanal.py | 174 +++++- mypy/strconv.py | 28 + mypy/subtypes.py | 83 ++- mypy/test/testparse.py | 4 + mypy/traverser.py | 6 + mypy/typestate.py | 10 +- mypy/visitor.py | 7 + mypyc/irbuild/visitor.py | 4 + test-data/unit/check-python312.test | 876 ++++++++++++++++++++++++++++ test-data/unit/parse-python312.test | 87 +++ test-data/unit/pythoneval.test | 21 + 17 files changed, 1441 insertions(+), 42 deletions(-) create mode 100644 test-data/unit/parse-python312.test diff --git a/mypy/checker.py b/mypy/checker.py index 9c10cd2fc30d..3daf64daaac4 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -146,6 +146,7 @@ from mypy.state import state from mypy.subtypes import ( find_member, + infer_class_variances, is_callable_compatible, is_equivalent, is_more_precise, @@ -2374,7 +2375,7 @@ def visit_class_def(self, defn: ClassDef) -> None: self.allow_abstract_call = old_allow_abstract_call # TODO: Apply the sig to the actual TypeInfo so we can handle decorators # that completely swap out the type. (e.g. Callable[[Type[A]], Type[B]]) - if typ.defn.type_vars: + if typ.defn.type_vars and typ.defn.type_args is None: for base_inst in typ.bases: for base_tvar, base_decl_tvar in zip( base_inst.args, base_inst.type.defn.type_vars @@ -2396,6 +2397,7 @@ def visit_class_def(self, defn: ClassDef) -> None: self.check_protocol_variance(defn) if not defn.has_incompatible_baseclass and defn.info.is_enum: self.check_enum(defn) + infer_class_variances(defn.info) def check_final_deletable(self, typ: TypeInfo) -> None: # These checks are only for mypyc. Only perform some checks that are easier @@ -2566,6 +2568,9 @@ def check_protocol_variance(self, defn: ClassDef) -> None: if they are actually covariant/contravariant, since this may break transitivity of subtyping, see PEP 544. """ + if defn.type_args is not None: + # Using new-style syntax (PEP 695), so variance will be inferred + return info = defn.info object_type = Instance(info.mro[-1], []) tvars = info.defn.type_vars @@ -3412,8 +3417,8 @@ def check_final(self, s: AssignmentStmt | OperatorAssignmentStmt | AssignmentExp if ( lv.node.final_unset_in_class and not lv.node.final_set_in_init - and not self.is_stub - and # It is OK to skip initializer in stub files. + and not self.is_stub # It is OK to skip initializer in stub files. + and # Avoid extra error messages, if there is no type in Final[...], # then we already reported the error about missing r.h.s. isinstance(s, AssignmentStmt) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index e208e4d0b7d9..ee042b96339f 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -17,6 +17,9 @@ ARG_POS, ARG_STAR, ARG_STAR2, + PARAM_SPEC_KIND, + TYPE_VAR_KIND, + TYPE_VAR_TUPLE_KIND, ArgKind, Argument, AssertStmt, @@ -79,6 +82,8 @@ TempNode, TryStmt, TupleExpr, + TypeAliasStmt, + TypeParam, UnaryExpr, Var, WhileStmt, @@ -87,7 +92,7 @@ YieldFromExpr, check_arg_names, ) -from mypy.options import Options +from mypy.options import NEW_GENERIC_SYNTAX, Options from mypy.patterns import ( AsPattern, ClassPattern, @@ -144,11 +149,6 @@ def ast3_parse( NamedExpr = ast3.NamedExpr Constant = ast3.Constant -if sys.version_info >= (3, 12): - ast_TypeAlias = ast3.TypeAlias -else: - ast_TypeAlias = Any - if sys.version_info >= (3, 10): Match = ast3.Match MatchValue = ast3.MatchValue @@ -171,11 +171,21 @@ def ast3_parse( MatchAs = Any MatchOr = Any AstNode = Union[ast3.expr, ast3.stmt, ast3.ExceptHandler] + if sys.version_info >= (3, 11): TryStar = ast3.TryStar else: TryStar = Any +if sys.version_info >= (3, 12): + ast_TypeAlias = ast3.TypeAlias + ast_ParamSpec = ast3.ParamSpec + ast_TypeVarTuple = ast3.TypeVarTuple +else: + ast_TypeAlias = Any + ast_ParamSpec = Any + ast_TypeVarTuple = Any + N = TypeVar("N", bound=Node) # There is no way to create reasonable fallbacks at this stage, @@ -884,6 +894,8 @@ def do_func_def( arg_kinds = [arg.kind for arg in args] arg_names = [None if arg.pos_only else arg.variable.name for arg in args] + # Type parameters, if using new syntax for generics (PEP 695) + explicit_type_params: list[TypeParam] | None = None arg_types: list[Type | None] = [] if no_type_check: @@ -937,12 +949,17 @@ def do_func_def( return_type = AnyType(TypeOfAny.from_error) else: if sys.version_info >= (3, 12) and n.type_params: - self.fail( - ErrorMessage("PEP 695 generics are not yet supported", code=codes.VALID_TYPE), - n.type_params[0].lineno, - n.type_params[0].col_offset, - blocker=False, - ) + if NEW_GENERIC_SYNTAX in self.options.enable_incomplete_feature: + explicit_type_params = self.translate_type_params(n.type_params) + else: + self.fail( + ErrorMessage( + "PEP 695 generics are not yet supported", code=codes.VALID_TYPE + ), + n.type_params[0].lineno, + n.type_params[0].col_offset, + blocker=False, + ) arg_types = [a.type_annotation for a in args] return_type = TypeConverter( @@ -986,7 +1003,7 @@ def do_func_def( self.class_and_function_stack.pop() self.class_and_function_stack.append("F") body = self.as_required_block(n.body, can_strip=True, is_coroutine=is_coroutine) - func_def = FuncDef(n.name, args, body, func_type) + func_def = FuncDef(n.name, args, body, func_type, explicit_type_params) if isinstance(func_def.type, CallableType): # semanal.py does some in-place modifications we want to avoid func_def.unanalyzed_type = func_def.type.copy_modified() @@ -1120,13 +1137,19 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef: self.class_and_function_stack.append("C") keywords = [(kw.arg, self.visit(kw.value)) for kw in n.keywords if kw.arg] + # Type parameters, if using new syntax for generics (PEP 695) + explicit_type_params: list[TypeParam] | None = None + if sys.version_info >= (3, 12) and n.type_params: - self.fail( - ErrorMessage("PEP 695 generics are not yet supported", code=codes.VALID_TYPE), - n.type_params[0].lineno, - n.type_params[0].col_offset, - blocker=False, - ) + if NEW_GENERIC_SYNTAX in self.options.enable_incomplete_feature: + explicit_type_params = self.translate_type_params(n.type_params) + else: + self.fail( + ErrorMessage("PEP 695 generics are not yet supported", code=codes.VALID_TYPE), + n.type_params[0].lineno, + n.type_params[0].col_offset, + blocker=False, + ) cdef = ClassDef( n.name, @@ -1135,6 +1158,7 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef: self.translate_expr_list(n.bases), metaclass=dict(keywords).get("metaclass"), keywords=keywords, + type_args=explicit_type_params, ) cdef.decorators = self.translate_expr_list(n.decorator_list) # Set lines to match the old mypy 0.700 lines, in order to keep @@ -1150,6 +1174,24 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef: self.class_and_function_stack.pop() return cdef + def translate_type_params(self, type_params: list[Any]) -> list[TypeParam]: + explicit_type_params = [] + for p in type_params: + bound = None + values: list[Type] = [] + if isinstance(p, ast_ParamSpec): # type: ignore[misc] + explicit_type_params.append(TypeParam(p.name, PARAM_SPEC_KIND, None, [])) + elif isinstance(p, ast_TypeVarTuple): # type: ignore[misc] + explicit_type_params.append(TypeParam(p.name, TYPE_VAR_TUPLE_KIND, None, [])) + else: + if isinstance(p.bound, ast3.Tuple): + conv = TypeConverter(self.errors, line=p.lineno) + values = [conv.visit(t) for t in p.bound.elts] + elif p.bound is not None: + bound = TypeConverter(self.errors, line=p.lineno).visit(p.bound) + explicit_type_params.append(TypeParam(p.name, TYPE_VAR_KIND, bound, values)) + return explicit_type_params + # Return(expr? value) def visit_Return(self, n: ast3.Return) -> ReturnStmt: node = ReturnStmt(self.visit(n.value)) @@ -1735,15 +1777,23 @@ def visit_MatchOr(self, n: MatchOr) -> OrPattern: node = OrPattern([self.visit(pattern) for pattern in n.patterns]) return self.set_line(node, n) - def visit_TypeAlias(self, n: ast_TypeAlias) -> AssignmentStmt: - self.fail( - ErrorMessage("PEP 695 type aliases are not yet supported", code=codes.VALID_TYPE), - n.lineno, - n.col_offset, - blocker=False, - ) - node = AssignmentStmt([NameExpr(n.name.id)], self.visit(n.value)) - return self.set_line(node, n) + # TypeAlias(identifier name, type_param* type_params, expr value) + def visit_TypeAlias(self, n: ast_TypeAlias) -> TypeAliasStmt | AssignmentStmt: + node: TypeAliasStmt | AssignmentStmt + if NEW_GENERIC_SYNTAX in self.options.enable_incomplete_feature: + type_params = self.translate_type_params(n.type_params) + value = self.visit(n.value) + node = TypeAliasStmt(self.visit_Name(n.name), type_params, value) + return self.set_line(node, n) + else: + self.fail( + ErrorMessage("PEP 695 type aliases are not yet supported", code=codes.VALID_TYPE), + n.lineno, + n.col_offset, + blocker=False, + ) + node = AssignmentStmt([NameExpr(n.name.id)], self.visit(n.value)) + return self.set_line(node, n) class TypeConverter: diff --git a/mypy/join.py b/mypy/join.py index 3603e9fefb7a..7e0ff301ebf8 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -6,7 +6,7 @@ import mypy.typeops from mypy.maptype import map_instance_to_supertype -from mypy.nodes import CONTRAVARIANT, COVARIANT, INVARIANT +from mypy.nodes import CONTRAVARIANT, COVARIANT, INVARIANT, VARIANCE_NOT_READY from mypy.state import state from mypy.subtypes import ( SubtypeContext, @@ -97,7 +97,7 @@ def join_instances(self, t: Instance, s: Instance) -> ProperType: elif isinstance(sa_proper, AnyType): new_type = AnyType(TypeOfAny.from_another_any, sa_proper) elif isinstance(type_var, TypeVarType): - if type_var.variance == COVARIANT: + if type_var.variance in (COVARIANT, VARIANCE_NOT_READY): new_type = join_types(ta, sa, self) if len(type_var.values) != 0 and new_type not in type_var.values: self.seen_instances.pop() diff --git a/mypy/nodes.py b/mypy/nodes.py index bb278d92392d..4c83d8081f6c 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -653,6 +653,28 @@ def set_line( self.variable.set_line(self.line, self.column, self.end_line, self.end_column) +# These specify the kind of a TypeParam +TYPE_VAR_KIND: Final = 0 +PARAM_SPEC_KIND: Final = 1 +TYPE_VAR_TUPLE_KIND: Final = 2 + + +class TypeParam: + __slots__ = ("name", "kind", "upper_bound", "values") + + def __init__( + self, + name: str, + kind: int, + upper_bound: mypy.types.Type | None, + values: list[mypy.types.Type], + ) -> None: + self.name = name + self.kind = kind + self.upper_bound = upper_bound + self.values = values + + FUNCITEM_FLAGS: Final = FUNCBASE_FLAGS + [ "is_overload", "is_generator", @@ -672,6 +694,7 @@ class FuncItem(FuncBase): "min_args", # Minimum number of arguments "max_pos", # Maximum number of positional arguments, -1 if no explicit # limit (*args not included) + "type_args", # New-style type parameters (PEP 695) "body", # Body of the function "is_overload", # Is this an overload variant of function with more than # one overload variant? @@ -689,12 +712,14 @@ def __init__( arguments: list[Argument] | None = None, body: Block | None = None, typ: mypy.types.FunctionLike | None = None, + type_args: list[TypeParam] | None = None, ) -> None: super().__init__() self.arguments = arguments or [] self.arg_names = [None if arg.pos_only else arg.variable.name for arg in self.arguments] self.arg_kinds: list[ArgKind] = [arg.kind for arg in self.arguments] self.max_pos: int = self.arg_kinds.count(ARG_POS) + self.arg_kinds.count(ARG_OPT) + self.type_args: list[TypeParam] | None = type_args self.body: Block = body or Block([]) self.type = typ self.unanalyzed_type = typ @@ -761,8 +786,9 @@ def __init__( arguments: list[Argument] | None = None, body: Block | None = None, typ: mypy.types.FunctionLike | None = None, + type_args: list[TypeParam] | None = None, ) -> None: - super().__init__(arguments, body, typ) + super().__init__(arguments, body, typ, type_args) self._name = name self.is_decorated = False self.is_conditional = False # Defined conditionally (within block)? @@ -1070,6 +1096,7 @@ class ClassDef(Statement): "name", "_fullname", "defs", + "type_args", "type_vars", "base_type_exprs", "removed_base_type_exprs", @@ -1089,6 +1116,9 @@ class ClassDef(Statement): name: str # Name of the class without module prefix _fullname: str # Fully qualified name of the class defs: Block + # New-style type parameters (PEP 695), unanalyzed + type_args: list[TypeParam] | None + # Semantically analyzed type parameters (all syntax variants) type_vars: list[mypy.types.TypeVarLikeType] # Base class expressions (not semantically analyzed -- can be arbitrary expressions) base_type_exprs: list[Expression] @@ -1111,12 +1141,14 @@ def __init__( base_type_exprs: list[Expression] | None = None, metaclass: Expression | None = None, keywords: list[tuple[str, Expression]] | None = None, + type_args: list[TypeParam] | None = None, ) -> None: super().__init__() self.name = name self._fullname = "" self.defs = defs self.type_vars = type_vars or [] + self.type_args = type_args self.base_type_exprs = base_type_exprs or [] self.removed_base_type_exprs = [] self.info = CLASSDEF_NO_INFO @@ -1607,6 +1639,25 @@ def accept(self, visitor: StatementVisitor[T]) -> T: return visitor.visit_match_stmt(self) +class TypeAliasStmt(Statement): + __slots__ = ("name", "type_args", "value") + + __match_args__ = ("name", "type_args", "value") + + name: NameExpr + type_args: list[TypeParam] + value: Expression # Will get translated into a type + + def __init__(self, name: NameExpr, type_args: list[TypeParam], value: Expression) -> None: + super().__init__() + self.name = name + self.type_args = type_args + self.value = value + + def accept(self, visitor: StatementVisitor[T]) -> T: + return visitor.visit_type_alias_stmt(self) + + # Expressions @@ -2442,6 +2493,7 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T: INVARIANT: Final = 0 COVARIANT: Final = 1 CONTRAVARIANT: Final = 2 +VARIANCE_NOT_READY: Final = 3 # Variance hasn't been inferred (using Python 3.12 syntax) class TypeVarLikeExpr(SymbolNode, Expression): diff --git a/mypy/options.py b/mypy/options.py index 91639828801e..5ef6bc2a35e7 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -73,7 +73,8 @@ class BuildType: TYPE_VAR_TUPLE: Final = "TypeVarTuple" UNPACK: Final = "Unpack" PRECISE_TUPLE_TYPES: Final = "PreciseTupleTypes" -INCOMPLETE_FEATURES: Final = frozenset((PRECISE_TUPLE_TYPES,)) +NEW_GENERIC_SYNTAX: Final = "NewGenericSyntax" +INCOMPLETE_FEATURES: Final = frozenset((PRECISE_TUPLE_TYPES, NEW_GENERIC_SYNTAX)) COMPLETE_FEATURES: Final = frozenset((TYPE_VAR_TUPLE, UNPACK)) diff --git a/mypy/partially_defined.py b/mypy/partially_defined.py index b7f577110fa8..da0bb517189a 100644 --- a/mypy/partially_defined.py +++ b/mypy/partially_defined.py @@ -36,6 +36,7 @@ SymbolTable, TryStmt, TupleExpr, + TypeAliasStmt, WhileStmt, WithStmt, implicit_module_attrs, @@ -673,3 +674,7 @@ def visit_import_from(self, o: ImportFrom) -> None: name = mod self.tracker.record_definition(name) super().visit_import_from(o) + + def visit_type_alias_stmt(self, o: TypeAliasStmt) -> None: + # Type alias target may contain forward references + self.tracker.record_definition(o.name.name) diff --git a/mypy/semanal.py b/mypy/semanal.py index 91a6b1808987..f92471c159de 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -81,9 +81,13 @@ LDEF, MDEF, NOT_ABSTRACT, + PARAM_SPEC_KIND, REVEAL_LOCALS, REVEAL_TYPE, RUNTIME_PROTOCOL_DECOS, + TYPE_VAR_KIND, + TYPE_VAR_TUPLE_KIND, + VARIANCE_NOT_READY, ArgKind, AssertStmt, AssertTypeExpr, @@ -159,9 +163,11 @@ TupleExpr, TypeAlias, TypeAliasExpr, + TypeAliasStmt, TypeApplication, TypedDictExpr, TypeInfo, + TypeParam, TypeVarExpr, TypeVarLikeExpr, TypeVarTupleExpr, @@ -787,6 +793,7 @@ def file_context( self.num_incomplete_refs = 0 if active_type: + self.push_type_args(active_type.defn.type_args, active_type.defn) self.incomplete_type_stack.append(False) scope.enter_class(active_type) self.enter_class(active_type.defn.info) @@ -800,6 +807,7 @@ def file_context( self.leave_class() self._type = None self.incomplete_type_stack.pop() + self.pop_type_args(active_type.defn.type_args) del self.options # @@ -835,6 +843,10 @@ def visit_func_def(self, defn: FuncDef) -> None: self.analyze_func_def(defn) def analyze_func_def(self, defn: FuncDef) -> None: + if self.push_type_args(defn.type_args, defn) is None: + self.defer(defn) + return + self.function_stack.append(defn) if defn.type: @@ -943,6 +955,8 @@ def analyze_func_def(self, defn: FuncDef) -> None: defn.type = defn.type.copy_modified(ret_type=ret_type) self.wrapped_coro_return_types[defn] = defn.type + self.pop_type_args(defn.type_args) + def remove_unpack_kwargs(self, defn: FuncDef, typ: CallableType) -> CallableType: if not typ.arg_kinds or typ.arg_kinds[-1] is not ArgKind.ARG_STAR2: return typ @@ -1618,9 +1632,79 @@ def visit_class_def(self, defn: ClassDef) -> None: self.incomplete_type_stack.append(not defn.info) namespace = self.qualified_name(defn.name) with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)): + if self.push_type_args(defn.type_args, defn) is None: + self.mark_incomplete(defn.name, defn) + return + self.analyze_class(defn) + self.pop_type_args(defn.type_args) self.incomplete_type_stack.pop() + def push_type_args( + self, type_args: list[TypeParam] | None, context: Context + ) -> list[tuple[str, TypeVarLikeExpr]] | None: + if not type_args: + return [] + tvs: list[tuple[str, TypeVarLikeExpr]] = [] + for p in type_args: + tv = self.analyze_type_param(p) + if tv is None: + return None + tvs.append((p.name, tv)) + + for name, tv in tvs: + self.add_symbol(name, tv, context, no_progress=True) + + return tvs + + def analyze_type_param(self, type_param: TypeParam) -> TypeVarLikeExpr | None: + fullname = self.qualified_name(type_param.name) + if type_param.upper_bound: + upper_bound = self.anal_type(type_param.upper_bound) + if upper_bound is None: + return None + else: + upper_bound = self.named_type("builtins.object") + default = AnyType(TypeOfAny.from_omitted_generics) + if type_param.kind == TYPE_VAR_KIND: + values = [] + if type_param.values: + for value in type_param.values: + analyzed = self.anal_type(value) + if analyzed is None: + return None + values.append(analyzed) + return TypeVarExpr( + name=type_param.name, + fullname=fullname, + values=values, + upper_bound=upper_bound, + default=default, + variance=VARIANCE_NOT_READY, + ) + elif type_param.kind == PARAM_SPEC_KIND: + return ParamSpecExpr( + name=type_param.name, fullname=fullname, upper_bound=upper_bound, default=default + ) + else: + assert type_param.kind == TYPE_VAR_TUPLE_KIND + tuple_fallback = self.named_type("builtins.tuple", [self.object_type()]) + return TypeVarTupleExpr( + name=type_param.name, + fullname=fullname, + # Upper bound for *Ts is *tuple[object, ...], it can never be object. + upper_bound=tuple_fallback.copy_modified(), + tuple_fallback=tuple_fallback, + default=default, + ) + + def pop_type_args(self, type_args: list[TypeParam] | None) -> None: + if not type_args: + return + for tv in type_args: + names = self.current_symbol_table() + del names[tv.name] + def analyze_class(self, defn: ClassDef) -> None: fullname = self.qualified_name(defn.name) if not defn.info and not self.is_core_builtin_class(defn): @@ -1914,6 +1998,13 @@ class Foo(Bar, Generic[T]): ... removed: list[int] = [] declared_tvars: TypeVarLikeList = [] is_protocol = False + if defn.type_args is not None: + for p in defn.type_args: + node = self.lookup(p.name, context) + assert node is not None + assert isinstance(node.node, TypeVarLikeExpr) + declared_tvars.append((p.name, node.node)) + for i, base_expr in enumerate(base_type_exprs): if isinstance(base_expr, StarExpr): base_expr.valid = True @@ -5125,6 +5216,79 @@ def visit_match_stmt(self, s: MatchStmt) -> None: guard.accept(self) self.visit_block(s.bodies[i]) + def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: + self.statement = s + type_params = self.push_type_args(s.type_args, s) + if type_params is None: + self.defer(s) + return + all_type_params_names = [p.name for p in s.type_args] + + try: + tag = self.track_incomplete_refs() + res, alias_tvars, depends_on, qualified_tvars, empty_tuple_index = self.analyze_alias( + s.name.name, + s.value, + allow_placeholder=True, + declared_type_vars=type_params, + all_declared_type_params_names=all_type_params_names, + ) + if not res: + res = AnyType(TypeOfAny.from_error) + + if not self.is_func_scope(): + # Only marking incomplete for top-level placeholders makes recursive aliases like + # `A = Sequence[str | A]` valid here, similar to how we treat base classes in class + # definitions, allowing `class str(Sequence[str]): ...` + incomplete_target = isinstance(res, ProperType) and isinstance( + res, PlaceholderType + ) + else: + incomplete_target = has_placeholder(res) + + if self.found_incomplete_ref(tag) or incomplete_target: + # Since we have got here, we know this must be a type alias (incomplete refs + # may appear in nested positions), therefore use becomes_typeinfo=True. + self.mark_incomplete(s.name.name, s.value, becomes_typeinfo=True) + return + + self.add_type_alias_deps(depends_on) + # In addition to the aliases used, we add deps on unbound + # type variables, since they are erased from target type. + self.add_type_alias_deps(qualified_tvars) + # The above are only direct deps on other aliases. + # For subscripted aliases, type deps from expansion are added in deps.py + # (because the type is stored). + check_for_explicit_any( + res, self.options, self.is_typeshed_stub_file, self.msg, context=s + ) + # When this type alias gets "inlined", the Any is not explicit anymore, + # so we need to replace it with non-explicit Anys. + res = make_any_non_explicit(res) + eager = self.is_func_scope() + alias_node = TypeAlias( + res, + self.qualified_name(s.name.name), + s.line, + s.column, + alias_tvars=alias_tvars, + no_args=False, + eager=eager, + ) + + existing = self.current_symbol_table().get(s.name.name) + if ( + existing + and isinstance(existing.node, (PlaceholderNode, TypeAlias)) + and existing.node.line == s.line + ): + existing.node = alias_node + else: + self.add_symbol(s.name.name, alias_node, s) + + finally: + self.pop_type_args(s.type_args) + # # Expressions # @@ -5803,6 +5967,7 @@ def lookup( for table in reversed(self.locals): if table is not None and name in table: return table[name] + # 4. Current file global scope if name in self.globals: return self.globals[name] @@ -6115,6 +6280,7 @@ def add_symbol( module_hidden: bool = False, can_defer: bool = True, escape_comprehensions: bool = False, + no_progress: bool = False, ) -> bool: """Add symbol to the currently active symbol table. @@ -6136,7 +6302,9 @@ def add_symbol( symbol = SymbolTableNode( kind, node, module_public=module_public, module_hidden=module_hidden ) - return self.add_symbol_table_node(name, symbol, context, can_defer, escape_comprehensions) + return self.add_symbol_table_node( + name, symbol, context, can_defer, escape_comprehensions, no_progress + ) def add_symbol_skip_local(self, name: str, node: SymbolNode) -> None: """Same as above, but skipping the local namespace. @@ -6167,6 +6335,7 @@ def add_symbol_table_node( context: Context | None = None, can_defer: bool = True, escape_comprehensions: bool = False, + no_progress: bool = False, ) -> bool: """Add symbol table node to the currently active symbol table. @@ -6215,7 +6384,8 @@ def add_symbol_table_node( self.name_already_defined(name, context, existing) elif name not in self.missing_names[-1] and "*" not in self.missing_names[-1]: names[name] = symbol - self.progress = True + if not no_progress: + self.progress = True return True return False diff --git a/mypy/strconv.py b/mypy/strconv.py index 42a07c7f62fa..a96a27c45d75 100644 --- a/mypy/strconv.py +++ b/mypy/strconv.py @@ -86,6 +86,9 @@ def func_helper(self, o: mypy.nodes.FuncItem) -> list[object]: elif kind == mypy.nodes.ARG_STAR2: extra.append(("DictVarArg", [arg.variable])) a: list[Any] = [] + if o.type_args: + for p in o.type_args: + a.append(self.type_param(p)) if args: a.append(("Args", args)) if o.type: @@ -187,6 +190,9 @@ def visit_class_def(self, o: mypy.nodes.ClassDef) -> str: a.insert(1, ("TupleType", [o.info.tuple_type])) if o.info and o.info.fallback_to_any: a.insert(1, "FallbackToAny") + if o.type_args: + for p in reversed(o.type_args): + a.insert(1, self.type_param(p)) return self.dump(a, o) def visit_var(self, o: mypy.nodes.Var) -> str: @@ -323,6 +329,28 @@ def visit_match_stmt(self, o: mypy.nodes.MatchStmt) -> str: a.append(("Body", o.bodies[i].body)) return self.dump(a, o) + def visit_type_alias_stmt(self, o: mypy.nodes.TypeAliasStmt) -> str: + a: list[Any] = [o.name] + for p in o.type_args: + a.append(self.type_param(p)) + a.append(o.value) + return self.dump(a, o) + + def type_param(self, p: mypy.nodes.TypeParam) -> list[Any]: + a: list[Any] = [] + if p.kind == mypy.nodes.PARAM_SPEC_KIND: + prefix = "**" + elif p.kind == mypy.nodes.TYPE_VAR_TUPLE_KIND: + prefix = "*" + else: + prefix = "" + a.append(prefix + p.name) + if p.upper_bound: + a.append(p.upper_bound) + if p.values: + a.append(("Values", p.values)) + return [("TypeParam", a)] + # Expressions # Simple expressions diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 4d5e7335b14f..a5523fbe0d45 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -8,7 +8,7 @@ import mypy.constraints import mypy.typeops from mypy.erasetype import erase_type -from mypy.expandtype import expand_self_type, expand_type_by_instance +from mypy.expandtype import expand_self_type, expand_type, expand_type_by_instance from mypy.maptype import map_instance_to_supertype # Circular import; done in the function instead. @@ -19,6 +19,7 @@ CONTRAVARIANT, COVARIANT, INVARIANT, + VARIANCE_NOT_READY, Decorator, FuncBase, OverloadedFuncDef, @@ -66,7 +67,7 @@ ) from mypy.types_utils import flatten_types from mypy.typestate import SubtypeKind, type_state -from mypy.typevars import fill_typevars_with_any +from mypy.typevars import fill_typevars, fill_typevars_with_any # Flags for detected protocol members IS_SETTABLE: Final = 1 @@ -361,7 +362,10 @@ def check_type_parameter( p_left = get_proper_type(left) if isinstance(p_left, UninhabitedType) and p_left.ambiguous: variance = COVARIANT - if variance == COVARIANT: + # If variance hasn't been inferred yet, we are lenient and default to + # covariance. This shouldn't happen often, but it's very difficult to + # avoid these cases altogether. + if variance == COVARIANT or variance == VARIANCE_NOT_READY: if proper_subtype: return is_proper_subtype(left, right, subtype_context=subtype_context) else: @@ -575,8 +579,12 @@ def visit_instance(self, left: Instance) -> bool: else: type_params = zip(t.args, right.args, right.type.defn.type_vars) if not self.subtype_context.ignore_type_params: + tried_infer = False for lefta, righta, tvar in type_params: if isinstance(tvar, TypeVarType): + if tvar.variance == VARIANCE_NOT_READY and not tried_infer: + infer_class_variances(right.type) + tried_infer = True if not check_type_parameter( lefta, righta, @@ -1978,3 +1986,72 @@ def is_more_precise(left: Type, right: Type, *, ignore_promotions: bool = False) if isinstance(right, AnyType): return True return is_proper_subtype(left, right, ignore_promotions=ignore_promotions) + + +def all_non_object_members(info: TypeInfo) -> set[str]: + members = set(info.names) + for base in info.mro[1:-1]: + members.update(base.names) + return members + + +def infer_variance(info: TypeInfo, i: int) -> bool: + """Infer the variance of the ith type variable of a generic class. + + Return True if successful. This can fail if some inferred types aren't ready. + """ + object_type = Instance(info.mro[-1], []) + + for variance in COVARIANT, CONTRAVARIANT, INVARIANT: + tv = info.defn.type_vars[i] + assert isinstance(tv, TypeVarType) + if tv.variance != VARIANCE_NOT_READY: + continue + tv.variance = variance + co = True + contra = True + tvar = info.defn.type_vars[i] + self_type = fill_typevars(info) + for member in all_non_object_members(info): + if member in ("__init__", "__new__"): + continue + node = info[member].node + if isinstance(node, Var) and node.type is None: + tv.variance = VARIANCE_NOT_READY + return False + if isinstance(self_type, TupleType): + self_type = mypy.typeops.tuple_fallback(self_type) + + flags = get_member_flags(member, self_type) + typ = find_member(member, self_type, self_type) + settable = IS_SETTABLE in flags + if typ: + typ2 = expand_type(typ, {tvar.id: object_type}) + if not is_subtype(typ, typ2): + co = False + if not is_subtype(typ2, typ): + contra = False + if settable: + co = False + if co: + v = COVARIANT + elif contra: + v = CONTRAVARIANT + else: + v = INVARIANT + if v == variance: + break + tv.variance = VARIANCE_NOT_READY + return True + + +def infer_class_variances(info: TypeInfo) -> bool: + if not info.defn.type_args: + return True + tvs = info.defn.type_vars + success = True + for i, tv in enumerate(tvs): + if isinstance(tv, TypeVarType) and tv.variance == VARIANCE_NOT_READY: + if not infer_variance(info, i): + success = False + return success diff --git a/mypy/test/testparse.py b/mypy/test/testparse.py index e33fa7e53ff0..e215920a6797 100644 --- a/mypy/test/testparse.py +++ b/mypy/test/testparse.py @@ -23,6 +23,8 @@ class ParserSuite(DataSuite): if sys.version_info < (3, 10): files.remove("parse-python310.test") + if sys.version_info < (3, 12): + files.remove("parse-python312.test") def run_case(self, testcase: DataDrivenTestCase) -> None: test_parser(testcase) @@ -39,6 +41,8 @@ def test_parser(testcase: DataDrivenTestCase) -> None: if testcase.file.endswith("python310.test"): options.python_version = (3, 10) + elif testcase.file.endswith("python312.test"): + options.python_version = (3, 12) else: options.python_version = defaults.PYTHON3_VERSION diff --git a/mypy/traverser.py b/mypy/traverser.py index d11dd395f978..225de27e7002 100644 --- a/mypy/traverser.py +++ b/mypy/traverser.py @@ -71,6 +71,7 @@ TupleExpr, TypeAlias, TypeAliasExpr, + TypeAliasStmt, TypeApplication, TypedDictExpr, TypeVarExpr, @@ -243,6 +244,11 @@ def visit_match_stmt(self, o: MatchStmt) -> None: guard.accept(self) o.bodies[i].accept(self) + def visit_type_alias_stmt(self, o: TypeAliasStmt) -> None: + o.name.accept(self) + # TODO: params + o.value.accept(self) + def visit_member_expr(self, o: MemberExpr) -> None: o.expr.accept(self) diff --git a/mypy/typestate.py b/mypy/typestate.py index c5a5da03eae5..0082c5564705 100644 --- a/mypy/typestate.py +++ b/mypy/typestate.py @@ -8,9 +8,9 @@ from typing import Dict, Final, Set, Tuple from typing_extensions import TypeAlias as _TypeAlias -from mypy.nodes import TypeInfo +from mypy.nodes import VARIANCE_NOT_READY, TypeInfo from mypy.server.trigger import make_trigger -from mypy.types import Instance, Type, TypeVarId, get_proper_type +from mypy.types import Instance, Type, TypeVarId, TypeVarType, get_proper_type MAX_NEGATIVE_CACHE_TYPES: Final = 1000 MAX_NEGATIVE_CACHE_ENTRIES: Final = 10000 @@ -192,6 +192,12 @@ def record_subtype_cache_entry( # These are unlikely to match, due to the large space of # possible values. Avoid uselessly increasing cache sizes. return + if any( + (isinstance(tv, TypeVarType) and tv.variance == VARIANCE_NOT_READY) + for tv in right.type.defn.type_vars + ): + # Variance indeterminate -- don't know the result + return cache = self._subtype_caches.setdefault(right.type, {}) cache.setdefault(kind, set()).add((left, right)) diff --git a/mypy/visitor.py b/mypy/visitor.py index c5aa3caa8295..340e1af64e00 100644 --- a/mypy/visitor.py +++ b/mypy/visitor.py @@ -309,6 +309,10 @@ def visit_try_stmt(self, o: mypy.nodes.TryStmt) -> T: def visit_match_stmt(self, o: mypy.nodes.MatchStmt) -> T: pass + @abstractmethod + def visit_type_alias_stmt(self, o: mypy.nodes.TypeAliasStmt) -> T: + pass + @trait @mypyc_attr(allow_interpreted_subclasses=True) @@ -460,6 +464,9 @@ def visit_with_stmt(self, o: mypy.nodes.WithStmt) -> T: def visit_match_stmt(self, o: mypy.nodes.MatchStmt) -> T: pass + def visit_type_alias_stmt(self, o: mypy.nodes.TypeAliasStmt) -> T: + pass + # Expressions (default no-op implementation) def visit_int_expr(self, o: mypy.nodes.IntExpr) -> T: diff --git a/mypyc/irbuild/visitor.py b/mypyc/irbuild/visitor.py index 12e186fd40d8..e7256f036e4c 100644 --- a/mypyc/irbuild/visitor.py +++ b/mypyc/irbuild/visitor.py @@ -70,6 +70,7 @@ TryStmt, TupleExpr, TypeAliasExpr, + TypeAliasStmt, TypeApplication, TypedDictExpr, TypeVarExpr, @@ -249,6 +250,9 @@ def visit_nonlocal_decl(self, stmt: NonlocalDecl) -> None: def visit_match_stmt(self, stmt: MatchStmt) -> None: transform_match_stmt(self.builder, stmt) + def visit_type_alias_stmt(self, stmt: TypeAliasStmt) -> None: + self.bail('The "type" statement is not yet supported by mypyc', stmt.line) + # Expressions def visit_name_expr(self, expr: NameExpr) -> Value: diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index 2b99a42628b1..53656ae5e3fb 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -82,3 +82,879 @@ reveal_type(ba2) # N: Revealed type is "def (*Any) -> builtins.str" [builtins fixtures/tuple.pyi] [typing fixtures/typing-full.pyi] + +[case testPEP695GenericFunctionSyntax] +# flags: --enable-incomplete-feature=NewGenericSyntax + +def ident[TV](x: TV) -> TV: + y: TV = x + y = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "TV") + return x + +reveal_type(ident(1)) # N: Revealed type is "builtins.int" +reveal_type(ident('x')) # N: Revealed type is "builtins.str" + +a: TV # E: Name "TV" is not defined + +def tup[T, S](x: T, y: S) -> tuple[T, S]: + reveal_type((x, y)) # N: Revealed type is "Tuple[T`-1, S`-2]" + return (x, y) + +reveal_type(tup(1, 'x')) # N: Revealed type is "Tuple[builtins.int, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testPEP695GenericClassSyntax] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T]: + x: T + + def __init__(self, x: T) -> None: + self.x = x + + def ident(self, x: T) -> T: + y: T = x + if int(): + return self.x + else: + return y + +reveal_type(C("x")) # N: Revealed type is "__main__.C[builtins.str]" +c: C[int] = C(1) +reveal_type(c.x) # N: Revealed type is "builtins.int" +reveal_type(c.ident(1)) # N: Revealed type is "builtins.int" + +[case testPEP695GenericMethodInGenericClass] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T]: + def m[S](self, x: S) -> T | S: ... + +a: C[int] = C[object]() # E: Incompatible types in assignment (expression has type "C[object]", variable has type "C[int]") +b: C[object] = C[int]() + +reveal_type(C[str]().m(1)) # N: Revealed type is "Union[builtins.str, builtins.int]" + +[case testPEP695InferVarianceSimpleFromMethod] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Invariant[T]: + def f(self, x: T) -> None: + pass + + def g(self) -> T | None: + return None + +a: Invariant[object] +b: Invariant[int] +if int(): + a = b # E: Incompatible types in assignment (expression has type "Invariant[int]", variable has type "Invariant[object]") +if int(): + b = a # E: Incompatible types in assignment (expression has type "Invariant[object]", variable has type "Invariant[int]") + +class Covariant[T]: + def g(self) -> T | None: + return None + +c: Covariant[object] +d: Covariant[int] +if int(): + c = d +if int(): + d = c # E: Incompatible types in assignment (expression has type "Covariant[object]", variable has type "Covariant[int]") + +class Contravariant[T]: + def f(self, x: T) -> None: + pass + +e: Contravariant[object] +f: Contravariant[int] +if int(): + e = f # E: Incompatible types in assignment (expression has type "Contravariant[int]", variable has type "Contravariant[object]") +if int(): + f = e + +[case testPEP695InferVarianceSimpleFromAttribute] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Invariant1[T]: + def __init__(self, x: T) -> None: + self.x = x + +a: Invariant1[object] +b: Invariant1[int] +if int(): + a = b # E: Incompatible types in assignment (expression has type "Invariant1[int]", variable has type "Invariant1[object]") +if int(): + b = a # E: Incompatible types in assignment (expression has type "Invariant1[object]", variable has type "Invariant1[int]") + +class Invariant2[T]: + def __init__(self) -> None: + self.x: list[T] = [] + +a2: Invariant2[object] +b2: Invariant2[int] +if int(): + a2 = b2 # E: Incompatible types in assignment (expression has type "Invariant2[int]", variable has type "Invariant2[object]") +if int(): + b2 = a2 # E: Incompatible types in assignment (expression has type "Invariant2[object]", variable has type "Invariant2[int]") + +class Invariant3[T]: + def __init__(self) -> None: + self.x: T | None = None + +a3: Invariant3[object] +b3: Invariant3[int] +if int(): + a3 = b3 # E: Incompatible types in assignment (expression has type "Invariant3[int]", variable has type "Invariant3[object]") +if int(): + b3 = a3 # E: Incompatible types in assignment (expression has type "Invariant3[object]", variable has type "Invariant3[int]") + +[case testPEP695InferVarianceRecursive] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Invariant[T]: + def f(self, x: Invariant[T]) -> Invariant[T]: + return x + +class Covariant[T]: + def f(self) -> Covariant[T]: + return self + +class Contravariant[T]: + def f(self, x: Contravariant[T]) -> None: + pass + +a: Invariant[object] +b: Invariant[int] +if int(): + a = b # E: Incompatible types in assignment (expression has type "Invariant[int]", variable has type "Invariant[object]") +if int(): + b = a # E: Incompatible types in assignment (expression has type "Invariant[object]", variable has type "Invariant[int]") + +c: Covariant[object] +d: Covariant[int] +if int(): + c = d +if int(): + d = c # E: Incompatible types in assignment (expression has type "Covariant[object]", variable has type "Covariant[int]") + +e: Contravariant[object] +f: Contravariant[int] +if int(): + e = f # E: Incompatible types in assignment (expression has type "Contravariant[int]", variable has type "Contravariant[object]") +if int(): + f = e + +[case testPEP695InferVarianceCalculateOnDemand] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Covariant[T]: + def __init__(self) -> None: + self.x = [1] + + def f(self) -> None: + c = Covariant[int]() + # We need to know that T is covariant here + self.g(c) + c2 = Covariant[object]() + self.h(c2) # E: Argument 1 to "h" of "Covariant" has incompatible type "Covariant[object]"; expected "Covariant[int]" + + def g(self, x: Covariant[object]) -> None: pass + def h(self, x: Covariant[int]) -> None: pass + +[case testPEP695InferVarianceNotReadyWhenNeeded] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Covariant[T]: + def f(self) -> None: + c = Covariant[int]() + # We need to know that T is covariant here + self.g(c) + c2 = Covariant[object]() + self.h(c2) # E: Argument 1 to "h" of "Covariant" has incompatible type "Covariant[object]"; expected "Covariant[int]" + + def g(self, x: Covariant[object]) -> None: pass + def h(self, x: Covariant[int]) -> None: pass + + def __init__(self) -> None: + self.x = [1] + +class Invariant[T]: + def f(self) -> None: + c = Invariant(1) + # We need to know that T is invariant here, and for this we need the type + # of self.x, which won't be available on the first type checking pass, + # since __init__ is defined later in the file. In this case we fall back + # covariance. + self.g(c) + c2 = Invariant(object()) + self.h(c2) # E: Argument 1 to "h" of "Invariant" has incompatible type "Invariant[object]"; expected "Invariant[int]" + + def g(self, x: Invariant[object]) -> None: pass + def h(self, x: Invariant[int]) -> None: pass + + def __init__(self, x: T) -> None: + self.x = x + +# Now we should have the variance correct. +a: Invariant[object] +b: Invariant[int] +if int(): + a = b # E: Incompatible types in assignment (expression has type "Invariant[int]", variable has type "Invariant[object]") +if int(): + b = a # E: Incompatible types in assignment (expression has type "Invariant[object]", variable has type "Invariant[int]") + +[case testPEP695InferVarianceNotReadyForJoin] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Invariant[T]: + def f(self) -> None: + # Assume covariance if variance us not ready + reveal_type([Invariant(1), Invariant(object())]) \ + # N: Revealed type is "builtins.list[__main__.Invariant[builtins.object]]" + + def __init__(self, x: T) -> None: + self.x = x + +reveal_type([Invariant(1), Invariant(object())]) # N: Revealed type is "builtins.list[builtins.object]" + +[case testPEP695InferVarianceNotReadyForMeet] +# flags: --enable-incomplete-feature=NewGenericSyntax + +from typing import TypeVar, Callable + +S = TypeVar("S") +def c(a: Callable[[S], None], b: Callable[[S], None]) -> S: ... + +def a1(x: Invariant[int]) -> None: pass +def a2(x: Invariant[object]) -> None: pass + +class Invariant[T]: + def f(self) -> None: + reveal_type(c(a1, a2)) # N: Revealed type is "__main__.Invariant[builtins.int]" + + def __init__(self, x: T) -> None: + self.x = x + +reveal_type(c(a1, a2)) # N: Revealed type is "Never" + +[case testPEP695InheritInvariant] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Invariant[T]: + x: T + +class Subclass[T](Invariant[T]): + pass + +x: Invariant[int] +y: Invariant[object] +if int(): + x = y # E: Incompatible types in assignment (expression has type "Invariant[object]", variable has type "Invariant[int]") +if int(): + y = x # E: Incompatible types in assignment (expression has type "Invariant[int]", variable has type "Invariant[object]") + +a: Subclass[int] +b: Subclass[object] +if int(): + a = b # E: Incompatible types in assignment (expression has type "Subclass[object]", variable has type "Subclass[int]") +if int(): + b = a # E: Incompatible types in assignment (expression has type "Subclass[int]", variable has type "Subclass[object]") + +[case testPEP695InheritanceMakesInvariant] +# flags: --enable-incomplete-feature=NewGenericSyntax +class Covariant[T]: + def f(self) -> T: + ... + +class Subclass[T](Covariant[list[T]]): + pass + +x: Covariant[int] = Covariant[object]() # E: Incompatible types in assignment (expression has type "Covariant[object]", variable has type "Covariant[int]") +y: Covariant[object] = Covariant[int]() + +a: Subclass[int] = Subclass[object]() # E: Incompatible types in assignment (expression has type "Subclass[object]", variable has type "Subclass[int]") +b: Subclass[object] = Subclass[int]() # E: Incompatible types in assignment (expression has type "Subclass[int]", variable has type "Subclass[object]") + +[case testPEP695InheritCoOrContravariant] +# flags: --enable-incomplete-feature=NewGenericSyntax +class Contravariant[T]: + def f(self, x: T) -> None: pass + +class CovSubclass[T](Contravariant[T]): + pass + +a: CovSubclass[int] = CovSubclass[object]() +b: CovSubclass[object] = CovSubclass[int]() # E: Incompatible types in assignment (expression has type "CovSubclass[int]", variable has type "CovSubclass[object]") + +class Covariant[T]: + def f(self) -> T: ... + +class CoSubclass[T](Covariant[T]): + pass + +c: CoSubclass[int] = CoSubclass[object]() # E: Incompatible types in assignment (expression has type "CoSubclass[object]", variable has type "CoSubclass[int]") +d: CoSubclass[object] = CoSubclass[int]() + +class InvSubclass[T](Covariant[T]): + def g(self, x: T) -> None: pass + +e: InvSubclass[int] = InvSubclass[object]() # E: Incompatible types in assignment (expression has type "InvSubclass[object]", variable has type "InvSubclass[int]") +f: InvSubclass[object] = InvSubclass[int]() # E: Incompatible types in assignment (expression has type "InvSubclass[int]", variable has type "InvSubclass[object]") + +[case testPEP695FinalAttribute] +# flags: --enable-incomplete-feature=NewGenericSyntax +from typing import Final + +class C[T]: + def __init__(self, x: T) -> None: + self.x: Final = x + +a: C[int] = C[object](1) # E: Incompatible types in assignment (expression has type "C[object]", variable has type "C[int]") +b: C[object] = C[int](1) + +[case testPEP695TwoTypeVariables] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T, S]: + def f(self, x: T) -> None: ... + def g(self) -> S: ... + +a: C[int, int] = C[object, int]() +b: C[object, int] = C[int, int]() # E: Incompatible types in assignment (expression has type "C[int, int]", variable has type "C[object, int]") +c: C[int, int] = C[int, object]() # E: Incompatible types in assignment (expression has type "C[int, object]", variable has type "C[int, int]") +d: C[int, object] = C[int, int]() + +[case testPEP695Properties] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class R[T]: + @property + def p(self) -> T: ... + +class RW[T]: + @property + def p(self) -> T: ... + @p.setter + def p(self, x: T) -> None: ... + +a: R[int] = R[object]() # E: Incompatible types in assignment (expression has type "R[object]", variable has type "R[int]") +b: R[object] = R[int]() +c: RW[int] = RW[object]() # E: Incompatible types in assignment (expression has type "RW[object]", variable has type "RW[int]") +d: RW[object] = RW[int]() # E: Incompatible types in assignment (expression has type "RW[int]", variable has type "RW[object]") +[builtins fixtures/property.pyi] + +[case testPEP695Protocol] +# flags: --enable-incomplete-feature=NewGenericSyntax +from typing import Protocol + +class PContra[T](Protocol): + def f(self, x: T) -> None: ... + +PContra() # E: Cannot instantiate protocol class "PContra" +a: PContra[int] +b: PContra[object] +if int(): + a = b +if int(): + b = a # E: Incompatible types in assignment (expression has type "PContra[int]", variable has type "PContra[object]") + +class PCov[T](Protocol): + def f(self) -> T: ... + +PCov() # E: Cannot instantiate protocol class "PCov" +c: PCov[int] +d: PCov[object] +if int(): + c = d # E: Incompatible types in assignment (expression has type "PCov[object]", variable has type "PCov[int]") +if int(): + d = c + +class PInv[T](Protocol): + def f(self, x: T) -> T: ... + +PInv() # E: Cannot instantiate protocol class "PInv" +e: PInv[int] +f: PInv[object] +if int(): + e = f # E: Incompatible types in assignment (expression has type "PInv[object]", variable has type "PInv[int]") +if int(): + f = e # E: Incompatible types in assignment (expression has type "PInv[int]", variable has type "PInv[object]") + +[case testPEP695TypeAlias] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T]: pass +class D[T, S]: pass + +type A[S] = C[S] + +a: A[int] +reveal_type(a) # N: Revealed type is "__main__.C[builtins.int]" + +type A2[T] = C[C[T]] +a2: A2[str] +reveal_type(a2) # N: Revealed type is "__main__.C[__main__.C[builtins.str]]" + +type A3[T, S] = D[S, C[T]] +a3: A3[int, str] +reveal_type(a3) # N: Revealed type is "__main__.D[builtins.str, __main__.C[builtins.int]]" + +type A4 = int | str +a4: A4 +reveal_type(a4) # N: Revealed type is "Union[builtins.int, builtins.str]" + +[case testPEP695TypeAliasWithUnusedTypeParams] +# flags: --enable-incomplete-feature=NewGenericSyntax +type A[T] = int +a: A[str] +reveal_type(a) # N: Revealed type is "builtins.int" + +[case testPEP695TypeAliasForwardReference1] +# flags: --enable-incomplete-feature=NewGenericSyntax + +type A[T] = C[T] + +a: A[int] +reveal_type(a) # N: Revealed type is "__main__.C[builtins.int]" + +class C[T]: pass + +[case testPEP695TypeAliasForwardReference2] +# flags: --enable-incomplete-feature=NewGenericSyntax + +type X = C +type A = X + +a: A +reveal_type(a) # N: Revealed type is "__main__.C" + +class C: pass + +[case testPEP695TypeAliasForwardReference3] +# flags: --enable-incomplete-feature=NewGenericSyntax + +type X = D +type A = C[X] + +a: A +reveal_type(a) # N: Revealed type is "__main__.C[__main__.D]" + +class C[T]: pass +class D: pass + +[case testPEP695TypeAliasForwardReference4] +# flags: --enable-incomplete-feature=NewGenericSyntax + +type A = C + +# Note that this doesn't actually work at runtime, but we currently don't +# keep track whether a type alias is valid in various runtime type contexts. +class D(A): + pass + +class C: pass + +x: C = D() +y: D = C() # E: Incompatible types in assignment (expression has type "C", variable has type "D") + +[case testPEP695TypeAliasForwardReference5] +# flags: --enable-incomplete-feature=NewGenericSyntax +type A = str +type B[T] = C[T] +class C[T]: pass +a: A +b: B[int] +c: C[str] +reveal_type(a) # N: Revealed type is "builtins.str" +reveal_type(b) # N: Revealed type is "__main__.C[builtins.int]" +reveal_type(c) # N: Revealed type is "__main__.C[builtins.str]" + +[case testPEP695TypeAliasWithUndefineName] +# flags: --enable-incomplete-feature=NewGenericSyntax +type A[T] = XXX # E: Name "XXX" is not defined +a: A[int] +reveal_type(a) # N: Revealed type is "Any" + +[case testPEP695TypeAliasInvalidType] +# flags: --enable-incomplete-feature=NewGenericSyntax +type A = int | 1 # E: Invalid type: try using Literal[1] instead? +a: A +reveal_type(a) # N: Revealed type is "Union[builtins.int, Any]" +type B = int + str # E: Invalid type alias: expression is not a valid type +b: B +reveal_type(b) # N: Revealed type is "Any" + +[case testPEP695TypeAliasBoundForwardReference] +# mypy: enable-incomplete-feature=NewGenericSyntax +type B[T: Foo] = list[T] +class Foo: pass + +[case testPEP695UpperBound] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class D: + x: int +class E(D): pass + +class C[T: D]: pass + +a: C[D] +b: C[E] +reveal_type(a) # N: Revealed type is "__main__.C[__main__.D]" +reveal_type(b) # N: Revealed type is "__main__.C[__main__.E]" + +c: C[int] # E: Type argument "int" of "C" must be a subtype of "D" + +def f[T: D](a: T) -> T: + reveal_type(a.x) # N: Revealed type is "builtins.int" + return a + +reveal_type(f(D())) # N: Revealed type is "__main__.D" +reveal_type(f(E())) # N: Revealed type is "__main__.E" +f(1) # E: Value of type variable "T" of "f" cannot be "int" + +[case testPEP695UpperBoundForwardReference1] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T: D]: pass + +a: C[D] +b: C[E] +reveal_type(a) # N: Revealed type is "__main__.C[__main__.D]" +reveal_type(b) # N: Revealed type is "__main__.C[__main__.E]" + +c: C[int] # E: Type argument "int" of "C" must be a subtype of "D" + +class D: pass +class E(D): pass + +[case testPEP695UpperBoundForwardReference2] +# flags: --enable-incomplete-feature=NewGenericSyntax + +type A = D +class C[T: A]: pass + +class D: pass +class E(D): pass + +a: C[D] +b: C[E] +reveal_type(a) # N: Revealed type is "__main__.C[__main__.D]" +reveal_type(b) # N: Revealed type is "__main__.C[__main__.E]" + +c: C[int] # E: Type argument "int" of "C" must be a subtype of "D" + +[case testPEP695UpperBoundForwardReference3] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class D[T]: pass +class E[T](D[T]): pass + +type A = D[X] + +class C[T: A]: pass + +class X: pass + +a: C[D[X]] +b: C[E[X]] +reveal_type(a) # N: Revealed type is "__main__.C[__main__.D[__main__.X]]" +reveal_type(b) # N: Revealed type is "__main__.C[__main__.E[__main__.X]]" + +c: C[D[int]] # E: Type argument "D[int]" of "C" must be a subtype of "D[X]" + +[case testPEP695UpperBoundForwardReference4] +# flags: --enable-incomplete-feature=NewGenericSyntax + +def f[T: D](a: T) -> T: + reveal_type(a.x) # N: Revealed type is "builtins.int" + return a + +class D: + x: int +class E(D): pass + +reveal_type(f(D())) # N: Revealed type is "__main__.D" +reveal_type(f(E())) # N: Revealed type is "__main__.E" +f(1) # E: Value of type variable "T" of "f" cannot be "int" + +[case testPEP695UpperBoundUndefinedName] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T: XX]: # E: Name "XX" is not defined + pass + +a: C[int] + +def f[T: YY](x: T) -> T: # E: Name "YY" is not defined + return x +reveal_type(f) # N: Revealed type is "def [T <: Any] (x: T`-1) -> T`-1" + +[case testPEP695UpperBoundWithMultipleParams] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T, S: int]: pass +class D[A: int, B]: pass + +def f[T: int, S: int | str](x: T, y: S) -> T | S: + return x + +C[str, int]() +C[str, str]() # E: Value of type variable "S" of "C" cannot be "str" +D[int, str]() +D[str, str]() # E: Value of type variable "A" of "D" cannot be "str" +f(1, 1) +u: int | str +f(1, u) +f('x', None) # E: Value of type variable "T" of "f" cannot be "str" \ + # E: Value of type variable "S" of "f" cannot be "None" + +[case testPEP695InferVarianceOfTupleType] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Cov[T](tuple[int, str]): + def f(self) -> T: pass + +class Cov2[T](tuple[T, T]): + pass + +class Contra[T](tuple[int, str]): + def f(self, x: T) -> None: pass + +a: Cov[object] = Cov[int]() +b: Cov[int] = Cov[object]() # E: Incompatible types in assignment (expression has type "Cov[object]", variable has type "Cov[int]") + +c: Cov2[object] = Cov2[int]() +d: Cov2[int] = Cov2[object]() # E: Incompatible types in assignment (expression has type "Cov2[object]", variable has type "Cov2[int]") + +e: Contra[int] = Contra[object]() +f: Contra[object] = Contra[int]() # E: Incompatible types in assignment (expression has type "Contra[int]", variable has type "Contra[object]") +[builtins fixtures/tuple-simple.pyi] + +[case testPEP695ValueRestiction] +# flags: --enable-incomplete-feature=NewGenericSyntax + +def f[T: (int, str)](x: T) -> T: + reveal_type(x) # N: Revealed type is "builtins.int" \ + # N: Revealed type is "builtins.str" + return x + +reveal_type(f(1)) # N: Revealed type is "builtins.int" +reveal_type(f('x')) # N: Revealed type is "builtins.str" +f(None) # E: Value of type variable "T" of "f" cannot be "None" + +class C[T: (object, None)]: pass + +a: C[object] +b: C[None] +c: C[int] # E: Value of type variable "T" of "C" cannot be "int" + +[case testPEP695ValueRestictionForwardReference] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T: (int, D)]: + def __init__(self, x: T) -> None: + a = x + if int(): + a = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") \ + # E: Incompatible types in assignment (expression has type "str", variable has type "D") + self.x: T = x + +reveal_type(C(1).x) # N: Revealed type is "builtins.int" +C(None) # E: Value of type variable "T" of "C" cannot be "None" + +class D: pass + +C(D()) + +[case testPEP695ValueRestictionUndefinedName] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T: (int, XX)]: # E: Name "XX" is not defined + pass + +def f[S: (int, YY)](x: S) -> S: # E: Name "YY" is not defined + return x + +[case testPEP695ParamSpec] +# flags: --enable-incomplete-feature=NewGenericSyntax +from typing import Callable + +def g[**P](f: Callable[P, None], *args: P.args, **kwargs: P.kwargs) -> None: + f(*args, **kwargs) + f(1, *args, **kwargs) # E: Argument 1 has incompatible type "int"; expected "P.args" + +def h(x: int, y: str) -> None: pass + +g(h, 1, y='x') +g(h, 1, x=1) # E: "g" gets multiple values for keyword argument "x" \ + # E: Missing positional argument "y" in call to "g" + +class C[**P, T]: + def m(self, *args: P.args, **kwargs: P.kwargs) -> T: ... + +a: C[[int, str], None] +reveal_type(a) # N: Revealed type is "__main__.C[[builtins.int, builtins.str], None]" +reveal_type(a.m) # N: Revealed type is "def (builtins.int, builtins.str)" +[builtins fixtures/tuple.pyi] + +[case testPEP695ParamSpecTypeAlias] +# flags: --enable-incomplete-feature=NewGenericSyntax +from typing import Callable + +type C[**P] = Callable[P, int] + +f: C[[str, int | None]] +reveal_type(f) # N: Revealed type is "def (builtins.str, Union[builtins.int, None]) -> builtins.int" +[builtins fixtures/tuple.pyi] + +[case testPEP695TypeVarTuple] +# flags: --enable-incomplete-feature=NewGenericSyntax + +def f[*Ts](t: tuple[*Ts]) -> tuple[*Ts]: + reveal_type(t) # N: Revealed type is "Tuple[Unpack[Ts`-1]]" + return t + +reveal_type(f((1, 'x'))) # N: Revealed type is "Tuple[Literal[1]?, Literal['x']?]" +a: tuple[int, ...] +reveal_type(f(a)) # N: Revealed type is "builtins.tuple[builtins.int, ...]" + +class C[T, *Ts]: + pass + +b: C[int, str, None] +reveal_type(b) # N: Revealed type is "__main__.C[builtins.int, builtins.str, None]" +c: C[str] +reveal_type(c) # N: Revealed type is "__main__.C[builtins.str]" +b = c # E: Incompatible types in assignment (expression has type "C[str]", variable has type "C[int, str, None]") +[builtins fixtures/tuple.pyi] + +[case testPEP695TypeVarTupleAlias] +# flags: --enable-incomplete-feature=NewGenericSyntax +from typing import Callable + +type C[*Ts] = tuple[*Ts, int] + +a: C[str, None] +reveal_type(a) # N: Revealed type is "Tuple[builtins.str, None, builtins.int]" +[builtins fixtures/tuple.pyi] + +[case testPEP695IncrementalFunction] +# flags: --enable-incomplete-feature=NewGenericSyntax +import a + +[file a.py] +import b + +[file a.py.2] +import b +reveal_type(b.f(1)) +reveal_type(b.g(1, 'x')) +b.g('x', 'x') +b.g(1, 2) + +[file b.py] +def f[T](x: T) -> T: + return x + +def g[T: int, S: (str, None)](x: T, y: S) -> T | S: + return x + +[out2] +tmp/a.py:2: note: Revealed type is "builtins.int" +tmp/a.py:3: note: Revealed type is "Union[builtins.int, builtins.str]" +tmp/a.py:4: error: Value of type variable "T" of "g" cannot be "str" +tmp/a.py:5: error: Value of type variable "S" of "g" cannot be "int" + +[case testPEP695IncrementalClass] +# flags: --enable-incomplete-feature=NewGenericSyntax +import a + +[file a.py] +import b + +[file a.py.2] +from b import C, D +x: C[int] +reveal_type(x) + +class N(int): pass +class SS(str): pass + +y1: D[int, str] +y2: D[N, str] +y3: D[int, None] +y4: D[int, None] +y5: D[int, SS] # Error +y6: D[object, str] # Error + +[file b.py] +class C[T]: pass + +class D[T: int, S: (str, None)]: + pass + +[out2] +tmp/a.py:3: note: Revealed type is "b.C[builtins.int]" +tmp/a.py:12: error: Value of type variable "S" of "D" cannot be "SS" +tmp/a.py:13: error: Type argument "object" of "D" must be a subtype of "int" + +[case testPEP695IncrementalParamSpecAndTypeVarTuple] +# flags: --enable-incomplete-feature=NewGenericSyntax +import a + +[file a.py] +import b + +[file a.py.2] +from b import C, D +x1: C[()] +x2: C[int] +x3: C[int, str] +y: D[[int, str]] +reveal_type(y.m) + +[file b.py] +class C[*Ts]: pass +class D[**P]: + def m(self, *args: P.args, **kwargs: P.kwargs) -> None: pass + +[builtins fixtures/tuple.pyi] +[out2] +tmp/a.py:6: note: Revealed type is "def (builtins.int, builtins.str)" + +[case testPEP695IncrementalTypeAlias] +# flags: --enable-incomplete-feature=NewGenericSyntax +import a + +[file a.py] +import b + +[file a.py.2] +from b import A, B +a: A +reveal_type(a) +b: B[int] +reveal_type(b) + +[file b.py] +type A = str +class Foo[T]: pass +type B[T] = Foo[T] + +[builtins fixtures/tuple.pyi] +[out2] +tmp/a.py:3: note: Revealed type is "builtins.str" +tmp/a.py:5: note: Revealed type is "b.Foo[builtins.int]" + +[case testPEP695UndefinedNameInGenericFunction] +# mypy: enable-incomplete-feature=NewGenericSyntax + +def f[T](x: T) -> T: + return unknown() # E: Name "unknown" is not defined + +class C: + def m[T](self, x: T) -> T: + return unknown() # E: Name "unknown" is not defined diff --git a/test-data/unit/parse-python312.test b/test-data/unit/parse-python312.test new file mode 100644 index 000000000000..28204ccd647b --- /dev/null +++ b/test-data/unit/parse-python312.test @@ -0,0 +1,87 @@ +[case testPEP695TypeAlias] +# mypy: enable-incomplete-feature=NewGenericSyntax +type A[T] = C[T] +[out] +MypyFile:1( + TypeAliasStmt:2( + NameExpr(A) + TypeParam( + T) + IndexExpr:2( + NameExpr(C) + NameExpr(T)))) + +[case testPEP695GenericFunction] +# mypy: enable-incomplete-feature=NewGenericSyntax + +def f[T](): pass +def g[T: str](): pass +def h[T: (int, str)](): pass +[out] +MypyFile:1( + FuncDef:3( + f + TypeParam( + T) + Block:3( + PassStmt:3())) + FuncDef:4( + g + TypeParam( + T + str?) + Block:4( + PassStmt:4())) + FuncDef:5( + h + TypeParam( + T + Values( + int? + str?)) + Block:5( + PassStmt:5()))) + +[case testPEP695ParamSpec] +# mypy: enable-incomplete-feature=NewGenericSyntax + +def f[**P](): pass +class C[T: int, **P]: pass +[out] +MypyFile:1( + FuncDef:3( + f + TypeParam( + **P) + Block:3( + PassStmt:3())) + ClassDef:4( + C + TypeParam( + T + int?) + TypeParam( + **P) + PassStmt:4())) + +[case testPEP695TypeVarTuple] +# mypy: enable-incomplete-feature=NewGenericSyntax + +def f[*Ts](): pass +class C[T: int, *Ts]: pass +[out] +MypyFile:1( + FuncDef:3( + f + TypeParam( + *Ts) + Block:3( + PassStmt:3())) + ClassDef:4( + C + TypeParam( + T + int?) + TypeParam( + *Ts) + PassStmt:4())) diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index b51a965c95da..0ed3540b6bb9 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -2091,3 +2091,24 @@ def f(d: Description) -> None: reveal_type(d.name_fn) [out] _testDataclassStrictOptionalAlwaysSet.py:9: note: Revealed type is "def (Union[builtins.int, None]) -> Union[builtins.str, None]" + +[case testPEP695VarianceInference] +# flags: --python-version=3.12 --enable-incomplete-feature=NewGenericSyntax +from typing import Callable, Final + +class Job[_R_co]: + def __init__(self, target: Callable[[], _R_co]) -> None: + self.target: Final = target + +def func( + action: Job[int | None], + a1: Job[int | None], + a2: Job[int], + a3: Job[None], +) -> None: + action = a1 + action = a2 + action = a3 + a2 = action # Error +[out] +_testPEP695VarianceInference.py:17: error: Incompatible types in assignment (expression has type "Job[None]", variable has type "Job[int]")