Skip to content

Commit

Permalink
Fix isinstance with PEP 604 type aliases
Browse files Browse the repository at this point in the history
Fixes python#12155 and probably several duplicates
  • Loading branch information
hauntsaninja committed Jun 12, 2024
1 parent c3bbd1c commit 754d8f3
Show file tree
Hide file tree
Showing 9 changed files with 45 additions and 3 deletions.
2 changes: 2 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -7295,6 +7295,8 @@ def get_isinstance_type(self, expr: Expression) -> list[TypeRange] | None:
elif isinstance(typ, Instance) and typ.type.fullname == "builtins.type":
object_type = Instance(typ.type.mro[-1], [])
types.append(TypeRange(object_type, is_upper_bound=True))
elif isinstance(typ, Instance) and typ.type.fullname == "types.UnionType" and typ.args:
types.append(TypeRange(UnionType(typ.args), is_upper_bound=False))
elif isinstance(typ, AnyType):
types.append(TypeRange(typ, is_upper_bound=False))
else: # we didn't see an actual type, but rather a variable with unknown value
Expand Down
10 changes: 10 additions & 0 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,10 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) ->
and node
and isinstance(node.node, TypeAlias)
and not node.node.no_args
and not (
isinstance(node.node.target, UnionType)
and node.node.target.uses_pep604_syntax
)
):
self.msg.type_arguments_not_allowed(e)
if isinstance(typ, RefExpr) and isinstance(typ.node, TypeInfo):
Expand Down Expand Up @@ -4760,6 +4764,12 @@ class LongName(Generic[T]): ...
return TypeType(item, line=item.line, column=item.column)
elif isinstance(item, AnyType):
return AnyType(TypeOfAny.from_another_any, source_any=item)
elif (
isinstance(item, UnionType)
and item.uses_pep604_syntax
and self.chk.options.python_version >= (3, 10)
):
return self.chk.named_generic_type("types.UnionType", item.items)
else:
if alias_definition:
return AnyType(TypeOfAny.special_form)
Expand Down
3 changes: 2 additions & 1 deletion mypy/exprtotype.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ def expr_to_unanalyzed_type(
[
expr_to_unanalyzed_type(expr.left, options, allow_new_syntax),
expr_to_unanalyzed_type(expr.right, options, allow_new_syntax),
]
],
uses_pep604_syntax=True,
)
elif isinstance(expr, CallExpr) and isinstance(_parent, ListExpr):
c = expr.callee
Expand Down
7 changes: 6 additions & 1 deletion mypy/type_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,12 @@ def visit_literal_type(self, t: LiteralType) -> Type:
return LiteralType(value=t.value, fallback=fallback, line=t.line, column=t.column)

def visit_union_type(self, t: UnionType) -> Type:
return UnionType(self.translate_types(t.items), t.line, t.column)
return UnionType(
self.translate_types(t.items),
t.line,
t.column,
uses_pep604_syntax=t.uses_pep604_syntax,
)

def translate_types(self, types: Iterable[Type]) -> list[Type]:
return [t.accept(self) for t in types]
Expand Down
2 changes: 1 addition & 1 deletion mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1248,7 +1248,7 @@ def visit_union_type(self, t: UnionType) -> Type:
and not self.options.python_version >= (3, 10)
):
self.fail("X | Y syntax for unions requires Python 3.10", t, code=codes.SYNTAX)
return UnionType(self.anal_array(t.items), t.line)
return UnionType(self.anal_array(t.items), t.line, uses_pep604_syntax=t.uses_pep604_syntax)

def visit_partial_type(self, t: PartialType) -> Type:
assert False, "Internal error: Unexpected partial type"
Expand Down
1 change: 1 addition & 0 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2822,6 +2822,7 @@ def __init__(
items: Sequence[Type],
line: int = -1,
column: int = -1,
*,
is_evaluated: bool = True,
uses_pep604_syntax: bool = False,
) -> None:
Expand Down
16 changes: 16 additions & 0 deletions test-data/unit/check-python310.test
Original file line number Diff line number Diff line change
Expand Up @@ -1983,6 +1983,22 @@ X = None | C
Y = None | D
[builtins fixtures/type.pyi]

[case testTypeAliasWithNewUnionIsInstance]
SimpleAlias = int | str

def foo(x: int | str | tuple):
if isinstance(x, SimpleAlias):
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]"
else:
reveal_type(x) # N: Revealed type is "builtins.tuple[Any, ...]"

ParameterizedAlias = str | list[str]

# these are false negatives:
isinstance(5, str | list[str])
isinstance(5, ParameterizedAlias)
[builtins fixtures/type.pyi]

[case testMatchStatementWalrus]
class A:
a = 1
Expand Down
4 changes: 4 additions & 0 deletions test-data/unit/fixtures/type.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# builtins stub used in type-related test cases.

from typing import Any, Generic, TypeVar, List, Union
import sys
import types

T = TypeVar("T")
S = TypeVar("S")
Expand All @@ -25,3 +27,5 @@ class bool: pass
class int: pass
class str: pass
class ellipsis: pass

def isinstance(obj: object, class_or_tuple: type | types.UnionType, /) -> bool: ...
3 changes: 3 additions & 0 deletions test-data/unit/lib-stub/types.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ if sys.version_info >= (3, 10):

class NoneType:
...

class UnionType:
...

0 comments on commit 754d8f3

Please sign in to comment.