Skip to content

[ty] Add a diagnostic for an unused awaitable#23650

Merged
charliermarsh merged 1 commit intomainfrom
charlie/unused-await
Mar 4, 2026
Merged

[ty] Add a diagnostic for an unused awaitable#23650
charliermarsh merged 1 commit intomainfrom
charlie/unused-await

Conversation

@charliermarsh
Copy link
Member

@charliermarsh charliermarsh commented Mar 1, 2026

Summary

This is a warning by default.

Closes astral-sh/ty#2791.

@charliermarsh charliermarsh added the ty Multi-file analysis & type inference label Mar 1, 2026
@astral-sh-bot
Copy link

astral-sh-bot bot commented Mar 1, 2026

Typing conformance results

No changes detected ✅

@astral-sh-bot
Copy link

astral-sh-bot bot commented Mar 1, 2026

mypy_primer results

Changes were detected when running on open source projects
aioredis (https://github.com/aio-libs/aioredis)
+ aioredis/client.py:1697:13: warning[unused-awaitable] Object of type `CoroutineType[Any, Any, Unknown]` is not awaited
- Found 30 diagnostics
+ Found 31 diagnostics

pydantic (https://github.com/pydantic/pydantic)
- pydantic/_internal/_core_metadata.py:87:54: error[invalid-assignment] Invalid assignment to key "pydantic_js_extra" with declared type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | ((dict[str, int | float | str | ... omitted 3 union elements], type[Any], /) -> None)` on TypedDict `CoreMetadata`: value of type `dict[object, object]`
+ pydantic/_internal/_core_metadata.py:87:54: error[invalid-assignment] Invalid assignment to key "pydantic_js_extra" with declared type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | ((dict[str, Divergent], type[Any], /) -> None)` on TypedDict `CoreMetadata`: value of type `dict[object, object]`
- pydantic/fields.py:949:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
+ pydantic/fields.py:949:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
- pydantic/fields.py:989:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
+ pydantic/fields.py:989:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
- pydantic/fields.py:1032:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
+ pydantic/fields.py:1032:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
- pydantic/fields.py:1072:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
+ pydantic/fields.py:1072:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
- pydantic/fields.py:1115:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
+ pydantic/fields.py:1115:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
- pydantic/fields.py:1154:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
+ pydantic/fields.py:1154:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
- pydantic/fields.py:1194:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
+ pydantic/fields.py:1194:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
- pydantic/fields.py:1573:13: error[invalid-argument-type] Argument is incorrect: Expected `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`, found `Top[dict[Unknown, Unknown]] | (((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) & ~Top[dict[Unknown, Unknown]]) | None`
+ pydantic/fields.py:1573:13: error[invalid-argument-type] Argument is incorrect: Expected `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`, found `Top[dict[Unknown, Unknown]] | (((dict[str, Divergent], /) -> None) & ~Top[dict[Unknown, Unknown]]) | None`

dragonchain (https://github.com/dragonchain/dragonchain)
+ dragonchain/broadcast_processor/broadcast_processor.py:190:17: warning[unused-awaitable] Object of type `CoroutineType[Any, Any, int]` is not awaited
- Found 428 diagnostics
+ Found 429 diagnostics

psycopg (https://github.com/psycopg/psycopg)
+ psycopg_pool/psycopg_pool/pool_async.py:140:13: warning[unused-awaitable] Object of type `CoroutineType[Any, Any, None]` is not awaited
- Found 659 diagnostics
+ Found 660 diagnostics

trio (https://github.com/python-trio/trio)
- src/trio/_tests/test_threads.py:645:45: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- src/trio/_tests/test_threads.py:705:41: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- Found 471 diagnostics
+ Found 469 diagnostics

dd-trace-py (https://github.com/DataDog/dd-trace-py)
+ ddtrace/contrib/internal/grpc/aio_client_interceptor.py:192:13: warning[unused-awaitable] Object of type `CoroutineType[Any, Any, None]` is not awaited
- Found 9156 diagnostics
+ Found 9157 diagnostics

@astral-sh-bot
Copy link

astral-sh-bot bot commented Mar 1, 2026

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot
Copy link

astral-sh-bot bot commented Mar 1, 2026

ecosystem-analyzer results

Lint rule Added Removed Changed
unused-awaitable 15 0 0
unused-type-ignore-comment 0 2 0
Total 15 2 0

Full report with detailed diff (timing results)

@carljm carljm removed their request for review March 2, 2026 17:50
Copy link
Contributor

@oconnor663 oconnor663 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM overall, approving with comments.

# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because the method `__aenter__` may be missing"
async with Manager() as f:
reveal_type(f) # revealed: CoroutineType[Any, Any, str]
reveal_type(f) # revealed: CoroutineType[Any, Any, str] # ty: ignore[unused-awaitable]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we special-case KnownFunction::RevealType in this lint?

Copy link
Contributor

@oconnor663 oconnor663 Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might make sense to do AssertType at the same time, that shows up in the ecosystem report for trio.


async def main():
coro = fetch()
await coro
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, the await coro line here is extra, and coro = fetch() by itself is enough by itself to suppress this warning. Ruff will give an unused variable warning about coro in that case, but Ty alone will not. Maybe remove the second line here and add a TODO if we expect Ty to eventually emit a warning here?

from types import CoroutineType
from typing import Any

def get_coroutine(flag: bool) -> CoroutineType[Any, Any, int] | CoroutineType[Any, Any, str]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason there's a bool arg here and in the next test case?

Comment on lines +1012 to +1015
Type::Intersection(intersection) => intersection
.positive(db)
.iter()
.any(|ty| ty.is_awaitable(db)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably have a test case for this branch. CoroutineType is @final, so it's tricky to write an intersection for it that doesn't simplify, but something like this seems to work for me:

diff --git i/crates/ty_python_semantic/resources/mdtest/diagnostics/unused_awaitable.md w/crates/ty_python_semantic/resources/mdtest/diagnostics/unused_awaitable.md
index 162e730d65..edfe302793 100644
--- i/crates/ty_python_semantic/resources/mdtest/diagnostics/unused_awaitable.md
+++ w/crates/ty_python_semantic/resources/mdtest/diagnostics/unused_awaitable.md
@@ -87,6 +87,25 @@ async def main():
     get_maybe_coroutine(True)
 ```
 
+## Intersection with awaitable
+
+When an intersection type contains an awaitable element, the lint should fire.
+
+```py
+from collections.abc import Coroutine
+from types import CoroutineType
+from ty_extensions import Intersection
+
+class Foo: ...
+class Bar: ...
+
+def get_coroutine() -> Intersection[Coroutine[Foo, Foo, Foo], CoroutineType[Bar, Bar, Bar]]:
+    raise NotImplementedError
+
+async def main():
+    get_coroutine()  # error: [unused-awaitable]
+```
+
 ## Non-awaitable expression statement
 
 Regular non-awaitable expression statements should not trigger this lint.

(Formatting diffs with ``` in them is hard...)


/// Returns `true` if this type is an awaitable that should be awaited before being discarded.
///
/// Currently checks for instances of `types.CoroutineType` (returned by `async def` calls).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, did you consider checking for "assignability to Awaitable"? My instinct is that that's too expensive to evaluate on every single expression statement, but I'm not sure.

@oconnor663 oconnor663 removed their assignment Mar 4, 2026
@charliermarsh charliermarsh force-pushed the charlie/unused-await branch from 31011da to 184e2cc Compare March 4, 2026 02:55
@charliermarsh charliermarsh merged commit a546a11 into main Mar 4, 2026
81 of 83 checks passed
@charliermarsh charliermarsh deleted the charlie/unused-await branch March 4, 2026 03:14
@MichaReiser
Copy link
Member

This was probably one of the first "lintish" rule that we added to ty that isn't strictly necessary to be implemented in type inference because it doesn't change the inferred type. If we end up adding more such rules, it probably makes sense to consider introducing a dedicated linter pass for checks that aren't part of type inference (and may also get access to broader settings).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ecosystem-analyzer ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add unused-awaitable diagnostic (detect unawaited coroutines)

3 participants