Skip to content

Commit 98a22c4

Browse files
authored
Fix isinstance with type aliases to PEP 604 unions (#17371)
Fixes #12155, fixes #11673, seems pretty commonly reported issue
1 parent 8fb9969 commit 98a22c4

File tree

11 files changed

+53
-3
lines changed

11 files changed

+53
-3
lines changed

mypy/checker.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7323,6 +7323,8 @@ def get_isinstance_type(self, expr: Expression) -> list[TypeRange] | None:
73237323
elif isinstance(typ, Instance) and typ.type.fullname == "builtins.type":
73247324
object_type = Instance(typ.type.mro[-1], [])
73257325
types.append(TypeRange(object_type, is_upper_bound=True))
7326+
elif isinstance(typ, Instance) and typ.type.fullname == "types.UnionType" and typ.args:
7327+
types.append(TypeRange(UnionType(typ.args), is_upper_bound=False))
73267328
elif isinstance(typ, AnyType):
73277329
types.append(TypeRange(typ, is_upper_bound=False))
73287330
else: # we didn't see an actual type, but rather a variable with unknown value

mypy/checkexpr.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,10 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) ->
527527
and node
528528
and isinstance(node.node, TypeAlias)
529529
and not node.node.no_args
530+
and not (
531+
isinstance(union_target := get_proper_type(node.node.target), UnionType)
532+
and union_target.uses_pep604_syntax
533+
)
530534
):
531535
self.msg.type_arguments_not_allowed(e)
532536
if isinstance(typ, RefExpr) and isinstance(typ.node, TypeInfo):
@@ -4762,6 +4766,12 @@ class LongName(Generic[T]): ...
47624766
return TypeType(item, line=item.line, column=item.column)
47634767
elif isinstance(item, AnyType):
47644768
return AnyType(TypeOfAny.from_another_any, source_any=item)
4769+
elif (
4770+
isinstance(item, UnionType)
4771+
and item.uses_pep604_syntax
4772+
and self.chk.options.python_version >= (3, 10)
4773+
):
4774+
return self.chk.named_generic_type("types.UnionType", item.items)
47654775
else:
47664776
if alias_definition:
47674777
return AnyType(TypeOfAny.special_form)

mypy/exprtotype.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ def expr_to_unanalyzed_type(
122122
[
123123
expr_to_unanalyzed_type(expr.left, options, allow_new_syntax),
124124
expr_to_unanalyzed_type(expr.right, options, allow_new_syntax),
125-
]
125+
],
126+
uses_pep604_syntax=True,
126127
)
127128
elif isinstance(expr, CallExpr) and isinstance(_parent, ListExpr):
128129
c = expr.callee

mypy/type_visitor.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,12 @@ def visit_literal_type(self, t: LiteralType) -> Type:
266266
return LiteralType(value=t.value, fallback=fallback, line=t.line, column=t.column)
267267

268268
def visit_union_type(self, t: UnionType) -> Type:
269-
return UnionType(self.translate_types(t.items), t.line, t.column)
269+
return UnionType(
270+
self.translate_types(t.items),
271+
t.line,
272+
t.column,
273+
uses_pep604_syntax=t.uses_pep604_syntax,
274+
)
270275

271276
def translate_types(self, types: Iterable[Type]) -> list[Type]:
272277
return [t.accept(self) for t in types]

mypy/typeanal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1271,7 +1271,7 @@ def visit_union_type(self, t: UnionType) -> Type:
12711271
and not self.options.python_version >= (3, 10)
12721272
):
12731273
self.fail("X | Y syntax for unions requires Python 3.10", t, code=codes.SYNTAX)
1274-
return UnionType(self.anal_array(t.items), t.line)
1274+
return UnionType(self.anal_array(t.items), t.line, uses_pep604_syntax=t.uses_pep604_syntax)
12751275

12761276
def visit_partial_type(self, t: PartialType) -> Type:
12771277
assert False, "Internal error: Unexpected partial type"

mypy/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2821,6 +2821,7 @@ def __init__(
28212821
items: Sequence[Type],
28222822
line: int = -1,
28232823
column: int = -1,
2824+
*,
28242825
is_evaluated: bool = True,
28252826
uses_pep604_syntax: bool = False,
28262827
) -> None:

test-data/unit/check-type-aliases.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,7 @@ a: A
967967
b: B
968968
reveal_type(a) # N: Revealed type is "Union[builtins.list[Any], builtins.int]"
969969
reveal_type(b) # N: Revealed type is "Union[builtins.int, builtins.list[Any]]"
970+
[builtins fixtures/type.pyi]
970971

971972
[case testValidTypeAliasValues]
972973
from typing import TypeVar, Generic, List

test-data/unit/check-union-or-syntax.test

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,25 @@ foo: ReadableBuffer
207207
[file was_mmap.pyi]
208208
from was_builtins import *
209209
class mmap: ...
210+
[builtins fixtures/type.pyi]
211+
212+
[case testTypeAliasWithNewUnionIsInstance]
213+
# flags: --python-version 3.10
214+
SimpleAlias = int | str
215+
216+
def foo(x: int | str | tuple):
217+
if isinstance(x, SimpleAlias):
218+
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]"
219+
else:
220+
reveal_type(x) # N: Revealed type is "builtins.tuple[Any, ...]"
221+
222+
ParameterizedAlias = str | list[str]
223+
224+
# these are false negatives:
225+
isinstance(5, str | list[str])
226+
isinstance(5, ParameterizedAlias)
227+
[builtins fixtures/type.pyi]
228+
210229

211230
# TODO: Get this test to pass
212231
[case testImplicit604TypeAliasWithCyclicImportNotInStub-xfail]

test-data/unit/fine-grained.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10380,6 +10380,7 @@ from b import C, D
1038010380
A = C | D
1038110381
a: A
1038210382
reveal_type(a)
10383+
[builtins fixtures/type.pyi]
1038310384

1038410385
[file b.py]
1038510386
C = int

test-data/unit/fixtures/type.pyi

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# builtins stub used in type-related test cases.
22

33
from typing import Any, Generic, TypeVar, List, Union
4+
import sys
5+
import types
46

57
T = TypeVar("T")
68
S = TypeVar("S")
@@ -25,3 +27,8 @@ class bool: pass
2527
class int: pass
2628
class str: pass
2729
class ellipsis: pass
30+
31+
if sys.version_info >= (3, 10): # type: ignore
32+
def isinstance(obj: object, class_or_tuple: type | types.UnionType, /) -> bool: ...
33+
else:
34+
def isinstance(obj: object, class_or_tuple: type, /) -> bool: ...

0 commit comments

Comments
 (0)