From 3ec2859619526e4629666163c454c92dddcbedc2 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 17 Jun 2024 15:40:17 +0100 Subject: [PATCH 01/14] Infer GenericAlias when indexing types --- mypy/checkexpr.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 1cea4f6c19e64..7d235f38b4c95 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4341,6 +4341,11 @@ def visit_index_with_type( left_type.upper_bound, "__getitem__" ): return self.visit_index_with_type(left_type.upper_bound, e, original_type) + elif ( + isinstance(left_type, FunctionLike) + and left_type.is_type_obj() + ): + return self.named_type("types.GenericAlias") else: result, method_type = self.check_method_call_by_name( "__getitem__", left_type, [e.index], [ARG_POS], e, original_type=original_type From 75c1151fc548179a308d165a47b96712941af069 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 17 Jun 2024 16:11:29 +0100 Subject: [PATCH 02/14] Allow special forms to be used to form unions using | E.g. Callable is declared as a special form in typeshed --- mypy/checkexpr.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 7d235f38b4c95..160fa3446009d 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4346,6 +4346,9 @@ def visit_index_with_type( and left_type.is_type_obj() ): return self.named_type("types.GenericAlias") + elif isinstance(left_type, Instance) and left_type.type.fullname == "typing._SpecialForm": + # Allow special forms to be indexed and used to create union types + return self.named_type("typing._SpecialForm") else: result, method_type = self.check_method_call_by_name( "__getitem__", left_type, [e.index], [ARG_POS], e, original_type=original_type From 6b4205056f7475108b52edb0b2e25e357055a9c1 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 17 Jun 2024 16:53:33 +0100 Subject: [PATCH 03/14] Give typing._SpecialForm as runtime type of 'Any' --- mypy/checkexpr.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 160fa3446009d..9626cc13bdc2b 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -428,6 +428,9 @@ def analyze_var_ref(self, var: Var, context: Context) -> Type: if var.type: var_type = get_proper_type(var.type) if isinstance(var_type, Instance): + if var.fullname == "typing.Any": + # The typeshed type is 'object'; give a more useful type in runtime context + return self.named_type("typing._SpecialForm") if self.is_literal_context() and var_type.last_known_value is not None: return var_type.last_known_value if var.name in {"True", "False"}: From 4457c946f43ffe15fec97ea0368bd0fc8940a59f Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 17 Jun 2024 16:54:03 +0100 Subject: [PATCH 04/14] Test various types in a type alias context (runtime context) --- test-data/unit/check-python312.test | 23 +++++++++++++++++++++++ test-data/unit/fixtures/typing-full.pyi | 5 ++++- test-data/unit/lib-stub/types.pyi | 4 +++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index 348f2d11f9a78..0878aed16d320 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -1591,3 +1591,26 @@ c: E[str] d: E[int] # E: Type argument "int" of "E" must be a subtype of "str" [builtins fixtures/tuple.pyi] [typing fixtures/typing-full.pyi] + +[case testPEP695TypeAliasWithDifferentTargetTypes] +# flags: --enable-incomplete-feature=NewGenericSyntax +import types # We need GenericAlias from here, and test stubs don't bring in 'types' +from typing import Any, Callable + +type A1 = type[int] +type A2 = type[int] | None +type A3 = None | type[int] +type A4 = type[Any] + +type B1[**P, R] = Callable[P, R] | None +type B2[**P, R] = None | Callable[P, R] +type B3 = Callable[[str], int] +type B4 = Callable[..., int] + +type C1 = A1 | None +type C2 = None | A1 + +type D1 = Any | None +type D2 = None | Any +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] diff --git a/test-data/unit/fixtures/typing-full.pyi b/test-data/unit/fixtures/typing-full.pyi index 9d61361fc16ec..e77e77efc58f9 100644 --- a/test-data/unit/fixtures/typing-full.pyi +++ b/test-data/unit/fixtures/typing-full.pyi @@ -12,6 +12,8 @@ class GenericMeta(type): pass class _SpecialForm: def __getitem__(self, index: Any) -> Any: ... + def __or__(self, other): ... + def __ror__(self, other): ... class TypeVar: def __init__(self, name, *args, bound=None): ... def __or__(self, other): ... @@ -21,7 +23,7 @@ class TypeVarTuple: ... def cast(t, o): ... def assert_type(o, t): ... overload = 0 -Any = 0 +Any = object() Optional = 0 Generic = 0 Protocol = 0 @@ -216,3 +218,4 @@ class TypeAliasType: ) -> None: ... def __or__(self, other: Any) -> Any: ... + def __ror__(self, other: Any) -> Any: ... diff --git a/test-data/unit/lib-stub/types.pyi b/test-data/unit/lib-stub/types.pyi index c3ac244c2a518..3f713c31e4174 100644 --- a/test-data/unit/lib-stub/types.pyi +++ b/test-data/unit/lib-stub/types.pyi @@ -9,7 +9,9 @@ class ModuleType: __file__: str def __getattr__(self, name: str) -> Any: pass -class GenericAlias: ... +class GenericAlias: + def __or__(self, o): ... + def __ror__(self, o): ... if sys.version_info >= (3, 10): class NoneType: From c001a93b3015375a7ce91e4ed567e2a09bdd9a19 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 17 Jun 2024 17:33:16 +0100 Subject: [PATCH 05/14] Ignore a self check error --- mypy/stubgenc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index 9acd3f171a416..bacb68f6d1c73 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -733,7 +733,7 @@ def generate_property_stub( def get_type_fullname(self, typ: type) -> str: """Given a type, return a string representation""" - if typ is Any: + if typ is Any: # type: ignore[comparison-overlap] return "Any" typename = getattr(typ, "__qualname__", typ.__name__) module_name = self.get_obj_module(typ) From a1f55f68dda4e16bd7ed69365c04cb0f6a914dce Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 17 Jun 2024 17:33:45 +0100 Subject: [PATCH 06/14] Fix issue --- mypy/checkexpr.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 9626cc13bdc2b..26739e5efa7e4 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4347,6 +4347,7 @@ def visit_index_with_type( elif ( isinstance(left_type, FunctionLike) and left_type.is_type_obj() + and left_type.type_object().type_vars ): return self.named_type("types.GenericAlias") elif isinstance(left_type, Instance) and left_type.type.fullname == "typing._SpecialForm": From 35997def4f68cb2424f6ba1e8c6ff768940eb972 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 17 Jun 2024 17:34:26 +0100 Subject: [PATCH 07/14] Update tests and test stubs --- mypyc/test-data/fixtures/typing-full.pyi | 2 +- test-data/unit/check-classes.test | 3 +++ test-data/unit/check-functions.test | 5 ++--- test-data/unit/check-generics.test | 6 ++++-- test-data/unit/check-type-object-type-inference.test | 3 ++- test-data/unit/fixtures/typing-async.pyi | 4 +++- test-data/unit/fixtures/typing-medium.pyi | 2 +- test-data/unit/fixtures/typing-namedtuple.pyi | 4 +++- test-data/unit/fixtures/typing-override.pyi | 5 +++-- test-data/unit/fixtures/typing-typeddict-iror.pyi | 4 +++- test-data/unit/fixtures/typing-typeddict.pyi | 4 +++- test-data/unit/lib-stub/typing.pyi | 4 +++- 12 files changed, 31 insertions(+), 15 deletions(-) diff --git a/mypyc/test-data/fixtures/typing-full.pyi b/mypyc/test-data/fixtures/typing-full.pyi index 8bb3b1398f87d..32cdacfc0f536 100644 --- a/mypyc/test-data/fixtures/typing-full.pyi +++ b/mypyc/test-data/fixtures/typing-full.pyi @@ -15,7 +15,7 @@ class _SpecialForm: cast = 0 overload = 0 -Any = 0 +Any = object() Union = 0 Optional = 0 TypeVar = 0 diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 427133eca10bb..edfbb571e3b9b 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4786,12 +4786,15 @@ def g(x: Type[S]) -> str: return reveal_type(x * 0) # N: Revealed type is "builtins.str" [case testMetaclassGetitem] +import types + class M(type): def __getitem__(self, key) -> int: return 1 class A(metaclass=M): pass reveal_type(A[M]) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] [case testMetaclassSelfType] from typing import TypeVar, Type diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index ef6ca9f3b2850..29cd977fe5d63 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -1779,10 +1779,10 @@ def Arg(x, y): pass F = Callable[[Arg(int, 'x')], int] # E: Invalid argument constructor "__main__.Arg" [case testCallableParsingFromExpr] - from typing import Callable, List from mypy_extensions import Arg, VarArg, KwArg import mypy_extensions +import types # Needed for type checking def WrongArg(x, y): return y # Note that for this test, the 'Value of type "int" is not indexable' errors are silly, @@ -1799,11 +1799,10 @@ L = Callable[[Arg(name='x', type=int)], int] # ok # I have commented out the following test because I don't know how to expect the "defined here" note part of the error. # M = Callable[[Arg(gnome='x', type=int)], int] E: Invalid type alias: expression is not a valid type E: Unexpected keyword argument "gnome" for "Arg" N = Callable[[Arg(name=None, type=int)], int] # ok -O = Callable[[List[Arg(int)]], int] # E: Invalid type alias: expression is not a valid type # E: Value of type "int" is not indexable # E: Type expected within [...] # E: The type "Type[List[Any]]" is not generic and not indexable +O = Callable[[List[Arg(int)]], int] # E: Invalid type alias: expression is not a valid type # E: Value of type "int" is not indexable # E: Type expected within [...] P = Callable[[mypy_extensions.VarArg(int)], int] # ok Q = Callable[[Arg(int, type=int)], int] # E: Invalid type alias: expression is not a valid type # E: Value of type "int" is not indexable # E: "Arg" gets multiple values for keyword argument "type" R = Callable[[Arg(int, 'x', name='y')], int] # E: Invalid type alias: expression is not a valid type # E: Value of type "int" is not indexable # E: "Arg" gets multiple values for keyword argument "name" - [builtins fixtures/dict.pyi] [case testCallableParsing] diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index abcb2a4bbc482..eb0d1aa5bd7a9 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -454,11 +454,12 @@ A[int, str, int]() # E: Type application has too many types (2 expected) [out] [case testInvalidTypeApplicationType] +import types a: A class A: pass a[A]() # E: Value of type "A" is not indexable A[A]() # E: The type "Type[A]" is not generic and not indexable -[out] +[builtins fixtures/tuple.pyi] [case testTypeApplicationArgTypes] from typing import TypeVar, Generic @@ -513,8 +514,9 @@ Alias[int]("a") # E: Argument 1 to "Node" has incompatible type "str"; expected [out] [case testTypeApplicationCrash] +import types type[int] # this was crashing, see #2302 (comment) # E: The type "Type[type]" is not generic and not indexable -[out] +[builtins fixtures/tuple.pyi] -- Generic type aliases diff --git a/test-data/unit/check-type-object-type-inference.test b/test-data/unit/check-type-object-type-inference.test index baeca1e22ac7c..5a4afa0c92481 100644 --- a/test-data/unit/check-type-object-type-inference.test +++ b/test-data/unit/check-type-object-type-inference.test @@ -2,6 +2,7 @@ # flags: --python-version 3.9 from typing import TypeVar, Generic, Type from abc import abstractmethod +import types # Explicitly bring in stubs for 'types' T = TypeVar('T') class E(Generic[T]): @@ -37,5 +38,5 @@ def i(f: F): f.f(tuple[int,tuple[int,str]]).e( (27,(28,'z')) ) # OK reveal_type(f.f(tuple[int,tuple[int,str]]).e) # N: Revealed type is "def (t: Tuple[builtins.int, Tuple[builtins.int, builtins.str]]) -> builtins.str" -x = tuple[int,str][str] # E: The type "Type[Tuple[Any, ...]]" is not generic and not indexable +x = tuple[int,str][str] # False negative [builtins fixtures/tuple.pyi] diff --git a/test-data/unit/fixtures/typing-async.pyi b/test-data/unit/fixtures/typing-async.pyi index 9897dfd0b2701..03728f8223162 100644 --- a/test-data/unit/fixtures/typing-async.pyi +++ b/test-data/unit/fixtures/typing-async.pyi @@ -10,7 +10,7 @@ from abc import abstractmethod, ABCMeta cast = 0 overload = 0 -Any = 0 +Any = object() Union = 0 Optional = 0 TypeVar = 0 @@ -125,3 +125,5 @@ class AsyncContextManager(Generic[T]): def __aenter__(self) -> Awaitable[T]: pass # Use Any because not all the precise types are in the fixtures. def __aexit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> Awaitable[Any]: pass + +class _SpecialForm: pass diff --git a/test-data/unit/fixtures/typing-medium.pyi b/test-data/unit/fixtures/typing-medium.pyi index c19c5d5d96e20..c722a9ddb12c8 100644 --- a/test-data/unit/fixtures/typing-medium.pyi +++ b/test-data/unit/fixtures/typing-medium.pyi @@ -8,7 +8,7 @@ cast = 0 overload = 0 -Any = 0 +Any = object() Union = 0 Optional = 0 TypeVar = 0 diff --git a/test-data/unit/fixtures/typing-namedtuple.pyi b/test-data/unit/fixtures/typing-namedtuple.pyi index f4744575fc092..a464edfdf96c6 100644 --- a/test-data/unit/fixtures/typing-namedtuple.pyi +++ b/test-data/unit/fixtures/typing-namedtuple.pyi @@ -1,6 +1,8 @@ +class _SpecialForm: pass + TypeVar = 0 Generic = 0 -Any = 0 +Any = object() overload = 0 Type = 0 Literal = 0 diff --git a/test-data/unit/fixtures/typing-override.pyi b/test-data/unit/fixtures/typing-override.pyi index 606ca63d4f0d8..e9d2dfcf55c45 100644 --- a/test-data/unit/fixtures/typing-override.pyi +++ b/test-data/unit/fixtures/typing-override.pyi @@ -1,6 +1,6 @@ TypeVar = 0 Generic = 0 -Any = 0 +Any = object() overload = 0 Type = 0 Literal = 0 @@ -21,5 +21,6 @@ class Mapping(Iterable[KT], Generic[KT, T_co]): def keys(self) -> Iterable[T]: pass # Approximate return type def __getitem__(self, key: T) -> T_co: pass - def override(__arg: T) -> T: ... + +class _SpecialForm: pass diff --git a/test-data/unit/fixtures/typing-typeddict-iror.pyi b/test-data/unit/fixtures/typing-typeddict-iror.pyi index e452c8497109d..845ac6cf208fd 100644 --- a/test-data/unit/fixtures/typing-typeddict-iror.pyi +++ b/test-data/unit/fixtures/typing-typeddict-iror.pyi @@ -12,7 +12,7 @@ from abc import ABCMeta cast = 0 assert_type = 0 overload = 0 -Any = 0 +Any = object() Union = 0 Optional = 0 TypeVar = 0 @@ -64,3 +64,5 @@ class _TypedDict(Mapping[str, object]): def __ror__(self, __value: dict[str, Any]) -> dict[str, object]: ... # supposedly incompatible definitions of __or__ and __ior__ def __ior__(self, __value: Self) -> Self: ... # type: ignore[misc] + +class _SpecialForm: pass diff --git a/test-data/unit/fixtures/typing-typeddict.pyi b/test-data/unit/fixtures/typing-typeddict.pyi index 24a2f13289812..d136ac4ab8bee 100644 --- a/test-data/unit/fixtures/typing-typeddict.pyi +++ b/test-data/unit/fixtures/typing-typeddict.pyi @@ -11,7 +11,7 @@ from abc import ABCMeta cast = 0 assert_type = 0 overload = 0 -Any = 0 +Any = object() Union = 0 Optional = 0 TypeVar = 0 @@ -71,3 +71,5 @@ class _TypedDict(Mapping[str, object]): def pop(self, k: NoReturn, default: T = ...) -> object: ... def update(self: T, __m: T) -> None: ... def __delitem__(self, k: NoReturn) -> None: ... + +class _SpecialForm: pass diff --git a/test-data/unit/lib-stub/typing.pyi b/test-data/unit/lib-stub/typing.pyi index 5f458ca687c0f..3cb164140883d 100644 --- a/test-data/unit/lib-stub/typing.pyi +++ b/test-data/unit/lib-stub/typing.pyi @@ -11,7 +11,7 @@ cast = 0 assert_type = 0 overload = 0 -Any = 0 +Any = object() Union = 0 Optional = 0 TypeVar = 0 @@ -63,3 +63,5 @@ class Coroutine(Awaitable[V], Generic[T, U, V]): pass def final(meth: T) -> T: pass def reveal_type(__obj: T) -> T: pass + +class _SpecialForm: pass From ad39b49c2121936fb22109c4789a5ebeb2ff097d Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 17 Jun 2024 17:43:58 +0100 Subject: [PATCH 08/14] Update tests --- test-data/unit/check-python312.test | 2 +- test-data/unit/pythoneval.test | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index 0878aed16d320..0c8c767d18138 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -1612,5 +1612,5 @@ type C2 = None | A1 type D1 = Any | None type D2 = None | Any -[builtins fixtures/tuple.pyi] +[builtins fixtures/type.pyi] [typing fixtures/typing-full.pyi] diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index a76d3abd71148..9096a7b3ed003 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1606,8 +1606,8 @@ class Foo(Enum): Bar: Foo = Callable[[str], None] Baz: Foo = Callable[[Dict[str, "Missing"]], None] [out] -_testEnumValueWithPlaceholderNodeType.py:5: error: Incompatible types in assignment (expression has type "object", variable has type "Foo") -_testEnumValueWithPlaceholderNodeType.py:6: error: Incompatible types in assignment (expression has type "object", variable has type "Foo") +_testEnumValueWithPlaceholderNodeType.py:5: error: Incompatible types in assignment (expression has type "", variable has type "Foo") +_testEnumValueWithPlaceholderNodeType.py:6: error: Incompatible types in assignment (expression has type "", variable has type "Foo") _testEnumValueWithPlaceholderNodeType.py:6: error: Name "Missing" is not defined [case testTypeshedRecursiveTypesExample] From c402c100ae7b5dc7dbd65911fb586def039b9aa7 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 17 Jun 2024 17:44:51 +0100 Subject: [PATCH 09/14] Update test case --- test-data/unit/check-python312.test | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index 0c8c767d18138..6657040c83e9f 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -1595,7 +1595,7 @@ d: E[int] # E: Type argument "int" of "E" must be a subtype of "str" [case testPEP695TypeAliasWithDifferentTargetTypes] # flags: --enable-incomplete-feature=NewGenericSyntax import types # We need GenericAlias from here, and test stubs don't bring in 'types' -from typing import Any, Callable +from typing import Any, Callable, List type A1 = type[int] type A2 = type[int] | None @@ -1612,5 +1612,9 @@ type C2 = None | A1 type D1 = Any | None type D2 = None | Any + +type E1 = List[int] +type E2 = List[int] | None +type E3 = None | List[int] [builtins fixtures/type.pyi] [typing fixtures/typing-full.pyi] From e1ff8bb527116f5959ef66e38b9e2854491d1b3c Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 18 Jun 2024 10:56:15 +0100 Subject: [PATCH 10/14] Minor refactoring --- test-data/unit/fixtures/typing-namedtuple.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/fixtures/typing-namedtuple.pyi b/test-data/unit/fixtures/typing-namedtuple.pyi index a464edfdf96c6..bcdcfc44c3d21 100644 --- a/test-data/unit/fixtures/typing-namedtuple.pyi +++ b/test-data/unit/fixtures/typing-namedtuple.pyi @@ -1,5 +1,3 @@ -class _SpecialForm: pass - TypeVar = 0 Generic = 0 Any = object() @@ -28,3 +26,5 @@ class NamedTuple(tuple[Any, ...]): def __init__(self, typename: str, fields: Iterable[tuple[str, Any]] = ...) -> None: ... @overload def __init__(self, typename: str, fields: None = None, **kwargs: Any) -> None: ... + +class _SpecialForm: pass From 62d17076b4bb31974d480eacd5bb30edd329dc75 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 18 Jun 2024 10:59:25 +0100 Subject: [PATCH 11/14] Test more cases --- mypyc/test-data/fixtures/typing-full.pyi | 4 ++-- test-data/unit/check-python312.test | 10 +++++++++- test-data/unit/fixtures/typing-full.pyi | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/mypyc/test-data/fixtures/typing-full.pyi b/mypyc/test-data/fixtures/typing-full.pyi index 32cdacfc0f536..6b6aba6802b15 100644 --- a/mypyc/test-data/fixtures/typing-full.pyi +++ b/mypyc/test-data/fixtures/typing-full.pyi @@ -16,7 +16,6 @@ class _SpecialForm: cast = 0 overload = 0 Any = object() -Union = 0 Optional = 0 TypeVar = 0 Generic = 0 @@ -28,11 +27,12 @@ Type = 0 no_type_check = 0 ClassVar = 0 Final = 0 -Literal = 0 TypedDict = 0 NoReturn = 0 NewType = 0 Callable: _SpecialForm +Union: _SpecialForm +Literal: _SpecialForm T = TypeVar('T') T_co = TypeVar('T_co', covariant=True) diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index 6657040c83e9f..212768142f3ea 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -1595,7 +1595,7 @@ d: E[int] # E: Type argument "int" of "E" must be a subtype of "str" [case testPEP695TypeAliasWithDifferentTargetTypes] # flags: --enable-incomplete-feature=NewGenericSyntax import types # We need GenericAlias from here, and test stubs don't bring in 'types' -from typing import Any, Callable, List +from typing import Any, Callable, List, Literal type A1 = type[int] type A2 = type[int] | None @@ -1616,5 +1616,13 @@ type D2 = None | Any type E1 = List[int] type E2 = List[int] | None type E3 = None | List[int] + +type F1 = Literal[1] +type F2 = Literal['x'] | None +type F3 = None | Literal[True] + +type G1 = tuple[int, Any] +type G2 = tuple[int, Any] | None +type G3 = None | tuple[int, Any] [builtins fixtures/type.pyi] [typing fixtures/typing-full.pyi] diff --git a/test-data/unit/fixtures/typing-full.pyi b/test-data/unit/fixtures/typing-full.pyi index e77e77efc58f9..8e0116aab1c29 100644 --- a/test-data/unit/fixtures/typing-full.pyi +++ b/test-data/unit/fixtures/typing-full.pyi @@ -33,7 +33,6 @@ Type = 0 no_type_check = 0 ClassVar = 0 Final = 0 -Literal = 0 TypedDict = 0 NoReturn = 0 NewType = 0 @@ -41,6 +40,7 @@ Self = 0 Unpack = 0 Callable: _SpecialForm Union: _SpecialForm +Literal: _SpecialForm T = TypeVar('T') T_co = TypeVar('T_co', covariant=True) From 5e42cd957248b483af6d60a7860ce4d4e9b35960 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 18 Jun 2024 16:56:33 +0100 Subject: [PATCH 12/14] Test more and some fixes --- mypy/checkexpr.py | 26 +++++------ test-data/unit/check-generics.test | 1 + test-data/unit/check-python312.test | 19 +++++++- test-data/unit/pythoneval.test | 70 ++++++++++++++++++++++++++++- 4 files changed, 100 insertions(+), 16 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 26739e5efa7e4..734a9e1687bd7 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4334,22 +4334,22 @@ def visit_index_with_type( return self.nonliteral_tuple_index_helper(left_type, index) elif isinstance(left_type, TypedDictType): return self.visit_typeddict_index_expr(left_type, e.index) - elif ( - isinstance(left_type, FunctionLike) - and left_type.is_type_obj() - and left_type.type_object().is_enum - ): - return self.visit_enum_index_expr(left_type.type_object(), e.index, e) - elif isinstance(left_type, TypeVarType) and not self.has_member( + elif isinstance(left_type, FunctionLike) and left_type.is_type_obj(): + if left_type.type_object().is_enum: + return self.visit_enum_index_expr(left_type.type_object(), e.index, e) + elif left_type.type_object().type_vars: + return self.named_type("types.GenericAlias") + elif ( + left_type.type_object().fullname == "builtins.type" + and self.chk.options.python_version >= (3, 9) + ): + # builtins.type is special: it's not generic in stubs, but it supports indexing + return self.named_type("typing._SpecialForm") + + if isinstance(left_type, TypeVarType) and not self.has_member( left_type.upper_bound, "__getitem__" ): return self.visit_index_with_type(left_type.upper_bound, e, original_type) - elif ( - isinstance(left_type, FunctionLike) - and left_type.is_type_obj() - and left_type.type_object().type_vars - ): - return self.named_type("types.GenericAlias") elif isinstance(left_type, Instance) and left_type.type.fullname == "typing._SpecialForm": # Allow special forms to be indexed and used to create union types return self.named_type("typing._SpecialForm") diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index eb0d1aa5bd7a9..a95f73706aa70 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -460,6 +460,7 @@ class A: pass a[A]() # E: Value of type "A" is not indexable A[A]() # E: The type "Type[A]" is not generic and not indexable [builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] [case testTypeApplicationArgTypes] from typing import TypeVar, Generic diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index 212768142f3ea..7c3d565b1b446 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -1595,7 +1595,16 @@ d: E[int] # E: Type argument "int" of "E" must be a subtype of "str" [case testPEP695TypeAliasWithDifferentTargetTypes] # flags: --enable-incomplete-feature=NewGenericSyntax import types # We need GenericAlias from here, and test stubs don't bring in 'types' -from typing import Any, Callable, List, Literal +from typing import Any, Callable, List, Literal, TypedDict + +# Test that various type expressions don't generate false positives as type alias +# values, as they are type checked as expressions. There is a similar test case in +# pythoneval.test that uses typeshed stubs. + +class C[T]: pass + +class TD(TypedDict): + x: int type A1 = type[int] type A2 = type[int] | None @@ -1624,5 +1633,13 @@ type F3 = None | Literal[True] type G1 = tuple[int, Any] type G2 = tuple[int, Any] | None type G3 = None | tuple[int, Any] + +type H1 = TD +type H2 = TD | None +type H3 = None | TD + +type I1 = C[int] +type I2 = C[Any] | None +type I3 = None | C[TD] [builtins fixtures/type.pyi] [typing fixtures/typing-full.pyi] diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 9096a7b3ed003..6e1bc68b434c7 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1781,9 +1781,9 @@ C = str | int D: TypeAlias = str | int [out] _testTypeAliasNotSupportedWithNewStyleUnion.py:3: error: Invalid type alias: expression is not a valid type -_testTypeAliasNotSupportedWithNewStyleUnion.py:3: error: The type "Type[type]" is not generic and not indexable +_testTypeAliasNotSupportedWithNewStyleUnion.py:3: error: Unsupported left operand type for | ("") _testTypeAliasNotSupportedWithNewStyleUnion.py:4: error: Invalid type alias: expression is not a valid type -_testTypeAliasNotSupportedWithNewStyleUnion.py:4: error: The type "Type[type]" is not generic and not indexable +_testTypeAliasNotSupportedWithNewStyleUnion.py:4: error: Unsupported left operand type for | ("Type[str]") _testTypeAliasNotSupportedWithNewStyleUnion.py:5: error: Invalid type alias: expression is not a valid type _testTypeAliasNotSupportedWithNewStyleUnion.py:5: error: Unsupported left operand type for | ("Type[str]") _testTypeAliasNotSupportedWithNewStyleUnion.py:6: error: Invalid type alias: expression is not a valid type @@ -2117,3 +2117,69 @@ def func( a2 = action # Error [out] _testPEP695VarianceInference.py:17: error: Incompatible types in assignment (expression has type "Job[None]", variable has type "Job[int]") + +[case testPEP695TypeAliasWithDifferentTargetTypes] +# flags: --python-version=3.12 --enable-incomplete-feature=NewGenericSyntax +from typing import Any, Callable, List, Literal, TypedDict, overload + +class C[T]: pass + +class O[T]: + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, x: int) -> None: ... + def __init__(self, x: int = 0) -> None: + pass + +class TD(TypedDict): + x: int + +type A1 = type[int] +type A2 = type[int] | None +type A3 = None | type[int] +type A4 = type[Any] +type A5 = type[C] | None +type A6 = None | type[C] +type A7 = type[O] | None +type A8 = None | type[O] + +type B1[**P, R] = Callable[P, R] | None +type B2[**P, R] = None | Callable[P, R] +type B3 = Callable[[str], int] +type B4 = Callable[..., int] + +type C1 = A1 | None +type C2 = None | A1 + +type D1 = Any | None +type D2 = None | Any + +type E1 = List[int] +type E2 = List[int] | None +type E3 = None | List[int] + +type F1 = Literal[1] +type F2 = Literal['x'] | None +type F3 = None | Literal[True] + +type G1 = tuple[int, Any] +type G2 = tuple[int, Any] | None +type G3 = None | tuple[int, Any] + +type H1 = TD +type H2 = TD | None +type H3 = None | TD + +type I1 = C[int] +type I2 = C[Any] | None +type I3 = None | C[TD] +type I4 = O[int] | None +type I5 = None | O[int] + +type J1[T] = T | None +type J2[T] = None | T +type J3[*Ts] = tuple[*Ts] +type J4[T] = J1[T] | None +type J5[T] = None | J1[T] +type J6[*Ts] = J3[*Ts] | None From 0f939da7c7cbb521aee5718c7d63177480fd5fc7 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 18 Jun 2024 16:58:33 +0100 Subject: [PATCH 13/14] Test more --- test-data/unit/pythoneval.test | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 6e1bc68b434c7..6feec73bb3b95 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -2120,7 +2120,7 @@ _testPEP695VarianceInference.py:17: error: Incompatible types in assignment (exp [case testPEP695TypeAliasWithDifferentTargetTypes] # flags: --python-version=3.12 --enable-incomplete-feature=NewGenericSyntax -from typing import Any, Callable, List, Literal, TypedDict, overload +from typing import Any, Callable, List, Literal, TypedDict, overload, TypeAlias, TypeVar class C[T]: pass @@ -2135,6 +2135,10 @@ class O[T]: class TD(TypedDict): x: int +S = TypeVar("S") +A = list[S] +B: TypeAlias = list[S] + type A1 = type[int] type A2 = type[int] | None type A3 = None | type[int] @@ -2183,3 +2187,8 @@ type J3[*Ts] = tuple[*Ts] type J4[T] = J1[T] | None type J5[T] = None | J1[T] type J6[*Ts] = J3[*Ts] | None + +type K1 = A[int] | None +type K2 = None | A[int] +type K3 = B[int] | None +type K4 = None | B[int] From 4f8b8ad81fa6098edbdf233683ab3e7dc531043e Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 18 Jun 2024 16:59:23 +0100 Subject: [PATCH 14/14] Test more --- test-data/unit/pythoneval.test | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 6feec73bb3b95..bdf830050f9fd 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -2120,7 +2120,7 @@ _testPEP695VarianceInference.py:17: error: Incompatible types in assignment (exp [case testPEP695TypeAliasWithDifferentTargetTypes] # flags: --python-version=3.12 --enable-incomplete-feature=NewGenericSyntax -from typing import Any, Callable, List, Literal, TypedDict, overload, TypeAlias, TypeVar +from typing import Any, Callable, List, Literal, TypedDict, overload, TypeAlias, TypeVar, Never class C[T]: pass @@ -2192,3 +2192,6 @@ type K1 = A[int] | None type K2 = None | A[int] type K3 = B[int] | None type K4 = None | B[int] + +type L1 = Never +type L2 = list[Never]