diff --git a/compiler/pavexc/src/compiler/codegen/mod.rs b/compiler/pavexc/src/compiler/codegen/mod.rs index 20bb9ed08..0e2dc22dd 100644 --- a/compiler/pavexc/src/compiler/codegen/mod.rs +++ b/compiler/pavexc/src/compiler/codegen/mod.rs @@ -464,7 +464,7 @@ fn collect_type_package_ids(package_ids: &mut IndexSet, t: &Type) { for generic in &t.generic_arguments { match generic { GenericArgument::TypeParameter(t) => collect_type_package_ids(package_ids, t), - GenericArgument::Lifetime(_) => {} + GenericArgument::Lifetime(_) | GenericArgument::Const(_) => {} } } } diff --git a/compiler/pavexc/src/compiler/framework_rustdoc.rs b/compiler/pavexc/src/compiler/framework_rustdoc.rs index d26618d8a..0ab79684c 100644 --- a/compiler/pavexc/src/compiler/framework_rustdoc.rs +++ b/compiler/pavexc/src/compiler/framework_rustdoc.rs @@ -167,8 +167,16 @@ pub(crate) fn resolve_type_path(raw_path: &str, krate_collection: &CrateCollecti })) } } - rustdoc_types::GenericParamDefKind::Const { .. } => { - unimplemented!("const generic parameters are not supported yet") + rustdoc_types::GenericParamDefKind::Const { default, .. } => { + if let Some(default) = default { + GenericArgument::Const(rustdoc_ir::ConstGenericArgument { + value: default.clone(), + }) + } else { + GenericArgument::Const(rustdoc_ir::ConstGenericArgument { + value: generic_def.name.clone(), + }) + } } }; generic_arguments.push(arg); diff --git a/compiler/ui_tests/Cargo.toml b/compiler/ui_tests/Cargo.toml index 917dcfe54..9e7e6e199 100644 --- a/compiler/ui_tests/Cargo.toml +++ b/compiler/ui_tests/Cargo.toml @@ -295,6 +295,8 @@ members = [ "reflection/bitflags_are_supported/generated_app", "reflection/common_response_types_are_supported", "reflection/common_response_types_are_supported/generated_app", + "reflection/const_generics_are_supported", + "reflection/const_generics_are_supported/generated_app", "reflection/crate_resolution/dependencies_can_register_local_items", "reflection/crate_resolution/dependencies_can_register_local_items/generated_app", "reflection/crate_resolution/multiple_versions_for_the_same_crate_are_supported", diff --git a/compiler/ui_tests/reflection/const_generics_are_supported/Cargo.toml b/compiler/ui_tests/reflection/const_generics_are_supported/Cargo.toml new file mode 100644 index 000000000..485b90e92 --- /dev/null +++ b/compiler/ui_tests/reflection/const_generics_are_supported/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "app_e22d4bbe" +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/const_generics_are_supported/diagnostics.dot b/compiler/ui_tests/reflection/const_generics_are_supported/diagnostics.dot new file mode 100644 index 000000000..29fd5e55d --- /dev/null +++ b/compiler/ui_tests/reflection/const_generics_are_supported/diagnostics.dot @@ -0,0 +1,45 @@ +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_e22d4bbe::character() -> app_e22d4bbe::CharConst<'a'>"] + 1 [ label = "1| app_e22d4bbe::boolean() -> app_e22d4bbe::BoolConst"] + 2 [ label = "2| app_e22d4bbe::numeric() -> app_e22d4bbe::NumericConst<8>"] + 3 [ label = "3| app_e22d4bbe::handler(app_e22d4bbe::NumericConst<8>, app_e22d4bbe::BoolConst, app_e22d4bbe::CharConst<'a'>) -> pavex::Response"] + 4 [ label = "4| ::into_response(pavex::Response) -> pavex::Response"] + 0 -> 3 [ ] + 1 -> 3 [ ] + 2 -> 3 [ ] + 3 -> 4 [ ] +} + +digraph app_state { + 0 [ label = "0| crate::ApplicationState() -> crate::ApplicationState"] +} diff --git a/compiler/ui_tests/reflection/const_generics_are_supported/expectations/app.rs b/compiler/ui_tests/reflection/const_generics_are_supported/expectations/app.rs new file mode 100644 index 000000000..fd8802fa6 --- /dev/null +++ b/compiler/ui_tests/reflection/const_generics_are_supported/expectations/app.rs @@ -0,0 +1,174 @@ +//! 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::character(); + let v1 = app::boolean(); + let v2 = app::numeric(); + let v3 = app::handler(v2, v1, v0); + ::into_response(v3) + } + 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/const_generics_are_supported/expectations/diagnostics.dot b/compiler/ui_tests/reflection/const_generics_are_supported/expectations/diagnostics.dot new file mode 100644 index 000000000..dcb8e1918 --- /dev/null +++ b/compiler/ui_tests/reflection/const_generics_are_supported/expectations/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::character() -> app::CharConst<'a'>"] + 1 [ label = "1| app::boolean() -> app::BoolConst"] + 2 [ label = "2| app::numeric() -> app::NumericConst<8>"] + 3 [ label = "3| app::handler(app::NumericConst<8>, app::BoolConst, app::CharConst<'a'>) -> pavex::Response"] + 4 [ label = "4| ::into_response(pavex::Response) -> pavex::Response"] + 0 -> 3 [ ] + 1 -> 3 [ ] + 2 -> 3 [ ] + 3 -> 4 [ ] +} +digraph app_state { + 0 [ label = "0| crate::ApplicationState() -> crate::ApplicationState"] +} \ No newline at end of file diff --git a/compiler/ui_tests/reflection/const_generics_are_supported/generated_app/Cargo.toml b/compiler/ui_tests/reflection/const_generics_are_supported/generated_app/Cargo.toml new file mode 100644 index 000000000..d5accecd0 --- /dev/null +++ b/compiler/ui_tests/reflection/const_generics_are_supported/generated_app/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "application_e22d4bbe" +version = "0.1.0" +edition = "2024" + +[package.metadata.px.generate] +generator_type = "cargo_workspace_binary" +generator_name = "app_e22d4bbe" + +[dependencies] +app_e22d4bbe = { 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/const_generics_are_supported/generated_app/src/lib.rs b/compiler/ui_tests/reflection/const_generics_are_supported/generated_app/src/lib.rs new file mode 100644 index 000000000..1ad3b68c5 --- /dev/null +++ b/compiler/ui_tests/reflection/const_generics_are_supported/generated_app/src/lib.rs @@ -0,0 +1,174 @@ +//! 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_e22d4bbe::character(); + let v1 = app_e22d4bbe::boolean(); + let v2 = app_e22d4bbe::numeric(); + let v3 = app_e22d4bbe::handler(v2, v1, v0); + ::into_response(v3) + } + 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/const_generics_are_supported/src/lib.rs b/compiler/ui_tests/reflection/const_generics_are_supported/src/lib.rs new file mode 100644 index 000000000..1c5e1db56 --- /dev/null +++ b/compiler/ui_tests/reflection/const_generics_are_supported/src/lib.rs @@ -0,0 +1,36 @@ +use pavex::{blueprint::from, Blueprint}; + +pub struct NumericConst; +pub struct BoolConst; +pub struct CharConst; + +#[pavex::request_scoped] +pub fn numeric() -> NumericConst<8> { + NumericConst +} + +#[pavex::request_scoped] +pub fn boolean() -> BoolConst { + BoolConst +} + +#[pavex::request_scoped] +pub fn character() -> CharConst<'a'> { + CharConst +} + +#[pavex::get(path = "/")] +pub fn handler( + _n: NumericConst<8>, + _b: BoolConst, + _c: CharConst<'a'>, +) -> 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/const_generics_are_supported/src/main.rs b/compiler/ui_tests/reflection/const_generics_are_supported/src/main.rs new file mode 100644 index 000000000..d3726b336 --- /dev/null +++ b/compiler/ui_tests/reflection/const_generics_are_supported/src/main.rs @@ -0,0 +1,24 @@ +//! This code is generated by `pavex_test_runner`, +//! Do NOT modify it manually. +use app_e22d4bbe::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/const_generics_are_supported/test_config.toml b/compiler/ui_tests/reflection/const_generics_are_supported/test_config.toml new file mode 100644 index 000000000..87d1d0a78 --- /dev/null +++ b/compiler/ui_tests/reflection/const_generics_are_supported/test_config.toml @@ -0,0 +1,4 @@ +description = "pavex supports types with concrete const generic parameters" + +[expectations] +codegen = "pass" diff --git a/rustdoc/rustdoc_ir/src/generic_argument.rs b/rustdoc/rustdoc_ir/src/generic_argument.rs index 52decbfee..a196cc52c 100644 --- a/rustdoc/rustdoc_ir/src/generic_argument.rs +++ b/rustdoc/rustdoc_ir/src/generic_argument.rs @@ -14,6 +14,15 @@ pub enum GenericArgument { TypeParameter(Type), /// A lifetime parameter, e.g. `'a` in `Cow<'a, str>`. Lifetime(GenericLifetimeParameter), + /// A const generic argument with a concrete evaluated value, e.g. `8` in `Size<8>`. + Const(ConstGenericArgument), +} + +/// A const generic argument with a concrete evaluated value, e.g. `8` in `Size<8>`. +#[derive(serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash, Clone)] +pub struct ConstGenericArgument { + /// The evaluated value as a string, e.g. "8", "true", "'a'". + pub value: String, } /// A lifetime used as a generic argument—e.g. `'a` in `Cow<'a, str>`. @@ -103,6 +112,9 @@ impl GenericArgument { } }, }, + GenericArgument::Const(c) => { + write!(buffer, "{}", c.value).unwrap(); + } } } } @@ -122,6 +134,7 @@ impl Display for GenericArgument { match self { GenericArgument::TypeParameter(t) => write!(f, "{t}"), GenericArgument::Lifetime(l) => write!(f, "{l}"), + GenericArgument::Const(c) => write!(f, "{}", c.value), } } } @@ -131,6 +144,7 @@ impl Debug for GenericArgument { match self { GenericArgument::TypeParameter(r) => write!(f, "{r:?}"), GenericArgument::Lifetime(l) => write!(f, "{l:?}"), + GenericArgument::Const(c) => write!(f, "{}", c.value), } } } diff --git a/rustdoc/rustdoc_ir/src/lib.rs b/rustdoc/rustdoc_ir/src/lib.rs index 1a31eb1f0..ac2392948 100644 --- a/rustdoc/rustdoc_ir/src/lib.rs +++ b/rustdoc/rustdoc_ir/src/lib.rs @@ -27,7 +27,7 @@ pub use callable_path::{ }; pub use function_pointer::{FunctionPointer, FunctionPointerInput}; pub use generic::Generic; -pub use generic_argument::{GenericArgument, GenericLifetimeParameter}; +pub use generic_argument::{ConstGenericArgument, GenericArgument, GenericLifetimeParameter}; pub use lifetime::Lifetime; pub use named_lifetime::NamedLifetime; pub use path_type::PathType; diff --git a/rustdoc/rustdoc_ir/src/path_type.rs b/rustdoc/rustdoc_ir/src/path_type.rs index a14015f11..cf2c43355 100644 --- a/rustdoc/rustdoc_ir/src/path_type.rs +++ b/rustdoc/rustdoc_ir/src/path_type.rs @@ -108,7 +108,17 @@ impl PathType { (Lifetime(_), Lifetime(_)) => { // Lifetimes are not relevant for specialization (yet). } - (TypeParameter(_), Lifetime(_)) | (Lifetime(_), TypeParameter(_)) => { + (Const(a), Const(b)) => { + if a != b { + return false; + } + } + (TypeParameter(_), Lifetime(_)) + | (Lifetime(_), TypeParameter(_)) + | (Const(_), TypeParameter(_)) + | (TypeParameter(_), Const(_)) + | (Const(_), Lifetime(_)) + | (Lifetime(_), Const(_)) => { return false; } } diff --git a/rustdoc/rustdoc_ir/src/type_.rs b/rustdoc/rustdoc_ir/src/type_.rs index cc3b34483..4de92c534 100644 --- a/rustdoc/rustdoc_ir/src/type_.rs +++ b/rustdoc/rustdoc_ir/src/type_.rs @@ -69,7 +69,9 @@ impl Type { GenericArgument::TypeParameter(t) => { GenericArgument::TypeParameter(t.bind_generic_type_parameters(bindings)) } - GenericArgument::Lifetime(_) => generic.to_owned(), + GenericArgument::Lifetime(_) | GenericArgument::Const(_) => { + generic.to_owned() + } }; bound_generics.push(bound_generic); } @@ -149,6 +151,7 @@ impl Type { | GenericLifetimeParameter::Named(_) | GenericLifetimeParameter::Inferred, ) => false, + GenericArgument::Const(_) => false, }) } Type::Reference(r) => r.inner.is_a_template(), @@ -182,7 +185,7 @@ impl Type { GenericArgument::TypeParameter(g) => { g._unassigned_generic_type_parameters(set); } - GenericArgument::Lifetime(_) => {} + GenericArgument::Lifetime(_) | GenericArgument::Const(_) => {} } } } @@ -432,6 +435,7 @@ impl Type { GenericArgument::Lifetime( GenericLifetimeParameter::Named(_) | GenericLifetimeParameter::Static, ) => false, + GenericArgument::Const(_) => false, }) } Type::Reference(r) => { @@ -483,6 +487,7 @@ impl Type { GenericArgument::TypeParameter(t) => { t.set_implicit_lifetimes(inferred_lifetime.clone()); } + GenericArgument::Const(_) => {} } } } @@ -537,6 +542,7 @@ impl Type { *l = GenericLifetimeParameter::from_name(new_name.clone()); } } + GenericArgument::Const(_) => {} } } } @@ -595,6 +601,7 @@ impl Type { GenericArgument::Lifetime(l) => { set.insert(l.clone().into()); } + GenericArgument::Const(_) => {} } } } @@ -643,6 +650,7 @@ impl Type { GenericArgument::Lifetime(GenericLifetimeParameter::Named(l)) => { set.insert(l.as_str().to_owned()); } + GenericArgument::Const(_) => {} } } } @@ -767,6 +775,7 @@ impl Type { GenericArgument::Lifetime(l) => GenericArgument::Lifetime( canonicalize_generic_lifetime(l, lifetime_counter), ), + GenericArgument::Const(_) => arg.clone(), }) .collect(); let path = PathType { diff --git a/rustdoc/rustdoc_resolver/src/lib.rs b/rustdoc/rustdoc_resolver/src/lib.rs index bbb82125d..5a927a794 100644 --- a/rustdoc/rustdoc_resolver/src/lib.rs +++ b/rustdoc/rustdoc_resolver/src/lib.rs @@ -29,6 +29,8 @@ pub struct GenericBindings { pub lifetimes: HashMap, /// Mapping from type parameter names to their resolved types. pub types: HashMap, + /// Mapping from const parameter names to their evaluated values. + pub consts: HashMap, } impl std::fmt::Debug for GenericBindings { @@ -48,6 +50,13 @@ impl std::fmt::Debug for GenericBindings { } write!(f, "}}, ")?; } + if !self.consts.is_empty() { + write!(f, "consts: {{ ")?; + for (name, value) in &self.consts { + writeln!(f, "{name} -> {value}, ")?; + } + write!(f, "}}, ")?; + } write!(f, "}}") } } diff --git a/rustdoc/rustdoc_resolver/src/method.rs b/rustdoc/rustdoc_resolver/src/method.rs index 58e0f9fa9..9b67b977e 100644 --- a/rustdoc/rustdoc_resolver/src/method.rs +++ b/rustdoc/rustdoc_resolver/src/method.rs @@ -118,8 +118,14 @@ pub fn rustdoc_method2callable( }; GenericArgument::TypeParameter(t) } - rustdoc_types::GenericArg::Const(_) => { - todo!() + rustdoc_types::GenericArg::Const(constant) => { + let raw = constant.value.as_ref().unwrap_or(&constant.expr); + let value = generic_bindings + .consts + .get(raw) + .cloned() + .unwrap_or_else(|| raw.clone()); + GenericArgument::Const(rustdoc_ir::ConstGenericArgument { value }) } rustdoc_types::GenericArg::Infer => { unreachable!() diff --git a/rustdoc/rustdoc_resolver/src/resolve_type.rs b/rustdoc/rustdoc_resolver/src/resolve_type.rs index 8426181e8..642be2e30 100644 --- a/rustdoc/rustdoc_resolver/src/resolve_type.rs +++ b/rustdoc/rustdoc_resolver/src/resolve_type.rs @@ -7,7 +7,7 @@ use once_cell::sync::OnceCell; use rustdoc_types::{GenericArg, GenericArgs, GenericParamDefKind, ItemEnum, Type as RustdocType}; use rustdoc_ir::{ - Array, FunctionPointer, FunctionPointerInput, Generic, GenericArgument, + Array, ConstGenericArgument, FunctionPointer, FunctionPointerInput, Generic, GenericArgument, GenericLifetimeParameter, PathType, RawPointer, Slice, Tuple, Type, TypeReference, }; use rustdoc_processor::CrateCollection; @@ -217,12 +217,25 @@ fn _resolve_type( GenericLifetimeParameter::from_name(lifetime), )); } - GenericParamDefKind::Const { .. } => { - return Err(TypeResolutionErrorDetails::UnsupportedConstGeneric( - UnsupportedConstGeneric { - name: generic_param_def.name.to_owned(), - }, - )); + GenericParamDefKind::Const { default, .. } => { + let provided_arg = generic_args.and_then(|v| v.get(i)); + let value = if let Some(GenericArg::Const(constant)) = provided_arg { + let raw = constant.value.as_ref().unwrap_or(&constant.expr); + generic_bindings + .consts + .get(raw) + .cloned() + .unwrap_or_else(|| raw.clone()) + } else if let Some(default) = default { + default.clone() + } else { + generic_param_def.name.clone() + }; + alias_generic_bindings + .consts + .insert(generic_param_def.name.to_string(), value.clone()); + resolved_alias_generics + .push(GenericArgument::Const(ConstGenericArgument { value })); } } } @@ -364,14 +377,26 @@ fn _resolve_type( .insert(arg_def.name.clone(), resolved.clone()); GenericArgument::TypeParameter(resolved) } - GenericParamDefKind::Const { .. } => { - return Err( - TypeResolutionErrorDetails::UnsupportedConstGeneric( - UnsupportedConstGeneric { - name: arg_def.name.to_owned(), - }, - ), - ); + GenericParamDefKind::Const { default, .. } => { + let value = if let Some(GenericArg::Const(constant)) = + args.get(i) + { + let raw = + constant.value.as_ref().unwrap_or(&constant.expr); + local_generic_bindings + .consts + .get(raw) + .cloned() + .unwrap_or_else(|| raw.clone()) + } else if let Some(default) = default { + default.clone() + } else { + arg_def.name.clone() + }; + local_generic_bindings + .consts + .insert(arg_def.name.clone(), value.clone()); + GenericArgument::Const(ConstGenericArgument { value }) } }; generics.push(generic_argument); diff --git a/rustdoc/rustdoc_resolver/src/type_def.rs b/rustdoc/rustdoc_resolver/src/type_def.rs index 81a880c94..08d275c84 100644 --- a/rustdoc/rustdoc_resolver/src/type_def.rs +++ b/rustdoc/rustdoc_resolver/src/type_def.rs @@ -3,7 +3,9 @@ use rustdoc_types::{Item, ItemEnum}; use rustdoc_ext::RustdocKindExt; -use rustdoc_ir::{Generic, GenericArgument, GenericLifetimeParameter, PathType, Type}; +use rustdoc_ir::{ + ConstGenericArgument, Generic, GenericArgument, GenericLifetimeParameter, PathType, Type, +}; use rustdoc_processor::CrateCollection; use rustdoc_processor::indexing::CrateIndexer; use rustdoc_processor::queries::Crate; @@ -46,7 +48,11 @@ pub fn rustdoc_new_type_def2type( name: arg.name.clone(), })) } - rustdoc_types::GenericParamDefKind::Const { .. } => todo!(), + rustdoc_types::GenericParamDefKind::Const { .. } => { + GenericArgument::Const(ConstGenericArgument { + value: arg.name.clone(), + }) + } }; generic_arguments.push(arg); }