From fd064fd6680b8b462c5440252cb00e1cbd0f4d62 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 26 Oct 2023 19:26:32 -0700 Subject: [PATCH] Initial work on type aliases --- mypy/fastparse.py | 44 +++++++++++++----- mypy/nodes.py | 70 +++++++++++++++++++++++++++++ mypy/semanal.py | 33 ++++++++++++++ mypy/strconv.py | 13 ++++++ mypy/traverser.py | 19 ++++++++ mypy/treetransform.py | 9 ++++ mypy/visitor.py | 14 ++++++ mypyc/irbuild/visitor.py | 8 ++++ test-data/unit/check-python312.test | 3 +- 9 files changed, 202 insertions(+), 11 deletions(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 95d99db84a15..5ebd9aa94ec6 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -79,6 +79,9 @@ TempNode, TryStmt, TupleExpr, + TypeAliasStmt, + TypeVarLikeKind, + TypeVarNode, UnaryExpr, Var, WhileStmt, @@ -146,8 +149,14 @@ def ast3_parse( if sys.version_info >= (3, 12): ast_TypeAlias = ast3.TypeAlias + ast_TypeVar = ast3.TypeVar + ast_ParamSpec = ast3.ParamSpec + ast_TypeVarTuple = ast3.TypeVarTuple else: ast_TypeAlias = Any + ast_TypeVar = Any + ast_ParamSpec = Any + ast_TypeVarTuple = Any if sys.version_info >= (3, 10): Match = ast3.Match @@ -1176,6 +1185,31 @@ def visit_Assign(self, n: ast3.Assign) -> AssignmentStmt: s = AssignmentStmt(lvalues, rvalue, type=typ, new_syntax=False) return self.set_line(s, n) + def visit_TypeVar(self, n: ast_TypeVar) -> TypeVarNode: + line = n.lineno + if n.bound is not None: + bound = TypeConverter(self.errors, line=line).visit(n.bound) + else: + bound = None + return TypeVarNode(n.name, TypeVarLikeKind.TYPEVAR, bound) + + def visit_ParamSpec(self, n: ast_ParamSpec) -> TypeVarNode: + return TypeVarNode(n.name, TypeVarLikeKind.PARAMSPEC) + + def visit_TypeVarTuple(self, n: ast_TypeVarTuple) -> TypeVarNode: + return TypeVarNode(n.name, TypeVarLikeKind.TYPEVARTUPLE) + + def _visit_type_params(self, n: list[AST]) -> list[TypeVarNode]: + return [self.visit(p) for p in n] + + # TypeAlias(expr name, type_param* type_params, expr value) + def visit_TypeAlias(self, n: ast_TypeAlias) -> TypeAliasStmt: + line = n.lineno + typ = TypeConverter(self.errors, line=line).visit(n.value) + assert isinstance(n.name, ast3.Name) + node = TypeAliasStmt(n.name.id, self._visit_type_params(n.type_params), typ) + return self.set_line(node, n) + # AnnAssign(expr target, expr annotation, expr? value, int simple) def visit_AnnAssign(self, n: ast3.AnnAssign) -> AssignmentStmt: line = n.lineno @@ -1738,16 +1772,6 @@ 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) - class TypeConverter: def __init__( diff --git a/mypy/nodes.py b/mypy/nodes.py index 0e5c078d0227..f3b79bdca4f2 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1232,6 +1232,76 @@ def accept(self, visitor: StatementVisitor[T]) -> T: return visitor.visit_expression_stmt(self) +@unique +class TypeVarLikeKind(Enum): + TYPEVAR = 1 + PARAMSPEC = 2 + TYPEVARTUPLE = 3 + + +class TypeVarNode(SymbolNode): + __slots__ = ("name", "kind", "bound") + __match_args__ = ("name", "kind", "bound") + + name: str + kind: TypeVarLikeKind + bound: mypy.types.Type | None + + def __init__(self, name: str, kind: TypeVarLikeKind, bound: mypy.types.Type | None = None) -> None: + super().__init__() + self.name = name + self.kind = kind + self.bound = bound + + @property + def fullname(self) -> str: + return self.name + + def accept(self, visitor: NodeVisitor[T]) -> T: + return visitor.visit_type_var_node(self) + + def serialize(self) -> JsonDict: + return { + ".class": "TypeVarNode", + "name": self.name, + "kind": self.kind.value, + "bound": None if self.bound is None else self.bound.serialize(), + } + + @classmethod + def deserialize(cls, data: JsonDict) -> TypeVarNode: + assert data[".class"] == "TypeVarNode" + return TypeVarNode( + data["name"], + TypeVarLikeKind(data["kind"]), + None if data["bound"] is None else mypy.types.deserialize_type(data["bound"]), + ) + + +class TypeAliasStmt(Statement): + """Type statement (new in Python 3.12, see PEP 695).""" + + __slots__ = ( + "name", + "type_params", + "rvalue" + ) + __match_args__ = ("name", "type_params", "rvalue") + + name: str + type_params: list[TypeVarNode] + rvalue: mypy.types.Type | None + + def __init__(self, name: str, type_params: list[TypeVarNode], rvalue: mypy.types.Type | None) -> None: + super().__init__() + self.name = name + self.type_params = type_params + self.rvalue = rvalue + + def accept(self, visitor: StatementVisitor[T]) -> T: + return visitor.visit_type_alias_stmt(self) + + class AssignmentStmt(Statement): """Assignment statement. diff --git a/mypy/semanal.py b/mypy/semanal.py index 179ee7c70bfb..21df7f0a2692 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -159,7 +159,9 @@ TupleExpr, TypeAlias, TypeAliasExpr, + TypeAliasStmt, TypeApplication, + TypeVarNode, TypedDictExpr, TypeInfo, TypeVarExpr, @@ -2808,6 +2810,37 @@ class C: break return True + def visit_type_var_node(self, s: TypeVarNode) -> None: + pass # TODO + + def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: + self.statement = s + res = s.rvalue + if res is None: + res = AnyType(TypeOfAny.from_error) + else: + res = self.anal_type(res) + check_for_explicit_any(res, self.options, self.is_typeshed_stub_file, self.msg, context=s) + if res is None: + res = AnyType(TypeOfAny.from_error) + else: + res = make_any_non_explicit(res) + + # Aliases defined within functions can't be accessed outside + # the function, since the symbol table will no longer + # exist. Work around by expanding them eagerly when used. + eager = self.is_func_scope() + alias_node = TypeAlias( + res, + self.qualified_name(s.name), + s.line, + s.column, + alias_tvars=s.type_params, + no_args=not s.type_params, + eager=eager, + ) + self.add_symbol(s.name, alias_node, s) + def visit_assignment_stmt(self, s: AssignmentStmt) -> None: self.statement = s diff --git a/mypy/strconv.py b/mypy/strconv.py index 42a07c7f62fa..35af306a988e 100644 --- a/mypy/strconv.py +++ b/mypy/strconv.py @@ -214,6 +214,19 @@ def visit_block(self, o: mypy.nodes.Block) -> str: def visit_expression_stmt(self, o: mypy.nodes.ExpressionStmt) -> str: return self.dump([o.expr], o) + def visit_type_var_node(self, o: mypy.nodes.TypeVarNode) -> str: + a: list[Any] = [o.name, o.kind.name] + if o.bound is not None: + a.append(("Bound", o.bound)) + return self.dump(a, o) + + def visit_type_alias_stmt(self, o: mypy.nodes.TypeAliasStmt) -> str: + a: list[Any] = [o.name] + if o.type_params: + a.append(("TypeParams", o.type_params)) + a.append(("Type", o.rvalue)) + return self.dump(a, o) + def visit_assignment_stmt(self, o: mypy.nodes.AssignmentStmt) -> str: a: list[Any] = [] if len(o.lvalues) > 1: diff --git a/mypy/traverser.py b/mypy/traverser.py index d11dd395f978..2a4a8bfa5399 100644 --- a/mypy/traverser.py +++ b/mypy/traverser.py @@ -71,7 +71,9 @@ TupleExpr, TypeAlias, TypeAliasExpr, + TypeAliasStmt, TypeApplication, + TypeVarNode, TypedDictExpr, TypeVarExpr, TypeVarTupleExpr, @@ -161,6 +163,13 @@ def visit_decorator(self, o: Decorator) -> None: def visit_expression_stmt(self, o: ExpressionStmt) -> None: o.expr.accept(self) + def visit_type_var_node(self, o: TypeVarNode) -> None: + pass + + def visit_type_alias_stmt(self, o: TypeAliasStmt) -> None: + for param in o.type_params: + param.accept(self) + def visit_assignment_stmt(self, o: AssignmentStmt) -> None: o.rvalue.accept(self) for l in o.lvalues: @@ -665,6 +674,16 @@ def visit_super_expr(self, o: SuperExpr) -> None: return super().visit_super_expr(o) + def visit_type_var_node(self, o: TypeVarNode) -> None: + if not self.visit(o): + return + super().visit_type_var_node(o) + + def visit_type_alias_stmt(self, o: TypeAliasStmt) -> None: + if not self.visit(o): + return + super().visit_type_alias_stmt(o) + def visit_assignment_expr(self, o: AssignmentExpr) -> None: if not self.visit(o): return diff --git a/mypy/treetransform.py b/mypy/treetransform.py index bb34d8de2884..ac0726a3dc1e 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -80,7 +80,9 @@ TryStmt, TupleExpr, TypeAliasExpr, + TypeAliasStmt, TypeApplication, + TypeVarNode, TypedDictExpr, TypeVarExpr, TypeVarTupleExpr, @@ -311,6 +313,13 @@ def visit_var(self, node: Var) -> Var: def visit_expression_stmt(self, node: ExpressionStmt) -> ExpressionStmt: return ExpressionStmt(self.expr(node.expr)) + def visit_type_var_node(self, node: TypeVarNode) -> TypeVarNode: + return TypeVarNode(node.name, node.kind, self.optional_type(node.bound)) + + def visit_type_alias_stmt(self, node: TypeAliasStmt) -> TypeAliasStmt: + type_params = [ self.visit_type_var_node(var) for var in node.type_params ] + return TypeAliasStmt(node.name, type_params, self.optional_type(node.rvalue)) + def visit_assignment_stmt(self, node: AssignmentStmt) -> AssignmentStmt: return self.duplicate_assignment(node) diff --git a/mypy/visitor.py b/mypy/visitor.py index c5aa3caa8295..499298c86e5a 100644 --- a/mypy/visitor.py +++ b/mypy/visitor.py @@ -205,6 +205,14 @@ class StatementVisitor(Generic[T]): def visit_assignment_stmt(self, o: mypy.nodes.AssignmentStmt) -> T: pass + @abstractmethod + def visit_type_alias_stmt(self, o: mypy.nodes.TypeAliasStmt) -> T: + pass + + @abstractmethod + def visit_type_var_node(self, o: mypy.nodes.TypeVarNode) -> T: + pass + @abstractmethod def visit_for_stmt(self, o: mypy.nodes.ForStmt) -> T: pass @@ -418,6 +426,12 @@ def visit_expression_stmt(self, o: mypy.nodes.ExpressionStmt) -> T: def visit_assignment_stmt(self, o: mypy.nodes.AssignmentStmt) -> T: pass + def visit_type_alias_stmt(self, o: mypy.nodes.TypeAliasStmt) -> T: + pass + + def visit_type_var_node(self, o: mypy.nodes.TypeVarNode) -> T: + pass + def visit_operator_assignment_stmt(self, o: mypy.nodes.OperatorAssignmentStmt) -> T: pass diff --git a/mypyc/irbuild/visitor.py b/mypyc/irbuild/visitor.py index 12e186fd40d8..12607d087382 100644 --- a/mypyc/irbuild/visitor.py +++ b/mypyc/irbuild/visitor.py @@ -70,7 +70,9 @@ TryStmt, TupleExpr, TypeAliasExpr, + TypeAliasStmt, TypeApplication, + TypeVarNode, TypedDictExpr, TypeVarExpr, TypeVarTupleExpr, @@ -249,6 +251,12 @@ 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, o: TypeAliasStmt) -> None: + self.bail("type alias not supported", o.line) + + def visit_type_var_node(self, o: TypeVarNode) -> None: + self.bail("type var not supported", o.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 cb89eb34880c..aff89123d3dc 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -1,9 +1,10 @@ [case test695TypeAlias] -type MyInt = int # E: PEP 695 type aliases are not yet supported +type MyInt = int def f(x: MyInt) -> MyInt: return reveal_type(x) # N: Revealed type is "builtins.int" +[case test695GenericTypeAlias] type MyList[T] = list[T] # E: PEP 695 type aliases are not yet supported \ # E: Name "T" is not defined