Skip to content

Commit acccdd8

Browse files
Fix error code on "Maybe you forgot to use await" note (#16203)
Fixes #16202
1 parent 181cbe8 commit acccdd8

File tree

5 files changed

+54
-13
lines changed

5 files changed

+54
-13
lines changed

mypy/checker.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6237,7 +6237,7 @@ def check_subtype(
62376237
assert call is not None
62386238
if not is_subtype(subtype, call, options=self.options):
62396239
self.msg.note_call(supertype, call, context, code=msg.code)
6240-
self.check_possible_missing_await(subtype, supertype, context)
6240+
self.check_possible_missing_await(subtype, supertype, context, code=msg.code)
62416241
return False
62426242

62436243
def get_precise_awaitable_type(self, typ: Type, local_errors: ErrorWatcher) -> Type | None:
@@ -6271,7 +6271,7 @@ def checking_await_set(self) -> Iterator[None]:
62716271
self.checking_missing_await = False
62726272

62736273
def check_possible_missing_await(
6274-
self, subtype: Type, supertype: Type, context: Context
6274+
self, subtype: Type, supertype: Type, context: Context, code: ErrorCode | None
62756275
) -> None:
62766276
"""Check if the given type becomes a subtype when awaited."""
62776277
if self.checking_missing_await:
@@ -6285,7 +6285,7 @@ def check_possible_missing_await(
62856285
aw_type, supertype, context, msg=message_registry.INCOMPATIBLE_TYPES
62866286
):
62876287
return
6288-
self.msg.possible_missing_await(context)
6288+
self.msg.possible_missing_await(context, code)
62896289

62906290
def contains_none(self, t: Type) -> bool:
62916291
t = get_proper_type(t)

mypy/checkexpr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2563,7 +2563,7 @@ def check_arg(
25632563
original_caller_type, callee_type, context, code=code
25642564
)
25652565
if not self.msg.prefer_simple_messages():
2566-
self.chk.check_possible_missing_await(caller_type, callee_type, context)
2566+
self.chk.check_possible_missing_await(caller_type, callee_type, context, code)
25672567

25682568
def check_overload_call(
25692569
self,

mypy/checkmember.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,11 +272,11 @@ def report_missing_attribute(
272272
mx: MemberContext,
273273
override_info: TypeInfo | None = None,
274274
) -> Type:
275-
res_type = mx.msg.has_no_attr(original_type, typ, name, mx.context, mx.module_symbol_table)
275+
error_code = mx.msg.has_no_attr(original_type, typ, name, mx.context, mx.module_symbol_table)
276276
if not mx.msg.prefer_simple_messages():
277277
if may_be_awaitable_attribute(name, typ, mx, override_info):
278-
mx.msg.possible_missing_await(mx.context)
279-
return res_type
278+
mx.msg.possible_missing_await(mx.context, error_code)
279+
return AnyType(TypeOfAny.from_error)
280280

281281

282282
# The several functions that follow implement analyze_member_access for various

mypy/messages.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ def has_no_attr(
355355
member: str,
356356
context: Context,
357357
module_symbol_table: SymbolTable | None = None,
358-
) -> Type:
358+
) -> ErrorCode | None:
359359
"""Report a missing or non-accessible member.
360360
361361
original_type is the top-level type on which the error occurred.
@@ -370,44 +370,49 @@ def has_no_attr(
370370
directly available on original_type
371371
372372
If member corresponds to an operator, use the corresponding operator
373-
name in the messages. Return type Any.
373+
name in the messages. Return the error code that was produced, if any.
374374
"""
375375
original_type = get_proper_type(original_type)
376376
typ = get_proper_type(typ)
377377

378378
if isinstance(original_type, Instance) and original_type.type.has_readable_member(member):
379379
self.fail(f'Member "{member}" is not assignable', context)
380+
return None
380381
elif member == "__contains__":
381382
self.fail(
382383
f"Unsupported right operand type for in ({format_type(original_type, self.options)})",
383384
context,
384385
code=codes.OPERATOR,
385386
)
387+
return codes.OPERATOR
386388
elif member in op_methods.values():
387389
# Access to a binary operator member (e.g. _add). This case does
388390
# not handle indexing operations.
389391
for op, method in op_methods.items():
390392
if method == member:
391393
self.unsupported_left_operand(op, original_type, context)
392-
break
394+
return codes.OPERATOR
393395
elif member == "__neg__":
394396
self.fail(
395397
f"Unsupported operand type for unary - ({format_type(original_type, self.options)})",
396398
context,
397399
code=codes.OPERATOR,
398400
)
401+
return codes.OPERATOR
399402
elif member == "__pos__":
400403
self.fail(
401404
f"Unsupported operand type for unary + ({format_type(original_type, self.options)})",
402405
context,
403406
code=codes.OPERATOR,
404407
)
408+
return codes.OPERATOR
405409
elif member == "__invert__":
406410
self.fail(
407411
f"Unsupported operand type for ~ ({format_type(original_type, self.options)})",
408412
context,
409413
code=codes.OPERATOR,
410414
)
415+
return codes.OPERATOR
411416
elif member == "__getitem__":
412417
# Indexed get.
413418
# TODO: Fix this consistently in format_type
@@ -418,12 +423,14 @@ def has_no_attr(
418423
),
419424
context,
420425
)
426+
return None
421427
else:
422428
self.fail(
423429
f"Value of type {format_type(original_type, self.options)} is not indexable",
424430
context,
425431
code=codes.INDEX,
426432
)
433+
return codes.INDEX
427434
elif member == "__setitem__":
428435
# Indexed set.
429436
self.fail(
@@ -433,19 +440,22 @@ def has_no_attr(
433440
context,
434441
code=codes.INDEX,
435442
)
443+
return codes.INDEX
436444
elif member == "__call__":
437445
if isinstance(original_type, Instance) and (
438446
original_type.type.fullname == "builtins.function"
439447
):
440448
# "'function' not callable" is a confusing error message.
441449
# Explain that the problem is that the type of the function is not known.
442450
self.fail("Cannot call function of unknown type", context, code=codes.OPERATOR)
451+
return codes.OPERATOR
443452
else:
444453
self.fail(
445454
message_registry.NOT_CALLABLE.format(format_type(original_type, self.options)),
446455
context,
447456
code=codes.OPERATOR,
448457
)
458+
return codes.OPERATOR
449459
else:
450460
# The non-special case: a missing ordinary attribute.
451461
extra = ""
@@ -501,6 +511,7 @@ def has_no_attr(
501511
context,
502512
code=codes.ATTR_DEFINED,
503513
)
514+
return codes.ATTR_DEFINED
504515
elif isinstance(original_type, UnionType):
505516
# The checker passes "object" in lieu of "None" for attribute
506517
# checks, so we manually convert it back.
@@ -518,6 +529,7 @@ def has_no_attr(
518529
context,
519530
code=codes.UNION_ATTR,
520531
)
532+
return codes.UNION_ATTR
521533
elif isinstance(original_type, TypeVarType):
522534
bound = get_proper_type(original_type.upper_bound)
523535
if isinstance(bound, UnionType):
@@ -531,6 +543,7 @@ def has_no_attr(
531543
context,
532544
code=codes.UNION_ATTR,
533545
)
546+
return codes.UNION_ATTR
534547
else:
535548
self.fail(
536549
'{} has no attribute "{}"{}'.format(
@@ -539,7 +552,8 @@ def has_no_attr(
539552
context,
540553
code=codes.ATTR_DEFINED,
541554
)
542-
return AnyType(TypeOfAny.from_error)
555+
return codes.ATTR_DEFINED
556+
return None
543557

544558
def unsupported_operand_types(
545559
self,
@@ -1107,8 +1121,8 @@ def unpacking_strings_disallowed(self, context: Context) -> None:
11071121
def type_not_iterable(self, type: Type, context: Context) -> None:
11081122
self.fail(f"{format_type(type, self.options)} object is not iterable", context)
11091123

1110-
def possible_missing_await(self, context: Context) -> None:
1111-
self.note('Maybe you forgot to use "await"?', context)
1124+
def possible_missing_await(self, context: Context, code: ErrorCode | None) -> None:
1125+
self.note('Maybe you forgot to use "await"?', context, code=code)
11121126

11131127
def incompatible_operator_assignment(self, op: str, context: Context) -> None:
11141128
self.fail(f"Result type of {op} incompatible in assignment", context)

test-data/unit/check-async-await.test

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,33 @@ async def f() -> None:
165165
[out]
166166
main:4: error: "List[int]" has no attribute "__aiter__" (not async iterable)
167167

168+
[case testAsyncForErrorNote]
169+
170+
from typing import AsyncIterator, AsyncGenerator
171+
async def g() -> AsyncGenerator[str, None]:
172+
pass
173+
174+
async def f() -> None:
175+
async for x in g():
176+
pass
177+
[builtins fixtures/async_await.pyi]
178+
[typing fixtures/typing-async.pyi]
179+
[out]
180+
main:7: error: "Coroutine[Any, Any, AsyncGenerator[str, None]]" has no attribute "__aiter__" (not async iterable)
181+
main:7: note: Maybe you forgot to use "await"?
182+
183+
[case testAsyncForErrorCanBeIgnored]
184+
185+
from typing import AsyncIterator, AsyncGenerator
186+
async def g() -> AsyncGenerator[str, None]:
187+
pass
188+
189+
async def f() -> None:
190+
async for x in g(): # type: ignore[attr-defined]
191+
pass
192+
[builtins fixtures/async_await.pyi]
193+
[typing fixtures/typing-async.pyi]
194+
168195
[case testAsyncForTypeComments]
169196

170197
from typing import AsyncIterator, Union

0 commit comments

Comments
 (0)