From 3b465a4c579fa89981334721d073aa1207906a86 Mon Sep 17 00:00:00 2001 From: Erik Soma Date: Wed, 1 Dec 2021 15:09:43 -0500 Subject: [PATCH 1/4] Allow PEP 604 type aliases to be used in runtime context. --- mypy/checkexpr.py | 2 ++ mypy/nodes.py | 6 ++++-- mypy/semanal.py | 3 ++- test-data/unit/check-union-or-syntax.test | 13 +++++++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index c58707519436c..884c0a466162c 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -3242,6 +3242,8 @@ class LongName(Generic[T]): ... return type_object_type(tuple_fallback(item).type, self.named_type) elif isinstance(item, AnyType): return AnyType(TypeOfAny.from_another_any, source_any=item) + elif isinstance(item, UnionType) and isinstance(alias.rvalue, OpExpr): + return self.accept(alias.rvalue) else: if alias_definition: return AnyType(TypeOfAny.special_form) diff --git a/mypy/nodes.py b/mypy/nodes.py index ac2dbf336634b..d736eb4867d15 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2971,14 +2971,15 @@ def f(x: B[T]) -> T: ... # without T, Any would be used here within functions that can't be looked up from the symbol table) """ __slots__ = ('target', '_fullname', 'alias_tvars', 'no_args', 'normalized', - 'line', 'column', '_is_recursive', 'eager') + 'line', 'column', '_is_recursive', 'eager', 'rvalue') def __init__(self, target: 'mypy.types.Type', fullname: str, line: int, column: int, *, alias_tvars: Optional[List[str]] = None, no_args: bool = False, normalized: bool = False, - eager: bool = False) -> None: + eager: bool = False, + rvalue: Optional[Expression] = None) -> None: self._fullname = fullname self.target = target if alias_tvars is None: @@ -2990,6 +2991,7 @@ def __init__(self, target: 'mypy.types.Type', fullname: str, line: int, column: # it is the cached value. self._is_recursive: Optional[bool] = None self.eager = eager + self.rvalue = rvalue super().__init__(line, column) @property diff --git a/mypy/semanal.py b/mypy/semanal.py index e55fada5689cf..4b8959d897458 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2732,7 +2732,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: s.column, alias_tvars=alias_tvars, no_args=no_args, - eager=eager) + eager=eager, + rvalue=rvalue) if isinstance(s.rvalue, (IndexExpr, CallExpr)): # CallExpr is for `void = type(None)` s.rvalue.analyzed = TypeAliasExpr(alias_node) s.rvalue.analyzed.line = s.line diff --git a/test-data/unit/check-union-or-syntax.test b/test-data/unit/check-union-or-syntax.test index 3e5a19b8fe61f..7189f663fc9f2 100644 --- a/test-data/unit/check-union-or-syntax.test +++ b/test-data/unit/check-union-or-syntax.test @@ -196,3 +196,16 @@ def f(x: Union[int, str, None]) -> None: else: reveal_type(x) # N: Revealed type is "None" [builtins fixtures/isinstance.pyi] + +[case testUnionOrSyntaxMetaclassOverride] +# flags: --python-version 3.10 +class U(type): pass + +class Meta(type): + def __or__(self, other) -> U: pass # type: ignore + +class F(metaclass=Meta): pass + +reveal_type(F | F) # N: Revealed type is "__main__.U" +UnionF = F | F +reveal_type(UF) # N: Revealed type is "__main__.U" From 4041acf668bc3fbcf5702732614d8767914e736b Mon Sep 17 00:00:00 2001 From: Erik Soma Date: Wed, 1 Dec 2021 17:08:42 -0500 Subject: [PATCH 2/4] Fix test typo. --- test-data/unit/check-union-or-syntax.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-union-or-syntax.test b/test-data/unit/check-union-or-syntax.test index 7189f663fc9f2..395e48031f0d4 100644 --- a/test-data/unit/check-union-or-syntax.test +++ b/test-data/unit/check-union-or-syntax.test @@ -207,5 +207,5 @@ class Meta(type): class F(metaclass=Meta): pass reveal_type(F | F) # N: Revealed type is "__main__.U" -UnionF = F | F +UF = F | F reveal_type(UF) # N: Revealed type is "__main__.U" From 2289ad4cf0a4cca71bb6b60cffac17d19919b57a Mon Sep 17 00:00:00 2001 From: Erik Soma Date: Thu, 2 Dec 2021 21:25:20 -0500 Subject: [PATCH 3/4] Obtain the rtype for TypeAlias so that it can be cached properly. --- mypy/checker.py | 2 ++ mypy/checkexpr.py | 9 +++++-- mypy/fixup.py | 2 ++ mypy/nodes.py | 14 ++++++---- mypy/semanal.py | 4 +-- test-data/unit/check-union-or-syntax.test | 31 +++++++++++++++++++++++ test-data/unit/fixtures/callable.pyi | 1 + test-data/unit/lib-stub/types.pyi | 2 ++ 8 files changed, 56 insertions(+), 9 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 6505100cec6f0..a4f553095f8b1 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2112,6 +2112,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: self.fail(message_registry.DEPENDENT_FINAL_IN_CLASS_BODY, s) def check_type_alias_rvalue(self, s: AssignmentStmt) -> None: + assert s.type_alias is not None if not (self.is_stub and isinstance(s.rvalue, OpExpr) and s.rvalue.op == '|'): # We do this mostly for compatibility with old semantic analyzer. # TODO: should we get rid of this? @@ -2132,6 +2133,7 @@ def accept_items(e: Expression) -> None: self.expr_checker.accept(e) accept_items(s.rvalue) + s.type_alias.rtype = get_proper_type(alias_type) self.store_type(s.lvalues[-1], alias_type) def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type: bool = True, diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 884c0a466162c..450e09a554935 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -3242,8 +3242,13 @@ class LongName(Generic[T]): ... return type_object_type(tuple_fallback(item).type, self.named_type) elif isinstance(item, AnyType): return AnyType(TypeOfAny.from_another_any, source_any=item) - elif isinstance(item, UnionType) and isinstance(alias.rvalue, OpExpr): - return self.accept(alias.rvalue) + elif isinstance(item, UnionType): + if alias.rtype is not None: + return alias.rtype + elif self.chk.options.python_version[:2] >= (3, 10): + return self.named_type('types.UnionType') + else: + return self.named_type('builtins.object') else: if alias_definition: return AnyType(TypeOfAny.special_form) diff --git a/mypy/fixup.py b/mypy/fixup.py index da54c40e733f0..5bbada16ccadb 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -139,6 +139,8 @@ def visit_var(self, v: Var) -> None: def visit_type_alias(self, a: TypeAlias) -> None: a.target.accept(self.type_fixer) + if a.rtype is not None: + a.rtype.accept(self.type_fixer) class TypeFixer(TypeVisitor[None]): diff --git a/mypy/nodes.py b/mypy/nodes.py index d736eb4867d15..0775065825fe0 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1099,7 +1099,7 @@ class AssignmentStmt(Statement): """ __slots__ = ('lvalues', 'rvalue', 'type', 'unanalyzed_type', 'new_syntax', - 'is_alias_def', 'is_final_def') + 'is_alias_def', 'type_alias', 'is_final_def') lvalues: List[Lvalue] # This is a TempNode if and only if no rvalue (x: t). @@ -1112,6 +1112,7 @@ class AssignmentStmt(Statement): new_syntax: bool # Does this assignment define a type alias? is_alias_def: bool + type_alias: Optional["TypeAlias"] # Is this a final definition? # Final attributes can't be re-assigned once set, and can't be overridden # in a subclass. This flag is not set if an attempted declaration was found to @@ -1129,6 +1130,7 @@ def __init__(self, lvalues: List[Lvalue], rvalue: Expression, self.unanalyzed_type = type self.new_syntax = new_syntax self.is_alias_def = False + self.type_alias = None self.is_final_def = False def accept(self, visitor: StatementVisitor[T]) -> T: @@ -2971,7 +2973,7 @@ def f(x: B[T]) -> T: ... # without T, Any would be used here within functions that can't be looked up from the symbol table) """ __slots__ = ('target', '_fullname', 'alias_tvars', 'no_args', 'normalized', - 'line', 'column', '_is_recursive', 'eager', 'rvalue') + 'line', 'column', '_is_recursive', 'eager', 'rtype') def __init__(self, target: 'mypy.types.Type', fullname: str, line: int, column: int, *, @@ -2979,7 +2981,7 @@ def __init__(self, target: 'mypy.types.Type', fullname: str, line: int, column: no_args: bool = False, normalized: bool = False, eager: bool = False, - rvalue: Optional[Expression] = None) -> None: + rtype: 'Optional[mypy.types.Type]' = None) -> None: self._fullname = fullname self.target = target if alias_tvars is None: @@ -2991,7 +2993,7 @@ def __init__(self, target: 'mypy.types.Type', fullname: str, line: int, column: # it is the cached value. self._is_recursive: Optional[bool] = None self.eager = eager - self.rvalue = rvalue + self.rtype = rtype super().__init__(line, column) @property @@ -3012,6 +3014,7 @@ def serialize(self) -> JsonDict: "normalized": self.normalized, "line": self.line, "column": self.column, + "rtype": self.rtype.serialize() if self.rtype is not None else None } return data @@ -3028,8 +3031,9 @@ def deserialize(cls, data: JsonDict) -> 'TypeAlias': normalized = data['normalized'] line = data['line'] column = data['column'] + rtype = mypy.types.deserialize_type(data['rtype']) if data['rtype'] is not None else None return cls(target, fullname, line, column, alias_tvars=alias_tvars, - no_args=no_args, normalized=normalized) + no_args=no_args, normalized=normalized, rtype=rtype) class PlaceholderNode(SymbolNode): diff --git a/mypy/semanal.py b/mypy/semanal.py index 4b8959d897458..8fef9a9988cb1 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2732,8 +2732,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: s.column, alias_tvars=alias_tvars, no_args=no_args, - eager=eager, - rvalue=rvalue) + eager=eager) + s.type_alias = alias_node if isinstance(s.rvalue, (IndexExpr, CallExpr)): # CallExpr is for `void = type(None)` s.rvalue.analyzed = TypeAliasExpr(alias_node) s.rvalue.analyzed.line = s.line diff --git a/test-data/unit/check-union-or-syntax.test b/test-data/unit/check-union-or-syntax.test index 395e48031f0d4..4387d2c2a1f92 100644 --- a/test-data/unit/check-union-or-syntax.test +++ b/test-data/unit/check-union-or-syntax.test @@ -199,6 +199,20 @@ def f(x: Union[int, str, None]) -> None: [case testUnionOrSyntaxMetaclassOverride] # flags: --python-version 3.10 +import types + +class Meta(type): + def __or__(self, other) -> types.UnionType: pass + +class F(metaclass=Meta): pass + +reveal_type(F | F) # N: Revealed type is "types.UnionType" +UF = F | F +reveal_type(UF) # N: Revealed type is "types.UnionType" +[builtins fixtures/tuple.pyi] + +[case testUnionOrSyntaxMetaclassOverrideType] +# flags: --python-version 3.10 class U(type): pass class Meta(type): @@ -209,3 +223,20 @@ class F(metaclass=Meta): pass reveal_type(F | F) # N: Revealed type is "__main__.U" UF = F | F reveal_type(UF) # N: Revealed type is "__main__.U" + +[case testUnionOrSyntaxMetaclassOverrideUnion] +# flags: --python-version 3.10 +from typing import TypeVar, Union, Type + +T = TypeVar('T') +U = TypeVar('U') + +class Meta(type): + def __or__(self: Type[T], other: Type[U]) -> Union[T, U]: pass + +class A(metaclass=Meta): pass +class B(metaclass=Meta): pass + +reveal_type(A | B) # N: Revealed type is "Union[__main__.A, __main__.B*]" +C = A | B +reveal_type(C) # N: Revealed type is "Union[__main__.A, __main__.B*]" diff --git a/test-data/unit/fixtures/callable.pyi b/test-data/unit/fixtures/callable.pyi index 4ad72bee93ec1..2b0df7c471da2 100644 --- a/test-data/unit/fixtures/callable.pyi +++ b/test-data/unit/fixtures/callable.pyi @@ -1,3 +1,4 @@ +import types from typing import Generic, Tuple, TypeVar, Union T = TypeVar('T') diff --git a/test-data/unit/lib-stub/types.pyi b/test-data/unit/lib-stub/types.pyi index 6fc596ecbf139..950dc2e151050 100644 --- a/test-data/unit/lib-stub/types.pyi +++ b/test-data/unit/lib-stub/types.pyi @@ -13,3 +13,5 @@ class ModuleType: if sys.version_info >= (3, 10): class Union: def __or__(self, x) -> Union: ... + + class UnionType: ... \ No newline at end of file From dda55bfec3e54289aa99bb9984551fc57c243c30 Mon Sep 17 00:00:00 2001 From: Erik Soma Date: Thu, 2 Dec 2021 22:21:14 -0500 Subject: [PATCH 4/4] Fix missing types module in Python 3.10 mypyc attrs tests. --- mypyc/test-data/fixtures/ir.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypyc/test-data/fixtures/ir.py b/mypyc/test-data/fixtures/ir.py index 786f77143e9ee..4e8e480a8df66 100644 --- a/mypyc/test-data/fixtures/ir.py +++ b/mypyc/test-data/fixtures/ir.py @@ -1,6 +1,7 @@ # These builtins stubs are used implicitly in AST to IR generation # test cases. +import types from typing import ( TypeVar, Generic, List, Iterator, Iterable, Dict, Optional, Tuple, Any, Set, overload, Mapping, Union, Callable, Sequence,