mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[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:
parent
e593761232
commit
eb02aa5676
17 changed files with 908 additions and 197 deletions
|
@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ use crate::types::mro::{Mro, MroError, MroIterator};
|
|||
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
||||
use crate::types::signatures::{Parameter, ParameterForm, Parameters, walk_signature};
|
||||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
use crate::unpack::EvaluationMode;
|
||||
pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
|
||||
use crate::{Db, FxOrderSet, Module, Program};
|
||||
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass};
|
||||
|
@ -4637,6 +4638,65 @@ impl<'db> Type<'db> {
|
|||
/// y(*x)
|
||||
/// ```
|
||||
fn try_iterate(self, db: &'db dyn Db) -> Result<Cow<'db, TupleSpec<'db>>, IterationError<'db>> {
|
||||
self.try_iterate_with_mode(db, EvaluationMode::Sync)
|
||||
}
|
||||
|
||||
fn try_iterate_with_mode(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
mode: EvaluationMode,
|
||||
) -> Result<Cow<'db, TupleSpec<'db>>, IterationError<'db>> {
|
||||
if mode.is_async() {
|
||||
let try_call_dunder_anext_on_iterator = |iterator: Type<'db>| {
|
||||
iterator
|
||||
.try_call_dunder(db, "__anext__", CallArguments::none())
|
||||
.map(|dunder_anext_outcome| {
|
||||
dunder_anext_outcome.return_type(db).resolve_await(db)
|
||||
})
|
||||
};
|
||||
|
||||
return match self.try_call_dunder(db, "__aiter__", CallArguments::none()) {
|
||||
Ok(dunder_aiter_bindings) => {
|
||||
let iterator = dunder_aiter_bindings.return_type(db);
|
||||
match try_call_dunder_anext_on_iterator(iterator) {
|
||||
Ok(result) => Ok(Cow::Owned(TupleSpec::homogeneous(result))),
|
||||
Err(dunder_anext_error) => {
|
||||
Err(IterationError::IterReturnsInvalidIterator {
|
||||
iterator,
|
||||
dunder_error: dunder_anext_error,
|
||||
mode,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(CallDunderError::PossiblyUnbound(dunder_aiter_bindings)) => {
|
||||
let iterator = dunder_aiter_bindings.return_type(db);
|
||||
match try_call_dunder_anext_on_iterator(iterator) {
|
||||
Ok(_) => Err(IterationError::IterCallError {
|
||||
kind: CallErrorKind::PossiblyNotCallable,
|
||||
bindings: dunder_aiter_bindings,
|
||||
mode,
|
||||
}),
|
||||
Err(dunder_anext_error) => {
|
||||
Err(IterationError::IterReturnsInvalidIterator {
|
||||
iterator,
|
||||
dunder_error: dunder_anext_error,
|
||||
mode,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(CallDunderError::CallError(kind, bindings)) => {
|
||||
Err(IterationError::IterCallError {
|
||||
kind,
|
||||
bindings,
|
||||
mode,
|
||||
})
|
||||
}
|
||||
Err(CallDunderError::MethodNotAvailable) => Err(IterationError::UnboundAiterError),
|
||||
};
|
||||
}
|
||||
|
||||
match self {
|
||||
Type::Tuple(tuple_type) => return Ok(Cow::Borrowed(tuple_type.tuple(db))),
|
||||
Type::GenericAlias(alias) if alias.origin(db).is_tuple(db) => {
|
||||
|
@ -4693,7 +4753,8 @@ impl<'db> Type<'db> {
|
|||
.map_err(
|
||||
|dunder_next_error| IterationError::IterReturnsInvalidIterator {
|
||||
iterator,
|
||||
dunder_next_error,
|
||||
dunder_error: dunder_next_error,
|
||||
mode,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -4728,15 +4789,18 @@ impl<'db> Type<'db> {
|
|||
|
||||
Err(dunder_next_error) => Err(IterationError::IterReturnsInvalidIterator {
|
||||
iterator,
|
||||
dunder_next_error,
|
||||
dunder_error: dunder_next_error,
|
||||
mode,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// `__iter__` is definitely bound but it can't be called with the expected arguments
|
||||
Err(CallDunderError::CallError(kind, bindings)) => {
|
||||
Err(IterationError::IterCallError(kind, bindings))
|
||||
}
|
||||
Err(CallDunderError::CallError(kind, bindings)) => Err(IterationError::IterCallError {
|
||||
kind,
|
||||
bindings,
|
||||
mode,
|
||||
}),
|
||||
|
||||
// There's no `__iter__` method. Try `__getitem__` instead...
|
||||
Err(CallDunderError::MethodNotAvailable) => try_call_dunder_getitem()
|
||||
|
@ -4818,7 +4882,7 @@ impl<'db> Type<'db> {
|
|||
fn generator_return_type(self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
// TODO: Ideally, we would first try to upcast `self` to an instance of `Generator` and *then*
|
||||
// match on the protocol instance to get the `ReturnType` type parameter. For now, implement
|
||||
// an ad-hoc solution that works for protocols and instances of classes that directly inherit
|
||||
// an ad-hoc solution that works for protocols and instances of classes that explicitly inherit
|
||||
// from the `Generator` protocol, such as `types.GeneratorType`.
|
||||
|
||||
let from_class_base = |base: ClassBase<'db>| {
|
||||
|
@ -6806,18 +6870,24 @@ impl<'db> ContextManagerError<'db> {
|
|||
/// Error returned if a type is not (or may not be) iterable.
|
||||
#[derive(Debug)]
|
||||
enum IterationError<'db> {
|
||||
/// The object being iterated over has a bound `__iter__` method,
|
||||
/// The object being iterated over has a bound `__(a)iter__` method,
|
||||
/// but calling it with the expected arguments results in an error.
|
||||
IterCallError(CallErrorKind, Box<Bindings<'db>>),
|
||||
IterCallError {
|
||||
kind: CallErrorKind,
|
||||
bindings: Box<Bindings<'db>>,
|
||||
mode: EvaluationMode,
|
||||
},
|
||||
|
||||
/// The object being iterated over has a bound `__iter__` method that can be called
|
||||
/// The object being iterated over has a bound `__(a)iter__` method that can be called
|
||||
/// with the expected types, but it returns an object that is not a valid iterator.
|
||||
IterReturnsInvalidIterator {
|
||||
/// The type of the object returned by the `__iter__` method.
|
||||
/// The type of the object returned by the `__(a)iter__` method.
|
||||
iterator: Type<'db>,
|
||||
/// The error we encountered when we tried to call `__next__` on the type
|
||||
/// returned by `__iter__`
|
||||
dunder_next_error: CallDunderError<'db>,
|
||||
/// The error we encountered when we tried to call `__(a)next__` on the type
|
||||
/// returned by `__(a)iter__`
|
||||
dunder_error: CallDunderError<'db>,
|
||||
/// Whether this is a synchronous or an asynchronous iterator.
|
||||
mode: EvaluationMode,
|
||||
},
|
||||
|
||||
/// The object being iterated over has a bound `__iter__` method that returns a
|
||||
|
@ -6838,6 +6908,9 @@ enum IterationError<'db> {
|
|||
UnboundIterAndGetitemError {
|
||||
dunder_getitem_error: CallDunderError<'db>,
|
||||
},
|
||||
|
||||
/// The asynchronous iterable has no `__aiter__` method.
|
||||
UnboundAiterError,
|
||||
}
|
||||
|
||||
impl<'db> IterationError<'db> {
|
||||
|
@ -6847,16 +6920,43 @@ impl<'db> IterationError<'db> {
|
|||
|
||||
/// Returns the element type if it is known, or `None` if the type is never iterable.
|
||||
fn element_type(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
let return_type = |result: Result<Bindings<'db>, CallDunderError<'db>>| {
|
||||
result
|
||||
.map(|outcome| Some(outcome.return_type(db)))
|
||||
.unwrap_or_else(|call_error| call_error.return_type(db))
|
||||
};
|
||||
|
||||
match self {
|
||||
Self::IterReturnsInvalidIterator {
|
||||
dunder_next_error, ..
|
||||
} => dunder_next_error.return_type(db),
|
||||
dunder_error, mode, ..
|
||||
} => dunder_error.return_type(db).map(|ty| {
|
||||
if mode.is_async() {
|
||||
ty.resolve_await(db)
|
||||
} else {
|
||||
ty
|
||||
}
|
||||
}),
|
||||
|
||||
Self::IterCallError(_, dunder_iter_bindings) => dunder_iter_bindings
|
||||
.return_type(db)
|
||||
.try_call_dunder(db, "__next__", CallArguments::none())
|
||||
.map(|dunder_next_outcome| Some(dunder_next_outcome.return_type(db)))
|
||||
.unwrap_or_else(|dunder_next_call_error| dunder_next_call_error.return_type(db)),
|
||||
Self::IterCallError {
|
||||
kind: _,
|
||||
bindings: dunder_iter_bindings,
|
||||
mode,
|
||||
} => {
|
||||
if mode.is_async() {
|
||||
return_type(dunder_iter_bindings.return_type(db).try_call_dunder(
|
||||
db,
|
||||
"__anext__",
|
||||
CallArguments::none(),
|
||||
))
|
||||
.map(|ty| ty.resolve_await(db))
|
||||
} else {
|
||||
return_type(dunder_iter_bindings.return_type(db).try_call_dunder(
|
||||
db,
|
||||
"__next__",
|
||||
CallArguments::none(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Self::PossiblyUnboundIterAndGetitemError {
|
||||
dunder_next_return,
|
||||
|
@ -6882,6 +6982,19 @@ impl<'db> IterationError<'db> {
|
|||
Self::UnboundIterAndGetitemError {
|
||||
dunder_getitem_error,
|
||||
} => dunder_getitem_error.return_type(db),
|
||||
|
||||
Self::UnboundAiterError => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Does this error concern a synchronous or asynchronous iterable?
|
||||
fn mode(&self) -> EvaluationMode {
|
||||
match self {
|
||||
Self::IterCallError { mode, .. } => *mode,
|
||||
Self::IterReturnsInvalidIterator { mode, .. } => *mode,
|
||||
Self::PossiblyUnboundIterAndGetitemError { .. }
|
||||
| Self::UnboundIterAndGetitemError { .. } => EvaluationMode::Sync,
|
||||
Self::UnboundAiterError => EvaluationMode::Async,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6898,6 +7011,7 @@ impl<'db> IterationError<'db> {
|
|||
db: &'a dyn Db,
|
||||
builder: LintDiagnosticGuardBuilder<'a, 'a>,
|
||||
iterable_type: Type<'a>,
|
||||
mode: EvaluationMode,
|
||||
}
|
||||
|
||||
impl<'a> Reporter<'a> {
|
||||
|
@ -6907,8 +7021,9 @@ impl<'db> IterationError<'db> {
|
|||
#[expect(clippy::wrong_self_convention)]
|
||||
fn is_not(self, because: impl std::fmt::Display) -> LintDiagnosticGuard<'a, 'a> {
|
||||
let mut diag = self.builder.into_diagnostic(format_args!(
|
||||
"Object of type `{iterable_type}` is not iterable",
|
||||
"Object of type `{iterable_type}` is not {maybe_async}iterable",
|
||||
iterable_type = self.iterable_type.display(self.db),
|
||||
maybe_async = if self.mode.is_async() { "async-" } else { "" }
|
||||
));
|
||||
diag.info(because);
|
||||
diag
|
||||
|
@ -6919,8 +7034,9 @@ impl<'db> IterationError<'db> {
|
|||
/// `because` should explain why `iterable_type` is likely not iterable.
|
||||
fn may_not(self, because: impl std::fmt::Display) -> LintDiagnosticGuard<'a, 'a> {
|
||||
let mut diag = self.builder.into_diagnostic(format_args!(
|
||||
"Object of type `{iterable_type}` may not be iterable",
|
||||
"Object of type `{iterable_type}` may not be {maybe_async}iterable",
|
||||
iterable_type = self.iterable_type.display(self.db),
|
||||
maybe_async = if self.mode.is_async() { "async-" } else { "" }
|
||||
));
|
||||
diag.info(because);
|
||||
diag
|
||||
|
@ -6931,106 +7047,132 @@ impl<'db> IterationError<'db> {
|
|||
return;
|
||||
};
|
||||
let db = context.db();
|
||||
let mode = self.mode();
|
||||
let reporter = Reporter {
|
||||
db,
|
||||
builder,
|
||||
iterable_type,
|
||||
mode,
|
||||
};
|
||||
|
||||
// TODO: for all of these error variants, the "explanation" for the diagnostic
|
||||
// (everything after the "because") should really be presented as a "help:", "note",
|
||||
// or similar, rather than as part of the same sentence as the error message.
|
||||
match self {
|
||||
Self::IterCallError(CallErrorKind::NotCallable, bindings) => {
|
||||
reporter.is_not(format_args!(
|
||||
"Its `__iter__` attribute has type `{dunder_iter_type}`, which is not callable",
|
||||
dunder_iter_type = bindings.callable_type().display(db),
|
||||
));
|
||||
}
|
||||
Self::IterCallError(CallErrorKind::PossiblyNotCallable, bindings)
|
||||
if bindings.is_single() =>
|
||||
{
|
||||
reporter.may_not(format_args!(
|
||||
"Its `__iter__` attribute (with type `{dunder_iter_type}`) \
|
||||
may not be callable",
|
||||
dunder_iter_type = bindings.callable_type().display(db),
|
||||
));
|
||||
}
|
||||
Self::IterCallError(CallErrorKind::PossiblyNotCallable, bindings) => {
|
||||
reporter.may_not(format_args!(
|
||||
"Its `__iter__` attribute (with type `{dunder_iter_type}`) \
|
||||
may not be callable",
|
||||
dunder_iter_type = bindings.callable_type().display(db),
|
||||
));
|
||||
}
|
||||
Self::IterCallError(CallErrorKind::BindingError, bindings) if bindings.is_single() => {
|
||||
reporter
|
||||
.is_not("Its `__iter__` method has an invalid signature")
|
||||
.info("Expected signature `def __iter__(self): ...`");
|
||||
}
|
||||
Self::IterCallError(CallErrorKind::BindingError, bindings) => {
|
||||
let mut diag =
|
||||
reporter.may_not("Its `__iter__` method may have an invalid signature");
|
||||
diag.info(format_args!(
|
||||
"Type of `__iter__` is `{dunder_iter_type}`",
|
||||
dunder_iter_type = bindings.callable_type().display(db),
|
||||
));
|
||||
diag.info("Expected signature for `__iter__` is `def __iter__(self): ...`");
|
||||
Self::IterCallError {
|
||||
kind,
|
||||
bindings,
|
||||
mode,
|
||||
} => {
|
||||
let method = if mode.is_async() {
|
||||
"__aiter__"
|
||||
} else {
|
||||
"__iter__"
|
||||
};
|
||||
|
||||
match kind {
|
||||
CallErrorKind::NotCallable => {
|
||||
reporter.is_not(format_args!(
|
||||
"Its `{method}` attribute has type `{dunder_iter_type}`, which is not callable",
|
||||
dunder_iter_type = bindings.callable_type().display(db),
|
||||
));
|
||||
}
|
||||
CallErrorKind::PossiblyNotCallable => {
|
||||
reporter.may_not(format_args!(
|
||||
"Its `{method}` attribute (with type `{dunder_iter_type}`) \
|
||||
may not be callable",
|
||||
dunder_iter_type = bindings.callable_type().display(db),
|
||||
));
|
||||
}
|
||||
CallErrorKind::BindingError => {
|
||||
if bindings.is_single() {
|
||||
reporter
|
||||
.is_not(format_args!(
|
||||
"Its `{method}` method has an invalid signature"
|
||||
))
|
||||
.info(format_args!("Expected signature `def {method}(self): ...`"));
|
||||
} else {
|
||||
let mut diag = reporter.may_not(format_args!(
|
||||
"Its `{method}` method may have an invalid signature"
|
||||
));
|
||||
diag.info(format_args!(
|
||||
"Type of `{method}` is `{dunder_iter_type}`",
|
||||
dunder_iter_type = bindings.callable_type().display(db),
|
||||
));
|
||||
diag.info(format_args!(
|
||||
"Expected signature for `{method}` is `def {method}(self): ...`",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::IterReturnsInvalidIterator {
|
||||
iterator,
|
||||
dunder_next_error,
|
||||
} => match dunder_next_error {
|
||||
CallDunderError::MethodNotAvailable => {
|
||||
reporter.is_not(format_args!(
|
||||
"Its `__iter__` method returns an object of type `{iterator_type}`, \
|
||||
which has no `__next__` method",
|
||||
dunder_error: dunder_next_error,
|
||||
mode,
|
||||
} => {
|
||||
let dunder_iter_name = if mode.is_async() {
|
||||
"__aiter__"
|
||||
} else {
|
||||
"__iter__"
|
||||
};
|
||||
let dunder_next_name = if mode.is_async() {
|
||||
"__anext__"
|
||||
} else {
|
||||
"__next__"
|
||||
};
|
||||
match dunder_next_error {
|
||||
CallDunderError::MethodNotAvailable => {
|
||||
reporter.is_not(format_args!(
|
||||
"Its `{dunder_iter_name}` method returns an object of type `{iterator_type}`, \
|
||||
which has no `{dunder_next_name}` method",
|
||||
iterator_type = iterator.display(db),
|
||||
));
|
||||
}
|
||||
CallDunderError::PossiblyUnbound(_) => {
|
||||
reporter.may_not(format_args!(
|
||||
"Its `__iter__` method returns an object of type `{iterator_type}`, \
|
||||
which may not have a `__next__` method",
|
||||
iterator_type = iterator.display(db),
|
||||
));
|
||||
}
|
||||
CallDunderError::CallError(CallErrorKind::NotCallable, _) => {
|
||||
reporter.is_not(format_args!(
|
||||
"Its `__iter__` method returns an object of type `{iterator_type}`, \
|
||||
which has a `__next__` attribute that is not callable",
|
||||
iterator_type = iterator.display(db),
|
||||
));
|
||||
}
|
||||
CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, _) => {
|
||||
reporter.may_not(format_args!(
|
||||
"Its `__iter__` method returns an object of type `{iterator_type}`, \
|
||||
which has a `__next__` attribute that may not be callable",
|
||||
iterator_type = iterator.display(db),
|
||||
));
|
||||
}
|
||||
CallDunderError::CallError(CallErrorKind::BindingError, bindings)
|
||||
if bindings.is_single() =>
|
||||
{
|
||||
reporter
|
||||
.is_not(format_args!(
|
||||
"Its `__iter__` method returns an object of type `{iterator_type}`, \
|
||||
which has an invalid `__next__` method",
|
||||
}
|
||||
CallDunderError::PossiblyUnbound(_) => {
|
||||
reporter.may_not(format_args!(
|
||||
"Its `{dunder_iter_name}` method returns an object of type `{iterator_type}`, \
|
||||
which may not have a `{dunder_next_name}` method",
|
||||
iterator_type = iterator.display(db),
|
||||
))
|
||||
.info("Expected signature for `__next__` is `def __next__(self): ...`");
|
||||
}
|
||||
CallDunderError::CallError(CallErrorKind::BindingError, _) => {
|
||||
reporter
|
||||
.may_not(format_args!(
|
||||
"Its `__iter__` method returns an object of type `{iterator_type}`, \
|
||||
which may have an invalid `__next__` method",
|
||||
));
|
||||
}
|
||||
CallDunderError::CallError(CallErrorKind::NotCallable, _) => {
|
||||
reporter.is_not(format_args!(
|
||||
"Its `{dunder_iter_name}` method returns an object of type `{iterator_type}`, \
|
||||
which has a `{dunder_next_name}` attribute that is not callable",
|
||||
iterator_type = iterator.display(db),
|
||||
))
|
||||
.info("Expected signature for `__next__` is `def __next__(self): ...`)");
|
||||
));
|
||||
}
|
||||
CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, _) => {
|
||||
reporter.may_not(format_args!(
|
||||
"Its `{dunder_iter_name}` method returns an object of type `{iterator_type}`, \
|
||||
which has a `{dunder_next_name}` attribute that may not be callable",
|
||||
iterator_type = iterator.display(db),
|
||||
));
|
||||
}
|
||||
CallDunderError::CallError(CallErrorKind::BindingError, bindings)
|
||||
if bindings.is_single() =>
|
||||
{
|
||||
reporter
|
||||
.is_not(format_args!(
|
||||
"Its `{dunder_iter_name}` method returns an object of type `{iterator_type}`, \
|
||||
which has an invalid `{dunder_next_name}` method",
|
||||
iterator_type = iterator.display(db),
|
||||
))
|
||||
.info(format_args!("Expected signature for `{dunder_next_name}` is `def {dunder_next_name}(self): ...`"));
|
||||
}
|
||||
CallDunderError::CallError(CallErrorKind::BindingError, _) => {
|
||||
reporter
|
||||
.may_not(format_args!(
|
||||
"Its `{dunder_iter_name}` method returns an object of type `{iterator_type}`, \
|
||||
which may have an invalid `{dunder_next_name}` method",
|
||||
iterator_type = iterator.display(db),
|
||||
))
|
||||
.info(format_args!("Expected signature for `{dunder_next_name}` is `def {dunder_next_name}(self): ...`"));
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Self::PossiblyUnboundIterAndGetitemError {
|
||||
dunder_getitem_error,
|
||||
|
@ -7167,6 +7309,10 @@ impl<'db> IterationError<'db> {
|
|||
);
|
||||
}
|
||||
},
|
||||
|
||||
IterationError::UnboundAiterError => {
|
||||
reporter.is_not("It has no `__aiter__` method");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ use crate::types::{
|
|||
TypeAndQualifiers, TypeIsType, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance,
|
||||
TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type,
|
||||
};
|
||||
use crate::unpack::{Unpack, UnpackPosition};
|
||||
use crate::unpack::{EvaluationMode, Unpack, UnpackPosition};
|
||||
use crate::util::diagnostics::format_enumeration;
|
||||
use crate::util::subscript::{PyIndex, PySlice};
|
||||
use crate::{Db, FxOrderSet, Program};
|
||||
|
@ -4560,29 +4560,28 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
let iterable = for_stmt.iterable(self.module());
|
||||
let target = for_stmt.target(self.module());
|
||||
|
||||
let loop_var_value_type = if for_stmt.is_async() {
|
||||
let _iterable_type = self.infer_standalone_expression(iterable);
|
||||
todo_type!("async iterables/iterators")
|
||||
} else {
|
||||
match for_stmt.target_kind() {
|
||||
TargetKind::Sequence(unpack_position, unpack) => {
|
||||
let unpacked = infer_unpack_types(self.db(), unpack);
|
||||
if unpack_position == UnpackPosition::First {
|
||||
self.context.extend(unpacked.diagnostics());
|
||||
}
|
||||
let loop_var_value_type = match for_stmt.target_kind() {
|
||||
TargetKind::Sequence(unpack_position, unpack) => {
|
||||
let unpacked = infer_unpack_types(self.db(), unpack);
|
||||
if unpack_position == UnpackPosition::First {
|
||||
self.context.extend(unpacked.diagnostics());
|
||||
}
|
||||
|
||||
unpacked.expression_type(target)
|
||||
}
|
||||
TargetKind::Single => {
|
||||
let iterable_type = self.infer_standalone_expression(iterable);
|
||||
iterable_type
|
||||
.try_iterate(self.db())
|
||||
.map(|tuple| tuple.homogeneous_element_type(self.db()))
|
||||
.unwrap_or_else(|err| {
|
||||
err.report_diagnostic(&self.context, iterable_type, iterable.into());
|
||||
err.fallback_element_type(self.db())
|
||||
})
|
||||
}
|
||||
unpacked.expression_type(target)
|
||||
}
|
||||
TargetKind::Single => {
|
||||
let iterable_type = self.infer_standalone_expression(iterable);
|
||||
|
||||
iterable_type
|
||||
.try_iterate_with_mode(
|
||||
self.db(),
|
||||
EvaluationMode::from_is_async(for_stmt.is_async()),
|
||||
)
|
||||
.map(|tuple| tuple.homogeneous_element_type(self.db()))
|
||||
.unwrap_or_else(|err| {
|
||||
err.report_diagnostic(&self.context, iterable_type, iterable.into());
|
||||
err.fallback_element_type(self.db())
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -5692,30 +5691,28 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
};
|
||||
|
||||
let target_type = if comprehension.is_async() {
|
||||
// TODO: async iterables/iterators! -- Alex
|
||||
let _iterable_type = infer_iterable_type();
|
||||
todo_type!("async iterables/iterators")
|
||||
} else {
|
||||
match comprehension.target_kind() {
|
||||
TargetKind::Sequence(unpack_position, unpack) => {
|
||||
let unpacked = infer_unpack_types(self.db(), unpack);
|
||||
if unpack_position == UnpackPosition::First {
|
||||
self.context.extend(unpacked.diagnostics());
|
||||
}
|
||||
let target_type = match comprehension.target_kind() {
|
||||
TargetKind::Sequence(unpack_position, unpack) => {
|
||||
let unpacked = infer_unpack_types(self.db(), unpack);
|
||||
if unpack_position == UnpackPosition::First {
|
||||
self.context.extend(unpacked.diagnostics());
|
||||
}
|
||||
|
||||
unpacked.expression_type(target)
|
||||
}
|
||||
TargetKind::Single => {
|
||||
let iterable_type = infer_iterable_type();
|
||||
iterable_type
|
||||
.try_iterate(self.db())
|
||||
.map(|tuple| tuple.homogeneous_element_type(self.db()))
|
||||
.unwrap_or_else(|err| {
|
||||
err.report_diagnostic(&self.context, iterable_type, iterable.into());
|
||||
err.fallback_element_type(self.db())
|
||||
})
|
||||
}
|
||||
unpacked.expression_type(target)
|
||||
}
|
||||
TargetKind::Single => {
|
||||
let iterable_type = infer_iterable_type();
|
||||
|
||||
iterable_type
|
||||
.try_iterate_with_mode(
|
||||
self.db(),
|
||||
EvaluationMode::from_is_async(comprehension.is_async()),
|
||||
)
|
||||
.map(|tuple| tuple.homogeneous_element_type(self.db()))
|
||||
.unwrap_or_else(|err| {
|
||||
err.report_diagnostic(&self.context, iterable_type, iterable.into());
|
||||
err.fallback_element_type(self.db())
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -64,8 +64,8 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
|
|||
value_type
|
||||
}
|
||||
}
|
||||
UnpackKind::Iterable => value_type
|
||||
.try_iterate(self.db())
|
||||
UnpackKind::Iterable { mode } => value_type
|
||||
.try_iterate_with_mode(self.db(), mode)
|
||||
.map(|tuple| tuple.homogeneous_element_type(self.db()))
|
||||
.unwrap_or_else(|err| {
|
||||
err.report_diagnostic(
|
||||
|
@ -75,8 +75,8 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
|
|||
);
|
||||
err.fallback_element_type(self.db())
|
||||
}),
|
||||
UnpackKind::ContextManager { is_async } => {
|
||||
if is_async {
|
||||
UnpackKind::ContextManager { mode } => {
|
||||
if mode.is_async() {
|
||||
value_type.aenter(self.db())
|
||||
} else {
|
||||
value_type.try_enter(self.db()).unwrap_or_else(|err| {
|
||||
|
|
|
@ -99,12 +99,32 @@ impl<'db> UnpackValue<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, salsa::Update)]
|
||||
pub(crate) enum EvaluationMode {
|
||||
Sync,
|
||||
Async,
|
||||
}
|
||||
|
||||
impl EvaluationMode {
|
||||
pub(crate) const fn from_is_async(is_async: bool) -> Self {
|
||||
if is_async {
|
||||
EvaluationMode::Async
|
||||
} else {
|
||||
EvaluationMode::Sync
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn is_async(self) -> bool {
|
||||
matches!(self, EvaluationMode::Async)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, salsa::Update)]
|
||||
pub(crate) enum UnpackKind {
|
||||
/// An iterable expression like the one in a `for` loop or a comprehension.
|
||||
Iterable,
|
||||
Iterable { mode: EvaluationMode },
|
||||
/// An context manager expression like the one in a `with` statement.
|
||||
ContextManager { is_async: bool },
|
||||
ContextManager { mode: EvaluationMode },
|
||||
/// An expression that is being assigned to a target.
|
||||
Assign,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue