Skip to content

Commit c4470f1

Browse files
authored
Make more type expressions valid in PEP 695 aliases and runtime contexts (#17404)
Previously some type expressions, when used as the value of a PEP 695 type alias or in an expression context, generated errors, even if the code would work at runtime. Improve type inference of types in expression contexts (this includes PEP 695 type aliases) to better reflect runtime behavior. This is still not perfect, since we don't have precise types for everything in stubs. Use `typing._SpecialForm` as a fallback, as it supports indexing and `|` operations, which are supported for types. Also update stubs used in tests to better match typeshed stubs. In particular, provide `_SpecialForm` and define `Any = object()`, similar to typeshed.
1 parent 4ba2696 commit c4470f1

18 files changed

+196
-32
lines changed

mypy/checkexpr.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,9 @@ def analyze_var_ref(self, var: Var, context: Context) -> Type:
428428
if var.type:
429429
var_type = get_proper_type(var.type)
430430
if isinstance(var_type, Instance):
431+
if var.fullname == "typing.Any":
432+
# The typeshed type is 'object'; give a more useful type in runtime context
433+
return self.named_type("typing._SpecialForm")
431434
if self.is_literal_context() and var_type.last_known_value is not None:
432435
return var_type.last_known_value
433436
if var.name in {"True", "False"}:
@@ -4331,16 +4334,25 @@ def visit_index_with_type(
43314334
return self.nonliteral_tuple_index_helper(left_type, index)
43324335
elif isinstance(left_type, TypedDictType):
43334336
return self.visit_typeddict_index_expr(left_type, e.index)
4334-
elif (
4335-
isinstance(left_type, FunctionLike)
4336-
and left_type.is_type_obj()
4337-
and left_type.type_object().is_enum
4338-
):
4339-
return self.visit_enum_index_expr(left_type.type_object(), e.index, e)
4340-
elif isinstance(left_type, TypeVarType) and not self.has_member(
4337+
elif isinstance(left_type, FunctionLike) and left_type.is_type_obj():
4338+
if left_type.type_object().is_enum:
4339+
return self.visit_enum_index_expr(left_type.type_object(), e.index, e)
4340+
elif left_type.type_object().type_vars:
4341+
return self.named_type("types.GenericAlias")
4342+
elif (
4343+
left_type.type_object().fullname == "builtins.type"
4344+
and self.chk.options.python_version >= (3, 9)
4345+
):
4346+
# builtins.type is special: it's not generic in stubs, but it supports indexing
4347+
return self.named_type("typing._SpecialForm")
4348+
4349+
if isinstance(left_type, TypeVarType) and not self.has_member(
43414350
left_type.upper_bound, "__getitem__"
43424351
):
43434352
return self.visit_index_with_type(left_type.upper_bound, e, original_type)
4353+
elif isinstance(left_type, Instance) and left_type.type.fullname == "typing._SpecialForm":
4354+
# Allow special forms to be indexed and used to create union types
4355+
return self.named_type("typing._SpecialForm")
43444356
else:
43454357
result, method_type = self.check_method_call_by_name(
43464358
"__getitem__", left_type, [e.index], [ARG_POS], e, original_type=original_type

mypy/stubgenc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,7 @@ def generate_property_stub(
733733

734734
def get_type_fullname(self, typ: type) -> str:
735735
"""Given a type, return a string representation"""
736-
if typ is Any:
736+
if typ is Any: # type: ignore[comparison-overlap]
737737
return "Any"
738738
typename = getattr(typ, "__qualname__", typ.__name__)
739739
module_name = self.get_obj_module(typ)

mypyc/test-data/fixtures/typing-full.pyi

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ class _SpecialForm:
1515

1616
cast = 0
1717
overload = 0
18-
Any = 0
19-
Union = 0
18+
Any = object()
2019
Optional = 0
2120
TypeVar = 0
2221
Generic = 0
@@ -28,11 +27,12 @@ Type = 0
2827
no_type_check = 0
2928
ClassVar = 0
3029
Final = 0
31-
Literal = 0
3230
TypedDict = 0
3331
NoReturn = 0
3432
NewType = 0
3533
Callable: _SpecialForm
34+
Union: _SpecialForm
35+
Literal: _SpecialForm
3636

3737
T = TypeVar('T')
3838
T_co = TypeVar('T_co', covariant=True)

test-data/unit/check-classes.test

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4790,12 +4790,15 @@ def g(x: Type[S]) -> str:
47904790
return reveal_type(x * 0) # N: Revealed type is "builtins.str"
47914791

47924792
[case testMetaclassGetitem]
4793+
import types
4794+
47934795
class M(type):
47944796
def __getitem__(self, key) -> int: return 1
47954797

47964798
class A(metaclass=M): pass
47974799

47984800
reveal_type(A[M]) # N: Revealed type is "builtins.int"
4801+
[builtins fixtures/tuple.pyi]
47994802

48004803
[case testMetaclassSelfType]
48014804
from typing import TypeVar, Type

test-data/unit/check-functions.test

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,10 +1779,10 @@ def Arg(x, y): pass
17791779
F = Callable[[Arg(int, 'x')], int] # E: Invalid argument constructor "__main__.Arg"
17801780

17811781
[case testCallableParsingFromExpr]
1782-
17831782
from typing import Callable, List
17841783
from mypy_extensions import Arg, VarArg, KwArg
17851784
import mypy_extensions
1785+
import types # Needed for type checking
17861786

17871787
def WrongArg(x, y): return y
17881788
# 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
17991799
# I have commented out the following test because I don't know how to expect the "defined here" note part of the error.
18001800
# 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"
18011801
N = Callable[[Arg(name=None, type=int)], int] # ok
1802-
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
1802+
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 [...]
18031803
P = Callable[[mypy_extensions.VarArg(int)], int] # ok
18041804
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"
18051805
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"
1806-
18071806
[builtins fixtures/dict.pyi]
18081807

18091808
[case testCallableParsing]

test-data/unit/check-generics.test

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,11 +454,13 @@ A[int, str, int]() # E: Type application has too many types (2 expected)
454454
[out]
455455

456456
[case testInvalidTypeApplicationType]
457+
import types
457458
a: A
458459
class A: pass
459460
a[A]() # E: Value of type "A" is not indexable
460461
A[A]() # E: The type "Type[A]" is not generic and not indexable
461-
[out]
462+
[builtins fixtures/tuple.pyi]
463+
[typing fixtures/typing-full.pyi]
462464

463465
[case testTypeApplicationArgTypes]
464466
from typing import TypeVar, Generic
@@ -513,8 +515,9 @@ Alias[int]("a") # E: Argument 1 to "Node" has incompatible type "str"; expected
513515
[out]
514516

515517
[case testTypeApplicationCrash]
518+
import types
516519
type[int] # this was crashing, see #2302 (comment) # E: The type "Type[type]" is not generic and not indexable
517-
[out]
520+
[builtins fixtures/tuple.pyi]
518521

519522

520523
-- Generic type aliases

test-data/unit/check-python312.test

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1591,3 +1591,55 @@ c: E[str]
15911591
d: E[int] # E: Type argument "int" of "E" must be a subtype of "str"
15921592
[builtins fixtures/tuple.pyi]
15931593
[typing fixtures/typing-full.pyi]
1594+
1595+
[case testPEP695TypeAliasWithDifferentTargetTypes]
1596+
# flags: --enable-incomplete-feature=NewGenericSyntax
1597+
import types # We need GenericAlias from here, and test stubs don't bring in 'types'
1598+
from typing import Any, Callable, List, Literal, TypedDict
1599+
1600+
# Test that various type expressions don't generate false positives as type alias
1601+
# values, as they are type checked as expressions. There is a similar test case in
1602+
# pythoneval.test that uses typeshed stubs.
1603+
1604+
class C[T]: pass
1605+
1606+
class TD(TypedDict):
1607+
x: int
1608+
1609+
type A1 = type[int]
1610+
type A2 = type[int] | None
1611+
type A3 = None | type[int]
1612+
type A4 = type[Any]
1613+
1614+
type B1[**P, R] = Callable[P, R] | None
1615+
type B2[**P, R] = None | Callable[P, R]
1616+
type B3 = Callable[[str], int]
1617+
type B4 = Callable[..., int]
1618+
1619+
type C1 = A1 | None
1620+
type C2 = None | A1
1621+
1622+
type D1 = Any | None
1623+
type D2 = None | Any
1624+
1625+
type E1 = List[int]
1626+
type E2 = List[int] | None
1627+
type E3 = None | List[int]
1628+
1629+
type F1 = Literal[1]
1630+
type F2 = Literal['x'] | None
1631+
type F3 = None | Literal[True]
1632+
1633+
type G1 = tuple[int, Any]
1634+
type G2 = tuple[int, Any] | None
1635+
type G3 = None | tuple[int, Any]
1636+
1637+
type H1 = TD
1638+
type H2 = TD | None
1639+
type H3 = None | TD
1640+
1641+
type I1 = C[int]
1642+
type I2 = C[Any] | None
1643+
type I3 = None | C[TD]
1644+
[builtins fixtures/type.pyi]
1645+
[typing fixtures/typing-full.pyi]

test-data/unit/check-type-object-type-inference.test

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# flags: --python-version 3.9
33
from typing import TypeVar, Generic, Type
44
from abc import abstractmethod
5+
import types # Explicitly bring in stubs for 'types'
56

67
T = TypeVar('T')
78
class E(Generic[T]):
@@ -37,5 +38,5 @@ def i(f: F):
3738
f.f(tuple[int,tuple[int,str]]).e( (27,(28,'z')) ) # OK
3839
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"
3940

40-
x = tuple[int,str][str] # E: The type "Type[Tuple[Any, ...]]" is not generic and not indexable
41+
x = tuple[int,str][str] # False negative
4142
[builtins fixtures/tuple.pyi]

test-data/unit/fixtures/typing-async.pyi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ from abc import abstractmethod, ABCMeta
1010

1111
cast = 0
1212
overload = 0
13-
Any = 0
13+
Any = object()
1414
Union = 0
1515
Optional = 0
1616
TypeVar = 0
@@ -125,3 +125,5 @@ class AsyncContextManager(Generic[T]):
125125
def __aenter__(self) -> Awaitable[T]: pass
126126
# Use Any because not all the precise types are in the fixtures.
127127
def __aexit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> Awaitable[Any]: pass
128+
129+
class _SpecialForm: pass

test-data/unit/fixtures/typing-full.pyi

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ class GenericMeta(type): pass
1212

1313
class _SpecialForm:
1414
def __getitem__(self, index: Any) -> Any: ...
15+
def __or__(self, other): ...
16+
def __ror__(self, other): ...
1517
class TypeVar:
1618
def __init__(self, name, *args, bound=None): ...
1719
def __or__(self, other): ...
@@ -21,7 +23,7 @@ class TypeVarTuple: ...
2123
def cast(t, o): ...
2224
def assert_type(o, t): ...
2325
overload = 0
24-
Any = 0
26+
Any = object()
2527
Optional = 0
2628
Generic = 0
2729
Protocol = 0
@@ -31,14 +33,14 @@ Type = 0
3133
no_type_check = 0
3234
ClassVar = 0
3335
Final = 0
34-
Literal = 0
3536
TypedDict = 0
3637
NoReturn = 0
3738
NewType = 0
3839
Self = 0
3940
Unpack = 0
4041
Callable: _SpecialForm
4142
Union: _SpecialForm
43+
Literal: _SpecialForm
4244

4345
T = TypeVar('T')
4446
T_co = TypeVar('T_co', covariant=True)
@@ -216,3 +218,4 @@ class TypeAliasType:
216218
) -> None: ...
217219

218220
def __or__(self, other: Any) -> Any: ...
221+
def __ror__(self, other: Any) -> Any: ...

test-data/unit/fixtures/typing-medium.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
cast = 0
1010
overload = 0
11-
Any = 0
11+
Any = object()
1212
Union = 0
1313
Optional = 0
1414
TypeVar = 0

test-data/unit/fixtures/typing-namedtuple.pyi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
TypeVar = 0
22
Generic = 0
3-
Any = 0
3+
Any = object()
44
overload = 0
55
Type = 0
66
Literal = 0
@@ -26,3 +26,5 @@ class NamedTuple(tuple[Any, ...]):
2626
def __init__(self, typename: str, fields: Iterable[tuple[str, Any]] = ...) -> None: ...
2727
@overload
2828
def __init__(self, typename: str, fields: None = None, **kwargs: Any) -> None: ...
29+
30+
class _SpecialForm: pass

test-data/unit/fixtures/typing-override.pyi

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
TypeVar = 0
22
Generic = 0
3-
Any = 0
3+
Any = object()
44
overload = 0
55
Type = 0
66
Literal = 0
@@ -21,5 +21,6 @@ class Mapping(Iterable[KT], Generic[KT, T_co]):
2121
def keys(self) -> Iterable[T]: pass # Approximate return type
2222
def __getitem__(self, key: T) -> T_co: pass
2323

24-
2524
def override(__arg: T) -> T: ...
25+
26+
class _SpecialForm: pass

test-data/unit/fixtures/typing-typeddict-iror.pyi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ from abc import ABCMeta
1212
cast = 0
1313
assert_type = 0
1414
overload = 0
15-
Any = 0
15+
Any = object()
1616
Union = 0
1717
Optional = 0
1818
TypeVar = 0
@@ -64,3 +64,5 @@ class _TypedDict(Mapping[str, object]):
6464
def __ror__(self, __value: dict[str, Any]) -> dict[str, object]: ...
6565
# supposedly incompatible definitions of __or__ and __ior__
6666
def __ior__(self, __value: Self) -> Self: ... # type: ignore[misc]
67+
68+
class _SpecialForm: pass

test-data/unit/fixtures/typing-typeddict.pyi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ from abc import ABCMeta
1111
cast = 0
1212
assert_type = 0
1313
overload = 0
14-
Any = 0
14+
Any = object()
1515
Union = 0
1616
Optional = 0
1717
TypeVar = 0
@@ -71,3 +71,5 @@ class _TypedDict(Mapping[str, object]):
7171
def pop(self, k: NoReturn, default: T = ...) -> object: ...
7272
def update(self: T, __m: T) -> None: ...
7373
def __delitem__(self, k: NoReturn) -> None: ...
74+
75+
class _SpecialForm: pass

test-data/unit/lib-stub/types.pyi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ class ModuleType:
99
__file__: str
1010
def __getattr__(self, name: str) -> Any: pass
1111

12-
class GenericAlias: ...
12+
class GenericAlias:
13+
def __or__(self, o): ...
14+
def __ror__(self, o): ...
1315

1416
if sys.version_info >= (3, 10):
1517
class NoneType:

test-data/unit/lib-stub/typing.pyi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
cast = 0
1212
assert_type = 0
1313
overload = 0
14-
Any = 0
14+
Any = object()
1515
Union = 0
1616
Optional = 0
1717
TypeVar = 0
@@ -63,3 +63,5 @@ class Coroutine(Awaitable[V], Generic[T, U, V]): pass
6363
def final(meth: T) -> T: pass
6464

6565
def reveal_type(__obj: T) -> T: pass
66+
67+
class _SpecialForm: pass

0 commit comments

Comments
 (0)