Skip to content

Commit 895a821

Browse files
authored
Fix line number if __iter__ is incorrectly reported as missing (#14893)
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.
1 parent 4365dad commit 895a821

File tree

3 files changed

+28
-10
lines changed

3 files changed

+28
-10
lines changed

mypy/checker.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3299,14 +3299,14 @@ def check_assignment_to_multiple_lvalues(
32993299
rvalues.extend([TempNode(typ) for typ in typs.items])
33003300
elif self.type_is_iterable(typs) and isinstance(typs, Instance):
33013301
if iterable_type is not None and iterable_type != self.iterable_item_type(
3302-
typs
3302+
typs, rvalue
33033303
):
33043304
self.fail(message_registry.CONTIGUOUS_ITERABLE_EXPECTED, context)
33053305
else:
33063306
if last_idx is None or last_idx + 1 == idx_rval:
33073307
rvalues.append(rval)
33083308
last_idx = idx_rval
3309-
iterable_type = self.iterable_item_type(typs)
3309+
iterable_type = self.iterable_item_type(typs, rvalue)
33103310
else:
33113311
self.fail(message_registry.CONTIGUOUS_ITERABLE_EXPECTED, context)
33123312
else:
@@ -3635,7 +3635,7 @@ def check_multi_assignment_from_iterable(
36353635
if self.type_is_iterable(rvalue_type) and isinstance(
36363636
rvalue_type, (Instance, CallableType, TypeType, Overloaded)
36373637
):
3638-
item_type = self.iterable_item_type(rvalue_type)
3638+
item_type = self.iterable_item_type(rvalue_type, context)
36393639
for lv in lvalues:
36403640
if isinstance(lv, StarExpr):
36413641
items_type = self.named_generic_type("builtins.list", [item_type])
@@ -6392,7 +6392,9 @@ def note(
63926392
return
63936393
self.msg.note(msg, context, offset=offset, code=code)
63946394

6395-
def iterable_item_type(self, it: Instance | CallableType | TypeType | Overloaded) -> Type:
6395+
def iterable_item_type(
6396+
self, it: Instance | CallableType | TypeType | Overloaded, context: Context
6397+
) -> Type:
63966398
if isinstance(it, Instance):
63976399
iterable = map_instance_to_supertype(it, self.lookup_typeinfo("typing.Iterable"))
63986400
item_type = iterable.args[0]
@@ -6401,7 +6403,7 @@ def iterable_item_type(self, it: Instance | CallableType | TypeType | Overloaded
64016403
# in case there is no explicit base class.
64026404
return item_type
64036405
# Try also structural typing.
6404-
return self.analyze_iterable_item_type_without_expression(it, it)[1]
6406+
return self.analyze_iterable_item_type_without_expression(it, context)[1]
64056407

64066408
def function_type(self, func: FuncBase) -> FunctionLike:
64076409
return function_type(func, self.named_type("builtins.function"))

mypy/checkpattern.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from mypy.maptype import map_instance_to_supertype
1616
from mypy.meet import narrow_declared_type
1717
from mypy.messages import MessageBuilder
18-
from mypy.nodes import ARG_POS, Expression, NameExpr, TypeAlias, TypeInfo, Var
18+
from mypy.nodes import ARG_POS, Context, Expression, NameExpr, TypeAlias, TypeInfo, Var
1919
from mypy.patterns import (
2020
AsPattern,
2121
ClassPattern,
@@ -242,7 +242,7 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType:
242242
elif size_diff > 0 and star_position is None:
243243
return self.early_non_match()
244244
else:
245-
inner_type = self.get_sequence_type(current_type)
245+
inner_type = self.get_sequence_type(current_type, o)
246246
if inner_type is None:
247247
inner_type = self.chk.named_type("builtins.object")
248248
inner_types = [inner_type] * len(o.patterns)
@@ -309,12 +309,12 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType:
309309
new_type = current_type
310310
return PatternType(new_type, rest_type, captures)
311311

312-
def get_sequence_type(self, t: Type) -> Type | None:
312+
def get_sequence_type(self, t: Type, context: Context) -> Type | None:
313313
t = get_proper_type(t)
314314
if isinstance(t, AnyType):
315315
return AnyType(TypeOfAny.from_another_any, t)
316316
if isinstance(t, UnionType):
317-
items = [self.get_sequence_type(item) for item in t.items]
317+
items = [self.get_sequence_type(item, context) for item in t.items]
318318
not_none_items = [item for item in items if item is not None]
319319
if len(not_none_items) > 0:
320320
return make_simplified_union(not_none_items)
@@ -324,7 +324,7 @@ def get_sequence_type(self, t: Type) -> Type | None:
324324
if self.chk.type_is_iterable(t) and isinstance(t, (Instance, TupleType)):
325325
if isinstance(t, TupleType):
326326
t = tuple_fallback(t)
327-
return self.chk.iterable_item_type(t)
327+
return self.chk.iterable_item_type(t, context)
328328
else:
329329
return None
330330

test-data/unit/check-classes.test

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7797,3 +7797,19 @@ class Element(Generic[_T]):
77977797
class Bar(Foo): ...
77987798
e: Element[Bar]
77997799
reveal_type(e.elements) # N: Revealed type is "typing.Sequence[__main__.Element[__main__.Bar]]"
7800+
7801+
[case testIterableUnpackingWithGetAttr]
7802+
from typing import Union, Tuple
7803+
7804+
class C:
7805+
def __getattr__(self, name):
7806+
pass
7807+
7808+
class D:
7809+
def f(self) -> C:
7810+
return C()
7811+
7812+
def g(self) -> None:
7813+
# TODO: This is a false positive
7814+
a, b = self.f() # E: "C" has no attribute "__iter__" (not iterable)
7815+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)