8000 Support Type[x] and `type` with isinstance (#2997) · python/mypy@0b28da6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0b28da6

Browse files
pkchJukkaL
authored andcommitted
Support Type[x] and type with isinstance (#2997)
1 parent 660018c commit 0b28da6

File tree

4 files changed

+104
-30
lines changed

4 files changed

+104
-30
lines changed

mypy/checker.py

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2429,28 +2429,46 @@ def push_type_map(self, type_map: Optional[Dict[Expression, Type]]) -> None:
24292429

24302430
TypeMap = Optional[Dict[Expression, Type]]
24312431

2432+
# An object that represents either a precise type or a type with an upper bound;
2433+
# it is important for correct type inference with isinstance.
2434+
TypeRange = NamedTuple(
2435+
'TypeRange',
2436+
[
2437+
('item', Type),
2438+
('is_upper_bound', bool), # False => precise type
2439+
])
2440+
24322441

24332442
def conditional_type_map(expr: Expression,
24342443
current_type: Optional[Type],
2435-
proposed_type: Optional[Type],
2444+
proposed_type_ranges: Optional[List[TypeRange]],
24362445
) -> Tuple[TypeMap, TypeMap]:
24372446
"""Takes in an expression, the current type of the expression, and a
24382447
proposed type of that expression.
24392448
24402449
Returns a 2-tuple: The first element is a map from the expression to
24412450
the proposed type, if the expression can be the proposed type. The
24422451
second element is a map from the expression to the type it would hold
2443-
if it was not the proposed type, if any."""
2444-
if proposed_type:
2452+
if it was not the proposed type, if any. None means bot, {} means top"""
2453+
if proposed_type_ranges:
2454+
if len(proposed_type_ranges) == 1:
2455+
proposed_type = proposed_type_ranges[0].item # Uni 8000 on with a single type breaks tests
2456+
else:
2457+
proposed_type = UnionType([type_range.item for type_range in proposed_type_ranges])
24452458
if current_type:
2446-
if is_proper_subtype(current_type, proposed_type):
2447-
# Expression is always of type proposed_type
2459+
if (not any(type_range.is_upper_bound for type_range in proposed_type_ranges)
2460+
and is_proper_subtype(current_type, proposed_type)):
2461+
# Expression is always of one of the types in proposed_type_ranges
24482462
return {}, None
24492463
elif not is_overlapping_types(current_type, proposed_type):
2450-
# Expression is never of type proposed_type
2464+
# Expression is never of any type in proposed_type_ranges
24512465
return None, {}
24522466
else:
2453-
remaining_type = restrict_subtype_away(current_type, proposed_type)
2467+
# we can only restrict when the type is precise, not bounded
2468+
proposed_precise_type = UnionType([type_range.item
2469+
for type_range in proposed_type_ranges
2470+
if not type_range.is_upper_bound])
2471+
remaining_type = restrict_subtype_away(current_type, proposed_precise_type)
24542472
return {expr: proposed_type}, {expr: remaining_type}
24552473
else:
24562474
return {expr: proposed_type}, {}
@@ -2621,8 +2639,8 @@ def find_isinstance_check(node: Expression,
26212639
expr = node.args[0]
26222640
if expr.literal == LITERAL_TYPE:
26232641
vartype = type_map[expr]
2624-
type = get_isinstance_type(node.args[1], type_map)
2625-
return conditional_type_map(expr, vartype, type)
2642+
types = get_isinstance_type(node.args[1], type_map)
2643+
return conditional_type_map(expr, vartype, types)
26262644
elif refers_to_fullname(node.callee, 'builtins.callable'):
26272645
expr = node.args[0]
26282646
if expr.literal == LITERAL_TYPE:
@@ -2640,7 +2658,8 @@ def find_isinstance_check(node: Expression,
26402658
# two elements in node.operands, and at least one of them
26412659
# should represent a None.
26422660
vartype = type_map[expr]
2643-
if_vars, else_vars = conditional_type_map(expr, vartype, NoneTyp())
2661+
none_typ = [TypeRange(NoneTyp(), is_upper_bound=False)]
2662+
if_vars, else_vars = conditional_type_map(expr, vartype, none_typ)
26442663
break
26452664

26462665
if is_not:
@@ -2702,26 +2721,30 @@ def flatten(t: Expression) -> List[Expression]:
27022721
return [t]
27032722

27042723

2705-
def get_isinstance_type(expr: Expression, type_map: Dict[Expression, Type]) -> Type:
2724+
def get_isinstance_type(expr: Expression, type_map: Dict[Expression, Type]) -> List[TypeRange]:
27062725
all_types = [type_map[e] for e in flatten(expr)]
2707-
2708-
types = [] # type: List[Type]
2709-
2726+
types = [] # type: List[TypeRange]
27102727
for type in all_types:
2711-
if isinstance(type, FunctionLike):
2712-
if type.is_type_obj():
2713-
# Type variables may be present -- erase them, which is the best
2714-
# we can do (outside disallowing them here).
2715-
type = erase_typevars(type.items()[0].ret_type)
2716-
2717-
types.append(type)
2718-
2719-
if len(types) == 0:
2728+
if isinstance(type, FunctionLike) and type.is_type_obj():
2729+
# Type variables may be present -- erase them, which is the best
2730+
# we can do (outside disallowing them here).
2731+
type = erase_typevars(type.items()[0].ret_type)
2732+
types.append(TypeRange(type, is_upper_bound=False))
2733+
elif isinstance(type, TypeType):
2734+
# Type[A] means "any type that is a subtype of A" rather than "precisely type A"
2735+
# we indicate this by setting is_upper_bound flag
2736+
types.append(TypeRange(type.item, is_upper_bound=True))
2737+
elif isinstance(type, Instance) and type.type.fullname() == 'builtins.type':
2738+
object_type = Instance(type.type.mro[-1], [])
2739+
types.append(TypeRange(object_type, is_upper_bound=True))
2740+
else: # we didn't see an actual type, but rather a variable whose value is unknown to us
2741+
return None
2742+
if not types:
2743+
# this can happen if someone has empty tuple as 2nd argument to isinstance
2744+
# strictly speaking, we should return UninhabitedType but for simplicity we will simply
2745+
# refuse to do any type inference for now
27202746
return None
2721-
elif len(types) == 1:
2722-
return types[0]
2723-
else:
2724-
return UnionType(types)
2747+
return types
27252748

27262749

27272750
def expand_func(defn: FuncItem, map: Dict[TypeVarId, Type]) -> FuncItem:

mypy/semanal.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2679,8 +2679,7 @@ def visit_member_expr(self, expr: MemberExpr) -> None:
26792679
# This branch handles the case foo.bar where foo is a module.
26802680
# In this case base.node is the module's MypyFile and we look up
26812681
# bar in its namespace. This must be done for all types of bar.
2682-
file = base.node
2683-
assert isinstance(file, (MypyFile, type(None)))
2682+
file = cast(Optional[MypyFile], base.node) # can't use isinstance due to issue #2999
26842683
n = file.names.get(expr.name, None) if file is not None else None
26852684
if n:
26862685
n = self.normalize_type_alias(n, expr)

test-data/unit/check-isinstance.test

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,3 +1360,55 @@ def f(x: object) -> None:
13601360
reveal_type(b) # E: Revealed type is '__main__.A'
13611361
[builtins fixtures/isinstance.pyi]
13621362
[out]
1363+
1364+
1365+
[case testIsInstanceWithUnknownType]
1366+
from typing import Union
1367+
def f(x: Union[int, str], typ: type) -> None:
1368+
if isinstance(x, (typ, int)):
1369+
x + 1 # E: Unsupported operand types for + (likely involving Union)
1370+
reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.str]'
1371+
else:
1372+
reveal_type(x) # E: Revealed type is 'builtins.str'
1373+
[builtins fixtures/isinstancelist.pyi]
1374+
1375+
1376+
[case testIsInstanceWithBoundedType]
1377+
from typing import Union, Type
1378+
1379+
class A: pass
1380+
def f(x: Union[int, A], a: Type[A]) -> None:
1381+
if isinstance(x, (a, int)):
1382+
reveal_type(x) # E: Revealed type is 'Union[builtins.int, __main__.A]'
1383+
else:
1384+
reveal_type(x) # E: Revealed type is '__main__.A'
1385+
1386+
[builtins fixtures/isinstancelist.pyi]
1387+
1388+
1389+
[case testIsInstanceWithEmtpy2ndArg]
1390+
from typing import Union
1391+
def f(x: Union[int, str]) -> None:
1392+
if isinstance(x, ()):
1393+
reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.str]'
1394+
else:
1395+
reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.str]'
1396+
[builtins fixtures/isinstancelist.pyi]
1397+
1398+
1399+
[case testIsInstanceWithTypeObject]
1400+
from typing import Union, Type
1401+
1402+
class A: pass
1403+
1404+
def f(x: Union[int, A], a: Type[A]) -> None:
1405+
if isinstance(x, a):
1406+
reveal_type(x) # E: Revealed type is '__main__.A'
1407+
elif isinstance(x, int):
1408+
reveal_type(x) # E: Revealed type is 'builtins.int'
1409+
else:
1410+
reveal_type(x) # E: Revealed type is '__main__.A'
1411+
reveal_type(x) # E: Revealed type is 'Union[builtins.int, __main__.A]'
1412+
1413+
[builtins fixtures/isinstancelist.pyi]
1414+

typeshed

Submodule typeshed updated 88 files

0 commit comments

Comments
 (0)
0