Improve compile errors for unimplemented traits (#1436)

* Improve `debug_handler` to use the correct span for specific bounds

This results in better localised error messages, as they now point
directly to the corresponding argument instead of to the macro itself.

* Improve some error messages behind a `nightly-error-messages` feature
flag

This uses the nightly only `rustc_on_unimplemented` attribute to improve
some error messages when users try to use invalid handler functions.
This should be seen as prove of concept, not as full solution for all
potential error cases.

The underlying feature is currently marked as permanently unstable, but
I'm working on getting this specific attribute (or an attribute with
different name, similar functionality) ready to work on a stable compiler.

* Apply suggestions from code review

Co-authored-by: Jonas Platte <jplatte+git@posteo.de>

* Enable the `nightly-error-messages` feature unconditionally for nightly compilers

* Use a nightly compiler to run the axum-marcos compile fail tests

* update to newer nightly

* Run axum-macros tests on nightly

* tweak compile error hints a bit

* more tweaks

* update test

Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
Co-authored-by: David Pedersen <david.pdrsn@gmail.com>
This commit is contained in:
Georg Semmler 2022-11-19 12:45:03 +01:00 committed by GitHub
parent 2e3000f1a3
commit d5de3bc7e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 88 additions and 19 deletions

View file

@ -75,8 +75,6 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
# nightly has an ICE, so ignore it for now
# rust: [stable, beta, nightly]
rust: [stable, beta]
steps:
- uses: actions/checkout@master
@ -92,6 +90,23 @@ jobs:
command: test
args: --all --all-features --all-targets
test-nightly:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions-rs/toolchain@v1
with:
# same as `axum-macros/rust-toolchain`
toolchain: nightly-2022-11-18
override: true
profile: minimal
- uses: Swatinem/rust-cache@v1
- name: Run nightly tests
working-directory: axum-macros
run: |
cargo test
# some examples doesn't support our MSRV (such as async-graphql)
# so we only test axum itself on our MSRV
test-msrv:

View file

@ -21,6 +21,9 @@ mime = "0.3.16"
tower-layer = "0.3"
tower-service = "0.3"
[build-dependencies]
rustversion = "1.0.9"
[dev-dependencies]
axum = { path = "../axum", version = "0.6.0-rc.2" }
futures-util = "0.3"

7
axum-core/build.rs Normal file
View file

@ -0,0 +1,7 @@
#[rustversion::nightly]
fn main() {
println!("cargo:rustc-cfg=nightly_error_messages");
}
#[rustversion::not(nightly)]
fn main() {}

View file

@ -39,6 +39,12 @@ mod private {
///
/// [`axum::extract`]: https://docs.rs/axum/0.6.0-rc.2/axum/extract/index.html
#[async_trait]
#[cfg_attr(
nightly_error_messages,
rustc_on_unimplemented(
note = "Function argument is not a valid axum extractor. \nSee `https://docs.rs/axum/latest/axum/extract/index.html` for details",
)
)]
pub trait FromRequestParts<S>: Sized {
/// If the extractor fails it'll use this "rejection" type. A rejection is
/// a kind of error that can be converted into a response.
@ -102,6 +108,12 @@ pub trait FromRequestParts<S>: Sized {
/// [`http::Request<B>`]: http::Request
/// [`axum::extract`]: https://docs.rs/axum/0.6.0-rc.2/axum/extract/index.html
#[async_trait]
#[cfg_attr(
nightly_error_messages,
rustc_on_unimplemented(
note = "Function argument is not a valid axum extractor. \nSee `https://docs.rs/axum/latest/axum/extract/index.html` for details",
)
)]
pub trait FromRequest<S, B, M = private::ViaRequest>: Sized {
/// If the extractor fails it'll use this "rejection" type. A rejection is
/// a kind of error that can be converted into a response.

View file

@ -1,3 +1,4 @@
#![cfg_attr(nightly_error_messages, feature(rustc_attrs))]
//! Core types and traits for [`axum`].
//!
//! Libraries authors that want to provide [`FromRequest`] or [`IntoResponse`] implementations

View file

@ -0,0 +1 @@
nightly-2022-11-18

View file

@ -251,11 +251,11 @@ fn check_inputs_impls_from_request(
};
let from_request_bound = if must_impl_from_request_parts {
quote! {
quote_spanned! {span=>
#ty: ::axum::extract::FromRequestParts<#state_ty> + Send
}
} else {
quote! {
quote_spanned! {span=>
#ty: ::axum::extract::FromRequest<#state_ty, #body_ty, M> + Send
}
};

View file

@ -699,7 +699,7 @@ where
#[cfg(test)]
fn run_ui_tests(directory: &str) {
#[rustversion::stable]
#[rustversion::nightly]
fn go(directory: &str) {
let t = trybuild::TestCases::new();
@ -725,7 +725,7 @@ fn run_ui_tests(directory: &str) {
}
}
#[rustversion::not(stable)]
#[rustversion::not(nightly)]
fn go(_directory: &str) {}
go(directory);

View file

@ -4,6 +4,8 @@ error[E0277]: the trait bound `bool: FromRequestParts<()>` is not satisfied
4 | async fn handler(foo: bool) {}
| ^^^^ the trait `FromRequestParts<()>` is not implemented for `bool`
|
= note: Function argument is not a valid axum extractor.
See `https://docs.rs/axum/latest/axum/extract/index.html` for details
= help: the following other types implement trait `FromRequestParts<S>`:
<() as FromRequestParts<S>>
<(T1, T2) as FromRequestParts<S>>
@ -16,10 +18,7 @@ error[E0277]: the trait bound `bool: FromRequestParts<()>` is not satisfied
and 25 others
= note: required for `bool` to implement `FromRequest<(), Body, axum_core::extract::private::ViaParts>`
note: required by a bound in `__axum_macros_check_handler_0_from_request_check`
--> tests/debug_handler/fail/argument_not_extractor.rs:3:1
--> tests/debug_handler/fail/argument_not_extractor.rs:4:23
|
3 | #[debug_handler]
| ^^^^^^^^^^^^^^^^ required by this bound in `__axum_macros_check_handler_0_from_request_check`
4 | async fn handler(foo: bool) {}
| ---- required by a bound in this
= note: this error originates in the attribute macro `debug_handler` (in Nightly builds, run with -Z macro-backtrace for more info)
| ^^^^ required by this bound in `__axum_macros_check_handler_0_from_request_check`

View file

@ -1,9 +1,11 @@
error[E0277]: the trait bound `String: FromRequestParts<()>` is not satisfied
--> tests/debug_handler/fail/doesnt_implement_from_request_parts.rs:4:1
--> tests/debug_handler/fail/doesnt_implement_from_request_parts.rs:5:21
|
4 | #[debug_handler]
| ^^^^^^^^^^^^^^^^ the trait `FromRequestParts<()>` is not implemented for `String`
5 | async fn handler(_: String, _: Method) {}
| ^^^^^^ the trait `FromRequestParts<()>` is not implemented for `String`
|
= note: Function argument is not a valid axum extractor.
See `https://docs.rs/axum/latest/axum/extract/index.html` for details
= help: the following other types implement trait `FromRequestParts<S>`:
<() as FromRequestParts<S>>
<(T1, T2) as FromRequestParts<S>>
@ -15,4 +17,4 @@ error[E0277]: the trait bound `String: FromRequestParts<()>` is not satisfied
<(T1, T2, T3, T4, T5, T6, T7, T8) as FromRequestParts<S>>
and 25 others
= help: see issue #48214
= note: this error originates in the attribute macro `debug_handler` (in Nightly builds, run with -Z macro-backtrace for more info)
= help: add `#![feature(trivial_bounds)]` to the crate attributes to enable

View file

@ -1,8 +1,11 @@
error: future cannot be sent between threads safely
--> tests/debug_handler/fail/not_send.rs:4:1
|
4 | async fn handler() {
| ^^^^^ future returned by `handler` is not `Send`
4 | / async fn handler() {
5 | | let rc = std::rc::Rc::new(());
6 | | async {}.await;
7 | | }
| |_^ future returned by `handler` is not `Send`
|
= help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `Rc<()>`
note: future is not `Send` as this value is used across an await
@ -17,5 +20,8 @@ note: future is not `Send` as this value is used across an await
note: required by a bound in `check`
--> tests/debug_handler/fail/not_send.rs:4:1
|
4 | async fn handler() {
| ^^^^^ required by this bound in `check`
4 | / async fn handler() {
5 | | let rc = std::rc::Rc::new(());
6 | | async {}.await;
7 | | }
| |_^ required by this bound in `check`

View file

@ -12,6 +12,7 @@ error[E0277]: the trait bound `fn(Extractor<()>) -> impl Future<Output = ()> {fo
| |
| required by a bound introduced by this call
|
= note: Consider using `#[axum::debug_handler]` to improve the error message
= help: the trait `Handler<T, S, B2>` is implemented for `Layered<L, H, T, S, B, B2>`
note: required by a bound in `axum::routing::get`
--> $WORKSPACE/axum/src/routing/method_routing.rs

View file

@ -12,6 +12,7 @@ error[E0277]: the trait bound `fn(Extractor<()>) -> impl Future<Output = ()> {fo
| |
| required by a bound introduced by this call
|
= note: Consider using `#[axum::debug_handler]` to improve the error message
= help: the trait `Handler<T, S, B2>` is implemented for `Layered<L, H, T, S, B, B2>`
note: required by a bound in `axum::routing::get`
--> $WORKSPACE/axum/src/routing/method_routing.rs

View file

@ -12,6 +12,7 @@ error[E0277]: the trait bound `fn(MyExtractor) -> impl Future<Output = ()> {hand
| |
| required by a bound introduced by this call
|
= note: Consider using `#[axum::debug_handler]` to improve the error message
= help: the trait `Handler<T, S, B2>` is implemented for `Layered<L, H, T, S, B, B2>`
note: required by a bound in `axum::routing::get`
--> $WORKSPACE/axum/src/routing/method_routing.rs
@ -28,6 +29,7 @@ error[E0277]: the trait bound `fn(Result<MyExtractor, MyRejection>) -> impl Futu
| |
| required by a bound introduced by this call
|
= note: Consider using `#[axum::debug_handler]` to improve the error message
= help: the trait `Handler<T, S, B2>` is implemented for `Layered<L, H, T, S, B, B2>`
note: required by a bound in `MethodRouter::<S, B>::post`
--> $WORKSPACE/axum/src/routing/method_routing.rs

View file

@ -4,6 +4,8 @@ error[E0277]: the trait bound `String: FromRequestParts<S>` is not satisfied
5 | body: String,
| ^^^^^^ the trait `FromRequestParts<S>` is not implemented for `String`
|
= note: Function argument is not a valid axum extractor.
See `https://docs.rs/axum/latest/axum/extract/index.html` for details
= help: the following other types implement trait `FromRequestParts<S>`:
<() as FromRequestParts<S>>
<(T1, T2) as FromRequestParts<S>>

View file

@ -63,6 +63,9 @@ sha-1 = { version = "0.10", optional = true }
tokio = { package = "tokio", version = "1.21", features = ["time"], optional = true }
tokio-tungstenite = { version = "0.17.2", optional = true }
[build-dependencies]
rustversion = "1.0.9"
[dev-dependencies]
anyhow = "1.0"
futures = "0.3"

7
axum/build.rs Normal file
View file

@ -0,0 +1,7 @@
#[rustversion::nightly]
fn main() {
println!("cargo:rustc-cfg=nightly_error_messages");
}
#[rustversion::not(nightly)]
fn main() {}

View file

@ -94,6 +94,12 @@ pub use self::service::HandlerService;
/// {}
/// ```
#[doc = include_str!("../docs/debugging_handler_type_errors.md")]
#[cfg_attr(
nightly_error_messages,
rustc_on_unimplemented(
note = "Consider using `#[axum::debug_handler]` to improve the error message"
)
)]
pub trait Handler<T, S, B = Body>: Clone + Send + Sized + 'static {
/// The type of future calling this handler returns.
type Future: Future<Output = Response> + Send + 'static;

View file

@ -1,3 +1,4 @@
#![cfg_attr(nightly_error_messages, feature(rustc_attrs))]
//! axum is a web application framework that focuses on ergonomics and modularity.
//!
//! # Table of contents