diff --git a/mypy/exprtotype.py b/mypy/exprtotype.py index e0d2fe7835432..486e78dae01f9 100644 --- a/mypy/exprtotype.py +++ b/mypy/exprtotype.py @@ -4,13 +4,13 @@ from mypy.nodes import ( Expression, NameExpr, MemberExpr, IndexExpr, RefExpr, TupleExpr, IntExpr, FloatExpr, UnaryExpr, - ComplexExpr, ListExpr, StrExpr, BytesExpr, UnicodeExpr, EllipsisExpr, CallExpr, + ComplexExpr, ListExpr, StrExpr, BytesExpr, UnicodeExpr, EllipsisExpr, CallExpr, OpExpr, get_member_expr_fullname ) from mypy.fastparse import parse_type_string from mypy.types import ( Type, UnboundType, TypeList, EllipsisType, AnyType, CallableArgument, TypeOfAny, - RawExpressionType, ProperType + RawExpressionType, ProperType, UnionType ) @@ -150,5 +150,8 @@ def expr_to_unanalyzed_type(expr: Expression, _parent: Optional[Expression] = No return RawExpressionType(None, 'builtins.complex', line=expr.line, column=expr.column) elif isinstance(expr, EllipsisExpr): return EllipsisType(expr.line) + elif isinstance(expr, OpExpr) and expr.op == '|': + return UnionType([expr_to_unanalyzed_type(expr.left), + expr_to_unanalyzed_type(expr.right)]) else: raise TypeTranslationError() diff --git a/mypy/semanal.py b/mypy/semanal.py index 96ef7401bf421..a875f4d40a2fc 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2101,7 +2101,7 @@ def should_wait_rhs(self, rv: Expression) -> bool: return self.should_wait_rhs(rv.callee) return False - def can_be_type_alias(self, rv: Expression) -> bool: + def can_be_type_alias(self, rv: Expression, allow_none: bool = False) -> bool: """Is this a valid r.h.s. for an alias definition? 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: return True if self.is_none_alias(rv): return True + if allow_none and isinstance(rv, NameExpr) and rv.fullname == 'builtins.None': + return True + if (isinstance(rv, OpExpr) + and rv.op == '|' + and self.can_be_type_alias(rv.left, allow_none=True) + and self.can_be_type_alias(rv.right, allow_none=True)): + return True return False def is_type_ref(self, rv: Expression, bare: bool = False) -> bool: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index b003bf731719e..953a545c5dd0f 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -3154,7 +3154,7 @@ def foo(arg: Type[Any]): from typing import Type, Any def foo(arg: Type[Any]): reveal_type(arg.__str__) # N: Revealed type is "def () -> builtins.str" - reveal_type(arg.mro()) # N: Revealed type is "builtins.list[builtins.type]" + reveal_type(arg.mro()) # N: Revealed type is "builtins.list[builtins.type[Any]]" [builtins fixtures/type.pyi] [out] diff --git a/test-data/unit/check-union-or-syntax.test b/test-data/unit/check-union-or-syntax.test index 11dff95231626..ba0a051fe7d57 100644 --- a/test-data/unit/check-union-or-syntax.test +++ b/test-data/unit/check-union-or-syntax.test @@ -96,10 +96,22 @@ reveal_type(y) # N: Revealed type is "Union[builtins.int, builtins.str]" [case testUnionOrSyntaxWithTypeAliasWorking] # flags: --python-version 3.10 -from typing import Union -T = Union[int, str] +T = int | str x: T reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]" +S = list[int] | str | None +y: S +reveal_type(y) # N: Revealed type is "Union[builtins.list[builtins.int], builtins.str, None]" +U = str | None +z: U +reveal_type(z) # N: Revealed type is "Union[builtins.str, None]" + +def f(): pass + +X = int | str | f() +b: X # E: Variable "__main__.X" is not valid as a type \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +[builtins fixtures/type.pyi] [case testUnionOrSyntaxWithTypeAliasNotAllowed] @@ -131,3 +143,17 @@ x: int | None # E: X | Y syntax for unions requires Python 3.10 from lib import x [file lib.pyi] x: int | None + + +[case testUnionOrSyntaxInMiscRuntimeContexts] +# flags: --python-version 3.10 +from typing import cast + +class C(list[int | None]): + pass + +def f() -> object: pass + +reveal_type(cast(str | None, f())) # N: Revealed type is "Union[builtins.str, None]" +reveal_type(list[str | None]()) # N: Revealed type is "builtins.list[Union[builtins.str, None]]" +[builtins fixtures/type.pyi] diff --git a/test-data/unit/fixtures/type.pyi b/test-data/unit/fixtures/type.pyi index 35cf0ad3ce732..755b45ff0bb56 100644 --- a/test-data/unit/fixtures/type.pyi +++ b/test-data/unit/fixtures/type.pyi @@ -1,6 +1,6 @@ # builtins stub used in type-related test cases. -from typing import Generic, TypeVar, List +from typing import Generic, TypeVar, List, Union T = TypeVar('T') @@ -10,8 +10,9 @@ class object: class list(Generic[T]): pass -class type: +class type(Generic[T]): __name__: str + def __or__(self, other: Union[type, None]) -> type: pass def mro(self) -> List['type']: pass class tuple(Generic[T]): pass