From bc6ed0124b241734ad5e41cf0d8784f9fd38274a Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 24 Apr 2018 15:46:36 +0100 Subject: [PATCH 1/9] Find-grained: __await__ support --- mypy/server/deps.py | 7 +++-- test-data/unit/fine-grained.test | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/mypy/server/deps.py b/mypy/server/deps.py index 9befbda047246..5e3c6da1907ba 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -92,7 +92,7 @@ class 'mod.Cls'. This can also refer to an attribute inherited from a ComparisonExpr, GeneratorExpr, DictionaryComprehension, StarExpr, PrintStmt, ForStmt, WithStmt, TupleExpr, ListExpr, OperatorAssignmentStmt, DelStmt, YieldFromExpr, Decorator, Block, TypeInfo, FuncBase, OverloadedFuncDef, RefExpr, SuperExpr, Var, NamedTupleExpr, TypedDictExpr, - LDEF, MDEF, GDEF, FuncItem, TypeAliasExpr, NewTypeExpr, ImportAll, EnumCallExpr, + LDEF, MDEF, GDEF, FuncItem, TypeAliasExpr, NewTypeExpr, ImportAll, EnumCallExpr, AwaitExpr, op_methods, reverse_op_methods, ops_with_inplace_method, unary_op_methods ) from mypy.traverser import TraverserVisitor @@ -166,7 +166,6 @@ def __init__(self, self.is_package_init_file = False # TODO (incomplete): - # await # protocols def visit_mypy_file(self, o: MypyFile) -> None: @@ -567,6 +566,10 @@ def visit_yield_from_expr(self, e: YieldFromExpr) -> None: super().visit_yield_from_expr(e) self.add_iter_dependency(e.expr) + def visit_await_expr(self, e: AwaitExpr) -> None: + super().visit_await_expr(e) + self.add_attribute_dependency_for_expr(e.expr, '__await__') + # Helpers def add_type_alias_deps(self, target: str) -> None: diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index f05ab4be56d2c..5d8de55c2d30c 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -5800,3 +5800,49 @@ class M(type): [out] == a.py:2: error: Argument 1 to "f" of "M" has incompatible type "int"; expected "str" + +[case testAwaitAndAsyncDef-skip-cache] +from a import g + +async def f() -> int: + return await g() + +[file a.py] +async def g() -> int: + return 0 + +[file a.py.2] +async def g() -> str: + return '' + +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-full.pyi] +[out] +== +main:4: error: Incompatible return value type (got "str", expected "int") + +[case testAwaitAnd__await__-skip-cache] +from a import C + +async def f(c: C) -> int: + return await c + +[file a.py] +from typing import Any, Generator +class C: + def __await__(self) -> Generator[Any, None, int]: + yield + return 0 + +[file a.py.2] +from typing import Any, Generator +class C: + def __await__(self) -> Generator[Any, None, str]: + yield + return '' + +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-full.pyi] +[out] +== +main:4: error: Incompatible return value type (got "str", expected "int") From 3068fecdbe244203a5895b439bd8426c9e2d86de Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 24 Apr 2018 16:24:15 +0100 Subject: [PATCH 2/9] Fine-grained: Support async for --- mypy/checker.py | 27 ++++++++++++----------- mypy/checkexpr.py | 6 +++--- mypy/nodes.py | 2 ++ mypy/server/deps.py | 20 +++++++++++++---- test-data/unit/fine-grained.test | 37 ++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 20 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index f022fec04a922..3bc05626b8fe0 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2518,15 +2518,16 @@ def get_types_from_except_handler(self, typ: Type, n: Expression) -> List[Type]: def visit_for_stmt(self, s: ForStmt) -> None: """Type check a for statement.""" if s.is_async: - item_type = self.analyze_async_iterable_item_type(s.expr) + iterator_type, item_type = self.analyze_async_iterable_item_type(s.expr) else: - item_type = self.analyze_iterable_item_type(s.expr) + iterator_type, item_type = self.analyze_iterable_item_type(s.expr) s.inferred_item_type = item_type + s.inferred_iterator_type = iterator_type self.analyze_index_variables(s.index, item_type, s.index_type is None, s) self.accept_loop(s.body, s.else_body) - def analyze_async_iterable_item_type(self, expr: Expression) -> Type: - """Analyse async iterable expression and return iterator item type.""" + def analyze_async_iterable_item_type(self, expr: Expression) -> Tuple[Type, Type]: + """Analyse async iterable expression and return iterator and iterator item types.""" echk = self.expr_checker iterable = echk.accept(expr) @@ -2539,19 +2540,22 @@ def analyze_async_iterable_item_type(self, expr: Expression) -> Type: iterator = echk.check_call(method, [], [], expr)[0] method = echk.analyze_external_member_access('__anext__', iterator, expr) awaitable = echk.check_call(method, [], [], expr)[0] - return echk.check_awaitable_expr(awaitable, expr, - messages.INCOMPATIBLE_TYPES_IN_ASYNC_FOR) + item_type = echk.check_awaitable_expr(awaitable, expr, + messages.INCOMPATIBLE_TYPES_IN_ASYNC_FOR) + return iterator, item_type - def analyze_iterable_item_type(self, expr: Expression) -> Type: - """Analyse iterable expression and return iterator item type.""" + def analyze_iterable_item_type(self, expr: Expression) -> Tuple[Type, Type]: + """Analyse iterable expression and return iterator and iterator item types.""" echk = self.expr_checker iterable = echk.accept(expr) + method = echk.analyze_external_member_access('__iter__', iterable, expr) + iterator = echk.check_call(method, [], [], expr)[0] if isinstance(iterable, TupleType): joined = UninhabitedType() # type: Type for item in iterable.items: joined = join_types(joined, item) - return joined + return iterator, joined else: # Non-tuple iterable. self.check_subtype(iterable, @@ -2559,16 +2563,13 @@ def analyze_iterable_item_type(self, expr: Expression) -> Type: [AnyType(TypeOfAny.special_form)]), expr, messages.ITERABLE_EXPECTED) - method = echk.analyze_external_member_access('__iter__', iterable, - expr) - iterator = echk.check_call(method, [], [], expr)[0] if self.options.python_version[0] >= 3: nextmethod = '__next__' else: nextmethod = 'next' method = echk.analyze_external_member_access(nextmethod, iterator, expr) - return echk.check_call(method, [], [], expr)[0] + return iterator, echk.check_call(method, [], [], expr)[0] def analyze_index_variables(self, index: Expression, item_type: Type, infer_lvalue_type: bool, context: Context) -> None: diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 1066fcc758aec..1b80cf31541ff 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1422,7 +1422,7 @@ def visit_comparison_expr(self, e: ComparisonExpr) -> Type: elif (local_errors.is_errors() and # is_valid_var_arg is True for any Iterable self.is_valid_var_arg(right_type)): - itertype = self.chk.analyze_iterable_item_type(right) + _, itertype = self.chk.analyze_iterable_item_type(right) method_type = CallableType( [left_type], [nodes.ARG_POS], @@ -2290,9 +2290,9 @@ def check_for_comp(self, e: Union[GeneratorExpr, DictionaryComprehension]) -> No for index, sequence, conditions, is_async in zip(e.indices, e.sequences, e.condlists, e.is_async): if is_async: - sequence_type = self.chk.analyze_async_iterable_item_type(sequence) + _, sequence_type = self.chk.analyze_async_iterable_item_type(sequence) else: - sequence_type = self.chk.analyze_iterable_item_type(sequence) + _, sequence_type = self.chk.analyze_iterable_item_type(sequence) self.chk.analyze_index_variables(index, sequence_type, True, e) for condition in conditions: self.accept(condition) diff --git a/mypy/nodes.py b/mypy/nodes.py index b9c5af469e152..ab9103c74a315 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -887,6 +887,8 @@ class ForStmt(Statement): index_type = None # type: Optional[mypy.types.Type] # Inferred iterable item type inferred_item_type = None # type: Optional[mypy.types.Type] + # Inferred iterator type + inferred_iterator_type = None # type: Optional[mypy.types.Type] # Expression to iterate expr = None # type: Expression body = None # type: Block diff --git a/mypy/server/deps.py b/mypy/server/deps.py index 5e3c6da1907ba..56dfd5471214b 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -397,10 +397,22 @@ def visit_operator_assignment_stmt(self, o: OperatorAssignmentStmt) -> None: def visit_for_stmt(self, o: ForStmt) -> None: super().visit_for_stmt(o) - # __getitem__ is only used if __iter__ is missing but for simplicity we - # just always depend on both. - self.add_attribute_dependency_for_expr(o.expr, '__iter__') - self.add_attribute_dependency_for_expr(o.expr, '__getitem__') + if not o.is_async: + # __getitem__ is only used if __iter__ is missing but for simplicity we + # just always depend on both. + self.add_attribute_dependency_for_expr(o.expr, '__iter__') + self.add_attribute_dependency_for_expr(o.expr, '__getitem__') + if o.inferred_iterator_type: + if self.python2: + method = 'next' + else: + method = '__next__' + self.add_attribute_dependency(o.inferred_iterator_type, method) + else: + self.add_attribute_dependency_for_expr(o.expr, '__aiter__') + if o.inferred_iterator_type: + self.add_attribute_dependency(o.inferred_iterator_type, '__anext__') + self.process_lvalue(o.index) if isinstance(o.index, TupleExpr): # Process multiple assignment to index variables. diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 5d8de55c2d30c..be2d0d1e7f6f3 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -5846,3 +5846,40 @@ class C: [out] == main:4: error: Incompatible return value type (got "str", expected "int") + +[case test__aiter__and__anext__] +from a import C + +async def f() -> int: + async for x in C(): + pass + return x + +[file a.py] +class C: + def __aiter__(self) -> D: pass +class D: + def __aiter__(self) -> D: pass + async def __anext__(self) -> int: return 0 + +[file a.py.2] +class C: + def __aiter__(self) -> D: pass +class D: + def __aiter__(self) -> D: pass + async def __anext__(self) -> str: return '' + +[file a.py.3] +class C: + def __aiter__(self) -> E: pass +class E: + def __aiter__(self) -> E: pass + async def __anext__(self) -> object: return 0 + +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-full.pyi] +[out] +== +main:6: error: Incompatible return value type (got "str", expected "int") +== +main:6: error: Incompatible return value type (got "object", expected "int") From 468b4ee927228f9cf1c951d604648c3cadb03a5b Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 24 Apr 2018 17:42:21 +0100 Subject: [PATCH 3/9] Fine-grained: Support async with --- mypy/server/deps.py | 8 +++++-- test-data/unit/fine-grained.test | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/mypy/server/deps.py b/mypy/server/deps.py index 56dfd5471214b..8f325b3bd383c 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -427,8 +427,12 @@ def visit_for_stmt(self, o: ForStmt) -> None: def visit_with_stmt(self, o: WithStmt) -> None: super().visit_with_stmt(o) for e in o.expr: - self.add_attribute_dependency_for_expr(e, '__enter__') - self.add_attribute_dependency_for_expr(e, '__exit__') + if not o.is_async: + self.add_attribute_dependency_for_expr(e, '__enter__') + self.add_attribute_dependency_for_expr(e, '__exit__') + else: + self.add_attribute_dependency_for_expr(e, '__aenter__') + self.add_attribute_dependency_for_expr(e, '__aexit__') if o.target_type: self.add_type_dependencies(o.target_type) diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index be2d0d1e7f6f3..be1b549550877 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -5883,3 +5883,40 @@ class E: main:6: error: Incompatible return value type (got "str", expected "int") == main:6: error: Incompatible return value type (got "object", expected "int") + +[case testAsyncWith2-skip-cache] +from a import C + +async def f() -> int: + async with C() as x: + return x + +[file a.py] +class C: + async def __aenter__(self) -> int: pass + async def __aexit__(self, x, y, z) -> None: pass + +[file a.py.2] +class C: + async def __aenter__(self) -> str: pass + async def __aexit__(self, x, y, z) -> None: pass + +[file a.py.3] +from typing import Awaitable +class C: + async def __aenter__(self) -> int: pass + async def __aexit__(self, x, y, z) -> None: pass + +[file a.py.4] +from typing import Awaitable +class C: + async def __aenter__(self) -> int: pass + +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-full.pyi] +[out] +== +main:5: error: Incompatible return value type (got "str", expected "int") +== +== +main:4: error: "C" has no attribute "__aexit__" From cc687a717a2e4ba006d47f747b3cc018a38d0a5b Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 24 Apr 2018 19:01:59 +0100 Subject: [PATCH 4/9] Tweak error messages --- mypy/checker.py | 11 ----------- mypy/messages.py | 20 ++++++++++++-------- test-data/unit/check-async-await.test | 18 ++++++------------ test-data/unit/check-incomplete-fixture.test | 3 +-- test-data/unit/check-inference.test | 4 ++-- test-data/unit/check-unions.test | 6 ++---- 6 files changed, 23 insertions(+), 39 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 3bc05626b8fe0..24bbcb933ce6d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2530,12 +2530,6 @@ def analyze_async_iterable_item_type(self, expr: Expression) -> Tuple[Type, Type """Analyse async iterable expression and return iterator and iterator item types.""" echk = self.expr_checker iterable = echk.accept(expr) - - self.check_subtype(iterable, - self.named_generic_type('typing.AsyncIterable', - [AnyType(TypeOfAny.special_form)]), - expr, messages.ASYNC_ITERABLE_EXPECTED) - method = echk.analyze_external_member_access('__aiter__', iterable, expr) iterator = echk.check_call(method, [], [], expr)[0] method = echk.analyze_external_member_access('__anext__', iterator, expr) @@ -2558,11 +2552,6 @@ def analyze_iterable_item_type(self, expr: Expression) -> Tuple[Type, Type]: return iterator, joined else: # Non-tuple iterable. - self.check_subtype(iterable, - self.named_generic_type('typing.Iterable', - [AnyType(TypeOfAny.special_form)]), - expr, messages.ITERABLE_EXPECTED) - if self.options.python_version[0] >= 3: nextmethod = '__next__' else: diff --git a/mypy/messages.py b/mypy/messages.py index c58b2d84bdb28..aa2be93a9d5f3 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -63,8 +63,6 @@ MUST_HAVE_NONE_RETURN_TYPE = 'The return type of "{}" must be None' INVALID_TUPLE_INDEX_TYPE = 'Invalid tuple index type' TUPLE_INDEX_OUT_OF_RANGE = 'Tuple index out of range' -ITERABLE_EXPECTED = 'Iterable expected' -ASYNC_ITERABLE_EXPECTED = 'AsyncIterable expected' INVALID_SLICE_INDEX = 'Slice index must be an integer or None' CANNOT_INFER_LAMBDA_TYPE = 'Cannot infer type of lambda' CANNOT_INFER_ITEM_TYPE = 'Cannot infer iterable item type' @@ -450,6 +448,11 @@ def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Cont self.fail('{} not callable'.format(self.format(original_type)), context) else: # The non-special case: a missing ordinary attribute. + extra = '' + if member == '__iter__': + extra = ' (not iterable)' + elif member == '__aiter__': + extra = ' (not async iterable)' if not self.disable_type_names: failed = False if isinstance(original_type, Instance) and original_type.type.names: @@ -457,12 +460,13 @@ def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Cont matches = [m for m in COMMON_MISTAKES.get(member, []) if m in alternatives] matches.extend(best_matches(member, alternatives)[:3]) if matches: - self.fail('{} has no attribute "{}"; maybe {}?'.format( - self.format(original_type), member, pretty_or(matches)), context) + self.fail('{} has no attribute "{}"; maybe {}?{}'.format( + self.format(original_type), member, pretty_or(matches), extra), + context) failed = True if not failed: - self.fail('{} has no attribute "{}"'.format(self.format(original_type), - member), context) + self.fail('{} has no attribute "{}"{}'.format(self.format(original_type), + member, extra), context) elif isinstance(original_type, UnionType): # The checker passes "object" in lieu of "None" for attribute # checks, so we manually convert it back. @@ -470,8 +474,8 @@ def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Cont if typ_format == '"object"' and \ any(type(item) == NoneTyp for item in original_type.items): typ_format = '"None"' - self.fail('Item {} of {} has no attribute "{}"'.format( - typ_format, self.format(original_type), member), context) + self.fail('Item {} of {} has no attribute "{}"{}'.format( + typ_format, self.format(original_type), member, extra), context) return AnyType(TypeOfAny.from_error) def unsupported_operand_types(self, op: str, left_type: Any, diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index e243bd50f4152..2f4e86fcae915 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -163,7 +163,6 @@ async def f() -> None: [builtins fixtures/async_await.pyi] [typing fixtures/typing-full.pyi] [out] -main:4: error: AsyncIterable expected main:4: error: "List[int]" has no attribute "__aiter__" [case testAsyncForTypeComments] @@ -247,14 +246,10 @@ async def wrong_iterable(obj: Iterable[int]): {i: i for i in asyncify(obj)} [out] -main:18: error: AsyncIterable expected -main:18: error: "Iterable[int]" has no attribute "__aiter__"; maybe "__iter__"? -main:19: error: Iterable expected -main:19: error: "asyncify[int]" has no attribute "__iter__"; maybe "__aiter__"? -main:20: error: AsyncIterable expected -main:20: error: "Iterable[int]" has no attribute "__aiter__"; maybe "__iter__"? -main:21: error: Iterable expected -main:21: error: "asyncify[int]" has no attribute "__iter__"; maybe "__aiter__"? +main:18: error: "Iterable[int]" has no attribute "__aiter__"; maybe "__iter__"? (not async iterable) +main:19: error: "asyncify[int]" has no attribute "__iter__"; maybe "__aiter__"? (not iterable) +main:20: error: "Iterable[int]" has no attribute "__aiter__"; maybe "__iter__"? (not async iterable) +main:21: error: "asyncify[int]" has no attribute "__iter__"; maybe "__aiter__"? (not iterable) [builtins fixtures/async_await.pyi] [typing fixtures/typing-full.pyi] @@ -538,7 +533,7 @@ async def h() -> None: from typing import AsyncGenerator async def gen() -> AsyncGenerator[int, None]: - for i in (1, 2, 3): + for i in [1, 2, 3]: yield i def h() -> None: @@ -549,8 +544,7 @@ def h() -> None: [typing fixtures/typing-full.pyi] [out] -main:9: error: Iterable expected -main:9: error: "AsyncGenerator[int, None]" has no attribute "__iter__"; maybe "__aiter__"? +main:9: error: "AsyncGenerator[int, None]" has no attribute "__iter__"; maybe "__aiter__"? (not iterable) [case testAsyncGeneratorNoYieldFrom] # flags: --fast-parser --python-version 3.6 diff --git a/test-data/unit/check-incomplete-fixture.test b/test-data/unit/check-incomplete-fixture.test index 68c7c6c9aa0fb..5796ba28377a8 100644 --- a/test-data/unit/check-incomplete-fixture.test +++ b/test-data/unit/check-incomplete-fixture.test @@ -76,8 +76,7 @@ for y in x: -- avoid things getting worse. main:2: error: "tuple" expects no type arguments, but 1 given main:3: error: Value of type "tuple" is not indexable -main:4: error: Iterable expected -main:4: error: "tuple" has no attribute "__iter__" +main:4: error: "tuple" has no attribute "__iter__" (not iterable) [case testClassmethodMissingFromStubs] class A: diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 1971fa40bcf81..f6ccf0cd579be 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -1563,7 +1563,7 @@ class A: pass [builtins fixtures/for.pyi] [out] -main:5: error: "None" has no attribute "__iter__" +main:5: error: "None" has no attribute "__iter__" (not iterable) [case testPartialTypeErrorSpecialCase2] # This used to crash. @@ -1584,7 +1584,7 @@ class A: pass [builtins fixtures/for.pyi] [out] -main:4: error: "None" has no attribute "__iter__" +main:4: error: "None" has no attribute "__iter__" (not iterable) -- Multipass diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index 0606ff9d4f950..681a667373c84 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -869,8 +869,7 @@ a: Any d: Dict[str, Tuple[List[Tuple[str, str]], str]] x, _ = d.get(a, (None, None)) -for y in x: pass # E: Iterable expected \ - # E: Item "None" of "Optional[List[Tuple[str, str]]]" has no attribute "__iter__" +for y in x: pass # E: Item "None" of "Optional[List[Tuple[str, str]]]" has no attribute "__iter__" (not iterable) if x: for s, t in x: reveal_type(s) # E: Revealed type is 'builtins.str' @@ -886,8 +885,7 @@ x = None d: Dict[str, Tuple[List[Tuple[str, str]], str]] x, _ = d.get(a, (None, None)) -for y in x: pass # E: Iterable expected \ - # E: Item "None" of "Optional[List[Tuple[str, str]]]" has no attribute "__iter__" +for y in x: pass # E: Item "None" of "Optional[List[Tuple[str, str]]]" has no attribute "__iter__" (not iterable) if x: for s, t in x: reveal_type(s) # E: Revealed type is 'builtins.str' From b56545598d82fe943cf172c5ac00f25a9740ee65 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 25 Apr 2018 12:59:39 +0100 Subject: [PATCH 5/9] Add non-async iterator test cases --- test-data/unit/deps.test | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test index 8efa3b9477f54..33ee51f699c24 100644 --- a/test-data/unit/deps.test +++ b/test-data/unit/deps.test @@ -1024,3 +1024,46 @@ class B(Base): -> , m.f -> , m.f -> m + +[case testCustomIterator] +class A: + def __iter__(self) -> B: pass +class B: + def __iter__(self) -> B: pass + def __next__(self) -> C: pass +class C: + pass +def f() -> None: + for x in A(): pass +[out] + -> m.f + -> m.f + -> m.f + -> m.f + -> m.A, m.f + -> m.f + -> , , m.A.__iter__, m.B, m.B.__iter__ + -> , m.B.__next__, m.C + +[case testCustomIterator_python2] +class A: + def __iter__(self): # type: () -> B + pass +class B: + def __iter__(self): # type: () -> B + pass + def next(self): # type: () -> C + pass +class C: + pass +def f(): # type: () -> None + for x in A(): pass +[out] + -> m.f + -> m.f + -> m.f + -> m.f + -> m.A, m.f + -> m.f + -> , , m.A.__iter__, m.B, m.B.__iter__ + -> , m.B.next, m.C From 86b1590d902b5b4dbd363bbe382080c15094096d Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 25 Apr 2018 13:05:18 +0100 Subject: [PATCH 6/9] Fix test case --- test-data/unit/check-async-await.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index 2f4e86fcae915..5e404f8f2f462 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -163,7 +163,7 @@ async def f() -> None: [builtins fixtures/async_await.pyi] [typing fixtures/typing-full.pyi] [out] -main:4: error: "List[int]" has no attribute "__aiter__" +main:4: error: "List[int]" has no attribute "__aiter__" (not async iterable) [case testAsyncForTypeComments] From 5dc83cae7ee4bcc5778d2aaff9a5936eafeac92f Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 25 Apr 2018 17:04:10 +0100 Subject: [PATCH 7/9] Minor error message tweak --- mypy/messages.py | 2 ++ test-data/unit/check-async-await.test | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index aa2be93a9d5f3..bc750fc0955c4 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -459,6 +459,8 @@ def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Cont alternatives = set(original_type.type.names.keys()) matches = [m for m in COMMON_MISTAKES.get(member, []) if m in alternatives] matches.extend(best_matches(member, alternatives)[:3]) + if member == '__aiter__' and matches == ['__iter__']: + matches = [] # Avoid misleading suggestion if matches: self.fail('{} has no attribute "{}"; maybe {}?{}'.format( self.format(original_type), member, pretty_or(matches), extra), diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index 5e404f8f2f462..02338b01f54bc 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -246,9 +246,9 @@ async def wrong_iterable(obj: Iterable[int]): {i: i for i in asyncify(obj)} [out] -main:18: error: "Iterable[int]" has no attribute "__aiter__"; maybe "__iter__"? (not async iterable) +main:18: error: "Iterable[int]" has no attribute "__aiter__" (not async iterable) main:19: error: "asyncify[int]" has no attribute "__iter__"; maybe "__aiter__"? (not iterable) -main:20: error: "Iterable[int]" has no attribute "__aiter__"; maybe "__iter__"? (not async iterable) +main:20: error: "Iterable[int]" has no attribute "__aiter__" (not async iterable) main:21: error: "asyncify[int]" has no attribute "__iter__"; maybe "__aiter__"? (not iterable) [builtins fixtures/async_await.pyi] [typing fixtures/typing-full.pyi] From e1fd64898f6e9ca4a9422987ab24a59bc06c3739 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 25 Apr 2018 17:06:52 +0100 Subject: [PATCH 8/9] Update test case --- test-data/unit/fine-grained.test | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index be1b549550877..7cd671270228f 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -5891,6 +5891,9 @@ async def f() -> int: async with C() as x: return x +async def g() -> None: + async with C(): pass + [file a.py] class C: async def __aenter__(self) -> int: pass @@ -5920,3 +5923,4 @@ main:5: error: Incompatible return value type (got "str", expected "int") == == main:4: error: "C" has no attribute "__aexit__" +main:8: error: "C" has no attribute "__aexit__" From f7d9e9f60b69445f28dae99c76fb936aa13abb87 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 25 Apr 2018 17:15:28 +0100 Subject: [PATCH 9/9] Strip additional attributes --- mypy/server/aststrip.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mypy/server/aststrip.py b/mypy/server/aststrip.py index f0ad85df0487f..86c02c8d7cba5 100644 --- a/mypy/server/aststrip.py +++ b/mypy/server/aststrip.py @@ -44,7 +44,7 @@ Node, FuncDef, NameExpr, MemberExpr, RefExpr, MypyFile, FuncItem, ClassDef, AssignmentStmt, ImportFrom, Import, TypeInfo, SymbolTable, Var, CallExpr, Decorator, OverloadedFuncDef, SuperExpr, UNBOUND_IMPORTED, GDEF, MDEF, IndexExpr, SymbolTableNode, ImportAll, TupleExpr, - ListExpr + ListExpr, ForStmt ) from mypy.semanal_shared import create_indirect_imported_name from mypy.traverser import TraverserVisitor @@ -217,6 +217,11 @@ def visit_import(self, node: Import) -> None: symnode.kind = UNBOUND_IMPORTED symnode.node = None + def visit_for_stmt(self, node: ForStmt) -> None: + node.index_type = None + node.inferred_item_type = None + super().visit_for_stmt(node) + def visit_import_all(self, node: ImportAll) -> None: # If the node is unreachable, we don't want to reset entries from a reachable import. if node.is_unreachable: