diff --git a/compiler/ui_tests/Cargo.toml b/compiler/ui_tests/Cargo.toml index cfda16db8..0498655a8 100644 --- a/compiler/ui_tests/Cargo.toml +++ b/compiler/ui_tests/Cargo.toml @@ -313,6 +313,8 @@ members = [ "reflection/local_glob_reexports_are_supported/generated_app", "reflection/non_static_methods_are_supported", "reflection/non_static_methods_are_supported/generated_app", + "reflection/nonnull_is_supported", + "reflection/nonnull_is_supported/generated_app", "reflection/output_parameter_cannot_be_handled", "reflection/output_parameter_cannot_be_handled/generated_app", "reflection/pattern_bindings_in_input_parameters_are_supported", diff --git a/compiler/ui_tests/reflection/nonnull_is_supported/Cargo.toml b/compiler/ui_tests/reflection/nonnull_is_supported/Cargo.toml new file mode 100644 index 000000000..bf4b7f1ce --- /dev/null +++ b/compiler/ui_tests/reflection/nonnull_is_supported/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "app_5680a8e5" +version = "0.1.0" +edition.workspace = true + +[lints.rust.unexpected_cfgs] +level = "allow" +check-cfg = ["cfg(pavex_ide_hint)"] + +[dependencies] +workspace_hack = { version = "0.1", path = "../../workspace_hack" } + +[dependencies.pavex] +workspace = true + +[dependencies.pavex_cli_client] +workspace = true diff --git a/compiler/ui_tests/reflection/nonnull_is_supported/diagnostics.dot b/compiler/ui_tests/reflection/nonnull_is_supported/diagnostics.dot new file mode 100644 index 000000000..06000d196 --- /dev/null +++ b/compiler/ui_tests/reflection/nonnull_is_supported/diagnostics.dot @@ -0,0 +1,41 @@ +digraph "* * - 0" { + 0 [ label = "0| &pavex::router::AllowedMethods"] + 1 [ label = "1| crate::route_0::Next0(&'a pavex::router::AllowedMethods) -> crate::route_0::Next0<'a>"] + 2 [ label = "2| pavex::middleware::Next::new(crate::route_0::Next0<'a>) -> pavex::middleware::Next>"] + 3 [ label = "3| pavex::middleware::wrap_noop(pavex::middleware::Next>) -> pavex::Response"] + 4 [ label = "4| ::into_response(pavex::Response) -> pavex::Response"] + 2 -> 3 [ ] + 1 -> 2 [ ] + 3 -> 4 [ ] + 0 -> 1 [ ] +} + +digraph "* * - 1" { + 0 [ label = "0| &pavex::router::AllowedMethods"] + 1 [ label = "1| pavex::router::default_fallback(&pavex::router::AllowedMethods) -> pavex::Response"] + 2 [ label = "2| ::into_response(pavex::Response) -> pavex::Response"] + 1 -> 2 [ ] + 0 -> 1 [ ] +} + +digraph "GET / - 0" { + 0 [ label = "0| crate::route_1::Next0() -> crate::route_1::Next0"] + 1 [ label = "1| pavex::middleware::Next::new(crate::route_1::Next0) -> pavex::middleware::Next"] + 2 [ label = "2| pavex::middleware::wrap_noop(pavex::middleware::Next) -> pavex::Response"] + 3 [ label = "3| ::into_response(pavex::Response) -> pavex::Response"] + 1 -> 2 [ ] + 0 -> 1 [ ] + 2 -> 3 [ ] +} + +digraph "GET / - 1" { + 0 [ label = "0| app_5680a8e5::non_null() -> core::ptr::NonNull"] + 1 [ label = "1| app_5680a8e5::handler(core::ptr::NonNull) -> pavex::Response"] + 2 [ label = "2| ::into_response(pavex::Response) -> pavex::Response"] + 0 -> 1 [ ] + 1 -> 2 [ ] +} + +digraph app_state { + 0 [ label = "0| crate::ApplicationState() -> crate::ApplicationState"] +} diff --git a/compiler/ui_tests/reflection/nonnull_is_supported/expectations/app.rs b/compiler/ui_tests/reflection/nonnull_is_supported/expectations/app.rs new file mode 100644 index 000000000..0dbdea816 --- /dev/null +++ b/compiler/ui_tests/reflection/nonnull_is_supported/expectations/app.rs @@ -0,0 +1,172 @@ +//! Do NOT edit this code. +//! It was automatically generated by Pavex. +//! All manual edits will be lost next time the code is generated. +extern crate alloc; +struct ServerState { + router: Router, + #[allow(dead_code)] + application_state: ApplicationState, +} +#[derive(Debug, Clone, serde::Deserialize)] +pub struct ApplicationConfig {} +pub struct ApplicationState {} +impl ApplicationState { + pub async fn new( + _app_config: crate::ApplicationConfig, + ) -> Result { + Ok(Self::_new().await) + } + async fn _new() -> crate::ApplicationState { + crate::ApplicationState {} + } +} +#[derive(Debug, thiserror::Error)] +pub enum ApplicationStateError {} +pub fn run( + server_builder: pavex::server::Server, + application_state: ApplicationState, +) -> pavex::server::ServerHandle { + async fn handler( + request: http::Request, + connection_info: Option, + server_state: std::sync::Arc, + ) -> pavex::Response { + let (router, state) = (&server_state.router, &server_state.application_state); + router.route(request, connection_info, state).await + } + let router = Router::new(); + let server_state = std::sync::Arc::new(ServerState { + router, + application_state, + }); + server_builder.serve(handler, server_state) +} +struct Router { + router: matchit::Router, +} +impl Router { + /// Create a new router instance. + /// + /// This method is invoked once, when the server starts. + pub fn new() -> Self { + Self { router: Self::router() } + } + fn router() -> matchit::Router { + let mut router = matchit::Router::new(); + router.insert("/", 0u32).unwrap(); + router + } + pub async fn route( + &self, + request: http::Request, + _connection_info: Option, + #[allow(unused)] + state: &ApplicationState, + ) -> pavex::Response { + let (request_head, _) = request.into_parts(); + let request_head: pavex::request::RequestHead = request_head.into(); + let Ok(matched_route) = self.router.at(&request_head.target.path()) else { + let allowed_methods: pavex::router::AllowedMethods = pavex::router::MethodAllowList::from_iter( + vec![], + ) + .into(); + return route_0::entrypoint(&allowed_methods).await; + }; + match matched_route.value { + 0u32 => { + match &request_head.method { + &pavex::http::Method::GET => route_1::entrypoint().await, + _ => { + let allowed_methods: pavex::router::AllowedMethods = pavex::router::MethodAllowList::from_iter([ + pavex::http::Method::GET, + ]) + .into(); + route_0::entrypoint(&allowed_methods).await + } + } + } + i => unreachable!("Unknown route id: {}", i), + } + } +} +pub mod route_0 { + pub async fn entrypoint<'a>( + s_0: &'a pavex::router::AllowedMethods, + ) -> pavex::Response { + let response = wrapping_0(s_0).await; + response + } + async fn stage_1<'a>(s_0: &'a pavex::router::AllowedMethods) -> pavex::Response { + let response = handler(s_0).await; + response + } + async fn wrapping_0(v0: &pavex::router::AllowedMethods) -> pavex::Response { + let v1 = crate::route_0::Next0 { + s_0: v0, + next: stage_1, + }; + let v2 = pavex::middleware::Next::new(v1); + let v3 = pavex::middleware::wrap_noop(v2).await; + ::into_response(v3) + } + async fn handler(v0: &pavex::router::AllowedMethods) -> pavex::Response { + let v1 = pavex::router::default_fallback(v0).await; + ::into_response(v1) + } + struct Next0<'a, T> + where + T: std::future::Future, + { + s_0: &'a pavex::router::AllowedMethods, + next: fn(&'a pavex::router::AllowedMethods) -> T, + } + impl<'a, T> std::future::IntoFuture for Next0<'a, T> + where + T: std::future::Future, + { + type Output = pavex::Response; + type IntoFuture = T; + fn into_future(self) -> Self::IntoFuture { + (self.next)(self.s_0) + } + } +} +pub mod route_1 { + pub async fn entrypoint() -> pavex::Response { + let response = wrapping_0().await; + response + } + async fn stage_1() -> pavex::Response { + let response = handler().await; + response + } + async fn wrapping_0() -> pavex::Response { + let v0 = crate::route_1::Next0 { + next: stage_1, + }; + let v1 = pavex::middleware::Next::new(v0); + let v2 = pavex::middleware::wrap_noop(v1).await; + ::into_response(v2) + } + async fn handler() -> pavex::Response { + let v0 = app::non_null(); + let v1 = app::handler(v0); + ::into_response(v1) + } + struct Next0 + where + T: std::future::Future, + { + next: fn() -> T, + } + impl std::future::IntoFuture for Next0 + where + T: std::future::Future, + { + type Output = pavex::Response; + type IntoFuture = T; + fn into_future(self) -> Self::IntoFuture { + (self.next)() + } + } +} \ No newline at end of file diff --git a/compiler/ui_tests/reflection/nonnull_is_supported/expectations/diagnostics.dot b/compiler/ui_tests/reflection/nonnull_is_supported/expectations/diagnostics.dot new file mode 100644 index 000000000..030945e2b --- /dev/null +++ b/compiler/ui_tests/reflection/nonnull_is_supported/expectations/diagnostics.dot @@ -0,0 +1,37 @@ +digraph "* * - 0" { + 0 [ label = "0| &pavex::router::AllowedMethods"] + 1 [ label = "1| crate::route_0::Next0(&'a pavex::router::AllowedMethods) -> crate::route_0::Next0<'a>"] + 2 [ label = "2| pavex::middleware::Next::new(crate::route_0::Next0<'a>) -> pavex::middleware::Next>"] + 3 [ label = "3| pavex::middleware::wrap_noop(pavex::middleware::Next>) -> pavex::Response"] + 4 [ label = "4| ::into_response(pavex::Response) -> pavex::Response"] + 2 -> 3 [ ] + 1 -> 2 [ ] + 3 -> 4 [ ] + 0 -> 1 [ ] +} +digraph "* * - 1" { + 0 [ label = "0| &pavex::router::AllowedMethods"] + 1 [ label = "1| pavex::router::default_fallback(&pavex::router::AllowedMethods) -> pavex::Response"] + 2 [ label = "2| ::into_response(pavex::Response) -> pavex::Response"] + 1 -> 2 [ ] + 0 -> 1 [ ] +} +digraph "GET / - 0" { + 0 [ label = "0| crate::route_1::Next0() -> crate::route_1::Next0"] + 1 [ label = "1| pavex::middleware::Next::new(crate::route_1::Next0) -> pavex::middleware::Next"] + 2 [ label = "2| pavex::middleware::wrap_noop(pavex::middleware::Next) -> pavex::Response"] + 3 [ label = "3| ::into_response(pavex::Response) -> pavex::Response"] + 1 -> 2 [ ] + 0 -> 1 [ ] + 2 -> 3 [ ] +} +digraph "GET / - 1" { + 0 [ label = "0| app::non_null() -> core::ptr::NonNull"] + 1 [ label = "1| app::handler(core::ptr::NonNull) -> pavex::Response"] + 2 [ label = "2| ::into_response(pavex::Response) -> pavex::Response"] + 0 -> 1 [ ] + 1 -> 2 [ ] +} +digraph app_state { + 0 [ label = "0| crate::ApplicationState() -> crate::ApplicationState"] +} \ No newline at end of file diff --git a/compiler/ui_tests/reflection/nonnull_is_supported/generated_app/Cargo.toml b/compiler/ui_tests/reflection/nonnull_is_supported/generated_app/Cargo.toml new file mode 100644 index 000000000..b42477ae6 --- /dev/null +++ b/compiler/ui_tests/reflection/nonnull_is_supported/generated_app/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "application_5680a8e5" +version = "0.1.0" +edition = "2024" + +[package.metadata.px.generate] +generator_type = "cargo_workspace_binary" +generator_name = "app_5680a8e5" + +[dependencies] +app_5680a8e5 = { version = "0.1", path = "..", default-features = false } +http = { version = "1", default-features = false } +hyper = { version = "1", default-features = false } +matchit = { version = "0.9", default-features = false } +pavex = { version = "0.2", path = "../../../../../runtime/pavex", default-features = false } +serde = { version = "1", default-features = false } +thiserror = { version = "2", default-features = false } diff --git a/compiler/ui_tests/reflection/nonnull_is_supported/generated_app/src/Cargo.toml b/compiler/ui_tests/reflection/nonnull_is_supported/generated_app/src/Cargo.toml new file mode 100644 index 000000000..bbc754983 --- /dev/null +++ b/compiler/ui_tests/reflection/nonnull_is_supported/generated_app/src/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "application_5680a8e5" +version = "0.1.0" +edition = "2021" + +[package.metadata.px.generate] +generator_type = "cargo_workspace_binary" +generator_name = "app_5680a8e5" diff --git a/compiler/ui_tests/reflection/nonnull_is_supported/generated_app/src/lib.rs b/compiler/ui_tests/reflection/nonnull_is_supported/generated_app/src/lib.rs new file mode 100644 index 000000000..d668ac116 --- /dev/null +++ b/compiler/ui_tests/reflection/nonnull_is_supported/generated_app/src/lib.rs @@ -0,0 +1,172 @@ +//! Do NOT edit this code. +//! It was automatically generated by Pavex. +//! All manual edits will be lost next time the code is generated. +extern crate alloc; +struct ServerState { + router: Router, + #[allow(dead_code)] + application_state: ApplicationState, +} +#[derive(Debug, Clone, serde::Deserialize)] +pub struct ApplicationConfig {} +pub struct ApplicationState {} +impl ApplicationState { + pub async fn new( + _app_config: crate::ApplicationConfig, + ) -> Result { + Ok(Self::_new().await) + } + async fn _new() -> crate::ApplicationState { + crate::ApplicationState {} + } +} +#[derive(Debug, thiserror::Error)] +pub enum ApplicationStateError {} +pub fn run( + server_builder: pavex::server::Server, + application_state: ApplicationState, +) -> pavex::server::ServerHandle { + async fn handler( + request: http::Request, + connection_info: Option, + server_state: std::sync::Arc, + ) -> pavex::Response { + let (router, state) = (&server_state.router, &server_state.application_state); + router.route(request, connection_info, state).await + } + let router = Router::new(); + let server_state = std::sync::Arc::new(ServerState { + router, + application_state, + }); + server_builder.serve(handler, server_state) +} +struct Router { + router: matchit::Router, +} +impl Router { + /// Create a new router instance. + /// + /// This method is invoked once, when the server starts. + pub fn new() -> Self { + Self { router: Self::router() } + } + fn router() -> matchit::Router { + let mut router = matchit::Router::new(); + router.insert("/", 0u32).unwrap(); + router + } + pub async fn route( + &self, + request: http::Request, + _connection_info: Option, + #[allow(unused)] + state: &ApplicationState, + ) -> pavex::Response { + let (request_head, _) = request.into_parts(); + let request_head: pavex::request::RequestHead = request_head.into(); + let Ok(matched_route) = self.router.at(&request_head.target.path()) else { + let allowed_methods: pavex::router::AllowedMethods = pavex::router::MethodAllowList::from_iter( + vec![], + ) + .into(); + return route_0::entrypoint(&allowed_methods).await; + }; + match matched_route.value { + 0u32 => { + match &request_head.method { + &pavex::http::Method::GET => route_1::entrypoint().await, + _ => { + let allowed_methods: pavex::router::AllowedMethods = pavex::router::MethodAllowList::from_iter([ + pavex::http::Method::GET, + ]) + .into(); + route_0::entrypoint(&allowed_methods).await + } + } + } + i => unreachable!("Unknown route id: {}", i), + } + } +} +pub mod route_0 { + pub async fn entrypoint<'a>( + s_0: &'a pavex::router::AllowedMethods, + ) -> pavex::Response { + let response = wrapping_0(s_0).await; + response + } + async fn stage_1<'a>(s_0: &'a pavex::router::AllowedMethods) -> pavex::Response { + let response = handler(s_0).await; + response + } + async fn wrapping_0(v0: &pavex::router::AllowedMethods) -> pavex::Response { + let v1 = crate::route_0::Next0 { + s_0: v0, + next: stage_1, + }; + let v2 = pavex::middleware::Next::new(v1); + let v3 = pavex::middleware::wrap_noop(v2).await; + ::into_response(v3) + } + async fn handler(v0: &pavex::router::AllowedMethods) -> pavex::Response { + let v1 = pavex::router::default_fallback(v0).await; + ::into_response(v1) + } + struct Next0<'a, T> + where + T: std::future::Future, + { + s_0: &'a pavex::router::AllowedMethods, + next: fn(&'a pavex::router::AllowedMethods) -> T, + } + impl<'a, T> std::future::IntoFuture for Next0<'a, T> + where + T: std::future::Future, + { + type Output = pavex::Response; + type IntoFuture = T; + fn into_future(self) -> Self::IntoFuture { + (self.next)(self.s_0) + } + } +} +pub mod route_1 { + pub async fn entrypoint() -> pavex::Response { + let response = wrapping_0().await; + response + } + async fn stage_1() -> pavex::Response { + let response = handler().await; + response + } + async fn wrapping_0() -> pavex::Response { + let v0 = crate::route_1::Next0 { + next: stage_1, + }; + let v1 = pavex::middleware::Next::new(v0); + let v2 = pavex::middleware::wrap_noop(v1).await; + ::into_response(v2) + } + async fn handler() -> pavex::Response { + let v0 = app_5680a8e5::non_null(); + let v1 = app_5680a8e5::handler(v0); + ::into_response(v1) + } + struct Next0 + where + T: std::future::Future, + { + next: fn() -> T, + } + impl std::future::IntoFuture for Next0 + where + T: std::future::Future, + { + type Output = pavex::Response; + type IntoFuture = T; + fn into_future(self) -> Self::IntoFuture { + (self.next)() + } + } +} diff --git a/compiler/ui_tests/reflection/nonnull_is_supported/src/lib.rs b/compiler/ui_tests/reflection/nonnull_is_supported/src/lib.rs new file mode 100644 index 000000000..e2dd7f72e --- /dev/null +++ b/compiler/ui_tests/reflection/nonnull_is_supported/src/lib.rs @@ -0,0 +1,20 @@ +use std::ptr::NonNull; + +use pavex::{blueprint::from, Blueprint}; + +#[pavex::request_scoped] +pub fn non_null() -> NonNull { + todo!() +} + +#[pavex::get(path = "/")] +pub fn handler(_n: NonNull) -> pavex::Response { + todo!() +} + +pub fn blueprint() -> Blueprint { + let mut bp = Blueprint::new(); + bp.import(from![crate]); + bp.routes(from![crate]); + bp +} diff --git a/compiler/ui_tests/reflection/nonnull_is_supported/src/main.rs b/compiler/ui_tests/reflection/nonnull_is_supported/src/main.rs new file mode 100644 index 000000000..3bf472b51 --- /dev/null +++ b/compiler/ui_tests/reflection/nonnull_is_supported/src/main.rs @@ -0,0 +1,24 @@ +//! This code is generated by `pavex_test_runner`, +//! Do NOT modify it manually. +use app_5680a8e5::blueprint; +use pavex_cli_client::{Client, config::Color}; +use pavex_cli_client::commands::generate::GenerateError; + +fn main() -> Result<(), Box> { + let ui_test_dir: std::path::PathBuf = std::env::var("UI_TEST_DIR").unwrap().into(); + let outcome = Client::new() + .color(Color::Always) + .pavex_cli_path(std::env::var("PAVEX_TEST_CLI_PATH").unwrap().into()) + .generate(blueprint(), ui_test_dir.join("generated_app")) + .diagnostics_path("diagnostics.dot".into()) + .execute(); + match outcome { + Ok(_) => {}, + Err(GenerateError::NonZeroExitCode(_)) => { std::process::exit(1); } + Err(e) => { + eprintln!("Failed to invoke `pavex generate`.\n{:?}", e); + std::process::exit(1); + } + } + Ok(()) +} diff --git a/compiler/ui_tests/reflection/nonnull_is_supported/test_config.toml b/compiler/ui_tests/reflection/nonnull_is_supported/test_config.toml new file mode 100644 index 000000000..e4b7f898c --- /dev/null +++ b/compiler/ui_tests/reflection/nonnull_is_supported/test_config.toml @@ -0,0 +1,4 @@ +description = "pavex supports NonNull pointer types as input parameters" + +[expectations] +codegen = "pass" diff --git a/rustdoc/rustdoc_processor/src/queries/mod.rs b/rustdoc/rustdoc_processor/src/queries/mod.rs index 5b970e184..e70c2bed2 100644 --- a/rustdoc/rustdoc_processor/src/queries/mod.rs +++ b/rustdoc/rustdoc_processor/src/queries/mod.rs @@ -113,6 +113,21 @@ impl Crate { } } + // Also index local items under their definition path from krate.paths. + // This handles types defined in private modules (e.g. core::ptr::non_null::NonNull) + // that are publicly re-exported under a shorter path (core::ptr::NonNull). + // The local crate's rustdoc JSON records the definition path, so we need + // both paths to be resolvable. + for (id, summary) in krate.paths.iter() { + if summary.crate_id() == 0 + && !matches!(summary.kind(), ItemKind::Macro | ItemKind::ProcDerive) + && import_index.items.contains_key(&id) + { + let path = summary.path().into_owned(); + import_path2id.entry(path).or_insert(id); + } + } + Crate::new( CrateCore { package_id, krate }, ImportPath2Id::Eager(EagerImportPath2Id(import_path2id)),