From 30d277f30a9293a000535fa6f5d343c01a42dbb8 Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Sun, 27 Oct 2024 11:47:49 +0100 Subject: [PATCH 01/16] fix: Pavex correctly handles type alises with generic parameters that differ from the target type --- .../analyses/components/db/diagnostics.rs | 12 +- .../src/compiler/component/prebuilt_type.rs | 2 +- .../src/compiler/path_parameter_validation.rs | 4 +- libs/pavexc/src/compiler/resolvers.rs | 140 ++++----- libs/pavexc/src/compiler/traits.rs | 14 +- .../src/diagnostic/callable_definition.rs | 2 +- libs/pavexc/src/language/mod.rs | 5 +- libs/pavexc/src/language/resolved_path.rs | 203 +++++++++++-- libs/pavexc/src/rustdoc/compute/toolchain.rs | 19 +- libs/pavexc/src/rustdoc/mod.rs | 4 +- libs/pavexc/src/rustdoc/queries.rs | 276 ++++++++---------- .../type_alias_are_supported/diagnostics.dot | 32 +- .../ephemeral_deps/dep/src/lib.rs | 18 +- .../expectations/app.rs | 61 ++-- .../expectations/diagnostics.dot | 32 +- .../type_alias_are_supported/src/lib.rs | 12 +- 16 files changed, 517 insertions(+), 319 deletions(-) diff --git a/libs/pavexc/src/compiler/analyses/components/db/diagnostics.rs b/libs/pavexc/src/compiler/analyses/components/db/diagnostics.rs index 9c93f9cea..3e84930dd 100644 --- a/libs/pavexc/src/compiler/analyses/components/db/diagnostics.rs +++ b/libs/pavexc/src/compiler/analyses/components/db/diagnostics.rs @@ -159,7 +159,7 @@ impl ComponentDb { package_graph: &PackageGraph, ) -> Option { let global_item_id = callable.source_coordinates.as_ref()?; - let item = krate_collection.get_type_by_global_type_id(global_item_id); + let item = krate_collection.get_item_by_global_type_id(global_item_id); let definition_span = item.span.as_ref()?; let source_contents = diagnostic::read_source_file( &definition_span.filename, @@ -177,6 +177,10 @@ impl ComponentDb { syn::parse_str::(&span_contents) { item.sig.output + } else if let Ok(item) = + syn::parse_str::(&span_contents) + { + item.sig.output } else { panic!("Could not parse as a function or method:\n{span_contents}") } @@ -271,7 +275,7 @@ impl ComponentDb { package_graph: &PackageGraph, ) -> Option { let global_item_id = callable.source_coordinates.as_ref()?; - let item = krate_collection.get_type_by_global_type_id(global_item_id); + let item = krate_collection.get_item_by_global_type_id(global_item_id); let definition_span = item.span.as_ref()?; let source_contents = diagnostic::read_source_file( &definition_span.filename, @@ -289,6 +293,10 @@ impl ComponentDb { syn::parse_str::(&span_contents) { item.sig.generics.params + } else if let Ok(item) = + syn::parse_str::(&span_contents) + { + item.sig.generics.params } else { panic!("Could not parse as a function or method:\n{span_contents}") } diff --git a/libs/pavexc/src/compiler/component/prebuilt_type.rs b/libs/pavexc/src/compiler/component/prebuilt_type.rs index fce1ce45e..c101e00d9 100644 --- a/libs/pavexc/src/compiler/component/prebuilt_type.rs +++ b/libs/pavexc/src/compiler/component/prebuilt_type.rs @@ -3,7 +3,7 @@ use crate::language::ResolvedType; pub struct PrebuiltType(ResolvedType); impl PrebuiltType { - pub fn new(ty: ResolvedType) -> Result { + pub(crate) fn new(ty: ResolvedType) -> Result { if ty.has_implicit_lifetime_parameters() || !ty.named_lifetime_parameters().is_empty() { return Err(PrebuiltTypeValidationError::CannotHaveLifetimeParameters { ty }); } diff --git a/libs/pavexc/src/compiler/path_parameter_validation.rs b/libs/pavexc/src/compiler/path_parameter_validation.rs index f5c1f145e..73d40bc95 100644 --- a/libs/pavexc/src/compiler/path_parameter_validation.rs +++ b/libs/pavexc/src/compiler/path_parameter_validation.rs @@ -141,7 +141,7 @@ pub(crate) fn verify_path_parameters( unreachable!() }; for field_id in field_ids { - let field_item = krate_collection.get_type_by_global_type_id(&GlobalItemId { + let field_item = krate_collection.get_item_by_global_type_id(&GlobalItemId { rustdoc_item_id: field_id.clone(), package_id: extracted_path_type.package_id.clone(), }); @@ -298,7 +298,7 @@ fn must_be_a_plain_struct( let Some(item_id) = t.rustdoc_id.clone() else { unreachable!() }; - let item = krate_collection.get_type_by_global_type_id(&GlobalItemId { + let item = krate_collection.get_item_by_global_type_id(&GlobalItemId { rustdoc_item_id: item_id, package_id: t.package_id.clone(), }); diff --git a/libs/pavexc/src/compiler/resolvers.rs b/libs/pavexc/src/compiler/resolvers.rs index 5e5fbfb91..8cf9884ce 100644 --- a/libs/pavexc/src/compiler/resolvers.rs +++ b/libs/pavexc/src/compiler/resolvers.rs @@ -11,11 +11,11 @@ use once_cell::sync::OnceCell; use rustdoc_types::{GenericArg, GenericArgs, GenericParamDefKind, ItemEnum, Type}; use crate::language::{ - Callable, Generic, GenericArgument, GenericLifetimeParameter, InvocationStyle, PathType, - ResolvedPath, ResolvedPathGenericArgument, ResolvedPathLifetime, ResolvedPathSegment, + Callable, CallableItem, Generic, GenericArgument, GenericLifetimeParameter, InvocationStyle, + PathType, ResolvedPath, ResolvedPathGenericArgument, ResolvedPathLifetime, ResolvedPathSegment, ResolvedPathType, ResolvedType, Slice, Tuple, TypeReference, UnknownPath, }; -use crate::rustdoc::{CannotGetCrateData, ResolvedItemWithParent, RustdocKindExt}; +use crate::rustdoc::{CannotGetCrateData, RustdocKindExt}; use crate::rustdoc::{CrateCollection, ResolvedItem}; use super::utils::process_framework_path; @@ -33,7 +33,7 @@ pub(crate) fn resolve_type( let re_exporter_crate_name = { let mut re_exporter = None; if let Some(krate) = krate_collection.get_crate_by_package_id(used_by_package_id) { - if let Some(item) = krate.maybe_get_type_by_local_type_id(id) { + if let Some(item) = krate.maybe_get_item_by_local_type_id(id) { // 0 is the crate index of local types. if item.crate_id == 0 { re_exporter = Some(None); @@ -57,7 +57,7 @@ pub(crate) fn resolve_type( id, re_exporter_crate_name, )?; - let type_item = krate_collection.get_type_by_global_type_id(&global_type_id); + let type_item = krate_collection.get_item_by_global_type_id(&global_type_id); // We want to remove any indirections (e.g. `type Foo = Bar;`) and get the actual type. if let ItemEnum::TypeAlias(type_alias) = &type_item.inner { let mut alias_generic_bindings = HashMap::new(); @@ -293,15 +293,14 @@ pub(crate) fn resolve_callable( krate_collection: &CrateCollection, callable_path: &ResolvedPath, ) -> Result { - let ( - ResolvedItemWithParent { - item: callable, - parent, - }, - qualified_self_type, - ) = callable_path.find_rustdoc_items(krate_collection)?; - let used_by_package_id = &callable_path.package_id; - let (header, decl, fn_generics_defs, invocation_style) = match &callable.item.inner { + let callable_items = callable_path.find_rustdoc_callable_items(krate_collection)??; + let (callable_item, new_callable_path) = match &callable_items { + CallableItem::Function(item, p) => (item, p), + CallableItem::Method { method, .. } => (&method.0, &method.1), + }; + let used_by_package_id = &new_callable_path.package_id; + + let (header, decl, fn_generics_defs, invocation_style) = match &callable_item.item.inner { ItemEnum::Function(f) => ( &f.header, &f.sig, @@ -319,40 +318,45 @@ pub(crate) fn resolve_callable( }; let mut generic_bindings = HashMap::new(); - if let Some(qself) = qualified_self_type { - generic_bindings.insert("Self".to_string(), qself); - } - let parent_info = parent.map(|p| { - let parent_segments = callable_path.segments[..callable_path.segments.len() - 1].to_vec(); - let parent_path = ResolvedPath { - segments: parent_segments, - qualified_self: callable_path.qualified_self.clone(), - package_id: callable_path.package_id.clone(), - }; - (p, parent_path) - }); - if let Some((parent, parent_path)) = &parent_info { - if matches!(parent.item.inner, ItemEnum::Trait(_)) { + if let CallableItem::Method { + method_owner, + qualified_self, + .. + } = &callable_items + { + if matches!(&method_owner.0.item.inner, ItemEnum::Trait(_)) { if let Err(e) = get_trait_generic_bindings( - parent, - &parent_path, + &method_owner.0, + &method_owner.1, krate_collection, &mut generic_bindings, ) { - tracing::trace!(error.msg = %e, error.details = ?e, "Error getting trait generic bindings"); + tracing::warn!(error.msg = %e, error.details = ?e, "Error getting trait generic bindings"); } - } else { - match resolve_type_path_with_item(parent_path, parent, krate_collection) { - Ok(parent_type) => { - generic_bindings.insert("Self".to_string(), parent_type); - } - Err(e) => { - tracing::trace!(error.msg = %e, error.details = ?e, "Error resolving the parent type"); - } + } + + let self_ = match qualified_self { + Some(q) => q, + None => method_owner, + }; + + let self_generic_ty = match resolve_type_path_with_item( + &self_.1, + &self_.0, + krate_collection, + ) { + Ok(ty) => Some(ty), + Err(e) => { + tracing::warn!(error.msg = %e, error.details = ?e, "Error resolving the `Self` type"); + None } + }; + if let Some(ty) = self_generic_ty { + generic_bindings.insert("Self".to_string(), ty); } } - let fn_generic_args = &callable_path.segments.last().unwrap().generic_arguments; + + let fn_generic_args = &new_callable_path.segments.last().unwrap().generic_arguments; for (generic_arg, generic_def) in fn_generic_args.iter().zip(&fn_generics_defs.params) { let generic_name = &generic_def.name; let generic_type = match generic_arg { @@ -364,8 +368,8 @@ pub(crate) fn resolve_callable( let resolved_type = generic_type.resolve(krate_collection).map_err(|e| { GenericParameterResolutionError { generic_type: generic_type.to_owned(), - callable_path: callable_path.to_owned(), - callable_item: callable.item.clone().into_owned(), + callable_path: new_callable_path.to_owned(), + callable_item: callable_item.item.clone().into_owned(), source: Arc::new(e), } })?; @@ -397,8 +401,8 @@ pub(crate) fn resolve_callable( Err(e) => { return Err(InputParameterResolutionError { parameter_type: parameter_type.to_owned(), - callable_path: callable_path.to_owned(), - callable_item: callable.item.into_owned(), + callable_path: new_callable_path.to_owned(), + callable_item: callable_item.item.clone().into_owned(), source: Arc::new(e), parameter_index, } @@ -420,8 +424,8 @@ pub(crate) fn resolve_callable( Err(e) => { return Err(OutputTypeResolutionError { output_type: output_type.to_owned(), - callable_path: callable_path.to_owned(), - callable_item: callable.item.into_owned(), + callable_path: new_callable_path.to_owned(), + callable_item: callable_item.item.clone().into_owned(), source: Arc::new(e), } .into()); @@ -437,9 +441,12 @@ pub(crate) fn resolve_callable( let canonical_path = { // If the item is a method, we start by finding the canonical path to its parent—i.e. the struct/enum/trait // to which the method is attached. - let parent_canonical_path = match parent_info { - Some((parent, parent_path)) => { - match krate_collection.get_canonical_path_by_global_type_id(&parent.item_id) { + let parent_canonical_path = match &callable_items { + CallableItem::Method { + method_owner: self_, + .. + } => { + match krate_collection.get_canonical_path_by_global_type_id(&self_.0.item_id) { Ok(canonical_segments) => { let mut segments: Vec<_> = canonical_segments .into_iter() @@ -450,26 +457,22 @@ pub(crate) fn resolve_callable( .collect(); // The canonical path doesn't include the (populated or omitted) generic arguments from the user-provided path, // so we need to add them back in. - segments.last_mut().unwrap().generic_arguments = parent_path - .segments - .last() - .unwrap() - .generic_arguments - .clone(); + segments.last_mut().unwrap().generic_arguments = + self_.1.segments.last().unwrap().generic_arguments.clone(); Some(ResolvedPath { segments, - qualified_self: parent_path.qualified_self.clone(), - package_id: parent.item_id.package_id.clone(), + qualified_self: self_.1.qualified_self.clone(), + package_id: self_.0.item_id.package_id.clone(), }) } Err(_) => { tracing::warn!( - package_id = parent.item_id.package_id.repr(), - item_id = ?parent.item_id.rustdoc_item_id, + package_id = self_.0.item_id.package_id.repr(), + item_id = ?self_.0.item_id.rustdoc_item_id, "Failed to find canonical path for {:?}", - parent_path + self_.1 ); - Some(parent_path) + Some(self_.1.clone()) } } } @@ -490,7 +493,8 @@ pub(crate) fn resolve_callable( None => { // There was no parent, it's a free function or a straight struct/enum. We need to go through the same process // we applied for the parent. - match krate_collection.get_canonical_path_by_global_type_id(&callable.item_id) { + match krate_collection.get_canonical_path_by_global_type_id(&callable_item.item_id) + { Ok(p) => { let mut segments: Vec<_> = p .into_iter() @@ -510,13 +514,13 @@ pub(crate) fn resolve_callable( ResolvedPath { segments, qualified_self: callable_path.qualified_self.clone(), - package_id: callable.item_id.package_id.clone(), + package_id: callable_item.item_id.package_id.clone(), } } Err(_) => { tracing::warn!( - package_id = callable.item_id.package_id.repr(), - item_id = ?callable.item_id.rustdoc_item_id, + package_id = callable_item.item_id.package_id.repr(), + item_id = ?callable_item.item_id.rustdoc_item_id, "Failed to find canonical path for {:?}", callable_path ); @@ -535,7 +539,7 @@ pub(crate) fn resolve_callable( path: canonical_path, inputs: resolved_parameter_types, invocation_style, - source_coordinates: Some(callable.item_id), + source_coordinates: Some(callable_item.item_id.clone()), }; Ok(callable) } @@ -573,8 +577,8 @@ pub(crate) fn resolve_type_path( path: &ResolvedPath, krate_collection: &CrateCollection, ) -> Result { - let (item, _) = path.find_rustdoc_items(krate_collection)?; - resolve_type_path_with_item(&path, &item.item, krate_collection) + let item = path.find_rustdoc_item_type(krate_collection)?.1; + resolve_type_path_with_item(&path, &item, krate_collection) } _resolve_type_path(path, krate_collection).map_err(|source| TypeResolutionError { diff --git a/libs/pavexc/src/compiler/traits.rs b/libs/pavexc/src/compiler/traits.rs index fe98a6734..352b8abfe 100644 --- a/libs/pavexc/src/compiler/traits.rs +++ b/libs/pavexc/src/compiler/traits.rs @@ -70,8 +70,8 @@ pub(crate) fn implements_trait( let trait_definition_crate = get_crate_by_package_id(krate_collection, &expected_trait.package_id)?; let trait_item_id = trait_definition_crate - .get_type_id_by_path(&expected_trait.base_type, krate_collection)??; - let trait_item = krate_collection.get_type_by_global_type_id(&trait_item_id); + .get_item_id_by_path(&expected_trait.base_type, krate_collection)??; + let trait_item = krate_collection.get_item_by_global_type_id(&trait_item_id); let ItemEnum::Trait(trait_item) = &trait_item.inner else { unreachable!() }; @@ -86,8 +86,8 @@ pub(crate) fn implements_trait( let type_definition_crate = get_crate_by_package_id(krate_collection, &our_path_type.package_id)?; let type_id = type_definition_crate - .get_type_id_by_path(&our_path_type.base_type, krate_collection)??; - let type_item = krate_collection.get_type_by_global_type_id(&type_id); + .get_item_id_by_path(&our_path_type.base_type, krate_collection)??; + let type_item = krate_collection.get_item_by_global_type_id(&type_id); // We want to see through type aliases here. if let ItemEnum::TypeAlias(type_alias) = &type_item.inner { let mut generic_bindings = HashMap::new(); @@ -132,7 +132,7 @@ pub(crate) fn implements_trait( } }; for impl_id in impls { - let item = type_definition_crate.get_type_by_local_type_id(impl_id); + let item = type_definition_crate.get_item_by_local_type_id(impl_id); let (trait_id, implementer_type) = match &item.inner { ItemEnum::Impl(impl_) => { if impl_.is_negative { @@ -241,7 +241,7 @@ pub(crate) fn implements_trait( } for impl_id in &trait_item.implementations { - let impl_item = trait_definition_crate.get_type_by_local_type_id(impl_id); + let impl_item = trait_definition_crate.get_item_by_local_type_id(impl_id); let implementer = match &impl_item.inner { ItemEnum::Impl(impl_) => { if impl_.is_negative { @@ -284,7 +284,7 @@ fn is_equivalent( tracing::trace!("Failed to look up {:?}", rustdoc_type_id); return false; }; - let rustdoc_item = krate_collection.get_type_by_global_type_id(&rustdoc_global_type_id); + let rustdoc_item = krate_collection.get_item_by_global_type_id(&rustdoc_global_type_id); // We want to see through type aliases if let ItemEnum::TypeAlias(type_alias) = &rustdoc_item.inner { return is_equivalent( diff --git a/libs/pavexc/src/diagnostic/callable_definition.rs b/libs/pavexc/src/diagnostic/callable_definition.rs index 1d56b4668..ee404c23f 100644 --- a/libs/pavexc/src/diagnostic/callable_definition.rs +++ b/libs/pavexc/src/diagnostic/callable_definition.rs @@ -73,7 +73,7 @@ impl CallableDefinition { package_graph: &PackageGraph, ) -> Option { let global_item_id = callable.source_coordinates.as_ref()?; - let item = krate_collection.get_type_by_global_type_id(global_item_id); + let item = krate_collection.get_item_by_global_type_id(global_item_id); Self::compute_from_item(&item, package_graph) } diff --git a/libs/pavexc/src/language/mod.rs b/libs/pavexc/src/language/mod.rs index 0465202dd..8deb5859f 100644 --- a/libs/pavexc/src/language/mod.rs +++ b/libs/pavexc/src/language/mod.rs @@ -1,8 +1,9 @@ pub(crate) use callable::{Callable, InvocationStyle}; pub(crate) use callable_path::{CallPath, InvalidCallPath}; pub(crate) use resolved_path::{ - ParseError, PathKind, ResolvedPath, ResolvedPathGenericArgument, ResolvedPathLifetime, - ResolvedPathQualifiedSelf, ResolvedPathSegment, ResolvedPathType, UnknownPath, + CallableItem, ParseError, PathKind, ResolvedPath, ResolvedPathGenericArgument, + ResolvedPathLifetime, ResolvedPathQualifiedSelf, ResolvedPathSegment, ResolvedPathType, + UnknownPath, }; pub(crate) use resolved_type::{ Generic, GenericArgument, GenericLifetimeParameter, Lifetime, PathType, ResolvedType, Slice, diff --git a/libs/pavexc/src/language/resolved_path.rs b/libs/pavexc/src/language/resolved_path.rs index 52fc00327..35e1a76c2 100644 --- a/libs/pavexc/src/language/resolved_path.rs +++ b/libs/pavexc/src/language/resolved_path.rs @@ -21,8 +21,11 @@ use crate::language::callable_path::{ }; use crate::language::resolved_type::{GenericArgument, Lifetime, ScalarPrimitive, Slice}; use crate::language::{CallPath, InvalidCallPath, ResolvedType, Tuple, TypeReference}; -use crate::rustdoc::{CrateCollection, RustdocKindExt, CORE_PACKAGE_ID}; -use crate::rustdoc::{ResolvedItemWithParent, TOOLCHAIN_CRATES}; +use crate::rustdoc::TOOLCHAIN_CRATES; +use crate::rustdoc::{ + CannotGetCrateData, CrateCollection, GlobalItemId, ResolvedItem, RustdocKindExt, + CORE_PACKAGE_ID, +}; use super::resolved_type::GenericLifetimeParameter; @@ -50,6 +53,9 @@ use super::resolved_type::GenericLifetimeParameter; #[derive(Clone, Debug, Eq)] pub struct ResolvedPath { pub segments: Vec, + /// The qualified self of the path, if any. + /// + /// E.g. `Type` in `::Method`. pub qualified_self: Option, /// The package id of the crate that this path belongs to. pub package_id: PackageId, @@ -119,7 +125,7 @@ impl ResolvedPathType { ) -> Result { match self { ResolvedPathType::ResolvedPath(p) => { - let resolved_item = p.path.find_rustdoc_items(krate_collection)?.0.item; + let resolved_item = p.path.find_rustdoc_item_type(krate_collection)?.1; let item = &resolved_item.item; let used_by_package_id = resolved_item.item_id.package_id(); let (global_type_id, base_type) = krate_collection @@ -128,6 +134,7 @@ impl ResolvedPathType { let generic_param_def = match &item.inner { ItemEnum::Enum(e) => &e.generics.params, ItemEnum::Struct(s) => &s.generics.params, + ItemEnum::Primitive(_) => &Vec::new(), other => { anyhow::bail!( "Generic parameters can either be set to structs or enums, \ @@ -650,35 +657,168 @@ impl ResolvedPath { &self.segments.first().unwrap().ident } - /// Find information about the type that this path points at. - /// It also returns the type of the qualified self, if it is present. - /// - /// E.g. `MyType` will return `(MyType, None)`. - /// `::trait_method` will return `(MyType, Some(MyTrait::trait_method))`. - pub fn find_rustdoc_items<'a>( + /// Find the `rustdoc` items requied to analyze the callable that `self` points to. + pub fn find_rustdoc_callable_items<'a>( &self, krate_collection: &'a CrateCollection, - ) -> Result<(ResolvedItemWithParent<'a>, Option), UnknownPath> { - let path: Vec<_> = self + ) -> Result, UnknownPath>, CannotGetCrateData> { + let krate = krate_collection.get_or_compute_crate_by_package_id(&self.package_id)?; + + let path_without_generics: Vec<_> = self .segments .iter() .map(|path_segment| path_segment.ident.to_string()) .collect(); - let ty = krate_collection - .get_item_by_resolved_path(&path, &self.package_id) - .map_err(|e| UnknownPath(self.to_owned(), Arc::new(e.into())))? - .map_err(|e| UnknownPath(self.to_owned(), Arc::new(e.into())))?; - let qself_ty = self + if let Ok(type_id) = krate.get_item_id_by_path(&path_without_generics, krate_collection)? { + let i = krate_collection.get_item_by_global_type_id(&type_id); + return Ok(Ok(CallableItem::Function( + ResolvedItem { + item: i, + item_id: type_id, + }, + self.clone(), + ))); + } + + // The path might be pointing to a method, which is not a type. + // We drop the last segment to see if we can get a hit on the struct/enum type + // to which the method belongs. + if self.segments.len() < 3 { + // It has to be at least three segments—crate name, type name, method name. + // If it's shorter than three, it's just an unknown path. + return Ok(Err(UnknownPath( + self.clone(), + Arc::new(anyhow::anyhow!( + "Path is too short to be a method path, but there is no function at that path" + )), + ))); + } + + let qself = match self .qualified_self .as_ref() .map(|qself| { - qself - .type_ - .resolve(krate_collection) - .map_err(|e| UnknownPath(self.to_owned(), Arc::new(e))) + if let ResolvedPathType::ResolvedPath(p) = &qself.type_ { + p.path + .find_rustdoc_item_type(krate_collection) + .map_err(|e| UnknownPath(self.to_owned(), Arc::new(e.into()))) + } else { + Err(UnknownPath( + self.clone(), + Arc::new(anyhow::anyhow!("Qualified self type is not a path")), + )) + } }) - .transpose()?; - Ok((ty, qself_ty)) + .transpose() + { + Ok(x) => x.map(|(item, path)| (path, item)), + Err(e) => return Ok(Err(e)), + }; + + let (method_name_segment, type_path_segments) = self.segments.split_last().unwrap(); + + // Let's first try to see if the parent path points to a type. + let method_owner_path = ResolvedPath { + segments: type_path_segments.to_vec(), + qualified_self: None, + package_id: self.package_id.clone(), + }; + let (method_owner_path, method_owner_item) = + match krate_collection.get_type_by_resolved_path(method_owner_path)? { + Ok(p) => p, + Err(e) => { + return Ok(Err(UnknownPath(self.clone(), Arc::new(e.into())))); + } + }; + + let children_ids = match &method_owner_item.item.inner { + ItemEnum::Struct(s) => &s.impls, + ItemEnum::Enum(enum_) => &enum_.impls, + ItemEnum::Trait(trait_) => &trait_.items, + _ => { + unreachable!() + } + }; + let method_owner_krate = + krate_collection.get_or_compute_crate_by_package_id(&method_owner_path.package_id)?; + let mut method = None; + for child_id in children_ids { + let child = method_owner_krate.get_item_by_local_type_id(child_id); + match &child.inner { + ItemEnum::Impl(impl_block) => { + // We are completely ignoring the bounds attached to the implementation block. + // This can lead to issues: the same method can be defined multiple + // times in different implementation blocks with non-overlapping constraints. + for impl_item_id in &impl_block.items { + let impl_item = method_owner_krate.get_item_by_local_type_id(impl_item_id); + if impl_item.name.as_ref() == Some(&method_name_segment.ident) { + if let ItemEnum::Function(_) = &impl_item.inner { + method = Some(ResolvedItem { + item: impl_item, + item_id: GlobalItemId { + package_id: krate.core.package_id.clone(), + rustdoc_item_id: child_id.to_owned(), + }, + }); + } + } + } + } + ItemEnum::Function(_) => { + if child.name.as_ref() == Some(&method_name_segment.ident) { + method = Some(ResolvedItem { + item: child, + item_id: GlobalItemId { + package_id: krate.core.package_id.clone(), + rustdoc_item_id: child_id.to_owned(), + }, + }); + } + } + i => { + dbg!(i); + unreachable!() + } + } + } + + let method_path = ResolvedPath { + segments: method_owner_path + .segments + .iter() + .chain(std::iter::once(method_name_segment)) + .cloned() + .collect(), + qualified_self: self.qualified_self.clone(), + package_id: method_owner_path.package_id.clone(), + }; + if let Some(method) = method { + Ok(Ok(CallableItem::Method { + method_owner: (method_owner_item, method_owner_path), + method: (method, method_path), + qualified_self: qself, + })) + } else { + Ok(Err(UnknownPath( + self.clone(), + Arc::new(anyhow::anyhow!( + "Path is too short to be a method path, but there is no function at that path" + )), + ))) + } + } + + /// Find information about the type that this path points at. + /// It only works if the path points at a type (i.e. struct or enum). + /// It will return an error if the path points at a function or a method instead. + pub fn find_rustdoc_item_type<'a>( + &self, + krate_collection: &'a CrateCollection, + ) -> Result<(ResolvedPath, ResolvedItem<'a>), UnknownPath> { + krate_collection + .get_type_by_resolved_path(self.clone()) + .map_err(|e| UnknownPath(self.to_owned(), Arc::new(e.into())))? + .map_err(|e| UnknownPath(self.to_owned(), Arc::new(e.into()))) } pub fn render_path(&self, id2name: &BiHashMap, buffer: &mut String) { @@ -720,6 +860,25 @@ impl ResolvedPath { } } +/// There are two key callables in Rust: functions and methods. +pub enum CallableItem<'a> { + /// Functions are free-standing and map to a single `rustdoc` item. + Function(ResolvedItem<'a>, ResolvedPath), + /// Methods are associated with a type. + /// They can either be inherent or trait methods. + /// In the latter case, the `qualified_self` field will be populated with + /// the `Self` type of the method. + Method { + /// The item to which the method belongs. + /// This can be a trait, for a trait method, or a struct/enum for an inherent method. + method_owner: (ResolvedItem<'a>, ResolvedPath), + method: (ResolvedItem<'a>, ResolvedPath), + /// The `self` type of the method. + /// It's only populated when working with trait methods. + qualified_self: Option<(ResolvedItem<'a>, ResolvedPath)>, + }, +} + impl ResolvedPathType { pub fn render_path(&self, id2name: &BiHashMap, buffer: &mut String) { match self { diff --git a/libs/pavexc/src/rustdoc/compute/toolchain.rs b/libs/pavexc/src/rustdoc/compute/toolchain.rs index 04a07e4d3..a222513a5 100644 --- a/libs/pavexc/src/rustdoc/compute/toolchain.rs +++ b/libs/pavexc/src/rustdoc/compute/toolchain.rs @@ -1,5 +1,6 @@ use anyhow::Context; use once_cell::sync::OnceCell; +use rustdoc_types::ItemKind; use std::path::PathBuf; #[tracing::instrument( @@ -16,9 +17,21 @@ pub(crate) fn get_toolchain_crate_docs( let json_path = root_folder.join(format!("{}.json", name)); let json = fs_err::read_to_string(json_path) .with_context(|| format!("Failed to retrieve the JSON docs for {}", name))?; - serde_json::from_str::(&json) - .with_context(|| format!("Failed to deserialize the JSON docs for {}", name)) - .map_err(Into::into) + let mut krate = serde_json::from_str::(&json) + .with_context(|| format!("Failed to deserialize the JSON docs for {}", name))?; + + // Primitives, if using their fully qualified names, must be imported as `std::primitive::*`. + // Unfortunately, that `primitive` module doesn't exist in the JSON docs, so we have to + // manually add it. + if name == "std" || name == "core" { + krate.paths.values_mut().for_each(|summary| { + if summary.kind == ItemKind::Primitive { + summary.path.insert(1, "primitive".into()); + } + }) + } + + Ok(krate) } fn get_json_docs_root_folder_via_rustup(toolchain_name: &str) -> Result { diff --git a/libs/pavexc/src/rustdoc/mod.rs b/libs/pavexc/src/rustdoc/mod.rs index 3cdd43cc4..68713c78b 100644 --- a/libs/pavexc/src/rustdoc/mod.rs +++ b/libs/pavexc/src/rustdoc/mod.rs @@ -6,9 +6,7 @@ use guppy::PackageId; use once_cell::sync::Lazy; pub use compute::CannotGetCrateData; -pub use queries::{ - Crate, CrateCollection, GlobalItemId, ResolvedItem, ResolvedItemWithParent, RustdocKindExt, -}; +pub use queries::{Crate, CrateCollection, GlobalItemId, ResolvedItem, RustdocKindExt}; mod compute; mod package_id_spec; diff --git a/libs/pavexc/src/rustdoc/queries.rs b/libs/pavexc/src/rustdoc/queries.rs index c273c6631..89d0faa76 100644 --- a/libs/pavexc/src/rustdoc/queries.rs +++ b/libs/pavexc/src/rustdoc/queries.rs @@ -13,6 +13,8 @@ use indexmap::IndexSet; use rustdoc_types::{ExternalCrate, Item, ItemEnum, ItemKind, ItemSummary, Visibility}; use tracing::Span; +use crate::compiler::resolvers::resolve_type; +use crate::language::{ResolvedPathGenericArgument, ResolvedPathType}; use crate::rustdoc::version_matcher::VersionMatcher; use crate::rustdoc::{compute::compute_crate_docs, utils, CannotGetCrateData, TOOLCHAIN_CRATES}; use crate::rustdoc::{ALLOC_PACKAGE_ID, CORE_PACKAGE_ID, STD_PACKAGE_ID}; @@ -311,152 +313,127 @@ impl CrateCollection { /// Retrieve type information given its [`GlobalItemId`]. /// /// It panics if no item is found for the specified [`GlobalItemId`]. - pub fn get_type_by_global_type_id(&self, type_id: &GlobalItemId) -> Cow<'_, Item> { + pub fn get_item_by_global_type_id(&self, type_id: &GlobalItemId) -> Cow<'_, Item> { // Safe to unwrap since the package id is coming from a GlobalItemId. let krate = self.get_crate_by_package_id(&type_id.package_id).unwrap(); - krate.get_type_by_local_type_id(&type_id.rustdoc_item_id) + krate.get_item_by_local_type_id(&type_id.rustdoc_item_id) } - /// Retrieve information about an item given its path and the id of the package where - /// it was defined. - pub fn get_item_by_resolved_path( + pub fn get_type_by_resolved_path( &self, - path: &[String], - package_id: &PackageId, - ) -> Result, GetItemByResolvedPathError>, CannotGetCrateData> - { - let krate = self.get_or_compute_crate_by_package_id(package_id)?; - if let Ok(type_id) = krate.get_type_id_by_path(path, self)? { - let i = self.get_type_by_global_type_id(&type_id); - return Ok(Ok(ResolvedItemWithParent { - item: ResolvedItem { - item: i, - item_id: type_id.to_owned(), - }, - parent: None, - })); - } - // The path might be pointing to a method, which is not a type. - // We drop the last segment to see if we can get a hit on the struct/enum type - // to which the method belongs. - if path.len() < 3 { - // It has to be at least three segments—crate name, type name, method name. - // If it's shorter than three, it's just an unknown path. + mut resolved_path: crate::language::ResolvedPath, + ) -> Result< + Result<(crate::language::ResolvedPath, ResolvedItem<'_>), GetItemByResolvedPathError>, + CannotGetCrateData, + > { + let path_without_generics = resolved_path + .segments + .iter() + .map(|p| p.ident.clone()) + .collect::>(); + let krate = self.get_or_compute_crate_by_package_id(&resolved_path.package_id)?; + + let Ok(mut type_id) = krate.get_item_id_by_path(&path_without_generics, self)? else { return Ok(Err(UnknownItemPath { - path: path.to_vec(), + path: path_without_generics, } .into())); + }; + + let mut item = self.get_item_by_global_type_id(&type_id); + + if !matches!( + item.inner, + ItemEnum::Struct(_) + | ItemEnum::Enum(_) + | ItemEnum::TypeAlias(_) + | ItemEnum::Trait(_) + | ItemEnum::Primitive(_) + ) { + return Ok(Err(GetItemByResolvedPathError::UnsupportedItemKind( + UnsupportedItemKind { + path: path_without_generics, + kind: item.inner.kind().into(), + }, + ))); } - let (method_name, type_path_segments) = path.split_last().unwrap(); - - if let Ok(mut parent_type_id) = krate.get_type_id_by_path(type_path_segments, self)? { - let mut parent = self.get_type_by_global_type_id(&parent_type_id); - // The parent trait/struct might have been a re-export, so we need to make sure that we - // are looking at the crate where it was originally defined when we start - // following the local type ids that are encoded in the parent. - let mut krate = self.get_or_compute_crate_by_package_id(&parent_type_id.package_id)?; - - // We eagerly check if the parent item is an alias, and if so we follow it - // to the original type. - // This might take multiple iterations, since the alias might point to another - // alias. - loop { - let ItemEnum::TypeAlias(type_alias) = &parent.inner else { - break; - }; - let rustdoc_types::Type::ResolvedPath(p) = &type_alias.type_ else { - break; - }; - // The aliased type might be a re-export of a foreign type, - // therefore we go through the summary here rather than - // going straight for a local id lookup. - let summary = krate.get_type_summary_by_local_type_id(&p.id).unwrap(); - let source_package_id = krate - .compute_package_id_for_crate_id(summary.crate_id, self) - .map_err(|e| CannotGetCrateData { - package_spec: summary.crate_id.to_string(), - source: Arc::new(e), - })?; - krate = self.get_or_compute_crate_by_package_id(&source_package_id)?; - if let Ok(type_id) = krate.get_type_id_by_path(&summary.path, self)? { - parent_type_id = type_id; - } else { - return Ok(Err(UnknownItemPath { - path: summary.path.clone(), - } - .into())); - } - parent = self.get_type_by_global_type_id(&parent_type_id); - } - let children_ids = match &parent.inner { - ItemEnum::Struct(s) => &s.impls, - ItemEnum::Enum(enum_) => &enum_.impls, - ItemEnum::Trait(trait_) => &trait_.items, - item => { - return Ok(Err(UnsupportedItemKind { - path: path.to_owned(), - kind: item.kind().to_owned(), - } - .into())); + // We eagerly check if the item is an alias, and if so we follow it + // to the original type. + // This process might take multiple iterations, since the alias might point to another + // alias, recursively. + let mut krate = self.get_or_compute_crate_by_package_id(&type_id.package_id)?; + loop { + let ItemEnum::TypeAlias(type_alias) = &item.inner else { + break; + }; + let rustdoc_types::Type::ResolvedPath(aliased_path) = &type_alias.type_ else { + break; + }; + + // The aliased type might be a re-export of a foreign type, + // therefore we go through the summary here rather than + // going straight for a local id lookup. + let aliased_summary = krate + .get_summary_by_local_type_id(&aliased_path.id) + .unwrap(); + let aliased_package_id = krate + .compute_package_id_for_crate_id(aliased_summary.crate_id, self) + .map_err(|e| CannotGetCrateData { + package_spec: aliased_summary.crate_id.to_string(), + source: Arc::new(e), + })?; + let aliased_krate = self.get_or_compute_crate_by_package_id(&aliased_package_id)?; + let Ok(aliased_type_id) = + aliased_krate.get_item_id_by_path(&aliased_summary.path, self)? + else { + return Ok(Err(UnknownItemPath { + path: aliased_summary.path.clone(), } + .into())); }; - for child_id in children_ids { - let child = krate.get_type_by_local_type_id(child_id); - match &child.inner { - ItemEnum::Impl(impl_block) => { - // We are completely ignoring the bounds attached to the implementation block. - // This can lead to issues: the same method can be defined multiple - // times in different implementation blocks with non-overlapping constraints. - for impl_item_id in &impl_block.items { - let impl_item = krate.get_type_by_local_type_id(impl_item_id); - if impl_item.name.as_ref() == Some(method_name) { - if let ItemEnum::Function(_) = &impl_item.inner { - return Ok(Ok(ResolvedItemWithParent { - item: ResolvedItem { - item: impl_item, - item_id: GlobalItemId { - package_id: krate.core.package_id.clone(), - rustdoc_item_id: impl_item_id.to_owned(), - }, - }, - parent: Some(ResolvedItem { - item: parent, - item_id: parent_type_id.to_owned(), - }), - })); - } - } - } - } - ItemEnum::Function(_) => { - if child.name.as_ref() == Some(method_name) { - return Ok(Ok(ResolvedItemWithParent { - item: ResolvedItem { - item: child, - item_id: GlobalItemId { - package_id: krate.core.package_id.clone(), - rustdoc_item_id: child_id.to_owned(), - }, - }, - parent: Some(ResolvedItem { - item: parent, - item_id: parent_type_id.to_owned(), - }), - })); + let aliased_item = self.get_item_by_global_type_id(&aliased_type_id); + + let new_path = { + let path_args = &resolved_path.segments.last().unwrap().generic_arguments; + let alias_generics = &type_alias.generics.params; + let mut name2path_arg = HashMap::new(); + for (path_arg, alias_generic) in path_args.iter().zip(alias_generics.iter()) { + match path_arg { + ResolvedPathGenericArgument::Type(t) => { + let t = t.resolve(self).unwrap(); + name2path_arg.insert(alias_generic.name.clone(), t); } - } - i => { - dbg!(i); - unreachable!() + ResolvedPathGenericArgument::Lifetime(_) => unimplemented!(), } } - } - } - Ok(Err(UnknownItemPath { - path: path.to_owned(), + + let aliased = resolve_type( + &type_alias.type_, + type_id.package_id(), + self, + &name2path_arg, + ) + .unwrap(); + let aliased: ResolvedPathType = aliased.into(); + let ResolvedPathType::ResolvedPath(aliased_path) = aliased else { + unreachable!(); + }; + (*aliased_path.path).clone() + }; + + // Update the loop variables to reflect alias resolution. + type_id = aliased_type_id; + item = aliased_item; + krate = aliased_krate; + resolved_path = new_path; } - .into())) + + let resolved_item = ResolvedItem { + item, + item_id: type_id, + }; + Ok(Ok((resolved_path, resolved_item))) } /// Retrieve the canonical path for a struct, enum or function given its [`GlobalItemId`]. @@ -485,7 +462,7 @@ impl CrateCollection { ) -> Result<(GlobalItemId, &[String]), anyhow::Error> { let (definition_package_id, path) = { let used_by_krate = self.get_or_compute_crate_by_package_id(used_by_package_id)?; - let local_type_summary = used_by_krate.get_type_summary_by_local_type_id(item_id)?; + let local_type_summary = used_by_krate.get_summary_by_local_type_id(item_id)?; ( used_by_krate.compute_package_id_for_crate_id_with_hint( local_type_summary.crate_id, @@ -505,7 +482,7 @@ impl CrateCollection { ) }; let definition_krate = self.get_or_compute_crate_by_package_id(&definition_package_id)?; - let type_id = definition_krate.get_type_id_by_path(&path, self)??; + let type_id = definition_krate.get_item_id_by_path(&path, self)??; let canonical_path = self.get_canonical_path_by_global_type_id(&type_id)?; Ok((type_id.clone(), canonical_path)) } @@ -528,20 +505,6 @@ impl Drop for CrateCollection { } } -/// The output of [`CrateCollection::get_item_by_resolved_path`]. -/// -/// If the path points to a "free-standing" item, `parent` is set to `None`. -/// Examples: a function, a struct, an enum. -/// -/// If the item is "attached" to another parent item, `parent` is set to `Some`. -/// Examples: a trait method and the respective trait definition, a method and the struct it is -/// defined on, etc. -#[derive(Debug, Clone)] -pub struct ResolvedItemWithParent<'a> { - pub item: ResolvedItem<'a>, - pub parent: Option>, -} - #[derive(Debug, Clone)] pub struct ResolvedItem<'a> { pub item: Cow<'a, Item>, @@ -797,7 +760,7 @@ impl Crate { .compute_package_id_for_crate_id(crate_id, collection, maybe_dependent_crate_name) } - pub fn get_type_id_by_path( + pub fn get_item_id_by_path( &self, path: &[String], krate_collection: &CrateCollection, @@ -829,7 +792,7 @@ impl Crate { .get_or_compute_crate_by_package_id(&source_package_id) .unwrap(); if let Ok(source_id) = - source_krate.get_type_id_by_path(&original_source_path, krate_collection) + source_krate.get_item_id_by_path(&original_source_path, krate_collection) { return Ok(source_id); } @@ -845,7 +808,7 @@ impl Crate { /// /// It only works for structs, enums and functions. /// It **will** fail if the id points to a method! - fn get_type_summary_by_local_type_id( + fn get_summary_by_local_type_id( &self, id: &rustdoc_types::Id, ) -> Result<&rustdoc_types::ItemSummary, anyhow::Error> { @@ -859,8 +822,8 @@ impl Crate { }) } - pub fn get_type_by_local_type_id(&self, id: &rustdoc_types::Id) -> Cow<'_, Item> { - let type_ = self.maybe_get_type_by_local_type_id(id); + pub fn get_item_by_local_type_id(&self, id: &rustdoc_types::Id) -> Cow<'_, Item> { + let type_ = self.maybe_get_item_by_local_type_id(id); if type_.is_none() { panic!( "Failed to look up the type id `{}` in the rustdoc's index for package `{}`.", @@ -873,7 +836,7 @@ impl Crate { /// Same as `get_type_by_local_type_id`, but returns `None` instead of panicking /// if the type is not found. - pub fn maybe_get_type_by_local_type_id(&self, id: &rustdoc_types::Id) -> Option> { + pub fn maybe_get_item_by_local_type_id(&self, id: &rustdoc_types::Id) -> Option> { self.core.krate.index.get(id) } @@ -1032,13 +995,20 @@ fn index_local_types<'a>( } } ItemEnum::Trait(_) + | ItemEnum::Primitive(_) | ItemEnum::Function(_) | ItemEnum::Enum(_) | ItemEnum::Struct(_) | ItemEnum::TypeAlias(_) => { let name = current_item.name.as_deref().expect( - "All 'struct', 'function', 'enum', 'type_alias' and 'trait' items have a 'name' property", + "All 'struct', 'function', 'enum', 'type_alias', 'primitive' and 'trait' items have a 'name' property", ); + if matches!(current_item.inner, ItemEnum::Primitive(_)) { + // E.g. `std::bool` won't work, `std::primitive::bool` does work but the `primitive` module + // is not visible in the JSON docs for `std`/`core`. + // A hacky workaround, but it works. + current_path.push("primitive"); + } current_path.push(name); let path = current_path.into_iter().map(|s| s.to_string()).collect(); diff --git a/libs/ui_tests/reflection/type_alias_are_supported/diagnostics.dot b/libs/ui_tests/reflection/type_alias_are_supported/diagnostics.dot index 9c1567ac6..d53c1f68d 100644 --- a/libs/ui_tests/reflection/type_alias_are_supported/diagnostics.dot +++ b/libs/ui_tests/reflection/type_alias_are_supported/diagnostics.dot @@ -1,25 +1,29 @@ digraph "GET /home - 0" { - 0 [ label = "pavex::middleware::wrap_noop(pavex::middleware::Next>) -> pavex::response::Response"] - 1 [ label = "pavex::middleware::Next::new(crate::route_0::Next0<'a>) -> pavex::middleware::Next>"] - 2 [ label = "crate::route_0::Next0((bool, char, u8), &'a dep_f8f62968::ActualType) -> crate::route_0::Next0<'a>"] + 0 [ label = "pavex::middleware::wrap_noop(pavex::middleware::Next>) -> pavex::response::Response"] + 1 [ label = "pavex::middleware::Next::new(crate::route_0::Next0<'a, 'b>) -> pavex::middleware::Next>"] + 2 [ label = "crate::route_0::Next0((bool, char, u8), &'a dep_f8f62968::GenericType, &'b dep_f8f62968::ActualType) -> crate::route_0::Next0<'a, 'b>"] 3 [ label = "(bool, char, u8)"] - 5 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] - 6 [ label = "&dep_f8f62968::ActualType"] + 4 [ label = "&dep_f8f62968::ActualType"] + 6 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] + 7 [ label = "&dep_f8f62968::GenericType"] 1 -> 0 [ ] 2 -> 1 [ ] + 4 -> 2 [ ] 3 -> 2 [ ] - 0 -> 5 [ ] - 6 -> 2 [ ] + 0 -> 6 [ ] + 7 -> 2 [ ] } digraph "GET /home - 1" { - 0 [ label = "app_f8f62968::handler_with_input_tuple((bool, char, u8), &dep_f8f62968::ActualType) -> pavex::response::Response"] + 0 [ label = "app_f8f62968::handler_with_input_tuple((bool, char, u8), &dep_f8f62968::ActualType, &dep_f8f62968::GenericType) -> pavex::response::Response"] 1 [ label = "(bool, char, u8)"] - 3 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] - 4 [ label = "&dep_f8f62968::ActualType"] + 2 [ label = "&dep_f8f62968::GenericType"] + 4 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] + 5 [ label = "&dep_f8f62968::ActualType"] + 2 -> 0 [ ] 1 -> 0 [ ] - 0 -> 3 [ ] - 4 -> 0 [ ] + 0 -> 4 [ ] + 5 -> 0 [ ] } digraph "* /home - 0" { @@ -43,9 +47,11 @@ digraph "* /home - 1" { } digraph app_state { - 0 [ label = "crate::ApplicationState((bool, char, u8), dep_f8f62968::ActualType) -> crate::ApplicationState"] + 0 [ label = "crate::ApplicationState((bool, char, u8), dep_f8f62968::ActualType, dep_f8f62968::GenericType) -> crate::ApplicationState"] 1 [ label = "app_f8f62968::constructor_with_output_tuple() -> (bool, char, u8)"] 2 [ label = "dep_f8f62968::ActualType::new() -> dep_f8f62968::ActualType"] + 3 [ label = "dep_f8f62968::GenericType::::new() -> dep_f8f62968::GenericType"] + 3 -> 0 [ ] 2 -> 0 [ ] 1 -> 0 [ ] } diff --git a/libs/ui_tests/reflection/type_alias_are_supported/ephemeral_deps/dep/src/lib.rs b/libs/ui_tests/reflection/type_alias_are_supported/ephemeral_deps/dep/src/lib.rs index c16976247..ab8184edc 100644 --- a/libs/ui_tests/reflection/type_alias_are_supported/ephemeral_deps/dep/src/lib.rs +++ b/libs/ui_tests/reflection/type_alias_are_supported/ephemeral_deps/dep/src/lib.rs @@ -2,6 +2,7 @@ use pavex::blueprint::{constructor::Lifecycle, Blueprint}; use pavex::f; pub type IntermediateAlias = ActualType; +pub type IntermediateGenericAlias = GenericType; #[derive(Clone)] pub struct ActualType; @@ -10,4 +11,19 @@ impl ActualType { pub fn new() -> Self { todo!() } -} \ No newline at end of file +} + +#[derive(Clone)] +pub struct GenericType { + _a: A, + _b: B, +} + +// The naming of the generic parameters on this `impl` block is intentionally +// chosen to be different from the generic parameters on the struct definition +// to test Pavex's ability to handle this case. +impl GenericType { + pub fn new() -> GenericType { + todo!() + } +} diff --git a/libs/ui_tests/reflection/type_alias_are_supported/expectations/app.rs b/libs/ui_tests/reflection/type_alias_are_supported/expectations/app.rs index a079e3065..33a127d6d 100644 --- a/libs/ui_tests/reflection/type_alias_are_supported/expectations/app.rs +++ b/libs/ui_tests/reflection/type_alias_are_supported/expectations/app.rs @@ -9,13 +9,19 @@ struct ServerState { pub struct ApplicationState { s0: (bool, char, u8), s1: dep_f8f62968::ActualType, + s2: dep_f8f62968::GenericType, } pub async fn build_application_state() -> crate::ApplicationState { - let v0 = dep_f8f62968::ActualType::new(); - let v1 = app::constructor_with_output_tuple(); + let v0 = dep_f8f62968::GenericType::< + std::primitive::bool, + std::primitive::bool, + >::new(); + let v1 = dep_f8f62968::ActualType::new(); + let v2 = app::constructor_with_output_tuple(); crate::ApplicationState { - s0: v1, - s1: v0, + s0: v2, + s1: v1, + s2: v0, } } pub fn run( @@ -64,6 +70,7 @@ async fn route_request( route_0::entrypoint( server_state.application_state.s0.clone(), &server_state.application_state.s1, + &server_state.application_state.s2, ) .await } @@ -80,56 +87,66 @@ async fn route_request( } } pub mod route_0 { - pub async fn entrypoint<'a>( + pub async fn entrypoint<'a, 'b>( s_0: (bool, char, u8), s_1: &'a dep_f8f62968::ActualType, + s_2: &'b dep_f8f62968::GenericType, ) -> pavex::response::Response { - let response = wrapping_0(s_0, s_1).await; + let response = wrapping_0(s_0, s_1, s_2).await; response } - async fn stage_1<'a>( + async fn stage_1<'a, 'b>( s_0: (bool, char, u8), - s_1: &'a dep_f8f62968::ActualType, + s_1: &'a dep_f8f62968::GenericType, + s_2: &'b dep_f8f62968::ActualType, ) -> pavex::response::Response { - let response = handler(s_0, s_1).await; + let response = handler(s_0, s_1, s_2).await; response } async fn wrapping_0( v0: (bool, char, u8), v1: &dep_f8f62968::ActualType, + v2: &dep_f8f62968::GenericType, ) -> pavex::response::Response { - let v2 = crate::route_0::Next0 { + let v3 = crate::route_0::Next0 { s_0: v0, - s_1: v1, + s_1: v2, + s_2: v1, next: stage_1, }; - let v3 = pavex::middleware::Next::new(v2); - let v4 = pavex::middleware::wrap_noop(v3).await; - ::into_response(v4) + let v4 = pavex::middleware::Next::new(v3); + let v5 = pavex::middleware::wrap_noop(v4).await; + ::into_response(v5) } async fn handler( v0: (bool, char, u8), - v1: &dep_f8f62968::ActualType, + v1: &dep_f8f62968::GenericType, + v2: &dep_f8f62968::ActualType, ) -> pavex::response::Response { - let v2 = app::handler_with_input_tuple(v0, v1); - ::into_response(v2) + let v3 = app::handler_with_input_tuple(v0, v2, v1); + ::into_response(v3) } - struct Next0<'a, T> + struct Next0<'a, 'b, T> where T: std::future::Future, { s_0: (bool, char, u8), - s_1: &'a dep_f8f62968::ActualType, - next: fn((bool, char, u8), &'a dep_f8f62968::ActualType) -> T, + s_1: &'a dep_f8f62968::GenericType, + s_2: &'b dep_f8f62968::ActualType, + next: fn( + (bool, char, u8), + &'a dep_f8f62968::GenericType, + &'b dep_f8f62968::ActualType, + ) -> T, } - impl<'a, T> std::future::IntoFuture for Next0<'a, T> + impl<'a, 'b, T> std::future::IntoFuture for Next0<'a, 'b, T> where T: std::future::Future, { type Output = pavex::response::Response; type IntoFuture = T; fn into_future(self) -> Self::IntoFuture { - (self.next)(self.s_0, self.s_1) + (self.next)(self.s_0, self.s_1, self.s_2) } } } diff --git a/libs/ui_tests/reflection/type_alias_are_supported/expectations/diagnostics.dot b/libs/ui_tests/reflection/type_alias_are_supported/expectations/diagnostics.dot index 2f6d1a385..cc170c967 100644 --- a/libs/ui_tests/reflection/type_alias_are_supported/expectations/diagnostics.dot +++ b/libs/ui_tests/reflection/type_alias_are_supported/expectations/diagnostics.dot @@ -1,25 +1,29 @@ digraph "GET /home - 0" { - 0 [ label = "pavex::middleware::wrap_noop(pavex::middleware::Next>) -> pavex::response::Response"] - 1 [ label = "pavex::middleware::Next::new(crate::route_0::Next0<'a>) -> pavex::middleware::Next>"] - 2 [ label = "crate::route_0::Next0((bool, char, u8), &'a dep_f8f62968::ActualType) -> crate::route_0::Next0<'a>"] + 0 [ label = "pavex::middleware::wrap_noop(pavex::middleware::Next>) -> pavex::response::Response"] + 1 [ label = "pavex::middleware::Next::new(crate::route_0::Next0<'a, 'b>) -> pavex::middleware::Next>"] + 2 [ label = "crate::route_0::Next0((bool, char, u8), &'a dep_f8f62968::GenericType, &'b dep_f8f62968::ActualType) -> crate::route_0::Next0<'a, 'b>"] 3 [ label = "(bool, char, u8)"] - 5 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] - 6 [ label = "&dep_f8f62968::ActualType"] + 4 [ label = "&dep_f8f62968::ActualType"] + 6 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] + 7 [ label = "&dep_f8f62968::GenericType"] 1 -> 0 [ ] 2 -> 1 [ ] + 4 -> 2 [ ] 3 -> 2 [ ] - 0 -> 5 [ ] - 6 -> 2 [ ] + 0 -> 6 [ ] + 7 -> 2 [ ] } digraph "GET /home - 1" { - 0 [ label = "app::handler_with_input_tuple((bool, char, u8), &dep_f8f62968::ActualType) -> pavex::response::Response"] + 0 [ label = "app::handler_with_input_tuple((bool, char, u8), &dep_f8f62968::ActualType, &dep_f8f62968::GenericType) -> pavex::response::Response"] 1 [ label = "(bool, char, u8)"] - 3 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] - 4 [ label = "&dep_f8f62968::ActualType"] + 2 [ label = "&dep_f8f62968::GenericType"] + 4 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] + 5 [ label = "&dep_f8f62968::ActualType"] + 2 -> 0 [ ] 1 -> 0 [ ] - 0 -> 3 [ ] - 4 -> 0 [ ] + 0 -> 4 [ ] + 5 -> 0 [ ] } digraph "* /home - 0" { @@ -43,9 +47,11 @@ digraph "* /home - 1" { } digraph app_state { - 0 [ label = "crate::ApplicationState((bool, char, u8), dep_f8f62968::ActualType) -> crate::ApplicationState"] + 0 [ label = "crate::ApplicationState((bool, char, u8), dep_f8f62968::ActualType, dep_f8f62968::GenericType) -> crate::ApplicationState"] 1 [ label = "app::constructor_with_output_tuple() -> (bool, char, u8)"] 2 [ label = "dep_f8f62968::ActualType::new() -> dep_f8f62968::ActualType"] + 3 [ label = "dep_f8f62968::GenericType::::new() -> dep_f8f62968::GenericType"] + 3 -> 0 [ ] 2 -> 0 [ ] 1 -> 0 [ ] } \ No newline at end of file diff --git a/libs/ui_tests/reflection/type_alias_are_supported/src/lib.rs b/libs/ui_tests/reflection/type_alias_are_supported/src/lib.rs index e1497d34a..7577e774b 100644 --- a/libs/ui_tests/reflection/type_alias_are_supported/src/lib.rs +++ b/libs/ui_tests/reflection/type_alias_are_supported/src/lib.rs @@ -1,8 +1,9 @@ -use pavex::blueprint::{constructor::Lifecycle, router::GET, Blueprint}; +use pavex::blueprint::{router::GET, Blueprint}; use pavex::f; pub type MyTupleAlias = (bool, char, u8); pub type RemoteAlias = dep::IntermediateAlias; +pub type RemoteGenericAlias = dep::IntermediateGenericAlias; pub fn constructor_with_output_tuple() -> (bool, char, u8) { todo!() @@ -11,17 +12,16 @@ pub fn constructor_with_output_tuple() -> (bool, char, u8) { pub fn handler_with_input_tuple( _input: MyTupleAlias, _a: &RemoteAlias, + _b: &RemoteGenericAlias, ) -> pavex::response::Response { todo!() } pub fn blueprint() -> Blueprint { let mut bp = Blueprint::new(); - bp.constructor( - f!(crate::constructor_with_output_tuple), - Lifecycle::Singleton, - ); - bp.constructor(f!(crate::RemoteAlias::new), Lifecycle::Singleton); + bp.singleton(f!(crate::constructor_with_output_tuple)); + bp.singleton(f!(crate::RemoteAlias::new)); + bp.singleton(f!(crate::RemoteGenericAlias::::new)); bp.route(GET, "/home", f!(crate::handler_with_input_tuple)); bp } From 4fddb00c008272a0bff5ca20df7eba49470c04df Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:41:50 +0100 Subject: [PATCH 02/16] chore: Provide an easy-to-examine representation for the set of constructibles in a given scope tree --- .../src/compiler/analyses/constructibles.rs | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/libs/pavexc/src/compiler/analyses/constructibles.rs b/libs/pavexc/src/compiler/analyses/constructibles.rs index 3280d11dd..681c8c496 100644 --- a/libs/pavexc/src/compiler/analyses/constructibles.rs +++ b/libs/pavexc/src/compiler/analyses/constructibles.rs @@ -23,12 +23,26 @@ use crate::try_source; use super::framework_items::FrameworkItemDb; -#[derive(Debug)] /// The set of types that can be injected into request handlers, error handlers and (other) constructors. pub(crate) struct ConstructibleDb { scope_id2constructibles: IndexMap, } +impl std::fmt::Debug for ConstructibleDb { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Available constructibles:\n")?; + for (scope_id, constructibles) in &self.scope_id2constructibles { + writeln!( + f, + "- {scope_id}:\n{}", + // TODO: Use a PadAdapter down here to avoid allocating an intermediate string + textwrap::indent(&format!("{:?}", constructibles), " ") + )?; + } + Ok(()) + } +} + impl ConstructibleDb { /// Compute the set of types that can be injected into request handlers, error handlers and /// (other) constructors. @@ -935,7 +949,6 @@ impl ConstructibleDb { } } -#[derive(Debug)] /// The set of constructibles that have been registered in a given scope. /// /// Be careful! This is not the set of all types that can be constructed in the given scope! @@ -1090,3 +1103,13 @@ impl ConstructiblesInScope { } } } + +impl std::fmt::Debug for ConstructiblesInScope { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Constructibles:")?; + for (type_, component_id) in &self.type2constructor_id { + writeln!(f, "- {} -> {:?}", type_.display_for_error(), component_id)?; + } + Ok(()) + } +} From 192a2920050cb6474173cb06a9d0fbb261aa8ae2 Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:42:34 +0100 Subject: [PATCH 03/16] fix: Panic eagerly if internal invariants are not upheld when binding generic parameters --- libs/pavexc/src/compiler/analyses/constructibles.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/libs/pavexc/src/compiler/analyses/constructibles.rs b/libs/pavexc/src/compiler/analyses/constructibles.rs index 681c8c496..6b0fdb0df 100644 --- a/libs/pavexc/src/compiler/analyses/constructibles.rs +++ b/libs/pavexc/src/compiler/analyses/constructibles.rs @@ -1019,7 +1019,8 @@ impl ConstructiblesInScope { } for templated_constructible_type in &self.templated_constructors { if let Some(bindings) = templated_constructible_type.is_a_template_for(type_) { - let (templated_component_id, _) = self.get(templated_constructible_type).unwrap(); + let template = templated_constructible_type.clone(); + let (templated_component_id, _) = self.get(&template).unwrap(); self.bind_and_register_constructor( templated_component_id, component_db, @@ -1027,7 +1028,13 @@ impl ConstructiblesInScope { framework_item_db, &bindings, ); - return self.get(type_); + let bound = self.get(type_); + assert!(bound.is_some(), "I used {} as a templated constructor to build {} but the binding process didn't succeed as expected.\nBindings:\n{}", + template.display_for_error(), + type_.display_for_error(), + bindings.into_iter().map(|(k, v)| format!("- {k} -> {}", v.display_for_error())).collect::>().join("\n") + ); + return bound; } } From 73158929ef58a1360e7d14e3948c3a99ea57003e Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:43:59 +0100 Subject: [PATCH 04/16] feat: Support lifetime parameters in type aliases --- .../analyses/user_components/scope_graph.rs | 6 ++ libs/pavexc/src/compiler/resolvers.rs | 101 +++++++++++++----- libs/pavexc/src/compiler/traits.rs | 9 +- libs/pavexc/src/language/resolved_path.rs | 5 +- libs/pavexc/src/rustdoc/queries.rs | 17 ++- 5 files changed, 102 insertions(+), 36 deletions(-) diff --git a/libs/pavexc/src/compiler/analyses/user_components/scope_graph.rs b/libs/pavexc/src/compiler/analyses/user_components/scope_graph.rs index 19aa06f23..0bc389f8d 100644 --- a/libs/pavexc/src/compiler/analyses/user_components/scope_graph.rs +++ b/libs/pavexc/src/compiler/analyses/user_components/scope_graph.rs @@ -67,6 +67,12 @@ pub struct ScopeGraphBuilder { /// See [`ScopeGraph`] for more information. pub struct ScopeId(usize); +impl std::fmt::Display for ScopeId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Scope {}", self.0) + } +} + impl PartialEq for &ScopeId { fn eq(&self, other: &ScopeId) -> bool { self.0 == other.0 diff --git a/libs/pavexc/src/compiler/resolvers.rs b/libs/pavexc/src/compiler/resolvers.rs index 8cf9884ce..f56577e5a 100644 --- a/libs/pavexc/src/compiler/resolvers.rs +++ b/libs/pavexc/src/compiler/resolvers.rs @@ -4,7 +4,7 @@ use std::ops::Deref; use std::sync::Arc; -use ahash::{HashMap, HashMapExt}; +use ahash::HashMap; use anyhow::anyhow; use guppy::PackageId; use once_cell::sync::OnceCell; @@ -20,13 +20,40 @@ use crate::rustdoc::{CrateCollection, ResolvedItem}; use super::utils::process_framework_path; +#[derive(Default)] +pub(crate) struct GenericBindings { + pub lifetimes: HashMap, + pub types: HashMap, +} + +impl std::fmt::Debug for GenericBindings { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "GenericBindings {{ ")?; + if !self.lifetimes.is_empty() { + write!(f, "lifetimes: {{ ")?; + for (name, value) in &self.lifetimes { + writeln!(f, "{} -> {}, ", name, value)?; + } + write!(f, "}}, ")?; + } + if !self.types.is_empty() { + write!(f, "types: {{ ")?; + for (name, value) in &self.types { + writeln!(f, "{} -> {}, ", name, value.display_for_error())?; + } + write!(f, "}}, ")?; + } + write!(f, "}}") + } +} + pub(crate) fn resolve_type( type_: &Type, // The package id where the type we are trying to process has been referenced (e.g. as an // input/output parameter). used_by_package_id: &PackageId, krate_collection: &CrateCollection, - generic_bindings: &HashMap, + generic_bindings: &GenericBindings, ) -> Result { match type_ { Type::ResolvedPath(rustdoc_types::Path { id, args, name }) => { @@ -60,7 +87,7 @@ pub(crate) fn resolve_type( let type_item = krate_collection.get_item_by_global_type_id(&global_type_id); // We want to remove any indirections (e.g. `type Foo = Bar;`) and get the actual type. if let ItemEnum::TypeAlias(type_alias) = &type_item.inner { - let mut alias_generic_bindings = HashMap::new(); + let mut alias_generic_bindings = GenericBindings::default(); // The generic arguments that have been passed to the type alias. // E.g. `u32` in `Foo` for `type Foo = Bar;` let generic_args = if let Some(args) = args { @@ -76,7 +103,6 @@ pub(crate) fn resolve_type( // E.g. `T` in `type Foo = Bar;` let generic_param_defs = &type_alias.generics.params; for (i, generic_param_def) in generic_param_defs.iter().enumerate() { - // We also try to handle generic parameters, as long as they have a default value. match &generic_param_def.kind { GenericParamDefKind::Type { default, .. } => { let provided_arg = generic_args.and_then(|v| v.get(i)); @@ -108,10 +134,25 @@ pub(crate) fn resolve_type( }) }; alias_generic_bindings + .types .insert(generic_param_def.name.to_string(), generic_type); } - GenericParamDefKind::Const { .. } - | GenericParamDefKind::Lifetime { .. } => { + GenericParamDefKind::Lifetime { .. } => { + let provided_arg = generic_args.and_then(|v| v.get(i)); + let lifetime = if let Some(provided_arg) = provided_arg { + if let GenericArg::Lifetime(provided_arg) = provided_arg { + provided_arg + } else { + anyhow::bail!("Expected `{:?}` to be a generic _lifetime_ parameter, but it wasn't!", provided_arg) + } + } else { + &generic_param_def.name + }.to_owned(); + alias_generic_bindings + .lifetimes + .insert(generic_param_def.name.to_string(), lifetime); + } + GenericParamDefKind::Const { .. } => { anyhow::bail!("I can only work with generic type parameters when working with type aliases. I can't handle a `{:?}` yet, sorry!", generic_param_def) } } @@ -171,7 +212,7 @@ pub(crate) fn resolve_type( if let Some(GenericArg::Type(generic_type)) = args.get(i) { if let Type::Generic(generic) = generic_type { if let Some(resolved_type) = - generic_bindings.get(generic) + generic_bindings.types.get(generic) { GenericArgument::TypeParameter( resolved_type.to_owned(), @@ -252,7 +293,7 @@ pub(crate) fn resolve_type( Ok(t.into()) } Type::Generic(s) => { - if let Some(resolved_type) = generic_bindings.get(s) { + if let Some(resolved_type) = generic_bindings.types.get(s) { Ok(resolved_type.to_owned()) } else { Ok(ResolvedType::Generic(Generic { name: s.to_owned() })) @@ -317,7 +358,7 @@ pub(crate) fn resolve_callable( } }; - let mut generic_bindings = HashMap::new(); + let mut generic_bindings = GenericBindings::default(); if let CallableItem::Method { method_owner, qualified_self, @@ -352,28 +393,34 @@ pub(crate) fn resolve_callable( } }; if let Some(ty) = self_generic_ty { - generic_bindings.insert("Self".to_string(), ty); + generic_bindings.types.insert("Self".to_string(), ty); } } let fn_generic_args = &new_callable_path.segments.last().unwrap().generic_arguments; for (generic_arg, generic_def) in fn_generic_args.iter().zip(&fn_generics_defs.params) { let generic_name = &generic_def.name; - let generic_type = match generic_arg { - ResolvedPathGenericArgument::Type(t) => t, - _ => { - continue; + match generic_arg { + ResolvedPathGenericArgument::Type(t) => { + let resolved_type = + t.resolve(krate_collection) + .map_err(|e| GenericParameterResolutionError { + generic_type: t.to_owned(), + callable_path: new_callable_path.to_owned(), + callable_item: callable_item.item.clone().into_owned(), + source: Arc::new(e), + })?; + generic_bindings + .types + .insert(generic_name.to_owned(), resolved_type); } - }; - let resolved_type = generic_type.resolve(krate_collection).map_err(|e| { - GenericParameterResolutionError { - generic_type: generic_type.to_owned(), - callable_path: new_callable_path.to_owned(), - callable_item: callable_item.item.clone().into_owned(), - source: Arc::new(e), + ResolvedPathGenericArgument::Lifetime(l) => { + let resolved_lifetime = l.to_string(); + generic_bindings + .lifetimes + .insert(generic_name.to_owned(), resolved_lifetime); } - })?; - generic_bindings.insert(generic_name.to_owned(), resolved_type); + } } let mut resolved_parameter_types = Vec::with_capacity(decl.inputs.len()); @@ -548,7 +595,7 @@ fn get_trait_generic_bindings( resolved_item: &ResolvedItem, path: &ResolvedPath, krate_collection: &CrateCollection, - generic_bindings: &mut HashMap, + generic_bindings: &mut GenericBindings, ) -> Result<(), anyhow::Error> { let inner = &resolved_item.item.inner; let ItemEnum::Trait(trait_item) = inner else { @@ -563,7 +610,9 @@ fn get_trait_generic_bindings( { if let ResolvedPathGenericArgument::Type(t) = assigned_parameter { // TODO: handle conflicts - generic_bindings.insert(generic_slot.name.clone(), t.resolve(krate_collection)?); + generic_bindings + .types + .insert(generic_slot.name.clone(), t.resolve(krate_collection)?); } } Ok(()) @@ -657,7 +706,7 @@ pub(crate) fn resolve_type_path_with_item( default, used_by_package_id, krate_collection, - &HashMap::new(), + &GenericBindings::default(), )?; if skip_default(krate_collection, &default) { continue; diff --git a/libs/pavexc/src/compiler/traits.rs b/libs/pavexc/src/compiler/traits.rs index 352b8abfe..0b00edeba 100644 --- a/libs/pavexc/src/compiler/traits.rs +++ b/libs/pavexc/src/compiler/traits.rs @@ -1,6 +1,5 @@ use std::fmt::Formatter; -use ahash::{HashMap, HashMapExt}; use guppy::PackageId; use rustdoc_types::{GenericParamDefKind, ItemEnum, Type}; @@ -8,6 +7,8 @@ use crate::compiler::resolvers::resolve_type; use crate::language::{PathType, ResolvedType}; use crate::rustdoc::{Crate, CrateCollection}; +use super::resolvers::GenericBindings; + /// It returns an error if `type_` doesn't implement the specified trait. /// /// The trait path must be fully resolved: it should NOT point to a re-export @@ -90,7 +91,7 @@ pub(crate) fn implements_trait( let type_item = krate_collection.get_item_by_global_type_id(&type_id); // We want to see through type aliases here. if let ItemEnum::TypeAlias(type_alias) = &type_item.inner { - let mut generic_bindings = HashMap::new(); + let mut generic_bindings = GenericBindings::default(); for generic in &type_alias.generics.params { // We also try to handle generic parameters, as long as they have a default value. match &generic.kind { @@ -104,7 +105,9 @@ pub(crate) fn implements_trait( krate_collection, &generic_bindings, )?; - generic_bindings.insert(generic.name.to_string(), default); + generic_bindings + .types + .insert(generic.name.to_string(), default); } GenericParamDefKind::Type { default: None, .. } | GenericParamDefKind::Const { .. } diff --git a/libs/pavexc/src/language/resolved_path.rs b/libs/pavexc/src/language/resolved_path.rs index 35e1a76c2..8527147cf 100644 --- a/libs/pavexc/src/language/resolved_path.rs +++ b/libs/pavexc/src/language/resolved_path.rs @@ -4,7 +4,6 @@ use std::hash::{Hash, Hasher}; use std::ops::{Deref, DerefMut}; use std::sync::Arc; -use ahash::{HashMap, HashMapExt}; use anyhow::Context; use bimap::BiHashMap; use guppy::PackageId; @@ -15,7 +14,7 @@ use quote::format_ident; use pavex_bp_schema::RawIdentifiers; use rustdoc_types::ItemEnum; -use crate::compiler::resolvers::resolve_type; +use crate::compiler::resolvers::{resolve_type, GenericBindings}; use crate::language::callable_path::{ CallPathGenericArgument, CallPathLifetime, CallPathSegment, CallPathType, }; @@ -181,7 +180,7 @@ impl ResolvedPathType { default, &resolved_item.item_id.package_id, krate_collection, - &HashMap::new(), + &GenericBindings::default(), )?; GenericArgument::TypeParameter(ty) } diff --git a/libs/pavexc/src/rustdoc/queries.rs b/libs/pavexc/src/rustdoc/queries.rs index 89d0faa76..46f243275 100644 --- a/libs/pavexc/src/rustdoc/queries.rs +++ b/libs/pavexc/src/rustdoc/queries.rs @@ -13,7 +13,7 @@ use indexmap::IndexSet; use rustdoc_types::{ExternalCrate, Item, ItemEnum, ItemKind, ItemSummary, Visibility}; use tracing::Span; -use crate::compiler::resolvers::resolve_type; +use crate::compiler::resolvers::{resolve_type, GenericBindings}; use crate::language::{ResolvedPathGenericArgument, ResolvedPathType}; use crate::rustdoc::version_matcher::VersionMatcher; use crate::rustdoc::{compute::compute_crate_docs, utils, CannotGetCrateData, TOOLCHAIN_CRATES}; @@ -397,14 +397,23 @@ impl CrateCollection { let new_path = { let path_args = &resolved_path.segments.last().unwrap().generic_arguments; let alias_generics = &type_alias.generics.params; - let mut name2path_arg = HashMap::new(); + let mut name2path_arg = GenericBindings::default(); for (path_arg, alias_generic) in path_args.iter().zip(alias_generics.iter()) { match path_arg { ResolvedPathGenericArgument::Type(t) => { let t = t.resolve(self).unwrap(); - name2path_arg.insert(alias_generic.name.clone(), t); + name2path_arg.types.insert(alias_generic.name.clone(), t); + } + ResolvedPathGenericArgument::Lifetime(l) => { + let l = match l { + crate::language::ResolvedPathLifetime::Named(n) => n, + crate::language::ResolvedPathLifetime::Static => "static", + } + .to_owned(); + name2path_arg + .lifetimes + .insert(alias_generic.name.clone(), l); } - ResolvedPathGenericArgument::Lifetime(_) => unimplemented!(), } } From a657e0accfd0c7c501705932277fca1188fcc311 Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:44:16 +0100 Subject: [PATCH 05/16] fix: Bind generic parameters correctly in all instances --- libs/pavexc/src/language/resolved_type.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/pavexc/src/language/resolved_type.rs b/libs/pavexc/src/language/resolved_type.rs index 7fa5ce290..88f471564 100644 --- a/libs/pavexc/src/language/resolved_type.rs +++ b/libs/pavexc/src/language/resolved_type.rs @@ -212,7 +212,10 @@ impl ResolvedType { (ScalarPrimitive(concrete_primitive), ScalarPrimitive(templated_primitive)) => { concrete_primitive == templated_primitive } - (_, Generic(_)) => true, + (_, Generic(parameter)) => { + bindings.insert(parameter.name.clone(), concrete_type.clone()); + true + } (_, _) => false, } } From 62b893239f725f080de92067979cb95097d762ee Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:01:04 +0100 Subject: [PATCH 06/16] chore: Display the cyclic dependency graph when PAVEX_DEBUG is set --- .../src/compiler/analyses/call_graph/dependency_graph.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/pavexc/src/compiler/analyses/call_graph/dependency_graph.rs b/libs/pavexc/src/compiler/analyses/call_graph/dependency_graph.rs index 1a46eb7ad..e775a4aa9 100644 --- a/libs/pavexc/src/compiler/analyses/call_graph/dependency_graph.rs +++ b/libs/pavexc/src/compiler/analyses/call_graph/dependency_graph.rs @@ -351,7 +351,13 @@ fn cycle_error( .unwrap(); } - let diagnostic_builder = CompilerDiagnostic::builder(anyhow::anyhow!(error_msg)); + let error = anyhow::anyhow!( + "There is a cycle in this dependency graph. Graph:\n{}", + // TODO: Use a PadAdapter to indent the graph, avoiding the need for an intermediate stringa allocation. + textwrap::indent(&graph.debug_dot(component_db, computation_db), " ") + ) + .context(error_msg); + let diagnostic_builder = CompilerDiagnostic::builder(error); diagnostic_builder.help( "Break the cycle! Remove one of the 'depends-on' relationship by changing the signature of \ From 8b607969129376e957a5cceebb9b4ecbaf5989f2 Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:14:56 +0100 Subject: [PATCH 07/16] chore: Display the available constructibles when PAVEX_DEBUG is set and we incur into a 'missing constructor' error --- libs/pavexc/src/compiler/analyses/constructibles.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/libs/pavexc/src/compiler/analyses/constructibles.rs b/libs/pavexc/src/compiler/analyses/constructibles.rs index 6b0fdb0df..af35f1247 100644 --- a/libs/pavexc/src/compiler/analyses/constructibles.rs +++ b/libs/pavexc/src/compiler/analyses/constructibles.rs @@ -246,7 +246,7 @@ impl ConstructibleDb { continue; } if let Some(user_component_id) = component_db.user_component_id(component_id) { - Self::missing_constructor( + self.missing_constructor( user_component_id, component_db.user_component_db(), input, @@ -619,6 +619,7 @@ impl ConstructibleDb { } fn missing_constructor( + &self, user_component_id: UserComponentId, user_component_db: &UserComponentDb, unconstructible_type: &ResolvedType, @@ -654,10 +655,12 @@ impl ConstructibleDb { .flatten(); let callable = &computation_db[user_component_id]; - let e = anyhow::anyhow!( - "I can't find a constructor for `{unconstructible_type:?}`.\n\ - I need an instance of `{unconstructible_type:?}` to invoke your {component_kind}, `{}`.", - callable.path + let e = anyhow::anyhow!("I can't find a constructor for `{}`.\n{self:?}", unconstructible_type.display_for_error()).context( + format!( + "I can't find a constructor for `{unconstructible_type:?}`.\n\ + I need an instance of `{unconstructible_type:?}` to invoke your {component_kind}, `{}`.", + callable.path + ) ); let definition_info = get_definition_info( callable, From 288c6d59ee11f8f327b65fe89d80b85ea0c02425 Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:19:14 +0100 Subject: [PATCH 08/16] fix: Don't complain about missing constructors when looking at a naked generic input parameter --- .../pavexc/src/compiler/analyses/constructibles.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libs/pavexc/src/compiler/analyses/constructibles.rs b/libs/pavexc/src/compiler/analyses/constructibles.rs index af35f1247..ec181d37b 100644 --- a/libs/pavexc/src/compiler/analyses/constructibles.rs +++ b/libs/pavexc/src/compiler/analyses/constructibles.rs @@ -161,6 +161,20 @@ impl ConstructibleDb { } } + // Both `T` and `&T` are always constructibles, when looking at generic constructors that take them + // as input. The actual check will happen when they are bound to a specific type. + match input { + ResolvedType::Reference(ref_) => { + if let ResolvedType::Generic(_) = ref_.inner.as_ref() { + continue; + } + } + ResolvedType::Generic(_) => { + continue; + } + _ => {} + } + if let Some((input_component_id, mode)) = self.get_or_try_bind( scope_id, input, From 5162d33b3f704e4d0ff78108455d798138c75ec4 Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:50:19 +0100 Subject: [PATCH 09/16] chore: Improve test coverage for type aliases --- .../type_alias_are_supported/diagnostics.dot | 44 +++++++---- .../ephemeral_deps/dep/src/lib.rs | 11 +++ .../expectations/app.rs | 76 +++++++++++-------- .../expectations/diagnostics.dot | 44 +++++++---- .../type_alias_are_supported/src/lib.rs | 19 ++++- 5 files changed, 129 insertions(+), 65 deletions(-) diff --git a/libs/ui_tests/reflection/type_alias_are_supported/diagnostics.dot b/libs/ui_tests/reflection/type_alias_are_supported/diagnostics.dot index d53c1f68d..cfa99c6bb 100644 --- a/libs/ui_tests/reflection/type_alias_are_supported/diagnostics.dot +++ b/libs/ui_tests/reflection/type_alias_are_supported/diagnostics.dot @@ -1,29 +1,39 @@ digraph "GET /home - 0" { - 0 [ label = "pavex::middleware::wrap_noop(pavex::middleware::Next>) -> pavex::response::Response"] - 1 [ label = "pavex::middleware::Next::new(crate::route_0::Next0<'a, 'b>) -> pavex::middleware::Next>"] - 2 [ label = "crate::route_0::Next0((bool, char, u8), &'a dep_f8f62968::GenericType, &'b dep_f8f62968::ActualType) -> crate::route_0::Next0<'a, 'b>"] + 0 [ label = "pavex::middleware::wrap_noop(pavex::middleware::Next>) -> pavex::response::Response"] + 1 [ label = "pavex::middleware::Next::new(crate::route_0::Next0<'a, 'b, 'c>) -> pavex::middleware::Next>"] + 2 [ label = "crate::route_0::Next0((bool, char, u8), &'a dep_f8f62968::GenericType, &'b alloc::string::String, &'c dep_f8f62968::ActualType) -> crate::route_0::Next0<'a, 'b, 'c>"] 3 [ label = "(bool, char, u8)"] - 4 [ label = "&dep_f8f62968::ActualType"] - 6 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] - 7 [ label = "&dep_f8f62968::GenericType"] + 4 [ label = "&alloc::string::String"] + 5 [ label = "&dep_f8f62968::ActualType"] + 7 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] + 8 [ label = "&dep_f8f62968::GenericType"] 1 -> 0 [ ] 2 -> 1 [ ] + 5 -> 2 [ ] 4 -> 2 [ ] 3 -> 2 [ ] - 0 -> 6 [ ] - 7 -> 2 [ ] + 0 -> 7 [ ] + 8 -> 2 [ ] } digraph "GET /home - 1" { - 0 [ label = "app_f8f62968::handler_with_input_tuple((bool, char, u8), &dep_f8f62968::ActualType, &dep_f8f62968::GenericType) -> pavex::response::Response"] + 0 [ label = "app_f8f62968::handler_with_input_tuple((bool, char, u8), &dep_f8f62968::ActualType, &dep_f8f62968::GenericType, &dep_f8f62968::DoubleLifetimeType<'a, 'a>, app_f8f62968::MixedGenerics<'a, alloc::string::String>) -> pavex::response::Response"] 1 [ label = "(bool, char, u8)"] 2 [ label = "&dep_f8f62968::GenericType"] - 4 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] - 5 [ label = "&dep_f8f62968::ActualType"] + 3 [ label = "&alloc::string::String"] + 4 [ label = "dep_f8f62968::DoubleLifetimeType::::new(&''a dep_f8f62968::ActualType, &''b alloc::string::String) -> dep_f8f62968::DoubleLifetimeType<'a, 'b>"] + 5 [ label = "app_f8f62968::mixed_generics(&''a alloc::string::String) -> app_f8f62968::MixedGenerics<'a, alloc::string::String>"] + 7 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] + 8 [ label = "&dep_f8f62968::ActualType"] + 5 -> 0 [ ] + 4 -> 0 [ label = "&"] 2 -> 0 [ ] + 3 -> 4 [ ] + 3 -> 5 [ ] 1 -> 0 [ ] - 0 -> 4 [ ] - 5 -> 0 [ ] + 0 -> 7 [ ] + 8 -> 0 [ ] + 8 -> 4 [ ] } digraph "* /home - 0" { @@ -47,10 +57,12 @@ digraph "* /home - 1" { } digraph app_state { - 0 [ label = "crate::ApplicationState((bool, char, u8), dep_f8f62968::ActualType, dep_f8f62968::GenericType) -> crate::ApplicationState"] + 0 [ label = "crate::ApplicationState((bool, char, u8), alloc::string::String, dep_f8f62968::ActualType, dep_f8f62968::GenericType) -> crate::ApplicationState"] 1 [ label = "app_f8f62968::constructor_with_output_tuple() -> (bool, char, u8)"] - 2 [ label = "dep_f8f62968::ActualType::new() -> dep_f8f62968::ActualType"] - 3 [ label = "dep_f8f62968::GenericType::::new() -> dep_f8f62968::GenericType"] + 2 [ label = "alloc::string::String"] + 3 [ label = "dep_f8f62968::ActualType::new() -> dep_f8f62968::ActualType"] + 4 [ label = "dep_f8f62968::GenericType::::new() -> dep_f8f62968::GenericType"] + 4 -> 0 [ ] 3 -> 0 [ ] 2 -> 0 [ ] 1 -> 0 [ ] diff --git a/libs/ui_tests/reflection/type_alias_are_supported/ephemeral_deps/dep/src/lib.rs b/libs/ui_tests/reflection/type_alias_are_supported/ephemeral_deps/dep/src/lib.rs index ab8184edc..6daf165d8 100644 --- a/libs/ui_tests/reflection/type_alias_are_supported/ephemeral_deps/dep/src/lib.rs +++ b/libs/ui_tests/reflection/type_alias_are_supported/ephemeral_deps/dep/src/lib.rs @@ -4,6 +4,17 @@ use pavex::f; pub type IntermediateAlias = ActualType; pub type IntermediateGenericAlias = GenericType; +pub struct DoubleLifetimeType<'a, 'b> { + _a: &'a str, + _b: &'b str, +} + +impl<'a, 'b> DoubleLifetimeType<'a, 'b> { + pub fn new(_t1: &'a ActualType, _t2: &'b String) -> DoubleLifetimeType<'a, 'b> { + todo!() + } +} + #[derive(Clone)] pub struct ActualType; diff --git a/libs/ui_tests/reflection/type_alias_are_supported/expectations/app.rs b/libs/ui_tests/reflection/type_alias_are_supported/expectations/app.rs index 33a127d6d..f2388edff 100644 --- a/libs/ui_tests/reflection/type_alias_are_supported/expectations/app.rs +++ b/libs/ui_tests/reflection/type_alias_are_supported/expectations/app.rs @@ -8,20 +8,24 @@ struct ServerState { } pub struct ApplicationState { s0: (bool, char, u8), - s1: dep_f8f62968::ActualType, - s2: dep_f8f62968::GenericType, + s1: alloc::string::String, + s2: dep_f8f62968::ActualType, + s3: dep_f8f62968::GenericType, } -pub async fn build_application_state() -> crate::ApplicationState { - let v0 = dep_f8f62968::GenericType::< +pub async fn build_application_state( + v0: alloc::string::String, +) -> crate::ApplicationState { + let v1 = dep_f8f62968::GenericType::< std::primitive::bool, std::primitive::bool, >::new(); - let v1 = dep_f8f62968::ActualType::new(); - let v2 = app::constructor_with_output_tuple(); + let v2 = dep_f8f62968::ActualType::new(); + let v3 = app::constructor_with_output_tuple(); crate::ApplicationState { - s0: v2, - s1: v1, - s2: v0, + s0: v3, + s1: v0, + s2: v2, + s3: v1, } } pub fn run( @@ -71,6 +75,7 @@ async fn route_request( server_state.application_state.s0.clone(), &server_state.application_state.s1, &server_state.application_state.s2, + &server_state.application_state.s3, ) .await } @@ -87,66 +92,75 @@ async fn route_request( } } pub mod route_0 { - pub async fn entrypoint<'a, 'b>( + pub async fn entrypoint<'a, 'b, 'c>( s_0: (bool, char, u8), - s_1: &'a dep_f8f62968::ActualType, - s_2: &'b dep_f8f62968::GenericType, + s_1: &'a alloc::string::String, + s_2: &'b dep_f8f62968::ActualType, + s_3: &'c dep_f8f62968::GenericType, ) -> pavex::response::Response { - let response = wrapping_0(s_0, s_1, s_2).await; + let response = wrapping_0(s_0, s_1, s_2, s_3).await; response } - async fn stage_1<'a, 'b>( + async fn stage_1<'a, 'b, 'c>( s_0: (bool, char, u8), s_1: &'a dep_f8f62968::GenericType, - s_2: &'b dep_f8f62968::ActualType, + s_2: &'b alloc::string::String, + s_3: &'c dep_f8f62968::ActualType, ) -> pavex::response::Response { - let response = handler(s_0, s_1, s_2).await; + let response = handler(s_0, s_1, s_2, s_3).await; response } async fn wrapping_0( v0: (bool, char, u8), - v1: &dep_f8f62968::ActualType, - v2: &dep_f8f62968::GenericType, + v1: &alloc::string::String, + v2: &dep_f8f62968::ActualType, + v3: &dep_f8f62968::GenericType, ) -> pavex::response::Response { - let v3 = crate::route_0::Next0 { + let v4 = crate::route_0::Next0 { s_0: v0, - s_1: v2, + s_1: v3, s_2: v1, + s_3: v2, next: stage_1, }; - let v4 = pavex::middleware::Next::new(v3); - let v5 = pavex::middleware::wrap_noop(v4).await; - ::into_response(v5) + let v5 = pavex::middleware::Next::new(v4); + let v6 = pavex::middleware::wrap_noop(v5).await; + ::into_response(v6) } async fn handler( v0: (bool, char, u8), v1: &dep_f8f62968::GenericType, - v2: &dep_f8f62968::ActualType, + v2: &alloc::string::String, + v3: &dep_f8f62968::ActualType, ) -> pavex::response::Response { - let v3 = app::handler_with_input_tuple(v0, v2, v1); - ::into_response(v3) + let v4 = app::mixed_generics(v2); + let v5 = dep_f8f62968::DoubleLifetimeType::<'_, '_>::new(v3, v2); + let v6 = app::handler_with_input_tuple(v0, v3, v1, &v5, v4); + ::into_response(v6) } - struct Next0<'a, 'b, T> + struct Next0<'a, 'b, 'c, T> where T: std::future::Future, { s_0: (bool, char, u8), s_1: &'a dep_f8f62968::GenericType, - s_2: &'b dep_f8f62968::ActualType, + s_2: &'b alloc::string::String, + s_3: &'c dep_f8f62968::ActualType, next: fn( (bool, char, u8), &'a dep_f8f62968::GenericType, - &'b dep_f8f62968::ActualType, + &'b alloc::string::String, + &'c dep_f8f62968::ActualType, ) -> T, } - impl<'a, 'b, T> std::future::IntoFuture for Next0<'a, 'b, T> + impl<'a, 'b, 'c, T> std::future::IntoFuture for Next0<'a, 'b, 'c, T> where T: std::future::Future, { type Output = pavex::response::Response; type IntoFuture = T; fn into_future(self) -> Self::IntoFuture { - (self.next)(self.s_0, self.s_1, self.s_2) + (self.next)(self.s_0, self.s_1, self.s_2, self.s_3) } } } diff --git a/libs/ui_tests/reflection/type_alias_are_supported/expectations/diagnostics.dot b/libs/ui_tests/reflection/type_alias_are_supported/expectations/diagnostics.dot index cc170c967..6d03107ea 100644 --- a/libs/ui_tests/reflection/type_alias_are_supported/expectations/diagnostics.dot +++ b/libs/ui_tests/reflection/type_alias_are_supported/expectations/diagnostics.dot @@ -1,29 +1,39 @@ digraph "GET /home - 0" { - 0 [ label = "pavex::middleware::wrap_noop(pavex::middleware::Next>) -> pavex::response::Response"] - 1 [ label = "pavex::middleware::Next::new(crate::route_0::Next0<'a, 'b>) -> pavex::middleware::Next>"] - 2 [ label = "crate::route_0::Next0((bool, char, u8), &'a dep_f8f62968::GenericType, &'b dep_f8f62968::ActualType) -> crate::route_0::Next0<'a, 'b>"] + 0 [ label = "pavex::middleware::wrap_noop(pavex::middleware::Next>) -> pavex::response::Response"] + 1 [ label = "pavex::middleware::Next::new(crate::route_0::Next0<'a, 'b, 'c>) -> pavex::middleware::Next>"] + 2 [ label = "crate::route_0::Next0((bool, char, u8), &'a dep_f8f62968::GenericType, &'b alloc::string::String, &'c dep_f8f62968::ActualType) -> crate::route_0::Next0<'a, 'b, 'c>"] 3 [ label = "(bool, char, u8)"] - 4 [ label = "&dep_f8f62968::ActualType"] - 6 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] - 7 [ label = "&dep_f8f62968::GenericType"] + 4 [ label = "&alloc::string::String"] + 5 [ label = "&dep_f8f62968::ActualType"] + 7 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] + 8 [ label = "&dep_f8f62968::GenericType"] 1 -> 0 [ ] 2 -> 1 [ ] + 5 -> 2 [ ] 4 -> 2 [ ] 3 -> 2 [ ] - 0 -> 6 [ ] - 7 -> 2 [ ] + 0 -> 7 [ ] + 8 -> 2 [ ] } digraph "GET /home - 1" { - 0 [ label = "app::handler_with_input_tuple((bool, char, u8), &dep_f8f62968::ActualType, &dep_f8f62968::GenericType) -> pavex::response::Response"] + 0 [ label = "app::handler_with_input_tuple((bool, char, u8), &dep_f8f62968::ActualType, &dep_f8f62968::GenericType, &dep_f8f62968::DoubleLifetimeType<'a, 'a>, app::MixedGenerics<'a, alloc::string::String>) -> pavex::response::Response"] 1 [ label = "(bool, char, u8)"] 2 [ label = "&dep_f8f62968::GenericType"] - 4 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] - 5 [ label = "&dep_f8f62968::ActualType"] + 3 [ label = "&alloc::string::String"] + 4 [ label = "dep_f8f62968::DoubleLifetimeType::::new(&''a dep_f8f62968::ActualType, &''b alloc::string::String) -> dep_f8f62968::DoubleLifetimeType<'a, 'b>"] + 5 [ label = "app::mixed_generics(&''a alloc::string::String) -> app::MixedGenerics<'a, alloc::string::String>"] + 7 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] + 8 [ label = "&dep_f8f62968::ActualType"] + 5 -> 0 [ ] + 4 -> 0 [ label = "&"] 2 -> 0 [ ] + 3 -> 4 [ ] + 3 -> 5 [ ] 1 -> 0 [ ] - 0 -> 4 [ ] - 5 -> 0 [ ] + 0 -> 7 [ ] + 8 -> 0 [ ] + 8 -> 4 [ ] } digraph "* /home - 0" { @@ -47,10 +57,12 @@ digraph "* /home - 1" { } digraph app_state { - 0 [ label = "crate::ApplicationState((bool, char, u8), dep_f8f62968::ActualType, dep_f8f62968::GenericType) -> crate::ApplicationState"] + 0 [ label = "crate::ApplicationState((bool, char, u8), alloc::string::String, dep_f8f62968::ActualType, dep_f8f62968::GenericType) -> crate::ApplicationState"] 1 [ label = "app::constructor_with_output_tuple() -> (bool, char, u8)"] - 2 [ label = "dep_f8f62968::ActualType::new() -> dep_f8f62968::ActualType"] - 3 [ label = "dep_f8f62968::GenericType::::new() -> dep_f8f62968::GenericType"] + 2 [ label = "alloc::string::String"] + 3 [ label = "dep_f8f62968::ActualType::new() -> dep_f8f62968::ActualType"] + 4 [ label = "dep_f8f62968::GenericType::::new() -> dep_f8f62968::GenericType"] + 4 -> 0 [ ] 3 -> 0 [ ] 2 -> 0 [ ] 1 -> 0 [ ] diff --git a/libs/ui_tests/reflection/type_alias_are_supported/src/lib.rs b/libs/ui_tests/reflection/type_alias_are_supported/src/lib.rs index 7577e774b..1c0c0837b 100644 --- a/libs/ui_tests/reflection/type_alias_are_supported/src/lib.rs +++ b/libs/ui_tests/reflection/type_alias_are_supported/src/lib.rs @@ -1,24 +1,39 @@ use pavex::blueprint::{router::GET, Blueprint}; -use pavex::f; +use pavex::{f, t}; pub type MyTupleAlias = (bool, char, u8); +pub type MixedGenericsAlias<'a, T> = MixedGenerics<'a, T>; pub type RemoteAlias = dep::IntermediateAlias; pub type RemoteGenericAlias = dep::IntermediateGenericAlias; +pub type RemoteLifetimeAlias<'a> = dep::DoubleLifetimeType<'a, 'a>; pub fn constructor_with_output_tuple() -> (bool, char, u8) { todo!() } -pub fn handler_with_input_tuple( +pub struct MixedGenerics<'a, T> { + _a: &'a T, +} + +pub fn mixed_generics<'a, T>(_a: &'a T) -> MixedGenericsAlias<'a, T> { + todo!() +} + +pub fn handler_with_input_tuple<'a>( _input: MyTupleAlias, _a: &RemoteAlias, _b: &RemoteGenericAlias, + _c: &RemoteLifetimeAlias<'a>, + _d: MixedGenerics<'a, String>, ) -> pavex::response::Response { todo!() } pub fn blueprint() -> Blueprint { let mut bp = Blueprint::new(); + bp.prebuilt(t!(std::string::String)); + bp.request_scoped(f!(crate::RemoteLifetimeAlias::new)); + bp.request_scoped(f!(crate::mixed_generics)); bp.singleton(f!(crate::constructor_with_output_tuple)); bp.singleton(f!(crate::RemoteAlias::new)); bp.singleton(f!(crate::RemoteGenericAlias::::new)); From a49729ae9cef772346cba32cc88a45e55144f044 Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:37:52 +0100 Subject: [PATCH 10/16] chore: Improve panic message with details about the item we couldn't handle --- libs/pavexc/src/compiler/resolvers.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/pavexc/src/compiler/resolvers.rs b/libs/pavexc/src/compiler/resolvers.rs index f56577e5a..de1f879f2 100644 --- a/libs/pavexc/src/compiler/resolvers.rs +++ b/libs/pavexc/src/compiler/resolvers.rs @@ -181,7 +181,12 @@ pub(crate) fn resolve_type( let generic_arg_defs = match &type_item.inner { ItemEnum::Struct(s) => &s.generics, ItemEnum::Enum(e) => &e.generics, - _ => unreachable!(), + i => { + unimplemented!( + "I don't know how to handle a `{:?}` yet, sorry!", + i + ) + } } .params .as_slice(); From 0018b57db4e8f7d281492641a20e6d3bff01e601 Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:38:16 +0100 Subject: [PATCH 11/16] fix: Improve error message when we fail to find a method item in the JSON docs --- libs/pavexc/src/language/resolved_path.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/pavexc/src/language/resolved_path.rs b/libs/pavexc/src/language/resolved_path.rs index 8527147cf..5d8109111 100644 --- a/libs/pavexc/src/language/resolved_path.rs +++ b/libs/pavexc/src/language/resolved_path.rs @@ -688,7 +688,8 @@ impl ResolvedPath { return Ok(Err(UnknownPath( self.clone(), Arc::new(anyhow::anyhow!( - "Path is too short to be a method path, but there is no function at that path" + "{} is too short to be a method path, but there is no function at that path", + self )), ))); } From e4305c6fa4cc5e0e5bf2f95e790019f10057c4af Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:38:54 +0100 Subject: [PATCH 12/16] fix: Look for the 'impl' block in the crate that define the type, rather than the trait, when resolving trait methods --- libs/pavexc/src/language/resolved_path.rs | 106 +++++++++++++--------- 1 file changed, 62 insertions(+), 44 deletions(-) diff --git a/libs/pavexc/src/language/resolved_path.rs b/libs/pavexc/src/language/resolved_path.rs index 5d8109111..1ec8864a3 100644 --- a/libs/pavexc/src/language/resolved_path.rs +++ b/libs/pavexc/src/language/resolved_path.rs @@ -717,7 +717,7 @@ impl ResolvedPath { let (method_name_segment, type_path_segments) = self.segments.split_last().unwrap(); - // Let's first try to see if the parent path points to a type. + // Let's first try to see if the parent path points to a type, that we'll consider to be `Self` let method_owner_path = ResolvedPath { segments: type_path_segments.to_vec(), qualified_self: None, @@ -731,54 +731,72 @@ impl ResolvedPath { } }; - let children_ids = match &method_owner_item.item.inner { - ItemEnum::Struct(s) => &s.impls, - ItemEnum::Enum(enum_) => &enum_.impls, - ItemEnum::Trait(trait_) => &trait_.items, - _ => { - unreachable!() - } + // If we're dealing with a trait method, we want to search in the docs of the trait itself + // as well as the docs of the implementing type. + let mut parent_items = match &qself { + Some((item, _)) => vec![item, &method_owner_item], + None => vec![&method_owner_item], }; - let method_owner_krate = - krate_collection.get_or_compute_crate_by_package_id(&method_owner_path.package_id)?; - let mut method = None; - for child_id in children_ids { - let child = method_owner_krate.get_item_by_local_type_id(child_id); - match &child.inner { - ItemEnum::Impl(impl_block) => { - // We are completely ignoring the bounds attached to the implementation block. - // This can lead to issues: the same method can be defined multiple - // times in different implementation blocks with non-overlapping constraints. - for impl_item_id in &impl_block.items { - let impl_item = method_owner_krate.get_item_by_local_type_id(impl_item_id); - if impl_item.name.as_ref() == Some(&method_name_segment.ident) { - if let ItemEnum::Function(_) = &impl_item.inner { - method = Some(ResolvedItem { - item: impl_item, - item_id: GlobalItemId { - package_id: krate.core.package_id.clone(), - rustdoc_item_id: child_id.to_owned(), - }, - }); + let method; + let mut parent_item = parent_items.pop().unwrap(); + 'outer: loop { + let children_ids = match &parent_item.item.inner { + ItemEnum::Struct(s) => &s.impls, + ItemEnum::Enum(enum_) => &enum_.impls, + ItemEnum::Trait(trait_) => &trait_.items, + _ => { + unreachable!() + } + }; + let search_krate = krate_collection + .get_or_compute_crate_by_package_id(&parent_item.item_id.package_id)?; + for child_id in children_ids { + let child = search_krate.get_item_by_local_type_id(child_id); + match &child.inner { + ItemEnum::Impl(impl_block) => { + // We are completely ignoring the bounds attached to the implementation block. + // This can lead to issues: the same method can be defined multiple + // times in different implementation blocks with non-overlapping constraints. + for impl_item_id in &impl_block.items { + let impl_item = search_krate.get_item_by_local_type_id(impl_item_id); + if impl_item.name.as_ref() == Some(&method_name_segment.ident) { + if let ItemEnum::Function(_) = &impl_item.inner { + method = Some(ResolvedItem { + item: impl_item, + item_id: GlobalItemId { + package_id: search_krate.core.package_id.clone(), + rustdoc_item_id: child_id.to_owned(), + }, + }); + break 'outer; + } } } } - } - ItemEnum::Function(_) => { - if child.name.as_ref() == Some(&method_name_segment.ident) { - method = Some(ResolvedItem { - item: child, - item_id: GlobalItemId { - package_id: krate.core.package_id.clone(), - rustdoc_item_id: child_id.to_owned(), - }, - }); + ItemEnum::Function(_) => { + if child.name.as_ref() == Some(&method_name_segment.ident) { + method = Some(ResolvedItem { + item: child, + item_id: GlobalItemId { + package_id: search_krate.core.package_id.clone(), + rustdoc_item_id: child_id.to_owned(), + }, + }); + break 'outer; + } + } + i => { + dbg!(i); + unreachable!() } } - i => { - dbg!(i); - unreachable!() - } + } + + if let Some(next_parent) = parent_items.pop() { + parent_item = next_parent; + } else { + method = None; + break; } } @@ -790,7 +808,7 @@ impl ResolvedPath { .cloned() .collect(), qualified_self: self.qualified_self.clone(), - package_id: method_owner_path.package_id.clone(), + package_id: parent_item.item_id.package_id.clone(), }; if let Some(method) = method { Ok(Ok(CallableItem::Method { From 8161f570fa7930417eaf33ebd73010bd43b5e755 Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:39:32 +0100 Subject: [PATCH 13/16] fix: Improve error message --- libs/pavexc/src/language/resolved_path.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/pavexc/src/language/resolved_path.rs b/libs/pavexc/src/language/resolved_path.rs index 1ec8864a3..4758b8816 100644 --- a/libs/pavexc/src/language/resolved_path.rs +++ b/libs/pavexc/src/language/resolved_path.rs @@ -820,7 +820,9 @@ impl ResolvedPath { Ok(Err(UnknownPath( self.clone(), Arc::new(anyhow::anyhow!( - "Path is too short to be a method path, but there is no function at that path" + "There was no method named `{}` attached to `{}`", + method_name_segment.ident, + method_owner_path )), ))) } From a146063e6f2f74f4cd54c370697e3d8ca456814d Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:39:48 +0100 Subject: [PATCH 14/16] chore: Punctuation in error messages. --- libs/pavexc/src/language/resolved_path.rs | 2 +- libs/pavexc/src/rustdoc/queries.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/pavexc/src/language/resolved_path.rs b/libs/pavexc/src/language/resolved_path.rs index 4758b8816..4d97a2b11 100644 --- a/libs/pavexc/src/language/resolved_path.rs +++ b/libs/pavexc/src/language/resolved_path.rs @@ -1118,7 +1118,7 @@ impl Display for UnknownPath { let krate = path.crate_name().to_string(); write!( f, - "I could not find '{path}' in the auto-generated documentation for '{krate}'" + "I could not find '{path}' in the auto-generated documentation for '{krate}'." ) } } diff --git a/libs/pavexc/src/rustdoc/queries.rs b/libs/pavexc/src/rustdoc/queries.rs index 46f243275..517a798aa 100644 --- a/libs/pavexc/src/rustdoc/queries.rs +++ b/libs/pavexc/src/rustdoc/queries.rs @@ -1104,7 +1104,7 @@ impl std::fmt::Display for UnknownItemPath { let krate = self.path.first().unwrap(); write!( f, - "I could not find '{path}' in the auto-generated documentation for '{krate}'" + "I could not find '{path}' in the auto-generated documentation for '{krate}'." ) } } From 1befce5ffce0ca2c77ce87ec57a417b3d036da3d Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:40:08 +0100 Subject: [PATCH 15/16] chore: Cover foreign traits in tests --- .../diagnostics.dot | 22 +++++----- .../expectations/app.rs | 25 +++++------ .../expectations/diagnostics.dot | 22 +++++----- .../trait_methods_are_supported/src/lib.rs | 42 +++++++++---------- 4 files changed, 56 insertions(+), 55 deletions(-) diff --git a/libs/ui_tests/reflection/trait_methods_are_supported/diagnostics.dot b/libs/ui_tests/reflection/trait_methods_are_supported/diagnostics.dot index 179e9da05..d64bd220a 100644 --- a/libs/ui_tests/reflection/trait_methods_are_supported/diagnostics.dot +++ b/libs/ui_tests/reflection/trait_methods_are_supported/diagnostics.dot @@ -1,4 +1,4 @@ -digraph "GET /home - 0" { +digraph "GET / - 0" { 0 [ label = "pavex::middleware::wrap_noop(pavex::middleware::Next) -> pavex::response::Response"] 1 [ label = "pavex::middleware::Next::new(crate::route_0::Next0) -> pavex::middleware::Next"] 2 [ label = "crate::route_0::Next0() -> crate::route_0::Next0"] @@ -8,26 +8,28 @@ digraph "GET /home - 0" { 0 -> 3 [ ] } -digraph "GET /home - 1" { - 0 [ label = "app_a7fd6a2c::handler(app_a7fd6a2c::A, app_a7fd6a2c::C, app_a7fd6a2c::D, app_a7fd6a2c::E) -> pavex::response::Response"] +digraph "GET / - 1" { + 0 [ label = "app_a7fd6a2c::handler(app_a7fd6a2c::A, app_a7fd6a2c::C, app_a7fd6a2c::D, app_a7fd6a2c::E, app_a7fd6a2c::F) -> pavex::response::Response"] 1 [ label = "::a_method_that_returns_self() -> app_a7fd6a2c::A"] 2 [ label = "::a_method_that_consumes_self(app_a7fd6a2c::B) -> app_a7fd6a2c::C"] 3 [ label = "::a_method_with_a_generic::(&app_a7fd6a2c::A) -> app_a7fd6a2c::D"] 4 [ label = ">::a_method(&app_a7fd6a2c::C) -> app_a7fd6a2c::E"] - 5 [ label = "::a_method_that_borrows_self(&app_a7fd6a2c::A) -> app_a7fd6a2c::B"] - 6 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] + 5 [ label = "::default() -> app_a7fd6a2c::F"] + 6 [ label = "::a_method_that_borrows_self(&app_a7fd6a2c::A) -> app_a7fd6a2c::B"] + 7 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] + 5 -> 0 [ ] 4 -> 0 [ ] 2 -> 4 [ label = "&"] - 5 -> 2 [ ] - 1 -> 5 [ label = "&"] + 6 -> 2 [ ] + 1 -> 6 [ label = "&"] 3 -> 0 [ ] 1 -> 3 [ label = "&"] 2 -> 0 [ ] 1 -> 0 [ ] - 0 -> 6 [ ] + 0 -> 7 [ ] } -digraph "* /home - 0" { +digraph "* / - 0" { 0 [ label = "pavex::middleware::wrap_noop(pavex::middleware::Next>) -> pavex::response::Response"] 1 [ label = "pavex::middleware::Next::new(crate::route_1::Next0<'a>) -> pavex::middleware::Next>"] 2 [ label = "crate::route_1::Next0(&'a pavex::router::AllowedMethods) -> crate::route_1::Next0<'a>"] @@ -39,7 +41,7 @@ digraph "* /home - 0" { 5 -> 2 [ ] } -digraph "* /home - 1" { +digraph "* / - 1" { 0 [ label = "pavex::router::default_fallback(&pavex::router::AllowedMethods) -> pavex::response::Response"] 2 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] 3 [ label = "&pavex::router::AllowedMethods"] diff --git a/libs/ui_tests/reflection/trait_methods_are_supported/expectations/app.rs b/libs/ui_tests/reflection/trait_methods_are_supported/expectations/app.rs index 1af8c7177..3fe8ba6da 100644 --- a/libs/ui_tests/reflection/trait_methods_are_supported/expectations/app.rs +++ b/libs/ui_tests/reflection/trait_methods_are_supported/expectations/app.rs @@ -23,7 +23,7 @@ pub fn run( } fn build_router() -> pavex_matchit::Router { let mut router = pavex_matchit::Router::new(); - router.insert("/home", 0u32).unwrap(); + router.insert("/", 0u32).unwrap(); router } async fn route_request( @@ -84,21 +84,22 @@ pub mod route_0 { ::into_response(v2) } async fn handler() -> pavex::response::Response { - let v0 = ::a_method_that_returns_self(); - let v1 = ::a_method_that_borrows_self( - &v0, + let v0 = ::default(); + let v1 = ::a_method_that_returns_self(); + let v2 = ::a_method_that_borrows_self( + &v1, ); - let v2 = ::a_method_that_consumes_self( - v1, + let v3 = ::a_method_that_consumes_self( + v2, ); - let v3 = >::a_method(&v2); - let v4 = ::a_method_with_a_generic::< + >>::a_method(&v3); + let v5 = ::a_method_with_a_generic::< std::string::String, - >(&v0); - let v5 = app::handler(v0, v2, v4, v3); - ::into_response(v5) + >(&v1); + let v6 = app::handler(v1, v3, v5, v4, v0); + ::into_response(v6) } struct Next0 where diff --git a/libs/ui_tests/reflection/trait_methods_are_supported/expectations/diagnostics.dot b/libs/ui_tests/reflection/trait_methods_are_supported/expectations/diagnostics.dot index 3528dffa4..cb359cecb 100644 --- a/libs/ui_tests/reflection/trait_methods_are_supported/expectations/diagnostics.dot +++ b/libs/ui_tests/reflection/trait_methods_are_supported/expectations/diagnostics.dot @@ -1,4 +1,4 @@ -digraph "GET /home - 0" { +digraph "GET / - 0" { 0 [ label = "pavex::middleware::wrap_noop(pavex::middleware::Next) -> pavex::response::Response"] 1 [ label = "pavex::middleware::Next::new(crate::route_0::Next0) -> pavex::middleware::Next"] 2 [ label = "crate::route_0::Next0() -> crate::route_0::Next0"] @@ -8,26 +8,28 @@ digraph "GET /home - 0" { 0 -> 3 [ ] } -digraph "GET /home - 1" { - 0 [ label = "app::handler(app::A, app::C, app::D, app::E) -> pavex::response::Response"] +digraph "GET / - 1" { + 0 [ label = "app::handler(app::A, app::C, app::D, app::E, app::F) -> pavex::response::Response"] 1 [ label = "::a_method_that_returns_self() -> app::A"] 2 [ label = "::a_method_that_consumes_self(app::B) -> app::C"] 3 [ label = "::a_method_with_a_generic::(&app::A) -> app::D"] 4 [ label = ">::a_method(&app::C) -> app::E"] - 5 [ label = "::a_method_that_borrows_self(&app::A) -> app::B"] - 6 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] + 5 [ label = "::default() -> app::F"] + 6 [ label = "::a_method_that_borrows_self(&app::A) -> app::B"] + 7 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] + 5 -> 0 [ ] 4 -> 0 [ ] 2 -> 4 [ label = "&"] - 5 -> 2 [ ] - 1 -> 5 [ label = "&"] + 6 -> 2 [ ] + 1 -> 6 [ label = "&"] 3 -> 0 [ ] 1 -> 3 [ label = "&"] 2 -> 0 [ ] 1 -> 0 [ ] - 0 -> 6 [ ] + 0 -> 7 [ ] } -digraph "* /home - 0" { +digraph "* / - 0" { 0 [ label = "pavex::middleware::wrap_noop(pavex::middleware::Next>) -> pavex::response::Response"] 1 [ label = "pavex::middleware::Next::new(crate::route_1::Next0<'a>) -> pavex::middleware::Next>"] 2 [ label = "crate::route_1::Next0(&'a pavex::router::AllowedMethods) -> crate::route_1::Next0<'a>"] @@ -39,7 +41,7 @@ digraph "* /home - 0" { 5 -> 2 [ ] } -digraph "* /home - 1" { +digraph "* / - 1" { 0 [ label = "pavex::router::default_fallback(&pavex::router::AllowedMethods) -> pavex::response::Response"] 2 [ label = "::into_response(pavex::response::Response) -> pavex::response::Response"] 3 [ label = "&pavex::router::AllowedMethods"] diff --git a/libs/ui_tests/reflection/trait_methods_are_supported/src/lib.rs b/libs/ui_tests/reflection/trait_methods_are_supported/src/lib.rs index 181a3f800..32dd0c22f 100644 --- a/libs/ui_tests/reflection/trait_methods_are_supported/src/lib.rs +++ b/libs/ui_tests/reflection/trait_methods_are_supported/src/lib.rs @@ -1,4 +1,4 @@ -use pavex::blueprint::{constructor::Lifecycle, router::GET, Blueprint}; +use pavex::blueprint::{router::GET, Blueprint}; use pavex::f; pub struct A; @@ -11,6 +11,9 @@ pub struct D; pub struct E; +#[derive(Default)] +pub struct F; + pub trait MyTrait { fn a_method_that_returns_self() -> Self; fn a_method_that_borrows_self(&self) -> B; @@ -49,32 +52,25 @@ impl GenericTrait for C { } } -pub fn handler(_a: A, _c: C, _d: D, _e: E) -> pavex::response::Response { +pub fn handler(_a: A, _c: C, _d: D, _e: E, _f: F) -> pavex::response::Response { todo!() } pub fn blueprint() -> Blueprint { let mut bp = Blueprint::new(); - bp.constructor( - f!(::a_method_that_returns_self), - Lifecycle::RequestScoped, - ); - bp.constructor( - f!(::a_method_that_borrows_self), - Lifecycle::RequestScoped, - ); - bp.constructor( - f!(::a_method_with_a_generic::), - Lifecycle::RequestScoped, - ); - bp.constructor( - f!(::a_method_that_consumes_self), - Lifecycle::RequestScoped, - ); - bp.constructor( - f!(>::a_method), - Lifecycle::RequestScoped, - ); - bp.route(GET, "/home", f!(crate::handler)); + // A foreign trait, from `std`. + bp.request_scoped(f!(::default)); + bp.request_scoped(f!(::a_method_that_returns_self)); + bp.request_scoped(f!(::a_method_that_borrows_self)); + bp.request_scoped(f!(::a_method_with_a_generic::< + std::string::String, + >)); + bp.request_scoped(f!( + ::a_method_that_consumes_self + )); + bp.request_scoped(f!( + >::a_method + )); + bp.route(GET, "/", f!(crate::handler)); bp } From 2293bc8be31107ea480374421cb33c13a3e62007 Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:50:06 +0100 Subject: [PATCH 16/16] chore: Update doc examples --- .../guide/cookies/installation/missing_process_config.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc_examples/guide/cookies/installation/missing_process_config.snap b/doc_examples/guide/cookies/installation/missing_process_config.snap index 026ca15dd..b2e1312a3 100644 --- a/doc_examples/guide/cookies/installation/missing_process_config.snap +++ b/doc_examples/guide/cookies/installation/missing_process_config.snap @@ -2,7 +2,7 @@ × I can't find a constructor for `biscotti::ProcessorConfig`. │ I need an instance of `biscotti::ProcessorConfig` to │ invoke your constructor, `>::from`. + │ core::convert::From::>::from`. │ │ ╭─[../../../../../libs/pavex/src/cookie/kit.rs:80:1] │ 80 │ .error_handler(f!(super::errors::InjectResponseCookiesError::into_response));