From c7621912e2451f7135169f7d458f91c6c947ddba Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 4 Jun 2024 13:32:15 +0100 Subject: [PATCH] [PEP 695] Generate error if 3.12 type alias is called (#17320) PEP 695 type aliases raise an exception at runtime if called. Work on #15238. --- mypy/checkexpr.py | 4 +++ mypy/nodes.py | 6 ++++ mypy/semanal.py | 2 ++ test-data/unit/check-python312.test | 47 +++++++++++++++++++++++++ test-data/unit/check-type-aliases.test | 7 ++++ test-data/unit/fixtures/typing-full.pyi | 1 + 6 files changed, 67 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 0a4af069ea17..f826d4c11dd3 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4659,6 +4659,8 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: is due to slight differences in how type arguments are applied and checked. """ if isinstance(tapp.expr, RefExpr) and isinstance(tapp.expr.node, TypeAlias): + if tapp.expr.node.python_3_12_type_alias: + return self.named_type("typing.TypeAliasType") # Subscription of a (generic) alias in runtime context, expand the alias. item = instantiate_type_alias( tapp.expr.node, @@ -4721,6 +4723,8 @@ class LongName(Generic[T]): ... x = A() y = cast(A, ...) """ + if alias.python_3_12_type_alias: + return self.named_type("typing.TypeAliasType") if isinstance(alias.target, Instance) and alias.target.invalid: # type: ignore[misc] # An invalid alias, error already has been reported return AnyType(TypeOfAny.from_error) diff --git a/mypy/nodes.py b/mypy/nodes.py index dbde3ddf4f1b..90561779051d 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -3578,6 +3578,7 @@ def f(x: B[T]) -> T: ... # without T, Any would be used here "_is_recursive", "eager", "tvar_tuple_index", + "python_3_12_type_alias", ) __match_args__ = ("name", "target", "alias_tvars", "no_args") @@ -3593,6 +3594,7 @@ def __init__( no_args: bool = False, normalized: bool = False, eager: bool = False, + python_3_12_type_alias: bool = False, ) -> None: self._fullname = fullname self.target = target @@ -3605,6 +3607,7 @@ def __init__( # it is the cached value. self._is_recursive: bool | None = None self.eager = eager + self.python_3_12_type_alias = python_3_12_type_alias self.tvar_tuple_index = None for i, t in enumerate(alias_tvars): if isinstance(t, mypy.types.TypeVarTupleType): @@ -3675,6 +3678,7 @@ def serialize(self) -> JsonDict: "normalized": self.normalized, "line": self.line, "column": self.column, + "python_3_12_type_alias": self.python_3_12_type_alias, } return data @@ -3692,6 +3696,7 @@ def deserialize(cls, data: JsonDict) -> TypeAlias: normalized = data["normalized"] line = data["line"] column = data["column"] + python_3_12_type_alias = data["python_3_12_type_alias"] return cls( target, fullname, @@ -3700,6 +3705,7 @@ def deserialize(cls, data: JsonDict) -> TypeAlias: alias_tvars=cast(List[mypy.types.TypeVarLikeType], alias_tvars), no_args=no_args, normalized=normalized, + python_3_12_type_alias=python_3_12_type_alias, ) diff --git a/mypy/semanal.py b/mypy/semanal.py index 44db7ddf5618..e4f123cbfc20 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3922,6 +3922,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: alias_tvars=alias_tvars, no_args=no_args, eager=eager, + python_3_12_type_alias=pep_695, ) if isinstance(s.rvalue, (IndexExpr, CallExpr, OpExpr)) and ( not isinstance(rvalue, OpExpr) @@ -5368,6 +5369,7 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: alias_tvars=alias_tvars, no_args=False, eager=eager, + python_3_12_type_alias=True, ) existing = self.current_symbol_table().get(s.name.name) diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index 8443aadb6905..77276d9ee079 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -1313,3 +1313,50 @@ reveal_type(E[int]().mm(b'x')) # N: Revealed type is "Tuple[__main__.E[builtins reveal_type(F[str]().m()) # N: Revealed type is "__main__.F[builtins.str]" reveal_type(F[str]().mm(b'x')) # N: Revealed type is "Tuple[__main__.F[builtins.str], builtins.bytes]" [builtins fixtures/tuple.pyi] + +[case testPEP695CallAlias] +# mypy: enable-incomplete-feature=NewGenericSyntax + +class C: + def __init__(self, x: str) -> None: ... +type A = C + +class D[T]: pass +type B[T] = D[T] + +reveal_type(A) # N: Revealed type is "typing.TypeAliasType" +reveal_type(B) # N: Revealed type is "typing.TypeAliasType" +reveal_type(B[int]) # N: Revealed type is "typing.TypeAliasType" + +A(1) # E: "TypeAliasType" not callable +B[int]() # E: "TypeAliasType" not callable + +A2 = C +B2 = D +A2(1) # E: Argument 1 to "C" has incompatible type "int"; expected "str" +B2[int]() +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] + +[case testPEP695IncrementalTypeAliasKinds] +# flags: --enable-incomplete-feature=NewGenericSyntax +import a + +[file a.py] +from b import A + +[file a.py.2] +from b import A, B, C +A() +B() +C() + +[file b.py] +from typing_extensions import TypeAlias +type A = int +B = int +C: TypeAlias = int +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] +[out2] +tmp/a.py:2: error: "TypeAliasType" not callable diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index f77c3c1c34e2..c13331e0a61b 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -1075,11 +1075,15 @@ x: TestType = 42 y: TestType = 'a' z: TestType = object() # E: Incompatible types in assignment (expression has type "object", variable has type "Union[int, str]") +reveal_type(TestType) # N: Revealed type is "typing.TypeAliasType" +TestType() # E: "TypeAliasType" not callable + class A: ClassAlias = TypeAliasType("ClassAlias", int) xc: A.ClassAlias = 1 yc: A.ClassAlias = "" # E: Incompatible types in assignment (expression has type "str", variable has type "int") [builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] [case testTypeAliasTypeInvalid] from typing_extensions import TypeAliasType @@ -1094,6 +1098,7 @@ T3 = TypeAliasType("T3", -1) # E: Invalid type: try using Literal[-1] instead? t3: T3 reveal_type(t3) # N: Revealed type is "Any" [builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] [case testTypeAliasTypeGeneric] from typing import Callable, Dict, Generic, TypeVar, Tuple @@ -1140,6 +1145,7 @@ ParamAlias2 = TypeAliasType("ParamAlias2", G[P, T], type_params=(P, T)) xp: ParamAlias2[[int], str] reveal_type(xp) # N: Revealed type is "__main__.G[[builtins.int], builtins.str]" [builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] [case testTypeAliasTypeInvalidGeneric] from typing_extensions import TypeAliasType, TypeVarTuple, ParamSpec @@ -1200,6 +1206,7 @@ class A(Generic[T]): x: A.Ta11 = {"a": 1} reveal_type(x) # N: Revealed type is "builtins.dict[builtins.str, Any]" [builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] [case testTypeAliasTypeNoUnpackInTypeParams311] # flags: --python-version 3.11 diff --git a/test-data/unit/fixtures/typing-full.pyi b/test-data/unit/fixtures/typing-full.pyi index f7da75fa4cd0..71d4dcb58853 100644 --- a/test-data/unit/fixtures/typing-full.pyi +++ b/test-data/unit/fixtures/typing-full.pyi @@ -35,6 +35,7 @@ TypedDict = 0 NoReturn = 0 NewType = 0 Self = 0 +Unpack = 0 T = TypeVar('T') T_co = TypeVar('T_co', covariant=True)