E.g. consider:
#![feature(async_await)]
fn foo() {
let x = async move {
Err(())?;
1
};
}
which yields the diagnostic:
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
--> src/lib.rs:5:9
|
5 | Err(())?;
| ^^^^^^^^ cannot use the `?` operator in a function that returns `{integer}`
|
= help: the trait `std::ops::Try` is not implemented for `{integer}`
= note: required by `std::ops::Try::from_error`
Ostensibly the user might think of fn foo as "the function" but it is in fact the async block that is "the function" here. That seems like something which would confuse users who are not familiar with the lowering and whatnot (most users).
I ran into this.
It's much more confusing if the outer function actually returns a result.
In a more complicated function the error message can be a real head scratcher.
#![feature(async_await)]
async fn f() -> Result<bool, ()> {
async {
Err(())?
}.await;
Ok(true)
}
This can be fixed as a rewording to "cannot use the ? operator in a function or async block that returns {integer}".
This same error message appears for closures:
fn main() {
let _ = || {
Err(5)?;
1
};
}
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
--> src/main.rs:3:9
|
3 | Err(5)?;
| ^^^^^^^ cannot use the `?` operator in a function that returns `{integer}`
|
= help: the trait `std::ops::Try` is not implemented for `{integer}`
= note: required by `std::ops::Try::from_error`
At this point there are enough cases that we would want to disambiguate between that it makes sense to extend on_unimplemented to have some way of obtaining the enclosing scope's description (closure/function/method/async block, etc.).
I think something like:
#[rustc_on_unimplemented(
on(all(
any(from_method="from_error", from_method="from_ok"),
from_desugaring="QuestionMark"),
message="the `?` operator can only be used in {ItemCtx} \
that returns `Result` or `Option` \
(or another type that implements `{Try}`)",
label="cannot use the `?` operator in {ItemCtx} that returns `{Self}`"),
on(all(from_method="into_result", from_desugaring="QuestionMark"),
message="the `?` operator can only be applied to values \
that implement `{Try}`",
label="the `?` operator cannot be applied to type `{Self}`")
)]
would make sense, which is basically @estebank's idea.
Almost there. Will have a PR for review soon.
There's almost no difference between an async closure and an async function call in the hir. For now async functions may also have to be reported as being an async closure...
E.g.
async fn an_async_function() -> u32 {
let x: Option<u32> = None;
x?; //~ ERROR the `?` operator
22
}
vs:
async fn an_async_block() -> u32 {
async {
let x: Option<u32> = None;
x?; //~ ERROR the `?` operator
22
}.await
}
All the other cases seem fine, but not sure for this one how to proceed...
Can't you add a field to the relevant hir element to signal the difference?
Edit: Looking at the code, make_async_expr has an argument async_gen_kind: hir::AsyncGeneratorKind which is
pub enum AsyncGeneratorKind {
/// An explicit `async` block written by the user.
Block,
/// An explicit `async` block written by the user.
Closure,
/// The `async` block generated as the body of an async function.
Fn,
}
I'm not sure if that gets propagated to the right place where you'd need it, but we already seem to be differentiating between the three cases, right?
Most helpful comment
Almost there. Will have a PR for review soon.