From 1f973067a5a6dfac6a6194a76df8a6191494953d Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:57:50 +0100 Subject: [PATCH 1/3] feat: Support raw pointers --- .../analyses/application_state/cloning.rs | 3 +- .../analyses/application_state/mod.rs | 8 +++ .../analyses/processing_pipeline/codegen.rs | 3 +- .../analyses/processing_pipeline/pipeline.rs | 2 + compiler/pavexc/src/compiler/codegen/mod.rs | 3 + compiler/pavexc/src/compiler/codegen/state.rs | 3 +- .../pavexc/src/compiler/path_parameters.rs | 1 + compiler/pavexc/src/compiler/resolvers.rs | 23 ++++++-- compiler/pavexc/src/compiler/traits.rs | 10 ++++ compiler/pavexc/src/language/fq_path.rs | 56 ++++++++++++++++++- compiler/pavexc/src/language/mod.rs | 2 +- rustdoc/rustdoc_ir/src/lib.rs | 4 ++ rustdoc/rustdoc_ir/src/raw_pointer.rs | 23 ++++++++ rustdoc/rustdoc_ir/src/render.rs | 8 +++ rustdoc/rustdoc_ir/src/type_.rs | 42 +++++++++++++- 15 files changed, 179 insertions(+), 12 deletions(-) create mode 100644 rustdoc/rustdoc_ir/src/raw_pointer.rs diff --git a/compiler/pavexc/src/compiler/analyses/application_state/cloning.rs b/compiler/pavexc/src/compiler/analyses/application_state/cloning.rs index 26c45a3e3..46bff8417 100644 --- a/compiler/pavexc/src/compiler/analyses/application_state/cloning.rs +++ b/compiler/pavexc/src/compiler/analyses/application_state/cloning.rs @@ -55,7 +55,8 @@ pub(crate) fn runtime_singletons_can_be_cloned_if_needed<'a>( match type_ { Type::ScalarPrimitive(_) | Type::Reference(_) - | Type::Slice(_) => { + | Type::Slice(_) + | Type::RawPointer(_) => { return None; } Type::Path(_) | Type::Tuple(_) => {} diff --git a/compiler/pavexc/src/compiler/analyses/application_state/mod.rs b/compiler/pavexc/src/compiler/analyses/application_state/mod.rs index 581d59194..bfcb59598 100644 --- a/compiler/pavexc/src/compiler/analyses/application_state/mod.rs +++ b/compiler/pavexc/src/compiler/analyses/application_state/mod.rs @@ -296,6 +296,14 @@ fn _field_name_candidate(ty_: &Type, strategy: NamingStrategy, candidate: &mut S // Same reasoning as for references. _field_name_candidate(&slice.element_type, strategy, candidate); } + Type::RawPointer(raw_pointer) => { + if raw_pointer.is_mutable { + candidate.push_str("mut_ptr_"); + } else { + candidate.push_str("const_ptr_"); + } + _field_name_candidate(&raw_pointer.inner, strategy, candidate); + } Type::Generic(generic) => { // We don't have unassigned generics in the application state, so this should never happen. // But, should it happen, there's really no other way to name it. diff --git a/compiler/pavexc/src/compiler/analyses/processing_pipeline/codegen.rs b/compiler/pavexc/src/compiler/analyses/processing_pipeline/codegen.rs index 7908207ee..1a88105ae 100644 --- a/compiler/pavexc/src/compiler/analyses/processing_pipeline/codegen.rs +++ b/compiler/pavexc/src/compiler/analyses/processing_pipeline/codegen.rs @@ -393,7 +393,8 @@ impl CodegenedRequestHandlerPipeline { Type::Slice(_) | Type::Path(_) | Type::Tuple(_) - | Type::ScalarPrimitive(_) => type_, + | Type::ScalarPrimitive(_) + | Type::RawPointer(_) => type_, Type::Generic(_) => { unreachable!("Generic types should have been resolved by now") } diff --git a/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs b/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs index f6890fd9c..37a983259 100644 --- a/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs +++ b/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs @@ -516,6 +516,8 @@ impl RequestHandlerPipeline { Type::ScalarPrimitive(_) => { continue; } + // Raw pointers are trivially `Copy`. + Type::RawPointer(_) | // We'd never encounter a raw slice as input type. Type::Slice(_) | // All types are concrete at this stage. diff --git a/compiler/pavexc/src/compiler/codegen/mod.rs b/compiler/pavexc/src/compiler/codegen/mod.rs index 31e1037ec..a25db0143 100644 --- a/compiler/pavexc/src/compiler/codegen/mod.rs +++ b/compiler/pavexc/src/compiler/codegen/mod.rs @@ -477,6 +477,9 @@ fn collect_type_package_ids(package_ids: &mut IndexSet, t: &Type) { Type::Slice(s) => { collect_type_package_ids(package_ids, &s.element_type); } + Type::RawPointer(r) => { + collect_type_package_ids(package_ids, &r.inner); + } Type::Generic(_) | Type::ScalarPrimitive(_) => {} } } diff --git a/compiler/pavexc/src/compiler/codegen/state.rs b/compiler/pavexc/src/compiler/codegen/state.rs index b88cbff00..9ccd4e960 100644 --- a/compiler/pavexc/src/compiler/codegen/state.rs +++ b/compiler/pavexc/src/compiler/codegen/state.rs @@ -134,7 +134,8 @@ pub(super) fn get_application_state_new( Type::Slice(_) | Type::Path(_) | Type::Tuple(_) - | Type::ScalarPrimitive(_) => type_, + | Type::ScalarPrimitive(_) + | Type::RawPointer(_) => type_, Type::Generic(_) => { unreachable!("Generic types should have been resolved by now") } diff --git a/compiler/pavexc/src/compiler/path_parameters.rs b/compiler/pavexc/src/compiler/path_parameters.rs index 82b33707a..82fe39a87 100644 --- a/compiler/pavexc/src/compiler/path_parameters.rs +++ b/compiler/pavexc/src/compiler/path_parameters.rs @@ -292,6 +292,7 @@ fn must_be_a_plain_struct( Type::Tuple(t) => format!("`{t:?}` is a tuple"), Type::ScalarPrimitive(s) => format!("`{s:?}` is a primitive"), Type::Slice(s) => format!("`{s:?}` is a slice"), + Type::RawPointer(r) => format!("`{r:?}` is a raw pointer"), Type::Generic(_) => { unreachable!() } diff --git a/compiler/pavexc/src/compiler/resolvers.rs b/compiler/pavexc/src/compiler/resolvers.rs index ff1bca045..b2063e76f 100644 --- a/compiler/pavexc/src/compiler/resolvers.rs +++ b/compiler/pavexc/src/compiler/resolvers.rs @@ -13,7 +13,7 @@ use tracing_log_error::log_error; use crate::language::{ Callable, CallableItem, FQGenericArgument, FQPath, FQPathSegment, FQPathType, Generic, GenericArgument, GenericLifetimeParameter, InvocationStyle, PathType, - Type, Slice, Tuple, TypeReference, UnknownPath, UnknownPrimitive, + RawPointer, Type, Slice, Tuple, TypeReference, UnknownPath, UnknownPrimitive, }; use crate::rustdoc::{CannotGetCrateData, CrateCollection, ResolvedItem}; use rustdoc_ext::RustdocKindExt; @@ -595,11 +595,22 @@ pub(crate) fn _resolve_type( kind: "inferred type", }, )), - RustdocType::RawPointer { .. } => Err(TypeResolutionErrorDetails::UnsupportedTypeKind( - UnsupportedTypeKind { - kind: "raw pointer", - }, - )), + RustdocType::RawPointer { is_mutable, type_ } => { + let resolved = + resolve_type(type_, used_by_package_id, krate_collection, generic_bindings) + .map_err(|source| { + TypeResolutionErrorDetails::TypePartResolutionError(Box::new( + TypePartResolutionError { + role: "pointee type".into(), + source, + }, + )) + })?; + Ok(Type::RawPointer(RawPointer { + is_mutable: *is_mutable, + inner: Box::new(resolved), + })) + } RustdocType::QualifiedPath { .. } => Err(TypeResolutionErrorDetails::UnsupportedTypeKind( UnsupportedTypeKind { kind: "qualified path", diff --git a/compiler/pavexc/src/compiler/traits.rs b/compiler/pavexc/src/compiler/traits.rs index 8adfe0ed2..1ead8a7b3 100644 --- a/compiler/pavexc/src/compiler/traits.rs +++ b/compiler/pavexc/src/compiler/traits.rs @@ -228,6 +228,16 @@ pub(crate) fn implements_trait( } // TODO: handle Unpin + other traits } + Type::RawPointer(_) => { + // Raw pointers are `Copy` and `Clone`, but not `Send` or `Sync`. + if expected_trait.base_type == COPY_TRAIT_PATH + || expected_trait.base_type == CLONE_TRAIT_PATH + || expected_trait.base_type == UNPIN_TRAIT_PATH + { + return Ok(true); + } + // TODO: handle other traits + } Type::Generic(_) => { // TODO: handle blanket implementations. As a first approximation, // we assume that if the type is generic, it implements all traits. diff --git a/compiler/pavexc/src/language/fq_path.rs b/compiler/pavexc/src/language/fq_path.rs index 20b23f8a6..9e33db2c6 100644 --- a/compiler/pavexc/src/language/fq_path.rs +++ b/compiler/pavexc/src/language/fq_path.rs @@ -16,7 +16,7 @@ use crate::compiler::resolvers::{GenericBindings, resolve_type}; use crate::language::callable_path::{CallPathGenericArgument, CallPathLifetime, CallPathType}; use crate::language::krate_name::dependency_name2package_id; use crate::language::resolved_type::{GenericArgument, Lifetime, ScalarPrimitive, Slice}; -use crate::language::{CallPath, InvalidCallPath, Type, Tuple, TypeReference}; +use crate::language::{CallPath, InvalidCallPath, RawPointer, Type, Tuple, TypeReference}; use crate::rustdoc::{CannotGetCrateData, CrateCollection, GlobalItemId, ResolvedItem}; use rustdoc_ext::RustdocKindExt; use rustdoc_processor::queries::CrateRegistry; @@ -131,6 +131,7 @@ pub enum FQPathType { Tuple(FQTuple), ScalarPrimitive(ScalarPrimitive), Slice(FQSlice), + RawPointer(FQRawPointer), } impl FQPathType { @@ -239,6 +240,13 @@ impl FQPathType { element_type: Box::new(inner), })) } + FQPathType::RawPointer(r) => { + let inner = r.inner.resolve(krate_collection)?; + Ok(Type::RawPointer(RawPointer { + is_mutable: r.is_mutable, + inner: Box::new(inner), + })) + } } } } @@ -287,6 +295,10 @@ impl From for FQPathType { Type::Slice(s) => FQPathType::Slice(FQSlice { element: Box::new((*s.element_type).into()), }), + Type::RawPointer(r) => FQPathType::RawPointer(FQRawPointer { + is_mutable: r.is_mutable, + inner: Box::new((*r.inner).into()), + }), Type::Generic(_) => { // ResolvedPath doesn't support unassigned generic parameters. unreachable!("UnassignedGeneric") @@ -317,6 +329,12 @@ pub struct FQSlice { pub element: Box, } +#[derive(Clone, Debug, Hash, Eq, PartialEq)] +pub struct FQRawPointer { + pub is_mutable: bool, + pub inner: Box, +} + impl PartialEq for FQPath { fn eq(&self, other: &Self) -> bool { // Using destructuring syntax to make sure we get a compiler error @@ -799,6 +817,7 @@ impl FQPathType { write!(buffer, "{s}").unwrap(); } FQPathType::Slice(s) => s.render_path(id2name, buffer), + FQPathType::RawPointer(r) => r.render_path(id2name, buffer), } } @@ -811,6 +830,7 @@ impl FQPathType { write!(buffer, "{s}").unwrap(); } FQPathType::Slice(s) => s.render_for_error(buffer), + FQPathType::RawPointer(r) => r.render_for_error(buffer), } } } @@ -921,6 +941,26 @@ impl FQReference { } } +impl FQRawPointer { + pub fn render_path(&self, id2name: &BiHashMap, buffer: &mut String) { + if self.is_mutable { + write!(buffer, "*mut ").unwrap(); + } else { + write!(buffer, "*const ").unwrap(); + } + self.inner.render_path(id2name, buffer); + } + + pub fn render_for_error(&self, buffer: &mut String) { + if self.is_mutable { + write!(buffer, "*mut ").unwrap(); + } else { + write!(buffer, "*const ").unwrap(); + } + self.inner.render_for_error(buffer); + } +} + impl Display for FQPath { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let last_segment_index = self.segments.len().saturating_sub(1); @@ -954,6 +994,9 @@ impl Display for FQPathType { FQPathType::Slice(s) => { write!(f, "{s}") } + FQPathType::RawPointer(r) => { + write!(f, "{r}") + } } } } @@ -980,6 +1023,17 @@ impl Display for FQReference { } } +impl Display for FQRawPointer { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if self.is_mutable { + write!(f, "*mut ")?; + } else { + write!(f, "*const ")?; + } + write!(f, "{}", self.inner) + } +} + impl Display for FQTuple { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(")?; diff --git a/compiler/pavexc/src/language/mod.rs b/compiler/pavexc/src/language/mod.rs index cb76c13b0..c6437f09c 100644 --- a/compiler/pavexc/src/language/mod.rs +++ b/compiler/pavexc/src/language/mod.rs @@ -11,7 +11,7 @@ pub use krate_name::{ use pavex_bp_schema::CreatedAt; pub(crate) use resolved_type::{ Generic, GenericArgument, GenericLifetimeParameter, Lifetime, PathType, - PathTypeExt, Type, Slice, Tuple, TypeReference, UnknownPrimitive, + PathTypeExt, RawPointer, Type, Slice, Tuple, TypeReference, UnknownPrimitive, }; mod callable; diff --git a/rustdoc/rustdoc_ir/src/lib.rs b/rustdoc/rustdoc_ir/src/lib.rs index 2ecde1e07..bfb2d0a61 100644 --- a/rustdoc/rustdoc_ir/src/lib.rs +++ b/rustdoc/rustdoc_ir/src/lib.rs @@ -4,6 +4,7 @@ pub(crate) mod generics_equivalence; mod lifetime; mod named_lifetime; mod path_type; +mod raw_pointer; pub(crate) mod render; mod scalar_primitive; mod slice; @@ -16,6 +17,7 @@ pub use generic_argument::{GenericArgument, GenericLifetimeParameter}; pub use named_lifetime::NamedLifetime; pub use lifetime::Lifetime; pub use path_type::PathType; +pub use raw_pointer::RawPointer; pub use scalar_primitive::{ScalarPrimitive, UnknownPrimitive}; pub use slice::Slice; pub use tuple::Tuple; @@ -34,6 +36,8 @@ pub enum Type { ScalarPrimitive(ScalarPrimitive), /// A slice type, e.g. `[u8]`. Slice(Slice), + /// A raw pointer type, e.g. `*const u8` or `*mut u8`. + RawPointer(RawPointer), /// An unassigned generic type parameter, e.g. `T`. Generic(Generic), } diff --git a/rustdoc/rustdoc_ir/src/raw_pointer.rs b/rustdoc/rustdoc_ir/src/raw_pointer.rs new file mode 100644 index 000000000..b0c195c76 --- /dev/null +++ b/rustdoc/rustdoc_ir/src/raw_pointer.rs @@ -0,0 +1,23 @@ +use std::fmt::{Debug, Formatter}; + +use crate::Type; + +#[derive(serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash, Clone)] +/// A Rust raw pointer—e.g. `*const u8` or `*mut Vec`. +pub struct RawPointer { + /// `true` if this is a `*mut T` pointer, `false` if `*const T`. + pub is_mutable: bool, + /// The type being pointed to. + pub inner: Box, +} + +impl Debug for RawPointer { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if self.is_mutable { + write!(f, "*mut ")?; + } else { + write!(f, "*const ")?; + } + write!(f, "{:?}", self.inner) + } +} diff --git a/rustdoc/rustdoc_ir/src/render.rs b/rustdoc/rustdoc_ir/src/render.rs index d75947900..9c65e4d31 100644 --- a/rustdoc/rustdoc_ir/src/render.rs +++ b/rustdoc/rustdoc_ir/src/render.rs @@ -105,6 +105,14 @@ impl Type { Type::Slice(s) => { write!(buffer, "[{}]", s.element_type.render_type(id2name)).unwrap(); } + Type::RawPointer(r) => { + if r.is_mutable { + write!(buffer, "*mut ").unwrap(); + } else { + write!(buffer, "*const ").unwrap(); + } + r.inner._render_type(id2name, buffer); + } Type::Generic(t) => { write!(buffer, "{}", t.name).unwrap(); } diff --git a/rustdoc/rustdoc_ir/src/type_.rs b/rustdoc/rustdoc_ir/src/type_.rs index 6a79234be..6b854ed58 100644 --- a/rustdoc/rustdoc_ir/src/type_.rs +++ b/rustdoc/rustdoc_ir/src/type_.rs @@ -5,7 +5,7 @@ use indexmap::{IndexMap, IndexSet}; use crate::generics_equivalence::UnassignedIdGenerator; use crate::{ - GenericArgument, GenericLifetimeParameter, Lifetime, PathType, Type, Slice, + GenericArgument, GenericLifetimeParameter, Lifetime, PathType, RawPointer, Type, Slice, Tuple, TypeReference, }; @@ -77,6 +77,10 @@ impl Type { Type::Slice(s) => Type::Slice(Slice { element_type: Box::new(s.element_type.bind_generic_type_parameters(bindings)), }), + Type::RawPointer(r) => Type::RawPointer(RawPointer { + is_mutable: r.is_mutable, + inner: Box::new(r.inner.bind_generic_type_parameters(bindings)), + }), Type::Generic(g) => { if let Some(bound_type) = bindings.get(&g.name) { bound_type.clone() @@ -104,6 +108,7 @@ impl Type { Type::Tuple(t) => t.elements.iter().any(|t| t.is_a_template()), Type::ScalarPrimitive(_) => false, Type::Slice(s) => s.element_type.is_a_template(), + Type::RawPointer(r) => r.inner.is_a_template(), Type::Generic(_) => true, } } @@ -137,6 +142,7 @@ impl Type { } Type::ScalarPrimitive(_) => {} Type::Slice(s) => s.element_type._unassigned_generic_type_parameters(set), + Type::RawPointer(r) => r.inner._unassigned_generic_type_parameters(set), Type::Generic(t) => { set.insert(t.name.clone()); } @@ -197,6 +203,12 @@ impl Type { (ScalarPrimitive(concrete_primitive), ScalarPrimitive(templated_primitive)) => { concrete_primitive == templated_primitive } + (RawPointer(concrete_ptr), RawPointer(templated_ptr)) => { + concrete_ptr.is_mutable == templated_ptr.is_mutable + && templated_ptr + .inner + ._is_a_template_for(&concrete_ptr.inner, bindings) + } (_, Generic(parameter)) => { let previous = bindings.insert(parameter.name.clone(), concrete_type.clone()); if let Some(previous) = previous @@ -266,6 +278,12 @@ impl Type { }) } (ScalarPrimitive(self_p), ScalarPrimitive(other_p)) => self_p == other_p, + (RawPointer(self_ptr), RawPointer(other_ptr)) => { + self_ptr.is_mutable == other_ptr.is_mutable + && self_ptr + .inner + ._is_equivalent_to(&other_ptr.inner, self_id_gen, other_id_gen) + } (Generic(self_g), Generic(other_g)) => { let first_id = self_id_gen.id(&self_g.name); let second_id = other_id_gen.id(&other_g.name); @@ -307,6 +325,7 @@ impl Type { .any(|t| t.has_implicit_lifetime_parameters()), Type::ScalarPrimitive(_) => false, Type::Slice(s) => s.element_type.has_implicit_lifetime_parameters(), + Type::RawPointer(r) => r.inner.has_implicit_lifetime_parameters(), Type::Generic(_) => false, } } @@ -348,6 +367,7 @@ impl Type { .iter_mut() .for_each(|e| e.set_implicit_lifetimes(inferred_lifetime.clone())), Type::Slice(s) => s.element_type.set_implicit_lifetimes(inferred_lifetime), + Type::RawPointer(r) => r.inner.set_implicit_lifetimes(inferred_lifetime), Type::Generic(_) | Type::ScalarPrimitive(_) => {} } } @@ -392,6 +412,9 @@ impl Type { Type::Slice(s) => { s.element_type.rename_lifetime_parameters(original2renamed); } + Type::RawPointer(r) => { + r.inner.rename_lifetime_parameters(original2renamed); + } Type::Generic(_) | Type::ScalarPrimitive(_) => {} } } @@ -427,6 +450,7 @@ impl Type { } } Type::Slice(s) => s.element_type._lifetime_parameters(set), + Type::RawPointer(r) => r.inner._lifetime_parameters(set), Type::ScalarPrimitive(_) | Type::Generic(_) => {} } } @@ -471,6 +495,7 @@ impl Type { } } Type::Slice(s) => s.element_type._named_lifetime_parameters(set), + Type::RawPointer(r) => r.inner._named_lifetime_parameters(set), Type::ScalarPrimitive(_) | Type::Generic(_) => {} } } @@ -543,6 +568,14 @@ impl Type { s.element_type._display_for_error(buffer); write!(buffer, "]").unwrap(); } + Type::RawPointer(r) => { + if r.is_mutable { + write!(buffer, "*mut ").unwrap(); + } else { + write!(buffer, "*const ").unwrap(); + } + r.inner._display_for_error(buffer); + } Type::Generic(t) => { write!(buffer, "{}", t.name).unwrap(); } @@ -558,6 +591,7 @@ impl Debug for Type { Type::Tuple(t) => write!(f, "{t:?}"), Type::ScalarPrimitive(s) => write!(f, "{s:?}"), Type::Slice(s) => write!(f, "{s:?}"), + Type::RawPointer(r) => write!(f, "{r:?}"), Type::Generic(g) => write!(f, "{g:?}"), } } @@ -581,6 +615,12 @@ impl From for Type { } } +impl From for Type { + fn from(value: RawPointer) -> Self { + Self::RawPointer(value) + } +} + #[cfg(test)] mod tests { use ahash::{HashSet, HashSetExt}; From fdf40ef4a9ce08f008105c717200d1c0b836d7f6 Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:31:40 +0100 Subject: [PATCH 2/3] feat: Add support for array as inputs --- .../analyses/application_state/cloning.rs | 1 + .../analyses/application_state/mod.rs | 4 + .../analyses/processing_pipeline/codegen.rs | 1 + .../analyses/processing_pipeline/pipeline.rs | 3 +- compiler/pavexc/src/compiler/codegen/mod.rs | 3 + compiler/pavexc/src/compiler/codegen/state.rs | 1 + .../pavexc/src/compiler/path_parameters.rs | 1 + compiler/pavexc/src/compiler/resolvers.rs | 39 +++- compiler/pavexc/src/compiler/traits.rs | 26 +++ compiler/pavexc/src/language/fq_path.rs | 45 ++++- compiler/pavexc/src/language/mod.rs | 2 +- compiler/ui_tests/Cargo.toml | 2 + .../arrays_are_supported/Cargo.toml | 17 ++ .../arrays_are_supported/diagnostics.dot | 45 +++++ .../arrays_are_supported/expectations/app.rs | 177 ++++++++++++++++++ .../expectations/diagnostics.dot | 41 ++++ .../generated_app/Cargo.toml | 17 ++ .../generated_app/src/lib.rs | 177 ++++++++++++++++++ .../arrays_are_supported/src/lib.rs | 18 ++ .../arrays_are_supported/src/main.rs | 24 +++ .../arrays_are_supported/test_config.toml | 4 + rustdoc/rustdoc_ir/src/array.rs | 18 ++ rustdoc/rustdoc_ir/src/lib.rs | 4 + rustdoc/rustdoc_ir/src/render.rs | 3 + rustdoc/rustdoc_ir/src/type_.rs | 41 +++- 25 files changed, 706 insertions(+), 8 deletions(-) create mode 100644 compiler/ui_tests/reflection/arrays_are_supported/Cargo.toml create mode 100644 compiler/ui_tests/reflection/arrays_are_supported/diagnostics.dot create mode 100644 compiler/ui_tests/reflection/arrays_are_supported/expectations/app.rs create mode 100644 compiler/ui_tests/reflection/arrays_are_supported/expectations/diagnostics.dot create mode 100644 compiler/ui_tests/reflection/arrays_are_supported/generated_app/Cargo.toml create mode 100644 compiler/ui_tests/reflection/arrays_are_supported/generated_app/src/lib.rs create mode 100644 compiler/ui_tests/reflection/arrays_are_supported/src/lib.rs create mode 100644 compiler/ui_tests/reflection/arrays_are_supported/src/main.rs create mode 100644 compiler/ui_tests/reflection/arrays_are_supported/test_config.toml create mode 100644 rustdoc/rustdoc_ir/src/array.rs diff --git a/compiler/pavexc/src/compiler/analyses/application_state/cloning.rs b/compiler/pavexc/src/compiler/analyses/application_state/cloning.rs index 46bff8417..a7086cd30 100644 --- a/compiler/pavexc/src/compiler/analyses/application_state/cloning.rs +++ b/compiler/pavexc/src/compiler/analyses/application_state/cloning.rs @@ -56,6 +56,7 @@ pub(crate) fn runtime_singletons_can_be_cloned_if_needed<'a>( Type::ScalarPrimitive(_) | Type::Reference(_) | Type::Slice(_) + | Type::Array(_) | Type::RawPointer(_) => { return None; } diff --git a/compiler/pavexc/src/compiler/analyses/application_state/mod.rs b/compiler/pavexc/src/compiler/analyses/application_state/mod.rs index bfcb59598..4f1d0cac5 100644 --- a/compiler/pavexc/src/compiler/analyses/application_state/mod.rs +++ b/compiler/pavexc/src/compiler/analyses/application_state/mod.rs @@ -296,6 +296,10 @@ fn _field_name_candidate(ty_: &Type, strategy: NamingStrategy, candidate: &mut S // Same reasoning as for references. _field_name_candidate(&slice.element_type, strategy, candidate); } + Type::Array(array) => { + // Same reasoning as for slices. + _field_name_candidate(&array.element_type, strategy, candidate); + } Type::RawPointer(raw_pointer) => { if raw_pointer.is_mutable { candidate.push_str("mut_ptr_"); diff --git a/compiler/pavexc/src/compiler/analyses/processing_pipeline/codegen.rs b/compiler/pavexc/src/compiler/analyses/processing_pipeline/codegen.rs index 1a88105ae..0b32be1da 100644 --- a/compiler/pavexc/src/compiler/analyses/processing_pipeline/codegen.rs +++ b/compiler/pavexc/src/compiler/analyses/processing_pipeline/codegen.rs @@ -391,6 +391,7 @@ impl CodegenedRequestHandlerPipeline { } } Type::Slice(_) + | Type::Array(_) | Type::Path(_) | Type::Tuple(_) | Type::ScalarPrimitive(_) diff --git a/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs b/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs index 37a983259..23bb8ce86 100644 --- a/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs +++ b/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs @@ -509,7 +509,8 @@ impl RequestHandlerPipeline { } } Type::Path(_) | - Type::Tuple(_) => { + Type::Tuple(_) | + Type::Array(_) => { type2info.entry(ty.clone()).or_default().consumed_by.push(ConsumerInfo { middleware_index: index, component_id }); } // Scalars are trivially `Copy`, this analysis doesn't concern them. diff --git a/compiler/pavexc/src/compiler/codegen/mod.rs b/compiler/pavexc/src/compiler/codegen/mod.rs index a25db0143..1828186f4 100644 --- a/compiler/pavexc/src/compiler/codegen/mod.rs +++ b/compiler/pavexc/src/compiler/codegen/mod.rs @@ -477,6 +477,9 @@ fn collect_type_package_ids(package_ids: &mut IndexSet, t: &Type) { Type::Slice(s) => { collect_type_package_ids(package_ids, &s.element_type); } + Type::Array(a) => { + collect_type_package_ids(package_ids, &a.element_type); + } Type::RawPointer(r) => { collect_type_package_ids(package_ids, &r.inner); } diff --git a/compiler/pavexc/src/compiler/codegen/state.rs b/compiler/pavexc/src/compiler/codegen/state.rs index 9ccd4e960..c329e585f 100644 --- a/compiler/pavexc/src/compiler/codegen/state.rs +++ b/compiler/pavexc/src/compiler/codegen/state.rs @@ -132,6 +132,7 @@ pub(super) fn get_application_state_new( } } Type::Slice(_) + | Type::Array(_) | Type::Path(_) | Type::Tuple(_) | Type::ScalarPrimitive(_) diff --git a/compiler/pavexc/src/compiler/path_parameters.rs b/compiler/pavexc/src/compiler/path_parameters.rs index 82fe39a87..2ec5f3931 100644 --- a/compiler/pavexc/src/compiler/path_parameters.rs +++ b/compiler/pavexc/src/compiler/path_parameters.rs @@ -292,6 +292,7 @@ fn must_be_a_plain_struct( Type::Tuple(t) => format!("`{t:?}` is a tuple"), Type::ScalarPrimitive(s) => format!("`{s:?}` is a primitive"), Type::Slice(s) => format!("`{s:?}` is a slice"), + Type::Array(a) => format!("`{a:?}` is an array"), Type::RawPointer(r) => format!("`{r:?}` is a raw pointer"), Type::Generic(_) => { unreachable!() diff --git a/compiler/pavexc/src/compiler/resolvers.rs b/compiler/pavexc/src/compiler/resolvers.rs index b2063e76f..aa8ea5617 100644 --- a/compiler/pavexc/src/compiler/resolvers.rs +++ b/compiler/pavexc/src/compiler/resolvers.rs @@ -11,7 +11,7 @@ use rustdoc_types::{GenericArg, GenericArgs, GenericParamDefKind, ItemEnum, Type use tracing_log_error::log_error; use crate::language::{ - Callable, CallableItem, FQGenericArgument, FQPath, FQPathSegment, FQPathType, Generic, + Array, Callable, CallableItem, FQGenericArgument, FQPath, FQPathSegment, FQPathType, Generic, GenericArgument, GenericLifetimeParameter, InvocationStyle, PathType, RawPointer, Type, Slice, Tuple, TypeReference, UnknownPath, UnknownPrimitive, }; @@ -83,6 +83,13 @@ impl std::fmt::Display for TypeResolutionError { unsupported_type_kind.kind ) } + TypeResolutionErrorDetails::UnsupportedArrayLength(unsupported_array_length) => { + write!( + f, + "It uses an array with a non-literal length (`{}`), which isn't currently supported.", + unsupported_array_length.len + ) + } TypeResolutionErrorDetails::GenericKindMismatch(generic_kind_mismatch) => { write!( f, @@ -121,6 +128,7 @@ pub enum TypeResolutionErrorDetails { UnsupportedFnPointer(UnsupportedFnPointer), UnsupportedReturnTypeNotation, UnsupportedTypeKind(UnsupportedTypeKind), + UnsupportedArrayLength(UnsupportedArrayLength), UnknownPrimitive(UnknownPrimitive), GenericKindMismatch(GenericKindMismatch), ItemResolutionError(anyhow::Error), @@ -151,6 +159,11 @@ pub struct UnsupportedTypeKind { pub kind: &'static str, } +#[derive(Debug)] +pub struct UnsupportedArrayLength { + pub len: String, +} + #[derive(Debug)] pub struct GenericKindMismatch { pub parameter_name: String, @@ -573,9 +586,27 @@ pub(crate) fn _resolve_type( element_type: Box::new(inner), })) } - RustdocType::Array { .. } => Err(TypeResolutionErrorDetails::UnsupportedTypeKind( - UnsupportedTypeKind { kind: "array" }, - )), + RustdocType::Array { type_, len } => { + let len: usize = len.parse().map_err(|_| { + TypeResolutionErrorDetails::UnsupportedArrayLength(UnsupportedArrayLength { + len: len.clone(), + }) + })?; + let resolved = + resolve_type(type_, used_by_package_id, krate_collection, generic_bindings) + .map_err(|source| { + TypeResolutionErrorDetails::TypePartResolutionError(Box::new( + TypePartResolutionError { + role: "array element type".into(), + source, + }, + )) + })?; + Ok(Type::Array(Array { + element_type: Box::new(resolved), + len, + })) + } RustdocType::DynTrait(_) => Err(TypeResolutionErrorDetails::UnsupportedTypeKind( UnsupportedTypeKind { kind: "dyn trait" }, )), diff --git a/compiler/pavexc/src/compiler/traits.rs b/compiler/pavexc/src/compiler/traits.rs index 1ead8a7b3..25bbc21f1 100644 --- a/compiler/pavexc/src/compiler/traits.rs +++ b/compiler/pavexc/src/compiler/traits.rs @@ -228,6 +228,19 @@ pub(crate) fn implements_trait( } // TODO: handle Unpin + other traits } + Type::Array(a) => { + // Arrays implement Send/Sync/Copy/Clone/Unpin if their element type does. + if (expected_trait.base_type == SEND_TRAIT_PATH + || expected_trait.base_type == SYNC_TRAIT_PATH + || expected_trait.base_type == COPY_TRAIT_PATH + || expected_trait.base_type == CLONE_TRAIT_PATH + || expected_trait.base_type == UNPIN_TRAIT_PATH) + && implements_trait(krate_collection, &a.element_type, expected_trait)? + { + return Ok(true); + } + // TODO: handle other traits + } Type::RawPointer(_) => { // Raw pointers are `Copy` and `Clone`, but not `Send` or `Sync`. if expected_trait.base_type == COPY_TRAIT_PATH @@ -371,6 +384,19 @@ fn is_equivalent( ); } } + RustdocType::Array { type_, len } => { + if let Type::Array(our_array) = our_type + && let Ok(parsed_len) = len.parse::() + { + return parsed_len == our_array.len + && is_equivalent( + type_, + &our_array.element_type, + krate_collection, + used_by_package_id, + ); + } + } n => { tracing::trace!("We don't handle {:?} yet", n); } diff --git a/compiler/pavexc/src/language/fq_path.rs b/compiler/pavexc/src/language/fq_path.rs index 9e33db2c6..dff75f958 100644 --- a/compiler/pavexc/src/language/fq_path.rs +++ b/compiler/pavexc/src/language/fq_path.rs @@ -15,7 +15,7 @@ use rustdoc_types::ItemEnum; use crate::compiler::resolvers::{GenericBindings, resolve_type}; use crate::language::callable_path::{CallPathGenericArgument, CallPathLifetime, CallPathType}; use crate::language::krate_name::dependency_name2package_id; -use crate::language::resolved_type::{GenericArgument, Lifetime, ScalarPrimitive, Slice}; +use crate::language::resolved_type::{Array, GenericArgument, Lifetime, ScalarPrimitive, Slice}; use crate::language::{CallPath, InvalidCallPath, RawPointer, Type, Tuple, TypeReference}; use crate::rustdoc::{CannotGetCrateData, CrateCollection, GlobalItemId, ResolvedItem}; use rustdoc_ext::RustdocKindExt; @@ -131,6 +131,7 @@ pub enum FQPathType { Tuple(FQTuple), ScalarPrimitive(ScalarPrimitive), Slice(FQSlice), + Array(FQArray), RawPointer(FQRawPointer), } @@ -240,6 +241,13 @@ impl FQPathType { element_type: Box::new(inner), })) } + FQPathType::Array(a) => { + let inner = a.element.resolve(krate_collection)?; + Ok(Type::Array(Array { + element_type: Box::new(inner), + len: a.len, + })) + } FQPathType::RawPointer(r) => { let inner = r.inner.resolve(krate_collection)?; Ok(Type::RawPointer(RawPointer { @@ -295,6 +303,10 @@ impl From for FQPathType { Type::Slice(s) => FQPathType::Slice(FQSlice { element: Box::new((*s.element_type).into()), }), + Type::Array(a) => FQPathType::Array(FQArray { + element: Box::new((*a.element_type).into()), + len: a.len, + }), Type::RawPointer(r) => FQPathType::RawPointer(FQRawPointer { is_mutable: r.is_mutable, inner: Box::new((*r.inner).into()), @@ -329,6 +341,12 @@ pub struct FQSlice { pub element: Box, } +#[derive(Clone, Debug, Hash, Eq, PartialEq)] +pub struct FQArray { + pub element: Box, + pub len: usize, +} + #[derive(Clone, Debug, Hash, Eq, PartialEq)] pub struct FQRawPointer { pub is_mutable: bool, @@ -817,6 +835,7 @@ impl FQPathType { write!(buffer, "{s}").unwrap(); } FQPathType::Slice(s) => s.render_path(id2name, buffer), + FQPathType::Array(a) => a.render_path(id2name, buffer), FQPathType::RawPointer(r) => r.render_path(id2name, buffer), } } @@ -830,6 +849,7 @@ impl FQPathType { write!(buffer, "{s}").unwrap(); } FQPathType::Slice(s) => s.render_for_error(buffer), + FQPathType::Array(a) => a.render_for_error(buffer), FQPathType::RawPointer(r) => r.render_for_error(buffer), } } @@ -849,6 +869,20 @@ impl FQSlice { } } +impl FQArray { + pub fn render_path(&self, id2name: &BiHashMap, buffer: &mut String) { + write!(buffer, "[").unwrap(); + self.element.render_path(id2name, buffer); + write!(buffer, "; {}]", self.len).unwrap(); + } + + pub fn render_for_error(&self, buffer: &mut String) { + write!(buffer, "[").unwrap(); + self.element.render_for_error(buffer); + write!(buffer, "; {}]", self.len).unwrap(); + } +} + impl FQGenericArgument { pub fn render_path(&self, id2name: &BiHashMap, buffer: &mut String) { match self { @@ -994,6 +1028,9 @@ impl Display for FQPathType { FQPathType::Slice(s) => { write!(f, "{s}") } + FQPathType::Array(a) => { + write!(f, "{a}") + } FQPathType::RawPointer(r) => { write!(f, "{r}") } @@ -1007,6 +1044,12 @@ impl Display for FQSlice { } } +impl Display for FQArray { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "[{}; {}]", self.element, self.len) + } +} + impl Display for FQResolvedPathType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.path) diff --git a/compiler/pavexc/src/language/mod.rs b/compiler/pavexc/src/language/mod.rs index c6437f09c..1d3c6f6cc 100644 --- a/compiler/pavexc/src/language/mod.rs +++ b/compiler/pavexc/src/language/mod.rs @@ -10,7 +10,7 @@ pub use krate_name::{ }; use pavex_bp_schema::CreatedAt; pub(crate) use resolved_type::{ - Generic, GenericArgument, GenericLifetimeParameter, Lifetime, PathType, + Array, Generic, GenericArgument, GenericLifetimeParameter, Lifetime, PathType, PathTypeExt, RawPointer, Type, Slice, Tuple, TypeReference, UnknownPrimitive, }; diff --git a/compiler/ui_tests/Cargo.toml b/compiler/ui_tests/Cargo.toml index 1492f4e77..6bd7730aa 100644 --- a/compiler/ui_tests/Cargo.toml +++ b/compiler/ui_tests/Cargo.toml @@ -289,6 +289,8 @@ members = [ "reflection/ambiguous_dependency_version_is_handled/generated_app", "reflection/arc_singletons_are_supported", "reflection/arc_singletons_are_supported/generated_app", + "reflection/arrays_are_supported", + "reflection/arrays_are_supported/generated_app", "reflection/common_response_types_are_supported", "reflection/common_response_types_are_supported/generated_app", "reflection/crate_resolution/dependencies_can_register_local_items", diff --git a/compiler/ui_tests/reflection/arrays_are_supported/Cargo.toml b/compiler/ui_tests/reflection/arrays_are_supported/Cargo.toml new file mode 100644 index 000000000..1adbb9ee9 --- /dev/null +++ b/compiler/ui_tests/reflection/arrays_are_supported/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "app_8f1b9097" +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/arrays_are_supported/diagnostics.dot b/compiler/ui_tests/reflection/arrays_are_supported/diagnostics.dot new file mode 100644 index 000000000..932f91ee5 --- /dev/null +++ b/compiler/ui_tests/reflection/arrays_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| [u8; 4]"] + 1 [ label = "1| crate::route_1::Next0([u8; 4]) -> crate::route_1::Next0"] + 2 [ label = "2| pavex::middleware::Next::new(crate::route_1::Next0) -> 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 [ ] + 0 -> 1 [ ] + 3 -> 4 [ ] +} + +digraph "GET / - 1" { + 0 [ label = "0| [u8; 4]"] + 1 [ label = "1| app_8f1b9097::handler([u8; 4]) -> pavex::Response"] + 2 [ label = "2| ::into_response(pavex::Response) -> pavex::Response"] + 0 -> 1 [ ] + 1 -> 2 [ ] +} + +digraph app_state { + 0 [ label = "0| app_8f1b9097::constructor_with_output_array() -> [u8; 4]"] + 1 [ label = "1| crate::ApplicationState([u8; 4]) -> crate::ApplicationState"] + 0 -> 1 [ ] +} diff --git a/compiler/ui_tests/reflection/arrays_are_supported/expectations/app.rs b/compiler/ui_tests/reflection/arrays_are_supported/expectations/app.rs new file mode 100644 index 000000000..1c3807df3 --- /dev/null +++ b/compiler/ui_tests/reflection/arrays_are_supported/expectations/app.rs @@ -0,0 +1,177 @@ +//! 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, + application_state: ApplicationState, +} +#[derive(Debug, Clone, serde::Deserialize)] +pub struct ApplicationConfig {} +pub struct ApplicationState { + pub u8_: [u8; 4], +} +impl ApplicationState { + pub async fn new( + _app_config: crate::ApplicationConfig, + ) -> Result { + Ok(Self::_new().await) + } + async fn _new() -> crate::ApplicationState { + let v0 = app::constructor_with_output_array(); + crate::ApplicationState { u8_: v0 } + } +} +#[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(state.u8_.clone()).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(s_0: [u8; 4]) -> pavex::Response { + let response = wrapping_0(s_0).await; + response + } + async fn stage_1(s_0: [u8; 4]) -> pavex::Response { + let response = handler(s_0).await; + response + } + async fn wrapping_0(v0: [u8; 4]) -> pavex::Response { + let v1 = crate::route_1::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: [u8; 4]) -> pavex::Response { + let v1 = app::handler(v0); + ::into_response(v1) + } + struct Next0 + where + T: std::future::Future, + { + s_0: [u8; 4], + next: fn([u8; 4]) -> 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)(self.s_0) + } + } +} \ No newline at end of file diff --git a/compiler/ui_tests/reflection/arrays_are_supported/expectations/diagnostics.dot b/compiler/ui_tests/reflection/arrays_are_supported/expectations/diagnostics.dot new file mode 100644 index 000000000..7ffc15195 --- /dev/null +++ b/compiler/ui_tests/reflection/arrays_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| [u8; 4]"] + 1 [ label = "1| crate::route_1::Next0([u8; 4]) -> crate::route_1::Next0"] + 2 [ label = "2| pavex::middleware::Next::new(crate::route_1::Next0) -> 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 [ ] + 0 -> 1 [ ] + 3 -> 4 [ ] +} +digraph "GET / - 1" { + 0 [ label = "0| [u8; 4]"] + 1 [ label = "1| app::handler([u8; 4]) -> pavex::Response"] + 2 [ label = "2| ::into_response(pavex::Response) -> pavex::Response"] + 0 -> 1 [ ] + 1 -> 2 [ ] +} +digraph app_state { + 0 [ label = "0| app::constructor_with_output_array() -> [u8; 4]"] + 1 [ label = "1| crate::ApplicationState([u8; 4]) -> crate::ApplicationState"] + 0 -> 1 [ ] +} \ No newline at end of file diff --git a/compiler/ui_tests/reflection/arrays_are_supported/generated_app/Cargo.toml b/compiler/ui_tests/reflection/arrays_are_supported/generated_app/Cargo.toml new file mode 100644 index 000000000..7c7ed2d6c --- /dev/null +++ b/compiler/ui_tests/reflection/arrays_are_supported/generated_app/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "application_8f1b9097" +version = "0.1.0" +edition = "2024" + +[package.metadata.px.generate] +generator_type = "cargo_workspace_binary" +generator_name = "app_8f1b9097" + +[dependencies] +app_8f1b9097 = { 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/arrays_are_supported/generated_app/src/lib.rs b/compiler/ui_tests/reflection/arrays_are_supported/generated_app/src/lib.rs new file mode 100644 index 000000000..dd5712ae3 --- /dev/null +++ b/compiler/ui_tests/reflection/arrays_are_supported/generated_app/src/lib.rs @@ -0,0 +1,177 @@ +//! 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, + application_state: ApplicationState, +} +#[derive(Debug, Clone, serde::Deserialize)] +pub struct ApplicationConfig {} +pub struct ApplicationState { + pub u8_: [u8; 4], +} +impl ApplicationState { + pub async fn new( + _app_config: crate::ApplicationConfig, + ) -> Result { + Ok(Self::_new().await) + } + async fn _new() -> crate::ApplicationState { + let v0 = app_8f1b9097::constructor_with_output_array(); + crate::ApplicationState { u8_: v0 } + } +} +#[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(state.u8_.clone()).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(s_0: [u8; 4]) -> pavex::Response { + let response = wrapping_0(s_0).await; + response + } + async fn stage_1(s_0: [u8; 4]) -> pavex::Response { + let response = handler(s_0).await; + response + } + async fn wrapping_0(v0: [u8; 4]) -> pavex::Response { + let v1 = crate::route_1::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: [u8; 4]) -> pavex::Response { + let v1 = app_8f1b9097::handler(v0); + ::into_response(v1) + } + struct Next0 + where + T: std::future::Future, + { + s_0: [u8; 4], + next: fn([u8; 4]) -> 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)(self.s_0) + } + } +} diff --git a/compiler/ui_tests/reflection/arrays_are_supported/src/lib.rs b/compiler/ui_tests/reflection/arrays_are_supported/src/lib.rs new file mode 100644 index 000000000..77f29a11c --- /dev/null +++ b/compiler/ui_tests/reflection/arrays_are_supported/src/lib.rs @@ -0,0 +1,18 @@ +use pavex::{blueprint::from, Blueprint}; + +#[pavex::singleton] +pub fn constructor_with_output_array() -> [u8; 4] { + todo!() +} + +#[pavex::get(path = "/")] +pub fn handler(_input: [u8; 4]) -> 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/arrays_are_supported/src/main.rs b/compiler/ui_tests/reflection/arrays_are_supported/src/main.rs new file mode 100644 index 000000000..d7fd72c56 --- /dev/null +++ b/compiler/ui_tests/reflection/arrays_are_supported/src/main.rs @@ -0,0 +1,24 @@ +//! This code is generated by `pavex_test_runner`, +//! Do NOT modify it manually. +use app_8f1b9097::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/arrays_are_supported/test_config.toml b/compiler/ui_tests/reflection/arrays_are_supported/test_config.toml new file mode 100644 index 000000000..c3d166966 --- /dev/null +++ b/compiler/ui_tests/reflection/arrays_are_supported/test_config.toml @@ -0,0 +1,4 @@ +description = "pavex supports array types as input parameters" + +[expectations] +codegen = "pass" diff --git a/rustdoc/rustdoc_ir/src/array.rs b/rustdoc/rustdoc_ir/src/array.rs new file mode 100644 index 000000000..04aa0ae35 --- /dev/null +++ b/rustdoc/rustdoc_ir/src/array.rs @@ -0,0 +1,18 @@ +use std::fmt::{Debug, Formatter}; + +use crate::Type; + +#[derive(serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash, Clone)] +/// A Rust fixed-size array—e.g. `[u8; 4]`. +pub struct Array { + /// The type of each element in the array. + pub element_type: Box, + /// The number of elements in the array. + pub len: usize, +} + +impl Debug for Array { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "[{:?}; {}]", self.element_type, self.len) + } +} diff --git a/rustdoc/rustdoc_ir/src/lib.rs b/rustdoc/rustdoc_ir/src/lib.rs index bfb2d0a61..4e0d827a4 100644 --- a/rustdoc/rustdoc_ir/src/lib.rs +++ b/rustdoc/rustdoc_ir/src/lib.rs @@ -1,3 +1,4 @@ +mod array; mod generic; mod generic_argument; pub(crate) mod generics_equivalence; @@ -12,6 +13,7 @@ mod tuple; mod type_; mod type_reference; +pub use array::Array; pub use generic::Generic; pub use generic_argument::{GenericArgument, GenericLifetimeParameter}; pub use named_lifetime::NamedLifetime; @@ -36,6 +38,8 @@ pub enum Type { ScalarPrimitive(ScalarPrimitive), /// A slice type, e.g. `[u8]`. Slice(Slice), + /// A fixed-size array type, e.g. `[u8; 4]`. + Array(Array), /// A raw pointer type, e.g. `*const u8` or `*mut u8`. RawPointer(RawPointer), /// An unassigned generic type parameter, e.g. `T`. diff --git a/rustdoc/rustdoc_ir/src/render.rs b/rustdoc/rustdoc_ir/src/render.rs index 9c65e4d31..65b86d1c5 100644 --- a/rustdoc/rustdoc_ir/src/render.rs +++ b/rustdoc/rustdoc_ir/src/render.rs @@ -105,6 +105,9 @@ impl Type { Type::Slice(s) => { write!(buffer, "[{}]", s.element_type.render_type(id2name)).unwrap(); } + Type::Array(a) => { + write!(buffer, "[{}; {}]", a.element_type.render_type(id2name), a.len).unwrap(); + } Type::RawPointer(r) => { if r.is_mutable { write!(buffer, "*mut ").unwrap(); diff --git a/rustdoc/rustdoc_ir/src/type_.rs b/rustdoc/rustdoc_ir/src/type_.rs index 6b854ed58..d6d8ee0bb 100644 --- a/rustdoc/rustdoc_ir/src/type_.rs +++ b/rustdoc/rustdoc_ir/src/type_.rs @@ -5,7 +5,7 @@ use indexmap::{IndexMap, IndexSet}; use crate::generics_equivalence::UnassignedIdGenerator; use crate::{ - GenericArgument, GenericLifetimeParameter, Lifetime, PathType, RawPointer, Type, Slice, + Array, GenericArgument, GenericLifetimeParameter, Lifetime, PathType, RawPointer, Type, Slice, Tuple, TypeReference, }; @@ -77,6 +77,10 @@ impl Type { Type::Slice(s) => Type::Slice(Slice { element_type: Box::new(s.element_type.bind_generic_type_parameters(bindings)), }), + Type::Array(a) => Type::Array(Array { + element_type: Box::new(a.element_type.bind_generic_type_parameters(bindings)), + len: a.len, + }), Type::RawPointer(r) => Type::RawPointer(RawPointer { is_mutable: r.is_mutable, inner: Box::new(r.inner.bind_generic_type_parameters(bindings)), @@ -108,6 +112,7 @@ impl Type { Type::Tuple(t) => t.elements.iter().any(|t| t.is_a_template()), Type::ScalarPrimitive(_) => false, Type::Slice(s) => s.element_type.is_a_template(), + Type::Array(a) => a.element_type.is_a_template(), Type::RawPointer(r) => r.inner.is_a_template(), Type::Generic(_) => true, } @@ -142,6 +147,7 @@ impl Type { } Type::ScalarPrimitive(_) => {} Type::Slice(s) => s.element_type._unassigned_generic_type_parameters(set), + Type::Array(a) => a.element_type._unassigned_generic_type_parameters(set), Type::RawPointer(r) => r.inner._unassigned_generic_type_parameters(set), Type::Generic(t) => { set.insert(t.name.clone()); @@ -185,6 +191,12 @@ impl Type { (Slice(concrete_slice), Slice(templated_slice)) => templated_slice .element_type ._is_a_template_for(&concrete_slice.element_type, bindings), + (Array(concrete_array), Array(templated_array)) => { + concrete_array.len == templated_array.len + && templated_array + .element_type + ._is_a_template_for(&concrete_array.element_type, bindings) + } (Reference(concrete_reference), Reference(templated_reference)) => templated_reference .inner ._is_a_template_for(&concrete_reference.inner, bindings), @@ -262,6 +274,14 @@ impl Type { self_id_gen, other_id_gen, ), + (Array(self_array), Array(other_array)) => { + self_array.len == other_array.len + && self_array.element_type._is_equivalent_to( + &other_array.element_type, + self_id_gen, + other_id_gen, + ) + } (Reference(self_reference), Reference(other_reference)) => self_reference .inner ._is_equivalent_to(&other_reference.inner, self_id_gen, other_id_gen), @@ -325,6 +345,7 @@ impl Type { .any(|t| t.has_implicit_lifetime_parameters()), Type::ScalarPrimitive(_) => false, Type::Slice(s) => s.element_type.has_implicit_lifetime_parameters(), + Type::Array(a) => a.element_type.has_implicit_lifetime_parameters(), Type::RawPointer(r) => r.inner.has_implicit_lifetime_parameters(), Type::Generic(_) => false, } @@ -367,6 +388,7 @@ impl Type { .iter_mut() .for_each(|e| e.set_implicit_lifetimes(inferred_lifetime.clone())), Type::Slice(s) => s.element_type.set_implicit_lifetimes(inferred_lifetime), + Type::Array(a) => a.element_type.set_implicit_lifetimes(inferred_lifetime), Type::RawPointer(r) => r.inner.set_implicit_lifetimes(inferred_lifetime), Type::Generic(_) | Type::ScalarPrimitive(_) => {} } @@ -412,6 +434,9 @@ impl Type { Type::Slice(s) => { s.element_type.rename_lifetime_parameters(original2renamed); } + Type::Array(a) => { + a.element_type.rename_lifetime_parameters(original2renamed); + } Type::RawPointer(r) => { r.inner.rename_lifetime_parameters(original2renamed); } @@ -450,6 +475,7 @@ impl Type { } } Type::Slice(s) => s.element_type._lifetime_parameters(set), + Type::Array(a) => a.element_type._lifetime_parameters(set), Type::RawPointer(r) => r.inner._lifetime_parameters(set), Type::ScalarPrimitive(_) | Type::Generic(_) => {} } @@ -495,6 +521,7 @@ impl Type { } } Type::Slice(s) => s.element_type._named_lifetime_parameters(set), + Type::Array(a) => a.element_type._named_lifetime_parameters(set), Type::RawPointer(r) => r.inner._named_lifetime_parameters(set), Type::ScalarPrimitive(_) | Type::Generic(_) => {} } @@ -568,6 +595,11 @@ impl Type { s.element_type._display_for_error(buffer); write!(buffer, "]").unwrap(); } + Type::Array(a) => { + write!(buffer, "[").unwrap(); + a.element_type._display_for_error(buffer); + write!(buffer, "; {}]", a.len).unwrap(); + } Type::RawPointer(r) => { if r.is_mutable { write!(buffer, "*mut ").unwrap(); @@ -591,6 +623,7 @@ impl Debug for Type { Type::Tuple(t) => write!(f, "{t:?}"), Type::ScalarPrimitive(s) => write!(f, "{s:?}"), Type::Slice(s) => write!(f, "{s:?}"), + Type::Array(a) => write!(f, "{a:?}"), Type::RawPointer(r) => write!(f, "{r:?}"), Type::Generic(g) => write!(f, "{g:?}"), } @@ -621,6 +654,12 @@ impl From for Type { } } +impl From for Type { + fn from(value: Array) -> Self { + Self::Array(value) + } +} + #[cfg(test)] mod tests { use ahash::{HashSet, HashSetExt}; From 52b383eaed7abc12b8d15f5042e3b0728689b1c0 Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Fri, 6 Mar 2026 10:47:06 +0100 Subject: [PATCH 3/3] testing: Test raw pointer constructors --- .../analyses/processing_pipeline/pipeline.rs | 6 +- compiler/ui_tests/Cargo.toml | 2 + .../generated_app/src/Cargo.toml | 8 + .../raw_pointers_are_supported/Cargo.toml | 17 ++ .../diagnostics.dot | 43 +++++ .../expectations/app.rs | 173 ++++++++++++++++++ .../expectations/diagnostics.dot | 39 ++++ .../generated_app/Cargo.toml | 17 ++ .../generated_app/src/Cargo.toml | 8 + .../generated_app/src/lib.rs | 173 ++++++++++++++++++ .../raw_pointers_are_supported/src/lib.rs | 23 +++ .../raw_pointers_are_supported/src/main.rs | 24 +++ .../test_config.toml | 4 + 13 files changed, 535 insertions(+), 2 deletions(-) create mode 100644 compiler/ui_tests/reflection/arrays_are_supported/generated_app/src/Cargo.toml create mode 100644 compiler/ui_tests/reflection/raw_pointers_are_supported/Cargo.toml create mode 100644 compiler/ui_tests/reflection/raw_pointers_are_supported/diagnostics.dot create mode 100644 compiler/ui_tests/reflection/raw_pointers_are_supported/expectations/app.rs create mode 100644 compiler/ui_tests/reflection/raw_pointers_are_supported/expectations/diagnostics.dot create mode 100644 compiler/ui_tests/reflection/raw_pointers_are_supported/generated_app/Cargo.toml create mode 100644 compiler/ui_tests/reflection/raw_pointers_are_supported/generated_app/src/Cargo.toml create mode 100644 compiler/ui_tests/reflection/raw_pointers_are_supported/generated_app/src/lib.rs create mode 100644 compiler/ui_tests/reflection/raw_pointers_are_supported/src/lib.rs create mode 100644 compiler/ui_tests/reflection/raw_pointers_are_supported/src/main.rs create mode 100644 compiler/ui_tests/reflection/raw_pointers_are_supported/test_config.toml diff --git a/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs b/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs index 23bb8ce86..56cdb54d9 100644 --- a/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs +++ b/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs @@ -517,8 +517,10 @@ impl RequestHandlerPipeline { Type::ScalarPrimitive(_) => { continue; } - // Raw pointers are trivially `Copy`. - Type::RawPointer(_) | + // Raw pointers are trivially `Copy`, this analysis doesn't concern them. + Type::RawPointer(_) => { + continue; + } // We'd never encounter a raw slice as input type. Type::Slice(_) | // All types are concrete at this stage. diff --git a/compiler/ui_tests/Cargo.toml b/compiler/ui_tests/Cargo.toml index 6bd7730aa..253911fec 100644 --- a/compiler/ui_tests/Cargo.toml +++ b/compiler/ui_tests/Cargo.toml @@ -315,6 +315,8 @@ members = [ "reflection/output_parameter_cannot_be_handled/generated_app", "reflection/pattern_bindings_in_input_parameters_are_supported", "reflection/pattern_bindings_in_input_parameters_are_supported/generated_app", + "reflection/raw_pointers_are_supported", + "reflection/raw_pointers_are_supported/generated_app", "reflection/reexported_type_alias_work", "reflection/reexported_type_alias_work/generated_app", "reflection/self_as_generic_parameter_is_supported", diff --git a/compiler/ui_tests/reflection/arrays_are_supported/generated_app/src/Cargo.toml b/compiler/ui_tests/reflection/arrays_are_supported/generated_app/src/Cargo.toml new file mode 100644 index 000000000..a45833fe2 --- /dev/null +++ b/compiler/ui_tests/reflection/arrays_are_supported/generated_app/src/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "application_8f1b9097" +version = "0.1.0" +edition = "2021" + +[package.metadata.px.generate] +generator_type = "cargo_workspace_binary" +generator_name = "app_8f1b9097" diff --git a/compiler/ui_tests/reflection/raw_pointers_are_supported/Cargo.toml b/compiler/ui_tests/reflection/raw_pointers_are_supported/Cargo.toml new file mode 100644 index 000000000..c7102b1c4 --- /dev/null +++ b/compiler/ui_tests/reflection/raw_pointers_are_supported/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "app_3703d048" +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/raw_pointers_are_supported/diagnostics.dot b/compiler/ui_tests/reflection/raw_pointers_are_supported/diagnostics.dot new file mode 100644 index 000000000..72b19eafe --- /dev/null +++ b/compiler/ui_tests/reflection/raw_pointers_are_supported/diagnostics.dot @@ -0,0 +1,43 @@ +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_3703d048::mut_ptr() -> *mut u8"] + 1 [ label = "1| app_3703d048::const_ptr() -> *const u8"] + 2 [ label = "2| app_3703d048::handler(*const u8, *mut u8) -> pavex::Response"] + 3 [ label = "3| ::into_response(pavex::Response) -> pavex::Response"] + 0 -> 2 [ ] + 1 -> 2 [ ] + 2 -> 3 [ ] +} + +digraph app_state { + 0 [ label = "0| crate::ApplicationState() -> crate::ApplicationState"] +} diff --git a/compiler/ui_tests/reflection/raw_pointers_are_supported/expectations/app.rs b/compiler/ui_tests/reflection/raw_pointers_are_supported/expectations/app.rs new file mode 100644 index 000000000..2d9eba99a --- /dev/null +++ b/compiler/ui_tests/reflection/raw_pointers_are_supported/expectations/app.rs @@ -0,0 +1,173 @@ +//! 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::mut_ptr(); + let v1 = app::const_ptr(); + let v2 = app::handler(v1, v0); + ::into_response(v2) + } + 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/raw_pointers_are_supported/expectations/diagnostics.dot b/compiler/ui_tests/reflection/raw_pointers_are_supported/expectations/diagnostics.dot new file mode 100644 index 000000000..4bf5f8d73 --- /dev/null +++ b/compiler/ui_tests/reflection/raw_pointers_are_supported/expectations/diagnostics.dot @@ -0,0 +1,39 @@ +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::mut_ptr() -> *mut u8"] + 1 [ label = "1| app::const_ptr() -> *const u8"] + 2 [ label = "2| app::handler(*const u8, *mut u8) -> pavex::Response"] + 3 [ label = "3| ::into_response(pavex::Response) -> pavex::Response"] + 0 -> 2 [ ] + 1 -> 2 [ ] + 2 -> 3 [ ] +} +digraph app_state { + 0 [ label = "0| crate::ApplicationState() -> crate::ApplicationState"] +} \ No newline at end of file diff --git a/compiler/ui_tests/reflection/raw_pointers_are_supported/generated_app/Cargo.toml b/compiler/ui_tests/reflection/raw_pointers_are_supported/generated_app/Cargo.toml new file mode 100644 index 000000000..2f0208c37 --- /dev/null +++ b/compiler/ui_tests/reflection/raw_pointers_are_supported/generated_app/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "application_3703d048" +version = "0.1.0" +edition = "2024" + +[package.metadata.px.generate] +generator_type = "cargo_workspace_binary" +generator_name = "app_3703d048" + +[dependencies] +app_3703d048 = { 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/raw_pointers_are_supported/generated_app/src/Cargo.toml b/compiler/ui_tests/reflection/raw_pointers_are_supported/generated_app/src/Cargo.toml new file mode 100644 index 000000000..8873c2a98 --- /dev/null +++ b/compiler/ui_tests/reflection/raw_pointers_are_supported/generated_app/src/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "application_3703d048" +version = "0.1.0" +edition = "2021" + +[package.metadata.px.generate] +generator_type = "cargo_workspace_binary" +generator_name = "app_3703d048" diff --git a/compiler/ui_tests/reflection/raw_pointers_are_supported/generated_app/src/lib.rs b/compiler/ui_tests/reflection/raw_pointers_are_supported/generated_app/src/lib.rs new file mode 100644 index 000000000..a68b12450 --- /dev/null +++ b/compiler/ui_tests/reflection/raw_pointers_are_supported/generated_app/src/lib.rs @@ -0,0 +1,173 @@ +//! 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_3703d048::mut_ptr(); + let v1 = app_3703d048::const_ptr(); + let v2 = app_3703d048::handler(v1, v0); + ::into_response(v2) + } + 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/raw_pointers_are_supported/src/lib.rs b/compiler/ui_tests/reflection/raw_pointers_are_supported/src/lib.rs new file mode 100644 index 000000000..34383a6b0 --- /dev/null +++ b/compiler/ui_tests/reflection/raw_pointers_are_supported/src/lib.rs @@ -0,0 +1,23 @@ +use pavex::{blueprint::from, Blueprint}; + +#[pavex::request_scoped] +pub fn const_ptr() -> *const u8 { + todo!() +} + +#[pavex::request_scoped] +pub fn mut_ptr() -> *mut u8 { + todo!() +} + +#[pavex::get(path = "/")] +pub fn handler(_c: *const u8, _m: *mut u8) -> 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/raw_pointers_are_supported/src/main.rs b/compiler/ui_tests/reflection/raw_pointers_are_supported/src/main.rs new file mode 100644 index 000000000..36283bbd1 --- /dev/null +++ b/compiler/ui_tests/reflection/raw_pointers_are_supported/src/main.rs @@ -0,0 +1,24 @@ +//! This code is generated by `pavex_test_runner`, +//! Do NOT modify it manually. +use app_3703d048::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/raw_pointers_are_supported/test_config.toml b/compiler/ui_tests/reflection/raw_pointers_are_supported/test_config.toml new file mode 100644 index 000000000..7ee99fc27 --- /dev/null +++ b/compiler/ui_tests/reflection/raw_pointers_are_supported/test_config.toml @@ -0,0 +1,4 @@ +description = "pavex supports raw pointer types as input parameters" + +[expectations] +codegen = "pass"