Skip to content

Commit

Permalink
Fix line number if __iter__ is incorrectly reported as missing (#14893)
Browse files Browse the repository at this point in the history
A type was used as error context, but types don't reliably have valid
line numbers during type checking. Pass context explicitly instead.

The error in the test case is actually a false positive, but I'm first
fixing the line number of the error, since it seems plausible that the
wrong line number could cause other problems.

Work on #14892.
  • Loading branch information
JukkaL authored Mar 13, 2023
1 parent 4365dad commit 895a821
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 10 deletions.
12 changes: 7 additions & 5 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -3299,14 +3299,14 @@ def check_assignment_to_multiple_lvalues(
rvalues.extend([TempNode(typ) for typ in typs.items])
elif self.type_is_iterable(typs) and isinstance(typs, Instance):
if iterable_type is not None and iterable_type != self.iterable_item_type(
typs
typs, rvalue
):
self.fail(message_registry.CONTIGUOUS_ITERABLE_EXPECTED, context)
else:
if last_idx is None or last_idx + 1 == idx_rval:
rvalues.append(rval)
last_idx = idx_rval
iterable_type = self.iterable_item_type(typs)
iterable_type = self.iterable_item_type(typs, rvalue)
else:
self.fail(message_registry.CONTIGUOUS_ITERABLE_EXPECTED, context)
else:
Expand Down Expand Up @@ -3635,7 +3635,7 @@ def check_multi_assignment_from_iterable(
if self.type_is_iterable(rvalue_type) and isinstance(
rvalue_type, (Instance, CallableType, TypeType, Overloaded)
):
item_type = self.iterable_item_type(rvalue_type)
item_type = self.iterable_item_type(rvalue_type, context)
for lv in lvalues:
if isinstance(lv, StarExpr):
items_type = self.named_generic_type("builtins.list", [item_type])
Expand Down Expand Up @@ -6392,7 +6392,9 @@ def note(
return
self.msg.note(msg, context, offset=offset, code=code)

def iterable_item_type(self, it: Instance | CallableType | TypeType | Overloaded) -> Type:
def iterable_item_type(
self, it: Instance | CallableType | TypeType | Overloaded, context: Context
) -> Type:
if isinstance(it, Instance):
iterable = map_instance_to_supertype(it, self.lookup_typeinfo("typing.Iterable"))
item_type = iterable.args[0]
Expand All @@ -6401,7 +6403,7 @@ def iterable_item_type(self, it: Instance | CallableType | TypeType | Overloaded
# in case there is no explicit base class.
return item_type
# Try also structural typing.
return self.analyze_iterable_item_type_without_expression(it, it)[1]
return self.analyze_iterable_item_type_without_expression(it, context)[1]

def function_type(self, func: FuncBase) -> FunctionLike:
return function_type(func, self.named_type("builtins.function"))
Expand Down
10 changes: 5 additions & 5 deletions mypy/checkpattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from mypy.maptype import map_instance_to_supertype
from mypy.meet import narrow_declared_type
from mypy.messages import MessageBuilder
from mypy.nodes import ARG_POS, Expression, NameExpr, TypeAlias, TypeInfo, Var
from mypy.nodes import ARG_POS, Context, Expression, NameExpr, TypeAlias, TypeInfo, Var
from mypy.patterns import (
AsPattern,
ClassPattern,
Expand Down Expand Up @@ -242,7 +242,7 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType:
elif size_diff > 0 and star_position is None:
return self.early_non_match()
else:
inner_type = self.get_sequence_type(current_type)
inner_type = self.get_sequence_type(current_type, o)
if inner_type is None:
inner_type = self.chk.named_type("builtins.object")
inner_types = [inner_type] * len(o.patterns)
Expand Down Expand Up @@ -309,12 +309,12 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType:
new_type = current_type
return PatternType(new_type, rest_type, captures)

def get_sequence_type(self, t: Type) -> Type | None:
def get_sequence_type(self, t: Type, context: Context) -> Type | None:
t = get_proper_type(t)
if isinstance(t, AnyType):
return AnyType(TypeOfAny.from_another_any, t)
if isinstance(t, UnionType):
items = [self.get_sequence_type(item) for item in t.items]
items = [self.get_sequence_type(item, context) for item in t.items]
not_none_items = [item for item in items if item is not None]
if len(not_none_items) > 0:
return make_simplified_union(not_none_items)
Expand All @@ -324,7 +324,7 @@ def get_sequence_type(self, t: Type) -> Type | None:
if self.chk.type_is_iterable(t) and isinstance(t, (Instance, TupleType)):
if isinstance(t, TupleType):
t = tuple_fallback(t)
return self.chk.iterable_item_type(t)
return self.chk.iterable_item_type(t, context)
else:
return None

Expand Down
16 changes: 16 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -7797,3 +7797,19 @@ class Element(Generic[_T]):
class Bar(Foo): ...
e: Element[Bar]
reveal_type(e.elements) # N: Revealed type is "typing.Sequence[__main__.Element[__main__.Bar]]"

[case testIterableUnpackingWithGetAttr]
from typing import Union, Tuple

class C:
def __getattr__(self, name):
pass

class D:
def f(self) -> C:
return C()

def g(self) -> None:
# TODO: This is a false positive
a, b = self.f() # E: "C" has no attribute "__iter__" (not iterable)
[builtins fixtures/tuple.pyi]

0 comments on commit 895a821

Please sign in to comment.