Skip to content

Commit 2fa26d1

Browse files
committed
Fix type inference with type objects in isinstance
1 parent 6fcc3d5 commit 2fa26d1

File tree

1 file changed

+35
-18
lines changed

1 file changed

+35
-18
lines changed

mypy/checker.py

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2419,28 +2419,46 @@ def push_type_map(self, type_map: Optional[Dict[Expression, Type]]) -> None:
24192419

24202420
TypeMap = Optional[Dict[Expression, Type]]
24212421

2422+
# An object that represents either a precise type or a type with an upper bound;
2423+
# it is important for correct type inference with isinstance.
2424+
TypeRange = NamedTuple(
2425+
'TypeRange',
2426+
[
2427+
('item', Type),
2428+
('is_upper_bound', bool), # False => precise type
2429+
])
2430+
24222431

24232432
def conditional_type_map(expr: Expression,
24242433
current_type: Optional[Type],
2425-
proposed_type: Optional[Type],
2434+
proposed_type_ranges: Optional[List[TypeRange]],
24262435
) -> Tuple[TypeMap, TypeMap]:
24272436
"""Takes in an expression, the current type of the expression, and a
24282437
proposed type of that expression.
24292438
24302439
Returns a 2-tuple: The first element is a map from the expression to
24312440
the proposed type, if the expression can be the proposed type. The
24322441
second element is a map from the expression to the type it would hold
2433-
if it was not the proposed type, if any."""
2434-
if proposed_type:
2442+
if it was not the proposed type, if any. None means bot, {} means top"""
2443+
if proposed_type_ranges:
2444+
if len(proposed_type_ranges) == 1:
2445+
proposed_type = proposed_type_ranges[0].item # Union with a single type breaks tests
2446+
else:
2447+
proposed_type = UnionType([type_range.item for type_range in proposed_type_ranges])
24352448
if current_type:
2436-
if is_proper_subtype(current_type, proposed_type):
2437-
# Expression is always of type proposed_type
2449+
if not any(type_range.is_upper_bound for type_range in proposed_type_ranges) \
2450+
and is_proper_subtype(current_type, proposed_type):
2451+
# Expression is always of one of the types in proposed_type_ranges
24382452
return {}, None
24392453
elif not is_overlapping_types(current_type, proposed_type):
2440-
# Expression is never of type proposed_type
2454+
# Expression is never of any type in proposed_type_ranges
24412455
return None, {}
24422456
else:
2443-
remaining_type = restrict_subtype_away(current_type, proposed_type)
2457+
# we can only restrict when the type is precise, not bounded
2458+
proposed_precise_type = UnionType([type_range.item
2459+
for type_range in proposed_type_ranges
2460+
if not type_range.is_upper_bound])
2461+
remaining_type = restrict_subtype_away(current_type, proposed_precise_type)
24442462
return {expr: proposed_type}, {expr: remaining_type}
24452463
else:
24462464
return {expr: proposed_type}, {}
@@ -2611,8 +2629,8 @@ def find_isinstance_check(node: Expression,
26112629
expr = node.args[0]
26122630
if expr.literal == LITERAL_TYPE:
26132631
vartype = type_map[expr]
2614-
type = get_isinstance_type(node.args[1], type_map)
2615-
return conditional_type_map(expr, vartype, type)
2632+
types = get_isinstance_type(node.args[1], type_map)
2633+
return conditional_type_map(expr, vartype, types)
26162634
elif refers_to_fullname(node.callee, 'builtins.callable'):
26172635
expr = node.args[0]
26182636
if expr.literal == LITERAL_TYPE:
@@ -2630,7 +2648,8 @@ def find_isinstance_check(node: Expression,
26302648
# two elements in node.operands, and at least one of them
26312649
# should represent a None.
26322650
vartype = type_map[expr]
2633-
if_vars, else_vars = conditional_type_map(expr, vartype, NoneTyp())
2651+
none_typ = [TypeRange(NoneTyp(), is_upper_bound=False)]
2652+
if_vars, else_vars = conditional_type_map(expr, vartype, none_typ)
26342653
break
26352654

26362655
if is_not:
@@ -2692,33 +2711,31 @@ def flatten(t: Expression) -> List[Expression]:
26922711
return [t]
26932712

26942713

2695-
def get_isinstance_type(expr: Expression, type_map: Dict[Expression, Type]) -> Type:
2714+
def get_isinstance_type(expr: Expression,
2715+
type_map: Dict[Expression, Type]) -> Optional[List[TypeRange]]:
26962716
type = type_map[expr]
26972717

26982718
if isinstance(type, TupleType):
26992719
all_types = type.items
27002720
else:
27012721
all_types = [type]
27022722

2703-
types = [] # type: List[Type]
2723+
types = [] # type: List[TypeRange]
27042724

27052725
for type in all_types:
27062726
if isinstance(type, FunctionLike):
27072727
if type.is_type_obj():
27082728
# Type variables may be present -- erase them, which is the best
27092729
# we can do (outside disallowing them here).
27102730
type = erase_typevars(type.items()[0].ret_type)
2711-
types.append(type)
2731+
types.append(TypeRange(type, is_upper_bound=False))
27122732
elif isinstance(type, TypeType):
2713-
types.append(type.item)
2733+
types.append(TypeRange(type.item, is_upper_bound=True))
27142734
else: # we didn't see an actual type, but rather a variable whose value is unknown to us
27152735
return None
27162736

27172737
assert len(types) != 0
2718-
if len(types) == 1:
2719-
return types[0]
2720-
else:
2721-
return UnionType(types)
2738+
return types
27222739

27232740

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

0 commit comments

Comments
 (0)