Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5fb8d62

Browse files
authored
[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.
1 parent b74829e commit 5fb8d62

17 files changed

Lines changed: 1441 additions & 42 deletions

mypy/checker.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@
146146
from mypy.state import state
147147
from mypy.subtypes import (
148148
find_member,
149+
infer_class_variances,
149150
is_callable_compatible,
150151
is_equivalent,
151152
is_more_precise,
@@ -2374,7 +2375,7 @@ def visit_class_def(self, defn: ClassDef) -> None:
23742375
self.allow_abstract_call = old_allow_abstract_call
23752376
# TODO: Apply the sig to the actual TypeInfo so we can handle decorators
23762377
# that completely swap out the type. (e.g. Callable[[Type[A]], Type[B]])
2377-
if typ.defn.type_vars:
2378+
if typ.defn.type_vars and typ.defn.type_args is None:
23782379
for base_inst in typ.bases:
23792380
for base_tvar, base_decl_tvar in zip(
23802381
base_inst.args, base_inst.type.defn.type_vars
@@ -2396,6 +2397,7 @@ def visit_class_def(self, defn: ClassDef) -> None:
23962397
self.check_protocol_variance(defn)
23972398
if not defn.has_incompatible_baseclass and defn.info.is_enum:
23982399
self.check_enum(defn)
2400+
infer_class_variances(defn.info)
23992401

24002402
def check_final_deletable(self, typ: TypeInfo) -> None:
24012403
# 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:
25662568
if they are actually covariant/contravariant, since this may break
25672569
transitivity of subtyping, see PEP 544.
25682570
"""
2571+
if defn.type_args is not None:
2572+
# Using new-style syntax (PEP 695), so variance will be inferred
2573+
return
25692574
info = defn.info
25702575
object_type = Instance(info.mro[-1], [])
25712576
tvars = info.defn.type_vars
@@ -3412,8 +3417,8 @@ def check_final(self, s: AssignmentStmt | OperatorAssignmentStmt | AssignmentExp
34123417
if (
34133418
lv.node.final_unset_in_class
34143419
and not lv.node.final_set_in_init
3415-
and not self.is_stub
3416-
and # It is OK to skip initializer in stub files.
3420+
and not self.is_stub # It is OK to skip initializer in stub files.
3421+
and
34173422
# Avoid extra error messages, if there is no type in Final[...],
34183423
# then we already reported the error about missing r.h.s.
34193424
isinstance(s, AssignmentStmt)

mypy/fastparse.py

Lines changed: 78 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
ARG_POS,
1818
ARG_STAR,
1919
ARG_STAR2,
20+
PARAM_SPEC_KIND,
21+
TYPE_VAR_KIND,
22+
TYPE_VAR_TUPLE_KIND,
2023
ArgKind,
2124
Argument,
2225
AssertStmt,
@@ -79,6 +82,8 @@
7982
TempNode,
8083
TryStmt,
8184
TupleExpr,
85+
TypeAliasStmt,
86+
TypeParam,
8287
UnaryExpr,
8388
Var,
8489
WhileStmt,
@@ -87,7 +92,7 @@
8792
YieldFromExpr,
8893
check_arg_names,
8994
)
90-
from mypy.options import Options
95+
from mypy.options import NEW_GENERIC_SYNTAX, Options
9196
from mypy.patterns import (
9297
AsPattern,
9398
ClassPattern,
@@ -144,11 +149,6 @@ def ast3_parse(
144149
NamedExpr = ast3.NamedExpr
145150
Constant = ast3.Constant
146151

147-
if sys.version_info >= (3, 12):
148-
ast_TypeAlias = ast3.TypeAlias
149-
else:
150-
ast_TypeAlias = Any
151-
152152
if sys.version_info >= (3, 10):
153153
Match = ast3.Match
154154
MatchValue = ast3.MatchValue
@@ -171,11 +171,21 @@ def ast3_parse(
171171
MatchAs = Any
172172
MatchOr = Any
173173
AstNode = Union[ast3.expr, ast3.stmt, ast3.ExceptHandler]
174+
174175
if sys.version_info >= (3, 11):
175176
TryStar = ast3.TryStar
176177
else:
177178
TryStar = Any
178179

180+
if sys.version_info >= (3, 12):
181+
ast_TypeAlias = ast3.TypeAlias
182+
ast_ParamSpec = ast3.ParamSpec
183+
ast_TypeVarTuple = ast3.TypeVarTuple
184+
else:
185+
ast_TypeAlias = Any
186+
ast_ParamSpec = Any
187+
ast_TypeVarTuple = Any
188+
179189
N = TypeVar("N", bound=Node)
180190

181191
# There is no way to create reasonable fallbacks at this stage,
@@ -884,6 +894,8 @@ def do_func_def(
884894

885895
arg_kinds = [arg.kind for arg in args]
886896
arg_names = [None if arg.pos_only else arg.variable.name for arg in args]
897+
# Type parameters, if using new syntax for generics (PEP 695)
898+
explicit_type_params: list[TypeParam] | None = None
887899

888900
arg_types: list[Type | None] = []
889901
if no_type_check:
@@ -937,12 +949,17 @@ def do_func_def(
937949
return_type = AnyType(TypeOfAny.from_error)
938950
else:
939951
if sys.version_info >= (3, 12) and n.type_params:
940-
self.fail(
941-
ErrorMessage("PEP 695 generics are not yet supported", code=codes.VALID_TYPE),
942-
n.type_params[0].lineno,
943-
n.type_params[0].col_offset,
944-
blocker=False,
945-
)
952+
if NEW_GENERIC_SYNTAX in self.options.enable_incomplete_feature:
953+
explicit_type_params = self.translate_type_params(n.type_params)
954+
else:
955+
self.fail(
956+
ErrorMessage(
957+
"PEP 695 generics are not yet supported", code=codes.VALID_TYPE
958+
),
959+
n.type_params[0].lineno,
960+
n.type_params[0].col_offset,
961+
blocker=False,
962+
)
946963

947964
arg_types = [a.type_annotation for a in args]
948965
return_type = TypeConverter(
@@ -986,7 +1003,7 @@ def do_func_def(
9861003
self.class_and_function_stack.pop()
9871004
self.class_and_function_stack.append("F")
9881005
body = self.as_required_block(n.body, can_strip=True, is_coroutine=is_coroutine)
989-
func_def = FuncDef(n.name, args, body, func_type)
1006+
func_def = FuncDef(n.name, args, body, func_type, explicit_type_params)
9901007
if isinstance(func_def.type, CallableType):
9911008
# semanal.py does some in-place modifications we want to avoid
9921009
func_def.unanalyzed_type = func_def.type.copy_modified()
@@ -1120,13 +1137,19 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef:
11201137
self.class_and_function_stack.append("C")
11211138
keywords = [(kw.arg, self.visit(kw.value)) for kw in n.keywords if kw.arg]
11221139

1140+
# Type parameters, if using new syntax for generics (PEP 695)
1141+
explicit_type_params: list[TypeParam] | None = None
1142+
11231143
if sys.version_info >= (3, 12) and n.type_params:
1124-
self.fail(
1125-
ErrorMessage("PEP 695 generics are not yet supported", code=codes.VALID_TYPE),
1126-
n.type_params[0].lineno,
1127-
n.type_params[0].col_offset,
1128-
blocker=False,
1129-
)
1144+
if NEW_GENERIC_SYNTAX in self.options.enable_incomplete_feature:
1145+
explicit_type_params = self.translate_type_params(n.type_params)
1146+
else:
1147+
self.fail(
1148+
ErrorMessage("PEP 695 generics are not yet supported", code=codes.VALID_TYPE),
1149+
n.type_params[0].lineno,
1150+
n.type_params[0].col_offset,
1151+
blocker=False,
1152+
)
11301153

11311154
cdef = ClassDef(
11321155
n.name,
@@ -1135,6 +1158,7 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef:
11351158
self.translate_expr_list(n.bases),
11361159
metaclass=dict(keywords).get("metaclass"),
11371160
keywords=keywords,
1161+
type_args=explicit_type_params,
11381162
)
11391163
cdef.decorators = self.translate_expr_list(n.decorator_list)
11401164
# 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:
11501174
self.class_and_function_stack.pop()
11511175
return cdef
11521176

1177+
def translate_type_params(self, type_params: list[Any]) -> list[TypeParam]:
1178+
explicit_type_params = []
1179+
for p in type_params:
1180+
bound = None
1181+
values: list[Type] = []
1182+
if isinstance(p, ast_ParamSpec): # type: ignore[misc]
1183+
explicit_type_params.append(TypeParam(p.name, PARAM_SPEC_KIND, None, []))
1184+
elif isinstance(p, ast_TypeVarTuple): # type: ignore[misc]
1185+
explicit_type_params.append(TypeParam(p.name, TYPE_VAR_TUPLE_KIND, None, []))
1186+
else:
1187+
if isinstance(p.bound, ast3.Tuple):
1188+
conv = TypeConverter(self.errors, line=p.lineno)
1189+
values = [conv.visit(t) for t in p.bound.elts]
1190+
elif p.bound is not None:
1191+
bound = TypeConverter(self.errors, line=p.lineno).visit(p.bound)
1192+
explicit_type_params.append(TypeParam(p.name, TYPE_VAR_KIND, bound, values))
1193+
return explicit_type_params
1194+
11531195
# Return(expr? value)
11541196
def visit_Return(self, n: ast3.Return) -> ReturnStmt:
11551197
node = ReturnStmt(self.visit(n.value))
@@ -1735,15 +1777,23 @@ def visit_MatchOr(self, n: MatchOr) -> OrPattern:
17351777
node = OrPattern([self.visit(pattern) for pattern in n.patterns])
17361778
return self.set_line(node, n)
17371779

1738-
def visit_TypeAlias(self, n: ast_TypeAlias) -> AssignmentStmt:
1739-
self.fail(
1740-
ErrorMessage("PEP 695 type aliases are not yet supported", code=codes.VALID_TYPE),
1741-
n.lineno,
1742-
n.col_offset,
1743-
blocker=False,
1744-
)
1745-
node = AssignmentStmt([NameExpr(n.name.id)], self.visit(n.value))
1746-
return self.set_line(node, n)
1780+
# TypeAlias(identifier name, type_param* type_params, expr value)
1781+
def visit_TypeAlias(self, n: ast_TypeAlias) -> TypeAliasStmt | AssignmentStmt:
1782+
node: TypeAliasStmt | AssignmentStmt
1783+
if NEW_GENERIC_SYNTAX in self.options.enable_incomplete_feature:
1784+
type_params = self.translate_type_params(n.type_params)
1785+
value = self.visit(n.value)
1786+
node = TypeAliasStmt(self.visit_Name(n.name), type_params, value)
1787+
return self.set_line(node, n)
1788+
else:
1789+
self.fail(
1790+
ErrorMessage("PEP 695 type aliases are not yet supported", code=codes.VALID_TYPE),
1791+
n.lineno,
1792+
n.col_offset,
1793+
blocker=False,
1794+
)
1795+
node = AssignmentStmt([NameExpr(n.name.id)], self.visit(n.value))
1796+
return self.set_line(node, n)
17471797

17481798

17491799
class TypeConverter:

mypy/join.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import mypy.typeops
88
from mypy.maptype import map_instance_to_supertype
9-
from mypy.nodes import CONTRAVARIANT, COVARIANT, INVARIANT
9+
from mypy.nodes import CONTRAVARIANT, COVARIANT, INVARIANT, VARIANCE_NOT_READY
1010
from mypy.state import state
1111
from mypy.subtypes import (
1212
SubtypeContext,
@@ -97,7 +97,7 @@ def join_instances(self, t: Instance, s: Instance) -> ProperType:
9797
elif isinstance(sa_proper, AnyType):
9898
new_type = AnyType(TypeOfAny.from_another_any, sa_proper)
9999
elif isinstance(type_var, TypeVarType):
100-
if type_var.variance == COVARIANT:
100+
if type_var.variance in (COVARIANT, VARIANCE_NOT_READY):
101101
new_type = join_types(ta, sa, self)
102102
if len(type_var.values) != 0 and new_type not in type_var.values:
103103
self.seen_instances.pop()

mypy/nodes.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,28 @@ def set_line(
653653
self.variable.set_line(self.line, self.column, self.end_line, self.end_column)
654654

655655

656+
# These specify the kind of a TypeParam
657+
TYPE_VAR_KIND: Final = 0
658+
PARAM_SPEC_KIND: Final = 1
659+
TYPE_VAR_TUPLE_KIND: Final = 2
660+
661+
662+
class TypeParam:
663+
__slots__ = ("name", "kind", "upper_bound", "values")
664+
665+
def __init__(
666+
self,
667+
name: str,
668+
kind: int,
669+
upper_bound: mypy.types.Type | None,
670+
values: list[mypy.types.Type],
671+
) -> None:
672+
self.name = name
673+
self.kind = kind
674+
self.upper_bound = upper_bound
675+
self.values = values
676+
677+
656678
FUNCITEM_FLAGS: Final = FUNCBASE_FLAGS + [
657679
"is_overload",
658680
"is_generator",
@@ -672,6 +694,7 @@ class FuncItem(FuncBase):
672694
"min_args", # Minimum number of arguments
673695
"max_pos", # Maximum number of positional arguments, -1 if no explicit
674696
# limit (*args not included)
697+
"type_args", # New-style type parameters (PEP 695)
675698
"body", # Body of the function
676699
"is_overload", # Is this an overload variant of function with more than
677700
# one overload variant?
@@ -689,12 +712,14 @@ def __init__(
689712
arguments: list[Argument] | None = None,
690713
body: Block | None = None,
691714
typ: mypy.types.FunctionLike | None = None,
715+
type_args: list[TypeParam] | None = None,
692716
) -> None:
693717
super().__init__()
694718
self.arguments = arguments or []
695719
self.arg_names = [None if arg.pos_only else arg.variable.name for arg in self.arguments]
696720
self.arg_kinds: list[ArgKind] = [arg.kind for arg in self.arguments]
697721
self.max_pos: int = self.arg_kinds.count(ARG_POS) + self.arg_kinds.count(ARG_OPT)
722+
self.type_args: list[TypeParam] | None = type_args
698723
self.body: Block = body or Block([])
699724
self.type = typ
700725
self.unanalyzed_type = typ
@@ -761,8 +786,9 @@ def __init__(
761786
arguments: list[Argument] | None = None,
762787
body: Block | None = None,
763788
typ: mypy.types.FunctionLike | None = None,
789+
type_args: list[TypeParam] | None = None,
764790
) -> None:
765-
super().__init__(arguments, body, typ)
791+
super().__init__(arguments, body, typ, type_args)
766792
self._name = name
767793
self.is_decorated = False
768794
self.is_conditional = False # Defined conditionally (within block)?
@@ -1070,6 +1096,7 @@ class ClassDef(Statement):
10701096
"name",
10711097
"_fullname",
10721098
"defs",
1099+
"type_args",
10731100
"type_vars",
10741101
"base_type_exprs",
10751102
"removed_base_type_exprs",
@@ -1089,6 +1116,9 @@ class ClassDef(Statement):
10891116
name: str # Name of the class without module prefix
10901117
_fullname: str # Fully qualified name of the class
10911118
defs: Block
1119+
# New-style type parameters (PEP 695), unanalyzed
1120+
type_args: list[TypeParam] | None
1121+
# Semantically analyzed type parameters (all syntax variants)
10921122
type_vars: list[mypy.types.TypeVarLikeType]
10931123
# Base class expressions (not semantically analyzed -- can be arbitrary expressions)
10941124
base_type_exprs: list[Expression]
@@ -1111,12 +1141,14 @@ def __init__(
11111141
base_type_exprs: list[Expression] | None = None,
11121142
metaclass: Expression | None = None,
11131143
keywords: list[tuple[str, Expression]] | None = None,
1144+
type_args: list[TypeParam] | None = None,
11141145
) -> None:
11151146
super().__init__()
11161147
self.name = name
11171148
self._fullname = ""
11181149
self.defs = defs
11191150
self.type_vars = type_vars or []
1151+
self.type_args = type_args
11201152
self.base_type_exprs = base_type_exprs or []
11211153
self.removed_base_type_exprs = []
11221154
self.info = CLASSDEF_NO_INFO
@@ -1607,6 +1639,25 @@ def accept(self, visitor: StatementVisitor[T]) -> T:
16071639
return visitor.visit_match_stmt(self)
16081640

16091641

1642+
class TypeAliasStmt(Statement):
1643+
__slots__ = ("name", "type_args", "value")
1644+
1645+
__match_args__ = ("name", "type_args", "value")
1646+
1647+
name: NameExpr
1648+
type_args: list[TypeParam]
1649+
value: Expression # Will get translated into a type
1650+
1651+
def __init__(self, name: NameExpr, type_args: list[TypeParam], value: Expression) -> None:
1652+
super().__init__()
1653+
self.name = name
1654+
self.type_args = type_args
1655+
self.value = value
1656+
1657+
def accept(self, visitor: StatementVisitor[T]) -> T:
1658+
return visitor.visit_type_alias_stmt(self)
1659+
1660+
16101661
# Expressions
16111662

16121663

@@ -2442,6 +2493,7 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T:
24422493
INVARIANT: Final = 0
24432494
COVARIANT: Final = 1
24442495
CONTRAVARIANT: Final = 2
2496+
VARIANCE_NOT_READY: Final = 3 # Variance hasn't been inferred (using Python 3.12 syntax)
24452497

24462498

24472499
class TypeVarLikeExpr(SymbolNode, Expression):

mypy/options.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ class BuildType:
7373
TYPE_VAR_TUPLE: Final = "TypeVarTuple"
7474
UNPACK: Final = "Unpack"
7575
PRECISE_TUPLE_TYPES: Final = "PreciseTupleTypes"
76-
INCOMPLETE_FEATURES: Final = frozenset((PRECISE_TUPLE_TYPES,))
76+
NEW_GENERIC_SYNTAX: Final = "NewGenericSyntax"
77+
INCOMPLETE_FEATURES: Final = frozenset((PRECISE_TUPLE_TYPES, NEW_GENERIC_SYNTAX))
7778
COMPLETE_FEATURES: Final = frozenset((TYPE_VAR_TUPLE, UNPACK))
7879

7980

0 commit comments

Comments
 (0)