Skip to content

Commit

Permalink
Initial work on type aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
JelleZijlstra committed Oct 27, 2023
1 parent 090a414 commit fd064fd
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 11 deletions.
44 changes: 34 additions & 10 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@
TempNode,
TryStmt,
TupleExpr,
TypeAliasStmt,
TypeVarLikeKind,
TypeVarNode,
UnaryExpr,
Var,
WhileStmt,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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__(
Expand Down
70 changes: 70 additions & 0 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
33 changes: 33 additions & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,9 @@
TupleExpr,
TypeAlias,
TypeAliasExpr,
TypeAliasStmt,
TypeApplication,
TypeVarNode,
TypedDictExpr,
TypeInfo,
TypeVarExpr,
Expand Down Expand Up @@ -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

Expand Down
13 changes: 13 additions & 0 deletions mypy/strconv.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
19 changes: 19 additions & 0 deletions mypy/traverser.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@
TupleExpr,
TypeAlias,
TypeAliasExpr,
TypeAliasStmt,
TypeApplication,
TypeVarNode,
TypedDictExpr,
TypeVarExpr,
TypeVarTupleExpr,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions mypy/treetransform.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@
TryStmt,
TupleExpr,
TypeAliasExpr,
TypeAliasStmt,
TypeApplication,
TypeVarNode,
TypedDictExpr,
TypeVarExpr,
TypeVarTupleExpr,
Expand Down Expand Up @@ -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)

Expand Down
14 changes: 14 additions & 0 deletions mypy/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
8 changes: 8 additions & 0 deletions mypyc/irbuild/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@
TryStmt,
TupleExpr,
TypeAliasExpr,
TypeAliasStmt,
TypeApplication,
TypeVarNode,
TypedDictExpr,
TypeVarExpr,
TypeVarTupleExpr,
Expand Down Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion test-data/unit/check-python312.test
Original file line number Diff line number Diff line change
@@ -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

Expand Down

0 comments on commit fd064fd

Please sign in to comment.