Skip to content

Commit 309d797

Browse files
committed
Support union type syntax in type applications, aliases and casts
These are only available in Python 3.10 mode. Work on #9880.
1 parent 6eafc5e commit 309d797

File tree

5 files changed

+45
-8
lines changed

5 files changed

+45
-8
lines changed

mypy/exprtotype.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44

55
from mypy.nodes import (
66
Expression, NameExpr, MemberExpr, IndexExpr, RefExpr, TupleExpr, IntExpr, FloatExpr, UnaryExpr,
7-
ComplexExpr, ListExpr, StrExpr, BytesExpr, UnicodeExpr, EllipsisExpr, CallExpr,
7+
ComplexExpr, ListExpr, StrExpr, BytesExpr, UnicodeExpr, EllipsisExpr, CallExpr, OpExpr,
88
get_member_expr_fullname
99
)
1010
from mypy.fastparse import parse_type_string
1111
from mypy.types import (
1212
Type, UnboundType, TypeList, EllipsisType, AnyType, CallableArgument, TypeOfAny,
13-
RawExpressionType, ProperType
13+
RawExpressionType, ProperType, UnionType
1414
)
1515

1616

@@ -150,5 +150,8 @@ def expr_to_unanalyzed_type(expr: Expression, _parent: Optional[Expression] = No
150150
return RawExpressionType(None, 'builtins.complex', line=expr.line, column=expr.column)
151151
elif isinstance(expr, EllipsisExpr):
152152
return EllipsisType(expr.line)
153+
elif isinstance(expr, OpExpr) and expr.op == '|':
154+
return UnionType([expr_to_unanalyzed_type(expr.left),
155+
expr_to_unanalyzed_type(expr.right)])
153156
else:
154157
raise TypeTranslationError()

mypy/semanal.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -2101,7 +2101,7 @@ def should_wait_rhs(self, rv: Expression) -> bool:
21012101
return self.should_wait_rhs(rv.callee)
21022102
return False
21032103

2104-
def can_be_type_alias(self, rv: Expression) -> bool:
2104+
def can_be_type_alias(self, rv: Expression, allow_none: bool = False) -> bool:
21052105
"""Is this a valid r.h.s. for an alias definition?
21062106
21072107
Note: this function should be only called for expressions where self.should_wait_rhs()
@@ -2113,6 +2113,13 @@ def can_be_type_alias(self, rv: Expression) -> bool:
21132113
return True
21142114
if self.is_none_alias(rv):
21152115
return True
2116+
if allow_none and isinstance(rv, NameExpr) and rv.fullname == 'builtins.None':
2117+
return True
2118+
if (isinstance(rv, OpExpr)
2119+
and rv.op == '|'
2120+
and self.can_be_type_alias(rv.left, allow_none=True)
2121+
and self.can_be_type_alias(rv.right, allow_none=True)):
2122+
return True
21162123
return False
21172124

21182125
def is_type_ref(self, rv: Expression, bare: bool = False) -> bool:

test-data/unit/check-classes.test

+1-1
Original file line numberDiff line numberDiff line change
@@ -3154,7 +3154,7 @@ def foo(arg: Type[Any]):
31543154
from typing import Type, Any
31553155
def foo(arg: Type[Any]):
31563156
reveal_type(arg.__str__) # N: Revealed type is "def () -> builtins.str"
3157-
reveal_type(arg.mro()) # N: Revealed type is "builtins.list[builtins.type]"
3157+
reveal_type(arg.mro()) # N: Revealed type is "builtins.list[builtins.type[Any]]"
31583158
[builtins fixtures/type.pyi]
31593159
[out]
31603160

test-data/unit/check-union-or-syntax.test

+28-2
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,22 @@ reveal_type(y) # N: Revealed type is "Union[builtins.int, builtins.str]"
9696

9797
[case testUnionOrSyntaxWithTypeAliasWorking]
9898
# flags: --python-version 3.10
99-
from typing import Union
100-
T = Union[int, str]
99+
T = int | str
101100
x: T
102101
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]"
102+
S = list[int] | str | None
103+
y: S
104+
reveal_type(y) # N: Revealed type is "Union[builtins.list[builtins.int], builtins.str, None]"
105+
U = str | None
106+
z: U
107+
reveal_type(z) # N: Revealed type is "Union[builtins.str, None]"
108+
109+
def f(): pass
110+
111+
X = int | str | f()
112+
b: X # E: Variable "__main__.X" is not valid as a type \
113+
# N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
114+
[builtins fixtures/type.pyi]
103115

104116

105117
[case testUnionOrSyntaxWithTypeAliasNotAllowed]
@@ -131,3 +143,17 @@ x: int | None # E: X | Y syntax for unions requires Python 3.10
131143
from lib import x
132144
[file lib.pyi]
133145
x: int | None
146+
147+
148+
[case testUnionOrSyntaxInMiscRuntimeContexts]
149+
# flags: --python-version 3.10
150+
from typing import cast
151+
152+
class C(list[int | None]):
153+
pass
154+
155+
def f() -> object: pass
156+
157+
reveal_type(cast(str | None, f())) # N: Revealed type is "Union[builtins.str, None]"
158+
reveal_type(list[str | None]()) # N: Revealed type is "builtins.list[Union[builtins.str, None]]"
159+
[builtins fixtures/type.pyi]

test-data/unit/fixtures/type.pyi

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# builtins stub used in type-related test cases.
22

3-
from typing import Generic, TypeVar, List
3+
from typing import Generic, TypeVar, List, Union
44

55
T = TypeVar('T')
66

@@ -10,8 +10,9 @@ class object:
1010

1111
class list(Generic[T]): pass
1212

13-
class type:
13+
class type(Generic[T]):
1414
__name__: str
15+
def __or__(self, other: Union[type, None]) -> type: pass
1516
def mro(self) -> List['type']: pass
1617

1718
class tuple(Generic[T]): pass

0 commit comments

Comments
 (0)