[ty] Async for loops and async iterables (#19634)

## Summary

Add support for `async for` loops and async iterables.

part of https://github.com/astral-sh/ty/issues/151

## Ecosystem impact

```diff
- boostedblob/listing.py:445:54: warning[unused-ignore-comment] Unused blanket `type: ignore` directive
```

This is correct. We now find a true positive in the `# type: ignore`'d
code.

All of the other ecosystem hits are of the type

```diff
trio (https://github.com/python-trio/trio)
+ src/trio/_core/_tests/test_guest_mode.py:532:24: error[not-iterable] Object of type `MemorySendChannel[int] | MemoryReceiveChannel[int]` may not be iterable
```

The message is correct, because only `MemoryReceiveChannel` has an
`__aiter__` method, but `MemorySendChannel` does not. What's not correct
is our inferred type here. It should be `MemoryReceiveChannel[int]`, not
the union of the two. This is due to missing unpacking support for tuple
subclasses, which @AlexWaygood is working on. I don't think this should
block merging this PR, because those wrong types are already there,
without this PR.

## Test Plan

New Markdown tests and snapshot tests for diagnostics.
This commit is contained in:
David Peter 2025-07-30 17:40:24 +02:00 committed by GitHub
parent e593761232
commit eb02aa5676
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 908 additions and 197 deletions

View file

@ -49,7 +49,7 @@ use crate::semantic_index::use_def::{
};
use crate::semantic_index::{ArcUseDefMap, ExpressionsScopeMap, SemanticIndex};
use crate::semantic_model::HasTrackedScope;
use crate::unpack::{Unpack, UnpackKind, UnpackPosition, UnpackValue};
use crate::unpack::{EvaluationMode, Unpack, UnpackKind, UnpackPosition, UnpackValue};
use crate::{Db, Program};
mod except_handlers;
@ -2804,9 +2804,17 @@ impl<'ast> Unpackable<'ast> {
const fn kind(&self) -> UnpackKind {
match self {
Unpackable::Assign(_) => UnpackKind::Assign,
Unpackable::For(_) | Unpackable::Comprehension { .. } => UnpackKind::Iterable,
Unpackable::For(ast::StmtFor { is_async, .. }) => UnpackKind::Iterable {
mode: EvaluationMode::from_is_async(*is_async),
},
Unpackable::Comprehension {
node: ast::Comprehension { is_async, .. },
..
} => UnpackKind::Iterable {
mode: EvaluationMode::from_is_async(*is_async),
},
Unpackable::WithItem { is_async, .. } => UnpackKind::ContextManager {
is_async: *is_async,
mode: EvaluationMode::from_is_async(*is_async),
},
}
}