From 0003fcb9b1cf140721310441d7e5f1d1a8f7218e Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Wed, 11 Mar 2026 17:52:54 +0100 Subject: [PATCH 1/3] refactor: Remove FQPath --- .../analyses/components/db/diagnostics.rs | 5 +- .../compiler/analyses/components/db/mod.rs | 49 +- .../src/compiler/analyses/computations.rs | 16 +- .../user_components/annotations/diagnostic.rs | 36 - compiler/pavexc/src/compiler/resolvers.rs | 613 +------------- compiler/pavexc/src/compiler/utils.rs | 400 ++++++++- compiler/pavexc/src/language/callable_path.rs | 324 -------- compiler/pavexc/src/language/fq_path.rs | 761 ------------------ .../pavexc/src/language/fq_path_resolution.rs | 525 ------------ compiler/pavexc/src/language/mod.rs | 24 +- compiler/pavexc/src/language/resolved_type.rs | 18 - compiler/pavexc/src/rustdoc/mod.rs | 2 +- .../pavexc/src/rustdoc/queries/collection.rs | 190 +---- compiler/pavexc/src/rustdoc/queries/mod.rs | 2 +- .../unions_are_supported/diagnostics.dot | 41 + 15 files changed, 438 insertions(+), 2568 deletions(-) delete mode 100644 compiler/pavexc/src/language/callable_path.rs delete mode 100644 compiler/pavexc/src/language/fq_path.rs delete mode 100644 compiler/pavexc/src/language/fq_path_resolution.rs create mode 100644 compiler/ui_tests/reflection/unions_are_supported/diagnostics.dot diff --git a/compiler/pavexc/src/compiler/analyses/components/db/diagnostics.rs b/compiler/pavexc/src/compiler/analyses/components/db/diagnostics.rs index 09d2c610b..5ed83ade1 100644 --- a/compiler/pavexc/src/compiler/analyses/components/db/diagnostics.rs +++ b/compiler/pavexc/src/compiler/analyses/components/db/diagnostics.rs @@ -6,7 +6,6 @@ use crate::compiler::component::{ PostProcessingMiddlewareValidationError, PreProcessingMiddlewareValidationError, RequestHandlerValidationError, WrappingMiddlewareValidationError, }; -use crate::compiler::resolvers::CallableResolutionError; use crate::compiler::traits::MissingTraitImplementationError; use crate::diagnostic::{ self, AnnotatedSource, CallableDefSource, CompilerDiagnostic, ComponentKind, SourceSpanExt, @@ -644,7 +643,7 @@ impl ComponentDb { } pub(super) fn cannot_handle_into_response_implementation( - e: CallableResolutionError, + e: anyhow::Error, output_type: &Type, id: UserComponentId, db: &UserComponentDb, @@ -655,7 +654,7 @@ impl ComponentDb { db.registration_target(id), format!("The {kind} was registered here"), ); - let error = anyhow::Error::from(e).context(format!( + let error = e.context(format!( "Something went wrong when I tried to analyze the implementation of \ `pavex::IntoResponse` for {output_type:?}, the type returned by \ one of your {kind}s.\n\ diff --git a/compiler/pavexc/src/compiler/analyses/components/db/mod.rs b/compiler/pavexc/src/compiler/analyses/components/db/mod.rs index 577b80270..57ab502e4 100644 --- a/compiler/pavexc/src/compiler/analyses/components/db/mod.rs +++ b/compiler/pavexc/src/compiler/analyses/components/db/mod.rs @@ -19,12 +19,11 @@ use crate::compiler::computation::{Computation, MatchResult}; use crate::compiler::interner::Interner; use crate::compiler::traits::assert_trait_is_implemented; use crate::compiler::utils::{ - get_err_variant, get_ok_variant, resolve_framework_callable_path, resolve_type_path, + get_err_variant, get_ok_variant, resolve_framework_free_function, + resolve_framework_inherent_method, resolve_framework_trait_method, resolve_type_path, }; use crate::diagnostic::{ParsedSourceFile, Registration, TargetSpan}; -use crate::language::{ - Callable, FQPath, FQPathSegment, FQQualifiedSelf, Lifetime, PathTypeExt, Type, TypeReference, -}; +use crate::language::{Callable, Lifetime, Type, TypeReference}; use crate::rustdoc::{CrateCollection, CrateCollectionExt}; use ahash::{HashMap, HashMapExt, HashSet}; use guppy::graph::PackageGraph; @@ -159,18 +158,17 @@ impl ComponentDb { }) }; let pavex_error_fallback_id = { - let callable = resolve_framework_callable_path( - "pavex::Error::to_response", - package_graph, + let callable = resolve_framework_inherent_method( + &["pavex", "Error"], + "to_response", krate_collection, ); let computation = Computation::Callable(Cow::Owned(callable)); computation_db.get_or_intern(computation) }; let pavex_noop_wrap_id = { - let callable = resolve_framework_callable_path( - "pavex::middleware::wrap_noop", - package_graph, + let callable = resolve_framework_free_function( + &["pavex", "middleware", "wrap_noop"], krate_collection, ); let computation = Computation::Callable(Cow::Owned(callable)); @@ -303,9 +301,9 @@ impl ComponentDb { // Add a synthetic constructor for the `pavex::middleware::Next` type. { - let callable = resolve_framework_callable_path( - "pavex::middleware::Next::new", - package_graph, + let callable = resolve_framework_inherent_method( + &["pavex", "middleware", "Next"], + "new", krate_collection, ); let computation = Computation::Callable(Cow::Owned(callable)); @@ -1275,7 +1273,6 @@ impl ComponentDb { }; into_response }; - let into_response_path = into_response.resolved_path(); let iter: Vec<_> = self .interner .iter() @@ -1341,21 +1338,15 @@ impl ComponentDb { } continue; } - let mut transformer_segments = into_response_path.segments.clone(); - transformer_segments.push(FQPathSegment { - ident: "into_response".into(), - generic_arguments: vec![], - }); - let transformer_path = FQPath { - segments: transformer_segments, - qualified_self: Some(FQQualifiedSelf { - position: into_response_path.segments.len(), - type_: output.clone().into(), - }), - package_id: into_response_path.package_id.clone(), - }; - match computation_db.resolve_and_intern(krate_collection, &transformer_path, None) { - Ok(callable_id) => { + match resolve_framework_trait_method( + &["pavex", "IntoResponse"], + "into_response", + output.clone(), + krate_collection, + ) { + Ok(callable) => { + let computation = Computation::Callable(Cow::Owned(callable)); + let callable_id = computation_db.get_or_intern(computation); let transformer = UnregisteredComponent::Transformer { computation_id: callable_id, transformed_component_id: component_id, diff --git a/compiler/pavexc/src/compiler/analyses/computations.rs b/compiler/pavexc/src/compiler/analyses/computations.rs index af18dbf12..ef70f7314 100644 --- a/compiler/pavexc/src/compiler/analyses/computations.rs +++ b/compiler/pavexc/src/compiler/analyses/computations.rs @@ -3,9 +3,7 @@ use ahash::HashMap; use crate::compiler::analyses::user_components::UserComponentId; use crate::compiler::computation::Computation; use crate::compiler::interner::Interner; -use crate::compiler::resolvers::{CallableResolutionError, resolve_callable}; -use crate::language::{Callable, FQPath}; -use crate::rustdoc::CrateCollection; +use crate::language::Callable; pub(crate) type ComputationId = la_arena::Idx>; @@ -34,18 +32,6 @@ impl ComputationDb { } } - /// Try to resolve a callable from a resolved path. - /// Returns the callable's id in the interner if it succeeds, an error otherwise. - pub(crate) fn resolve_and_intern( - &mut self, - krate_collection: &CrateCollection, - resolved_path: &FQPath, - user_component_id: Option, - ) -> Result { - let callable = resolve_callable(krate_collection, resolved_path)?; - Ok(self.get_or_intern_with_id(callable, user_component_id)) - } - /// Intern a callable (or retrieve its id if it already exists). /// /// Then associate it with the given user component id. diff --git a/compiler/pavexc/src/compiler/analyses/user_components/annotations/diagnostic.rs b/compiler/pavexc/src/compiler/analyses/user_components/annotations/diagnostic.rs index fd8b08d19..4240c5ac8 100644 --- a/compiler/pavexc/src/compiler/analyses/user_components/annotations/diagnostic.rs +++ b/compiler/pavexc/src/compiler/analyses/user_components/annotations/diagnostic.rs @@ -358,16 +358,6 @@ pub(super) fn cannot_resolve_callable_path( let component = &db[id]; let kind = component.kind(); match e { - CallableResolutionError::UnknownCallable(_) => { - let source = diagnostics.annotated( - TargetSpan::RawIdentifiers(&db.id2registration[id], kind), - format!("The {kind} that we can't resolve"), - ); - let diagnostic = CompilerDiagnostic::builder(e).optional_source(source) - .help("Check that the path is spelled correctly and that the function (or method) is public.".into()) - .build(); - diagnostics.push(diagnostic); - } CallableResolutionError::InputParameterResolutionError(ref inner_error) => { let definition_snippet = CallableDefSource::compute_from_item(&inner_error.callable_item, package_graph) @@ -388,22 +378,6 @@ pub(super) fn cannot_resolve_callable_path( .build(); diagnostics.push(diagnostic); } - CallableResolutionError::UnsupportedCallableKind(ref inner_error) => { - let source = diagnostics.annotated( - TargetSpan::RawIdentifiers(&db.id2registration[id], kind), - format!("It was registered as a {kind} here"), - ); - let message = format!( - "I can work with functions and methods, but `{}` is neither.\nIt is {} and I don't know how to use it as a {}.", - inner_error.import_path, inner_error.item_kind, kind - ); - let error = anyhow::anyhow!(e).context(message); - diagnostics.push( - CompilerDiagnostic::builder(error) - .optional_source(source) - .build(), - ); - } CallableResolutionError::OutputTypeResolutionError(ref inner_error) => { let output_snippet = CallableDefSource::compute_from_item(&inner_error.callable_item, package_graph) @@ -426,16 +400,6 @@ pub(super) fn cannot_resolve_callable_path( CallableResolutionError::CannotGetCrateData(_) => { diagnostics.push(CompilerDiagnostic::builder(e).build()); } - CallableResolutionError::GenericParameterResolutionError(_) => { - let source = diagnostics.annotated( - db.registration_target(&id), - format!("The {kind} was registered here"), - ); - let diagnostic = CompilerDiagnostic::builder(e) - .optional_source(source) - .build(); - diagnostics.push(diagnostic); - } CallableResolutionError::SelfResolutionError(_) => { let source = diagnostics.annotated( db.registration_target(&id), diff --git a/compiler/pavexc/src/compiler/resolvers.rs b/compiler/pavexc/src/compiler/resolvers.rs index 36f879210..6ef77ad80 100644 --- a/compiler/pavexc/src/compiler/resolvers.rs +++ b/compiler/pavexc/src/compiler/resolvers.rs @@ -1,592 +1,16 @@ -//! Given the fully qualified path to a function (be it a constructor or a handler), -//! find the corresponding item ("resolution") in `rustdoc`'s JSON output to determine -//! its input parameters and output type. -use std::ops::Deref; -use std::sync::Arc; +//! Resolution of callables and types from rustdoc information. -use rustdoc_types::{GenericParamDefKind, ItemEnum, Type as RustdocType}; -use tracing_log_error::log_error; - -use crate::language::{ - Callable, CallableInput, CallableItem, FQGenericArgument, FQPath, FQPathSegment, FQPathType, - FnHeader, FreeFunction, FreeFunctionPath, Generic, GenericArgument, GenericLifetimeParameter, - InherentMethod, InherentMethodPath, PathType, RustIdentifier, TraitMethod, TraitMethodPath, - Type, UnknownPath, find_rustdoc_callable_items, find_rustdoc_item_type, resolve_fq_path_type, -}; -use crate::rustdoc::{CannotGetCrateData, CrateCollection, ResolvedItem}; -use rustdoc_ext::RustdocKindExt; +use crate::rustdoc::CannotGetCrateData; // Re-export types that moved to `rustdoc_resolver` so that downstream code // within pavexc can keep importing them from this module. pub use rustdoc_resolver::{ - GenericBindings, InputParameterResolutionError, OutputTypeResolutionError, SelfResolutionError, - TypeResolutionError, UnsupportedConstGeneric, resolve_type, + InputParameterResolutionError, OutputTypeResolutionError, SelfResolutionError, + TypeResolutionError, UnsupportedConstGeneric, }; -pub(crate) fn resolve_callable( - krate_collection: &CrateCollection, - callable_path: &FQPath, -) -> Result { - let callable_items = find_rustdoc_callable_items(callable_path, 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) = match &callable_item.item.inner { - ItemEnum::Function(f) => (&f.header, &f.sig, &f.generics), - kind => { - let item_kind = kind.kind().to_owned(); - return Err(UnsupportedCallableKind { - import_path: callable_path.to_owned(), - item_kind, - } - .into()); - } - }; - - let mut generic_bindings = GenericBindings::default(); - if let CallableItem::Method { - method_owner, - qualified_self, - .. - } = &callable_items - { - if matches!(&method_owner.0.item.inner, ItemEnum::Trait(_)) - && let Err(e) = get_trait_generic_bindings( - &method_owner.0, - &method_owner.1, - krate_collection, - &mut generic_bindings, - ) - { - log_error!(*e, level: tracing::Level::WARN, "Error getting trait generic bindings"); - } - - 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) => { - log_error!(*e, level: tracing::Level::WARN, "Error resolving the `Self` type"); - None - } - }; - if let Some(ty) = self_generic_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; - match generic_arg { - FQGenericArgument::Type(t) => { - let resolved_type = resolve_fq_path_type(t, 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); - } - FQGenericArgument::Lifetime(l) => { - let resolved_lifetime = l.to_string(); - generic_bindings - .lifetimes - .insert(generic_name.to_owned(), resolved_lifetime); - } - } - } - - let mut resolved_parameter_types = Vec::with_capacity(decl.inputs.len()); - let mut takes_self_as_ref = false; - for (parameter_index, (parameter_name, parameter_type)) in decl.inputs.iter().enumerate() { - if parameter_index == 0 { - // The first parameter might be `&self` or `&mut self`. - // This is important to know for carrying out further analysis doing the line, - // e.g. undoing lifetime elision. - if let RustdocType::BorrowedRef { type_, .. } = parameter_type - && let RustdocType::Generic(g) = type_.deref() - && g == "Self" - { - takes_self_as_ref = true; - } - } - match resolve_type( - parameter_type, - used_by_package_id, - krate_collection, - &generic_bindings, - ) { - Ok(p) => resolved_parameter_types.push(CallableInput { - name: RustIdentifier::new(parameter_name.clone()), - type_: p, - }), - Err(e) => { - return Err(InputParameterResolutionError { - parameter_type: parameter_type.to_owned(), - callable_path: new_callable_path.to_string(), - callable_item: callable_item.item.clone().into_owned(), - source: Arc::new(e.into()), - parameter_index, - } - .into()); - } - } - } - let output_type_path = match &decl.output { - // Unit type - None => None, - Some(output_type) => { - match resolve_type( - output_type, - used_by_package_id, - krate_collection, - &generic_bindings, - ) { - Ok(p) => Some(p), - Err(e) => { - return Err(OutputTypeResolutionError { - output_type: output_type.to_owned(), - callable_path: new_callable_path.to_string(), - callable_item: callable_item.item.clone().into_owned(), - source: Arc::new(e.into()), - } - .into()); - } - } - } - }; - - // We need to make sure that we always refer to user-registered types using a public path, even though they - // might have been registered using a private path (e.g. `self::...` where `self` is a private module). - // For this reason, we fall back to the canonical path—i.e. the shortest public path for the given item. - // TODO: We should do the same for qualified self, if it's populated. - 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 &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 - .iter() - .map(|s| FQPathSegment { - ident: s.into(), - generic_arguments: vec![], - }) - .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 = - self_.1.segments.last().unwrap().generic_arguments.clone(); - Some(FQPath { - segments, - qualified_self: self_.1.qualified_self.clone(), - package_id: self_.0.item_id.package_id.clone(), - }) - } - Err(_) => { - tracing::warn!( - package_id = self_.0.item_id.package_id.repr(), - item_id = ?self_.0.item_id.rustdoc_item_id, - "Failed to find canonical path for {:?}", - self_.1 - ); - Some(self_.1.clone()) - } - } - } - _ => None, - }; - - match parent_canonical_path { - Some(p) => { - // We have already canonicalized the parent path, so we just need to append the method name and we're done. - let mut segments = p.segments; - segments.push(callable_path.segments.last().unwrap().clone()); - FQPath { - segments, - qualified_self: callable_path.qualified_self.clone(), - package_id: p.package_id.clone(), - } - } - 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.item_id) - { - Ok(p) => { - let mut segments: Vec<_> = p - .iter() - .map(|s| FQPathSegment { - ident: s.into(), - generic_arguments: vec![], - }) - .collect(); - // The canonical path doesn't include the (populated or omitted) generic arguments from the user-provided callable path, - // so we need to add them back in. - segments.last_mut().unwrap().generic_arguments = callable_path - .segments - .last() - .unwrap() - .generic_arguments - .clone(); - FQPath { - segments, - qualified_self: callable_path.qualified_self.clone(), - package_id: callable_item.item_id.package_id.clone(), - } - } - Err(_) => { - tracing::warn!( - 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 - ); - callable_path.to_owned() - } - } - } - } - }; - - let symbol_name = callable_item.item.attrs.iter().find_map(|attr| match attr { - rustdoc_types::Attribute::NoMangle => callable_item.item.name.clone(), - rustdoc_types::Attribute::ExportName(name) => Some(name.clone()), - _ => None, - }); - - let resolved_path = - fq_path_to_resolved_path(&canonical_path, &callable_items, krate_collection) - .expect("Failed to convert generic arguments when building callable path"); - - let fn_header = FnHeader { - output: output_type_path, - inputs: resolved_parameter_types, - is_async: header.is_async, - abi: header.abi.clone(), - is_unsafe: header.is_unsafe, - is_c_variadic: decl.is_c_variadic, - symbol_name, - }; - let source_coordinates = Some(callable_item.item_id.clone()); - - let callable = match resolved_path { - ResolvedPath::FreeFunction(path) => Callable::FreeFunction(FreeFunction { - path, - header: fn_header, - source_coordinates, - }), - ResolvedPath::InherentMethod(path) => Callable::InherentMethod(InherentMethod { - path, - header: fn_header, - source_coordinates, - takes_self_as_ref, - }), - ResolvedPath::TraitMethod(path) => Callable::TraitMethod(TraitMethod { - path, - header: fn_header, - source_coordinates, - takes_self_as_ref, - }), - }; - Ok(callable) -} - -/// Convert a single `FQGenericArgument` into a `GenericArgument`. -fn resolve_fq_generic_arg( - arg: &FQGenericArgument, - krate_collection: &CrateCollection, -) -> Result { - match arg { - FQGenericArgument::Type(t) => Ok(GenericArgument::TypeParameter(resolve_fq_path_type( - t, - krate_collection, - )?)), - FQGenericArgument::Lifetime(l) => Ok(GenericArgument::Lifetime(l.clone().into())), - } -} - -/// Convert a slice of `FQGenericArgument`s into a `Vec`. -fn resolve_fq_generic_args( - args: &[FQGenericArgument], - krate_collection: &CrateCollection, -) -> Result, anyhow::Error> { - args.iter() - .map(|arg| resolve_fq_generic_arg(arg, krate_collection)) - .collect() -} - -/// Internal helper to carry the resolved path before building the full `Callable`. -enum ResolvedPath { - FreeFunction(FreeFunctionPath), - InherentMethod(InherentMethodPath), - TraitMethod(TraitMethodPath), -} - -/// Convert a canonical `FQPath` and the resolved `CallableItem` into a structured path. -fn fq_path_to_resolved_path( - canonical_path: &FQPath, - callable_items: &CallableItem, - krate_collection: &CrateCollection, -) -> Result { - let crate_name = canonical_path.crate_name().to_owned(); - let package_id = canonical_path.package_id.clone(); - - let result = match callable_items { - CallableItem::Function(..) => { - let n = canonical_path.segments.len(); - let last = &canonical_path.segments[n - 1]; - let module_path = canonical_path.segments[1..n - 1] - .iter() - .map(|s| s.ident.clone()) - .collect(); - ResolvedPath::FreeFunction(FreeFunctionPath { - package_id, - crate_name, - module_path, - function_name: last.ident.clone(), - function_generics: resolve_fq_generic_args( - &last.generic_arguments, - krate_collection, - )?, - }) - } - CallableItem::Method { - qualified_self: Some(_), - method_owner, - .. - } => { - let n = canonical_path.segments.len(); - let method_segment = &canonical_path.segments[n - 1]; - let trait_segment = &canonical_path.segments[n - 2]; - let module_path = canonical_path.segments[1..n - 2] - .iter() - .map(|s| s.ident.clone()) - .collect(); - let fq_self_type = canonical_path - .qualified_self - .as_ref() - .map(|q| q.type_.clone()) - .unwrap_or_else(|| { - FQPathType::from(Type::Path(PathType { - package_id: method_owner.0.item_id.package_id.clone(), - rustdoc_id: None, - base_type: method_owner - .1 - .segments - .iter() - .map(|s| s.ident.clone()) - .collect(), - generic_arguments: vec![], - })) - }); - let self_type = resolve_fq_path_type(&fq_self_type, krate_collection)?; - ResolvedPath::TraitMethod(TraitMethodPath { - package_id, - crate_name, - module_path, - trait_name: trait_segment.ident.clone(), - trait_generics: resolve_fq_generic_args( - &trait_segment.generic_arguments, - krate_collection, - )?, - self_type, - method_name: method_segment.ident.clone(), - method_generics: resolve_fq_generic_args( - &method_segment.generic_arguments, - krate_collection, - )?, - }) - } - CallableItem::Method { .. } => { - let n = canonical_path.segments.len(); - let method_segment = &canonical_path.segments[n - 1]; - let type_segment = &canonical_path.segments[n - 2]; - let module_path = canonical_path.segments[1..n - 2] - .iter() - .map(|s| s.ident.clone()) - .collect(); - ResolvedPath::InherentMethod(InherentMethodPath { - package_id, - crate_name, - module_path, - type_name: type_segment.ident.clone(), - type_generics: resolve_fq_generic_args( - &type_segment.generic_arguments, - krate_collection, - )?, - method_name: method_segment.ident.clone(), - method_generics: resolve_fq_generic_args( - &method_segment.generic_arguments, - krate_collection, - )?, - }) - } - }; - Ok(result) -} - -fn get_trait_generic_bindings( - resolved_item: &ResolvedItem, - path: &FQPath, - krate_collection: &CrateCollection, - generic_bindings: &mut GenericBindings, -) -> Result<(), anyhow::Error> { - let inner = &resolved_item.item.inner; - let ItemEnum::Trait(trait_item) = inner else { - unreachable!() - }; - // TODO: handle defaults - for (generic_slot, assigned_parameter) in trait_item - .generics - .params - .iter() - .zip(path.segments.last().unwrap().generic_arguments.iter()) - { - if let FQGenericArgument::Type(t) = assigned_parameter { - // TODO: handle conflicts - generic_bindings.types.insert( - generic_slot.name.clone(), - resolve_fq_path_type(t, krate_collection)?, - ); - } - } - Ok(()) -} - -pub(crate) fn resolve_type_path( - path: &FQPath, - krate_collection: &CrateCollection, -) -> Result { - fn _resolve_type_path( - path: &FQPath, - krate_collection: &CrateCollection, - ) -> Result { - let item = find_rustdoc_item_type(path, krate_collection)?.1; - resolve_type_path_with_item(path, &item, krate_collection) - } - - _resolve_type_path(path, krate_collection).map_err(|source| TypePathResolutionError { - path: path.clone(), - source, - }) -} - -pub(crate) fn resolve_type_path_with_item( - path: &FQPath, - resolved_item: &ResolvedItem, - krate_collection: &CrateCollection, -) -> Result { - let item = &resolved_item.item; - let used_by_package_id = resolved_item.item_id.package_id(); - let (global_type_id, base_type) = - krate_collection.get_canonical_path_by_local_type_id(used_by_package_id, &item.id, None)?; - let mut generic_arguments = vec![]; - let (last_segment, first_segments) = path.segments.split_last().unwrap(); - for segment in first_segments { - for generic_path in &segment.generic_arguments { - let arg = match generic_path { - FQGenericArgument::Type(t) => { - GenericArgument::TypeParameter(resolve_fq_path_type(t, krate_collection)?) - } - FQGenericArgument::Lifetime(l) => GenericArgument::Lifetime(l.clone().into()), - }; - generic_arguments.push(arg); - } - } - // Some generic parameters might not be explicitly specified in the path, so we need to - // look at the definition of the type to take them into account. - let generic_defs = match &resolved_item.item.inner { - ItemEnum::Struct(s) => &s.generics.params, - ItemEnum::Enum(e) => &e.generics.params, - ItemEnum::Trait(t) => &t.generics.params, - _ => unreachable!(), - }; - for (i, generic_def) in generic_defs.iter().enumerate() { - let arg = if let Some(generic_path) = last_segment.generic_arguments.get(i) { - match generic_path { - FQGenericArgument::Type(t) => { - GenericArgument::TypeParameter(resolve_fq_path_type(t, krate_collection)?) - } - FQGenericArgument::Lifetime(l) => GenericArgument::Lifetime(l.clone().into()), - } - } else { - match &generic_def.kind { - GenericParamDefKind::Lifetime { .. } => GenericArgument::Lifetime( - GenericLifetimeParameter::from_name(&generic_def.name), - ), - GenericParamDefKind::Type { default, .. } => { - if let Some(default) = default { - let default = resolve_type( - default, - used_by_package_id, - krate_collection, - &GenericBindings::default(), - )?; - if skip_default(krate_collection, &default) { - continue; - } - GenericArgument::TypeParameter(default) - } else { - GenericArgument::TypeParameter(Type::Generic(Generic { - name: generic_def.name.clone(), - })) - } - } - GenericParamDefKind::Const { .. } => { - unimplemented!("const generic parameters are not supported yet") - } - } - }; - generic_arguments.push(arg); - } - Ok(PathType { - package_id: global_type_id.package_id().to_owned(), - rustdoc_id: Some(global_type_id.rustdoc_item_id), - base_type: base_type.to_vec(), - generic_arguments, - } - .into()) -} - -/// This is a gigantic hack to work around an issue with `std`'s collections: -/// they are all generic over the allocator type, but the default (`alloc::alloc::Global`) -/// is a nightly-only type. -/// If you spell it out, the code won't compile on stable, even though it does -/// exactly the same thing as omitting the parameter. -fn skip_default(krate_collection: &CrateCollection, default: &Type) -> bool { - use once_cell::sync::OnceCell; - static GLOBAL_ALLOCATOR: OnceCell = OnceCell::new(); - - let alloc = GLOBAL_ALLOCATOR - .get_or_init(|| super::utils::resolve_type_path("alloc::alloc::Global", krate_collection)); - alloc == default -} - #[derive(thiserror::Error, Debug, Clone)] pub(crate) enum CallableResolutionError { - #[error(transparent)] - UnsupportedCallableKind(#[from] UnsupportedCallableKind), - #[error(transparent)] - UnknownCallable(#[from] UnknownPath), - #[error(transparent)] - GenericParameterResolutionError(#[from] GenericParameterResolutionError), #[error(transparent)] SelfResolutionError(#[from] SelfResolutionError), #[error(transparent)] @@ -612,32 +36,3 @@ impl From for CallableResolutionError } } } - -#[derive(Debug, thiserror::Error)] -#[error("I can't resolve `{path}` to a type.")] -pub(crate) struct TypePathResolutionError { - path: FQPath, - #[source] - pub source: anyhow::Error, -} - -#[derive(Debug, thiserror::Error, Clone)] -#[error( - "I can work with functions and methods, but `{import_path}` is neither.\nIt is {item_kind} and I don't know how to handle it here." -)] -pub(crate) struct UnsupportedCallableKind { - pub import_path: FQPath, - pub item_kind: String, -} - -#[derive(Debug, thiserror::Error, Clone)] -#[error( - "I can't handle `{generic_type}`, one of the generic parameters you specified for `{callable_path}`." -)] -pub(crate) struct GenericParameterResolutionError { - pub callable_path: FQPath, - pub callable_item: rustdoc_types::Item, - pub generic_type: FQPathType, - #[source] - pub source: Arc, -} diff --git a/compiler/pavexc/src/compiler/utils.rs b/compiler/pavexc/src/compiler/utils.rs index cff6e087c..1431a2a3c 100644 --- a/compiler/pavexc/src/compiler/utils.rs +++ b/compiler/pavexc/src/compiler/utils.rs @@ -1,10 +1,14 @@ -use guppy::graph::PackageGraph; +use std::ops::Deref; -use pavex_bp_schema::CreatedAt; +use guppy::PackageId; +use rustdoc_types::ItemEnum; -use crate::compiler::resolvers::resolve_callable; -use crate::language::{Callable, GenericArgument, PathKind, RawIdentifiers, Type, parse_fq_path}; +use crate::language::{Callable, GenericArgument, PathType, Type}; use crate::rustdoc::CrateCollection; +use rustdoc_ext::GlobalItemId; +use rustdoc_ir::{CallableInput, FnHeader, RustIdentifier, TraitMethod, TraitMethodPath}; +use rustdoc_processor::queries::Crate; +use rustdoc_resolver::{GenericBindings, resolve_type}; use super::app::PAVEX_VERSION; @@ -30,47 +34,367 @@ pub(crate) fn get_err_variant(t: &Type) -> &Type { t } -/// Resolve a type path assuming that the crate it belongs to is either -/// a toolchain crate (i.e. `core`/`std`/`alloc`) or a direct dependency -/// of `pavex`. +/// Get the `PackageId` for the `pavex` crate. +fn pavex_package_id(krate_collection: &CrateCollection) -> PackageId { + crate::language::krate2package_id("pavex", PAVEX_VERSION, krate_collection.package_graph()) + .expect("Failed to find pavex in the package graph") +} + +/// Get the `Crate` for the `pavex` crate. +fn pavex_crate(krate_collection: &CrateCollection) -> &Crate { + let package_id = pavex_package_id(krate_collection); + krate_collection + .get_or_compute(&package_id) + .expect("Failed to compute pavex crate docs") +} + +/// Strip generic arguments from a Rust path string. +/// +/// E.g. `"pavex::request::path::RawPathParams::<'server, 'request>"` → +/// `["pavex", "request", "path", "RawPathParams"]` +fn strip_generics_from_path(raw_path: &str) -> Vec { + // Remove everything from the first '<' onwards, then split on '::' + let base = if let Some(idx) = raw_path.find('<') { + // Also strip a trailing `::` before `<` if present (turbofish syntax) + let before = &raw_path[..idx]; + before.trim_end_matches("::") + } else { + raw_path + }; + base.split("::").map(|s| s.to_owned()).collect() +} + +/// Resolve a type by looking it up directly in rustdoc. +/// +/// The first segment must be a crate name that is either a toolchain crate or +/// a direct dependency of `pavex`. +/// +/// Generic arguments in the path (e.g. `Foo::<'a, T>`) are stripped for the +/// rustdoc lookup; the resolved type's generics come from the type definition. pub(crate) fn resolve_type_path(raw_path: &str, krate_collection: &CrateCollection) -> Type { - let identifiers = RawIdentifiers { - import_path: raw_path.into(), - created_at: CreatedAt { - // We are relying on a little hack to anchor our search: - // all framework types belong to crates that are direct dependencies of `pavex`. - // TODO: find a better way in the future. - package_name: "pavex".to_owned(), - package_version: PAVEX_VERSION.to_owned(), - }, + // Strip generic arguments from the path for rustdoc lookup. + // E.g. "pavex::request::path::RawPathParams::<'server, 'request>" + // becomes ["pavex", "request", "path", "RawPathParams"] + let segments: Vec = strip_generics_from_path(raw_path); + let crate_name = &segments[0]; + + // Get the crate for the first segment + let package_id = if crate_name == "pavex" { + pavex_package_id(krate_collection) + } else { + // For toolchain crates or other dependencies, resolve via the package graph + // using pavex as the anchor (since these are direct dependencies of pavex) + let pavex_pid = pavex_package_id(krate_collection); + crate::language::dependency_name2package_id( + crate_name, + &pavex_pid, + krate_collection.package_graph(), + ) + .unwrap_or_else(|_| panic!("Failed to find crate `{crate_name}` in the package graph")) + }; + + let krate = krate_collection + .get_or_compute(&package_id) + .expect("Failed to compute crate docs"); + + let global_id = krate + .get_item_id_by_path(&segments, krate_collection) + .expect("Failed to get item by path") + .unwrap_or_else(|e| panic!("Unknown path: {e:?}")); + + let (canonical_global_id, base_type) = krate_collection + .get_canonical_path_by_local_type_id( + &global_id.package_id, + &global_id.rustdoc_item_id, + None, + ) + .expect("Failed to get canonical path"); + + let item = krate_collection.get_item_by_global_type_id(&global_id); + let generic_defs = match &item.inner { + ItemEnum::Struct(s) => &s.generics.params, + ItemEnum::Enum(e) => &e.generics.params, + ItemEnum::Trait(t) => &t.generics.params, + _ => { + // No generics for primitives, etc. + return PathType { + package_id: canonical_global_id.package_id().to_owned(), + rustdoc_id: Some(canonical_global_id.rustdoc_item_id), + base_type: base_type.to_vec(), + generic_arguments: vec![], + } + .into(); + } }; - let path = parse_fq_path( - &identifiers, - krate_collection.package_graph(), - PathKind::Type, - ) - .unwrap(); - super::resolvers::resolve_type_path(&path, krate_collection).unwrap() + + let mut generic_arguments = vec![]; + for generic_def in generic_defs { + let arg = match &generic_def.kind { + rustdoc_types::GenericParamDefKind::Lifetime { .. } => { + GenericArgument::Lifetime(rustdoc_ir::GenericLifetimeParameter::from_name( + &generic_def.name, + )) + } + rustdoc_types::GenericParamDefKind::Type { default, .. } => { + if let Some(default) = default { + let default = resolve_type( + default, + &global_id.package_id, + krate_collection, + &GenericBindings::default(), + ) + .expect("Failed to resolve default generic type"); + GenericArgument::TypeParameter(default) + } else { + GenericArgument::TypeParameter(Type::Generic(rustdoc_ir::Generic { + name: generic_def.name.clone(), + })) + } + } + rustdoc_types::GenericParamDefKind::Const { .. } => { + unimplemented!("const generic parameters are not supported yet") + } + }; + generic_arguments.push(arg); + } + + PathType { + package_id: canonical_global_id.package_id().to_owned(), + rustdoc_id: Some(canonical_global_id.rustdoc_item_id), + base_type: base_type.to_vec(), + generic_arguments, + } + .into() } -/// Resolve a callable path assuming that the crate is a dependency of `pavex`. -pub(crate) fn resolve_framework_callable_path( - raw_path: &str, - package_graph: &PackageGraph, +/// Resolve a free function from pavex's rustdoc by its path segments. +/// +/// E.g. `resolve_framework_free_function(&["pavex", "Error", "to_response"], krate_collection)`. +pub(crate) fn resolve_framework_free_function( + path_segments: &[&str], krate_collection: &CrateCollection, ) -> Callable { - let identifiers = RawIdentifiers { - import_path: raw_path.into(), - created_at: CreatedAt { - // We are relying on a little hack to anchor our search: - // all framework types belong to crates that are direct dependencies of `pavex`. - // TODO: find a better way in the future. - package_name: "pavex".to_owned(), - package_version: PAVEX_VERSION.to_owned(), - }, + let krate = pavex_crate(krate_collection); + let segments: Vec = path_segments.iter().map(|s| s.to_string()).collect(); + + let global_id = krate + .get_item_id_by_path(&segments, krate_collection) + .expect("Failed to look up free function path") + .unwrap_or_else(|e| panic!("Unknown free function path {}: {e:?}", segments.join("::"))); + + let item = krate.get_item_by_local_type_id(&global_id.rustdoc_item_id); + let free_fn = rustdoc_resolver::resolve_free_function(&item, krate, krate_collection) + .expect("Failed to resolve free function"); + Callable::FreeFunction(free_fn) +} + +/// Resolve an inherent method from pavex's rustdoc. +/// +/// `type_path` is the path to the type (e.g. `["pavex", "middleware", "Next"]`). +/// `method_name` is the method name (e.g. `"new"`). +pub(crate) fn resolve_framework_inherent_method( + type_path: &[&str], + method_name: &str, + krate_collection: &CrateCollection, +) -> Callable { + let krate = pavex_crate(krate_collection); + let segments: Vec = type_path.iter().map(|s| s.to_string()).collect(); + + let type_global_id = krate + .get_item_id_by_path(&segments, krate_collection) + .expect("Failed to look up type path") + .unwrap_or_else(|e| panic!("Unknown type path {}: {e:?}", segments.join("::"))); + + let type_item = krate.get_item_by_local_type_id(&type_global_id.rustdoc_item_id); + + // Find the method within the type's impl blocks + let impls = match &type_item.inner { + ItemEnum::Struct(s) => &s.impls, + ItemEnum::Enum(e) => &e.impls, + _ => panic!("Expected a struct or enum for inherent method lookup"), + }; + + for impl_id in impls { + let impl_item = krate.get_item_by_local_type_id(impl_id); + let ItemEnum::Impl(impl_block) = &impl_item.inner else { + continue; + }; + // Only look at inherent impls (no trait) + if impl_block.trait_.is_some() { + continue; + } + for item_id in &impl_block.items { + let item = krate.get_item_by_local_type_id(item_id); + if item.name.as_deref() == Some(method_name) + && matches!(item.inner, ItemEnum::Function(_)) + { + let callable = rustdoc_resolver::rustdoc_method2callable( + type_global_id.rustdoc_item_id, + *impl_id, + &item, + krate, + krate_collection, + ) + .expect("Failed to resolve inherent method"); + return callable; + } + } + } + + panic!( + "Could not find inherent method `{method_name}` on `{}`", + segments.join("::") + ); +} + +/// Resolve a trait method callable for a specific `Self` type. +/// +/// This is used for the `IntoResponse::into_response` case, where we need to +/// resolve the trait method with `Self` bound to a concrete output type. +/// +/// `trait_path` is the path to the trait (e.g. `["pavex", "IntoResponse"]`). +/// `method_name` is the method name (e.g. `"into_response"`). +/// `self_type` is the concrete type to bind to `Self`. +pub(crate) fn resolve_framework_trait_method( + trait_path: &[&str], + method_name: &str, + self_type: Type, + krate_collection: &CrateCollection, +) -> Result { + let krate = pavex_crate(krate_collection); + let segments: Vec = trait_path.iter().map(|s| s.to_string()).collect(); + + let trait_global_id = krate + .get_item_id_by_path(&segments, krate_collection)? + .map_err(|e| anyhow::anyhow!("Unknown trait path {}: {e:?}", segments.join("::")))?; + + let trait_item = krate.get_item_by_local_type_id(&trait_global_id.rustdoc_item_id); + let ItemEnum::Trait(trait_def) = &trait_item.inner else { + anyhow::bail!("Expected a trait item for {}", segments.join("::")); }; - let path = parse_fq_path(&identifiers, package_graph, PathKind::Callable).unwrap(); - resolve_callable(krate_collection, &path).unwrap() + + // Find the method item within the trait's items + let (method_item_id, method_item) = trait_def + .items + .iter() + .find_map(|item_id| { + let item = krate.get_item_by_local_type_id(item_id); + if item.name.as_deref() == Some(method_name) + && matches!(item.inner, ItemEnum::Function(_)) + { + Some((*item_id, item)) + } else { + None + } + }) + .ok_or_else(|| { + anyhow::anyhow!( + "Could not find method `{method_name}` in trait `{}`", + segments.join("::") + ) + })?; + + let ItemEnum::Function(fn_item) = &method_item.inner else { + unreachable!() + }; + + let mut generic_bindings = GenericBindings::default(); + generic_bindings + .types + .insert("Self".to_string(), self_type.clone()); + + // Resolve inputs + let mut inputs = Vec::new(); + let mut takes_self_as_ref = false; + for (parameter_index, (param_name, parameter_type)) in fn_item.sig.inputs.iter().enumerate() { + if parameter_index == 0 { + if let rustdoc_types::Type::BorrowedRef { type_, .. } = parameter_type + && let rustdoc_types::Type::Generic(g) = type_.deref() + && g == "Self" + { + takes_self_as_ref = true; + } + } + let resolved = resolve_type( + parameter_type, + &krate.core.package_id, + krate_collection, + &generic_bindings, + ) + .map_err(|e| { + anyhow::anyhow!( + "Failed to resolve parameter {parameter_index} of trait method `{method_name}`: {e}" + ) + })?; + inputs.push(CallableInput { + name: RustIdentifier::new(param_name.clone()), + type_: resolved, + }); + } + + // Resolve output + let output = fn_item + .sig + .output + .as_ref() + .map(|output_ty| { + resolve_type( + output_ty, + &krate.core.package_id, + krate_collection, + &generic_bindings, + ) + .map_err(|e| { + anyhow::anyhow!( + "Failed to resolve output type of trait method `{method_name}`: {e}" + ) + }) + }) + .transpose()?; + + let symbol_name = method_item.attrs.iter().find_map(|attr| match attr { + rustdoc_types::Attribute::NoMangle => method_item.name.clone(), + rustdoc_types::Attribute::ExportName(name) => Some(name.clone()), + _ => None, + }); + + // Build canonical path for the trait + let (canonical_trait_id, canonical_trait_path) = krate_collection + .get_canonical_path_by_local_type_id( + &trait_global_id.package_id, + &trait_global_id.rustdoc_item_id, + None, + )?; + + let n = canonical_trait_path.len(); + let path = TraitMethodPath { + package_id: canonical_trait_id.package_id().to_owned(), + crate_name: canonical_trait_path[0].clone(), + module_path: canonical_trait_path[1..n - 1].to_vec(), + trait_name: canonical_trait_path[n - 1].clone(), + trait_generics: vec![], + self_type, + method_name: method_name.to_owned(), + method_generics: vec![], + }; + + Ok(Callable::TraitMethod(TraitMethod { + path, + header: FnHeader { + output, + inputs, + is_async: fn_item.header.is_async, + abi: fn_item.header.abi.clone(), + is_unsafe: fn_item.header.is_unsafe, + is_c_variadic: fn_item.sig.is_c_variadic, + symbol_name, + }, + source_coordinates: Some(GlobalItemId { + rustdoc_item_id: method_item_id, + package_id: krate.core.package_id.clone(), + }), + takes_self_as_ref, + })) } /// A generator of unique lifetime names. diff --git a/compiler/pavexc/src/language/callable_path.rs b/compiler/pavexc/src/language/callable_path.rs deleted file mode 100644 index 99af2b454..000000000 --- a/compiler/pavexc/src/language/callable_path.rs +++ /dev/null @@ -1,324 +0,0 @@ -use std::fmt::{Display, Formatter}; - -use syn::{ExprPath, GenericArgument, PathArguments, Type, TypePath}; - -use super::Lifetime; -use super::RawIdentifiers; - -/// A path that can be used in expression position (i.e. to refer to a function or a static method). -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub(crate) struct CallPath { - pub has_leading_colon: bool, - pub qualified_self: Option, - pub segments: Vec, -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub(crate) struct CallPathQualifiedSelf { - pub position: usize, - pub type_: CallPathType, -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub(crate) enum CallPathType { - ResolvedPath(CallPathResolvedPathType), - Reference(CallPathReference), - Tuple(CallPathTuple), - Slice(CallPathSlice), -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub(crate) struct CallPathResolvedPathType { - pub path: Box, -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub(crate) struct CallPathSlice { - pub element_type: Box, -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub(crate) struct CallPathReference { - pub is_mutable: bool, - pub lifetime: Lifetime, - pub inner: Box, -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub(crate) struct CallPathTuple { - pub elements: Vec, -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub(crate) struct CallPathSegment { - pub ident: syn::Ident, - pub generic_arguments: Vec, -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub(crate) enum CallPathGenericArgument { - Type(CallPathType), - Lifetime(CallPathLifetime), -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub(crate) enum CallPathLifetime { - Static, - Named(String), -} - -impl CallPathLifetime { - fn new(l: String) -> Self { - match l.trim_start_matches('\'') { - "static" => Self::Static, - other => Self::Named(other.to_owned()), - } - } -} - -impl CallPath { - pub fn parse_type_path(identifiers: &RawIdentifiers) -> Result { - let callable_path: TypePath = - syn::parse_str(&identifiers.import_path).map_err(|e| InvalidCallPath { - raw_path: identifiers.import_path.to_owned(), - parsing_error: e, - })?; - Self::parse_from_path(callable_path.path, None) - } - - pub fn parse_callable_path(identifiers: &RawIdentifiers) -> Result { - let expr = - syn::parse_str::(&identifiers.import_path).map_err(|e| InvalidCallPath { - raw_path: identifiers.import_path.to_owned(), - parsing_error: e, - })?; - Self::parse_from_path(expr.path, expr.qself) - } - - fn parse_qself(qself: syn::QSelf) -> Result { - Ok(CallPathQualifiedSelf { - position: qself.position, - type_: Self::parse_type(*qself.ty)?, - }) - } - - fn parse_type(type_: Type) -> Result { - match type_ { - Type::Path(p) => { - let call_path = Self::parse_from_path(p.path, p.qself)?; - Ok(CallPathType::ResolvedPath(CallPathResolvedPathType { - path: Box::new(call_path), - })) - } - Type::Reference(r) => { - let is_mutable = r.mutability.is_some(); - let inner = Box::new(Self::parse_type(*r.elem)?); - Ok(CallPathType::Reference(CallPathReference { - is_mutable, - lifetime: r.lifetime.map(|l| l.ident.to_string()).into(), - inner, - })) - } - Type::Tuple(t) => { - let mut elements = Vec::with_capacity(t.elems.len()); - for element in t.elems { - elements.push(Self::parse_type(element)?) - } - Ok(CallPathType::Tuple(CallPathTuple { elements })) - } - Type::Slice(s) => { - let element_type = Box::new(Self::parse_type(s.elem.as_ref().to_owned())?); - Ok(CallPathType::Slice(CallPathSlice { element_type })) - } - _ => todo!("We don't handle {:?} as a type yet", type_), - } - } - - pub(crate) fn parse_from_path( - path: syn::Path, - qualified_self: Option, - ) -> Result { - let has_leading_colon = path.leading_colon.is_some(); - let mut segments = Vec::with_capacity(path.segments.len()); - for syn_segment in path.segments { - let generic_arguments = match syn_segment.arguments { - PathArguments::None => vec![], - PathArguments::AngleBracketed(syn_arguments) => { - let mut arguments = Vec::with_capacity(syn_arguments.args.len()); - for syn_argument in syn_arguments.args { - let argument = match syn_argument { - GenericArgument::Type(t) => { - CallPathGenericArgument::Type(Self::parse_type(t)?) - } - GenericArgument::Lifetime(l) => CallPathGenericArgument::Lifetime( - CallPathLifetime::new(l.ident.to_string()), - ), - GenericArgument::AssocType(_) - | GenericArgument::AssocConst(_) - | GenericArgument::Constraint(_) - | GenericArgument::Const(_) => todo!( - "We can only handle concrete types and lifetimes as generic parameters for the time being." - ), - a => todo!("Unknown class of generic arguments: {:?}", a), - }; - arguments.push(argument) - } - arguments - } - PathArguments::Parenthesized(_) => { - todo!("We don't handle paranthesized generic parameters") - } - }; - let segment = CallPathSegment { - ident: syn_segment.ident, - generic_arguments, - }; - segments.push(segment) - } - - let qualified_self = match qualified_self { - Some(qself) => Some(Self::parse_qself(qself)?), - _ => None, - }; - Ok(Self { - has_leading_colon, - qualified_self, - segments, - }) - } - - /// Return the first segment in the path. - /// - /// E.g. `my_crate::my_module::MyType` will return `my_crate` while `my_module::MyType` will - /// return `my_module`. - pub fn leading_path_segment(&self) -> &proc_macro2::Ident { - // This unwrap never fails thanks to the validation done in `parse` - &self.segments.first().unwrap().ident - } -} - -impl Display for CallPathType { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - CallPathType::ResolvedPath(p) => { - write!(f, "{p}")?; - } - CallPathType::Reference(r) => { - write!(f, "{r}")?; - } - CallPathType::Tuple(t) => { - write!(f, "{t}")?; - } - CallPathType::Slice(s) => { - write!(f, "{s}")?; - } - } - Ok(()) - } -} - -impl Display for CallPathSlice { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "[{}]", self.element_type) - } -} - -impl Display for CallPathTuple { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "(")?; - let last_argument_index = self.elements.len().saturating_sub(1); - for (i, element) in self.elements.iter().enumerate() { - write!(f, "{element}")?; - if i != last_argument_index { - write!(f, ", ")?; - } - } - write!(f, ")") - } -} - -impl Display for CallPathReference { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "&{}{}", - if self.is_mutable { "mut " } else { "" }, - self.inner - ) - } -} - -impl Display for CallPathResolvedPathType { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.path) - } -} - -impl Display for CallPath { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let mut qself_closing_wedge_index = None; - if let Some(qself) = &self.qualified_self { - write!(f, "<{} as ", qself.type_)?; - qself_closing_wedge_index = Some(qself.position); - } - if self.has_leading_colon { - write!(f, "::")?; - } - let last_segment_index = self.segments.len().saturating_sub(1); - for (i, segment) in self.segments.iter().enumerate() { - write!(f, "{segment}")?; - if Some(i) == qself_closing_wedge_index { - write!(f, ">")?; - } - if i != last_segment_index { - write!(f, "::")?; - } - } - Ok(()) - } -} - -impl Display for CallPathSegment { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.ident)?; - let last_argument_index = self.generic_arguments.len().saturating_sub(1); - for (j, argument) in self.generic_arguments.iter().enumerate() { - write!(f, "{argument}")?; - if j != last_argument_index { - write!(f, ", ")?; - } - } - Ok(()) - } -} - -impl Display for CallPathGenericArgument { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - CallPathGenericArgument::Type(t) => { - write!(f, "{t}")?; - } - CallPathGenericArgument::Lifetime(l) => { - write!(f, "{l}")?; - } - } - Ok(()) - } -} - -impl Display for CallPathLifetime { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - CallPathLifetime::Static => write!(f, "'static"), - CallPathLifetime::Named(name) => write!(f, "'{name}"), - } - } -} - -#[derive(Debug, thiserror::Error, Clone)] -#[error("`{raw_path}` is not a valid import path.")] -pub struct InvalidCallPath { - pub(crate) raw_path: String, - #[source] - pub(crate) parsing_error: syn::Error, -} diff --git a/compiler/pavexc/src/language/fq_path.rs b/compiler/pavexc/src/language/fq_path.rs deleted file mode 100644 index d8d09ece2..000000000 --- a/compiler/pavexc/src/language/fq_path.rs +++ /dev/null @@ -1,761 +0,0 @@ -use std::fmt::Write; -use std::fmt::{Display, Formatter}; -use std::hash::{Hash, Hasher}; - -use anyhow::Context; -use bimap::BiHashMap; -use guppy::PackageId; -use itertools::Itertools; - -use rustdoc_ir::function_pointer::write_fn_pointer_prefix; - -use crate::language::Type; -use crate::language::resolved_type::{GenericArgument, Lifetime, ScalarPrimitive}; - -use super::resolved_type::{GenericLifetimeParameter, NamedLifetime}; - -/// A fully-qualified import path. -/// -/// What does "fully qualified" mean in this contest? -/// -/// `FQPath` ensures that all paths are "fully qualified"—i.e. -/// the first path segment is either the name of the current package or the name of a -/// crate listed as a dependency of the current package. -/// -/// It also performs basic normalization. -/// There are ways, in Rust, to have different paths pointing at the same type. -/// -/// E.g. `crate_name::TypeName` and `::crate_name::TypeName` are equivalent when `crate_name` -/// is a third-party dependency of your project. `ResolvedPath` reduces those two different -/// representations to a canonical one, allowing for deduplication. -/// -/// Another common scenario: dependency renaming. -/// `crate_name::TypeName` and `renamed_crate_name::TypeName` can be equivalent if `crate_name` -/// has been renamed to `renamed_crate_name` in the `Cargo.toml` of the package that declares/uses -/// the path. `FQPath` takes this into account by using the `PackageId` of the target -/// crate as the authoritative answer to "What crate does this path belong to?". This is unique -/// and well-defined within a `cargo` workspace. -#[derive(Clone, Debug, Eq)] -pub struct FQPath { - 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. - /// - /// For trait methods, it must be set to the package id of the crate where the - /// trait is defined. - pub package_id: PackageId, -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub struct FQQualifiedSelf { - pub position: usize, - pub type_: FQPathType, -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub struct FQPathSegment { - pub ident: String, - pub generic_arguments: Vec, -} - -impl FQPathSegment { - /// Create a new segment without generic arguments. - pub fn new(ident: String) -> Self { - Self { - ident, - generic_arguments: Vec::new(), - } - } -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub enum FQGenericArgument { - Type(FQPathType), - Lifetime(ResolvedPathLifetime), -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub enum ResolvedPathLifetime { - Static, - Named(NamedLifetime), - Inferred, -} - -impl ResolvedPathLifetime { - /// Construct from a lifetime name, with or without the leading `'`. - /// - /// Routes `"_"` → `Inferred`, `"static"` → `Static`, everything else → `Named`. - /// Returns the lifetime name without the leading `'`, suitable for use as a generic binding key. - pub fn to_binding_name(&self) -> String { - match self { - ResolvedPathLifetime::Named(n) => n.as_str().to_owned(), - ResolvedPathLifetime::Static => "static".to_owned(), - ResolvedPathLifetime::Inferred => "_".to_owned(), - } - } - - /// Construct from a lifetime name, with or without the leading `'`. - /// - /// Routes `"_"` → `Inferred`, `"static"` → `Static`, everything else → `Named`. - pub fn from_name(name: impl Into) -> Self { - let mut name = name.into(); - if let Some(stripped) = name.strip_prefix('\'') { - name = stripped.to_owned(); - } - match name.as_str() { - "_" => ResolvedPathLifetime::Inferred, - "static" => ResolvedPathLifetime::Static, - _ => ResolvedPathLifetime::Named(NamedLifetime::new(name)), - } - } -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub enum FQPathType { - ResolvedPath(FQResolvedPathType), - Reference(FQReference), - Tuple(FQTuple), - ScalarPrimitive(ScalarPrimitive), - Slice(FQSlice), - Array(FQArray), - RawPointer(FQRawPointer), - FunctionPointer(FQFunctionPointer), -} - -impl From for FQPathType { - fn from(value: Type) -> Self { - match value { - Type::Path(p) => { - let mut segments: Vec = p - .base_type - .iter() - .map(|s| FQPathSegment { - ident: s.to_string(), - generic_arguments: vec![], - }) - .collect(); - if let Some(segment) = segments.last_mut() { - segment.generic_arguments = p - .generic_arguments - .into_iter() - .map(|t| match t { - GenericArgument::TypeParameter(t) => FQGenericArgument::Type(t.into()), - GenericArgument::Lifetime(l) => FQGenericArgument::Lifetime(l.into()), - }) - .collect(); - } - FQPathType::ResolvedPath(FQResolvedPathType { - path: Box::new(FQPath { - segments, - qualified_self: None, - package_id: p.package_id, - }), - }) - } - Type::Reference(r) => FQPathType::Reference(FQReference { - is_mutable: r.is_mutable, - lifetime: r.lifetime, - inner: Box::new((*r.inner).into()), - }), - Type::Tuple(t) => FQPathType::Tuple(FQTuple { - elements: t.elements.into_iter().map(|e| e.into()).collect(), - }), - Type::ScalarPrimitive(s) => FQPathType::ScalarPrimitive(s), - Type::Slice(s) => FQPathType::Slice(FQSlice { - element: Box::new((*s.element_type).into()), - }), - Type::Array(a) => FQPathType::Array(FQArray { - element: Box::new((*a.element_type).into()), - len: a.len, - }), - Type::RawPointer(r) => FQPathType::RawPointer(FQRawPointer { - is_mutable: r.is_mutable, - inner: Box::new((*r.inner).into()), - }), - Type::FunctionPointer(fp) => FQPathType::FunctionPointer(FQFunctionPointer { - inputs: fp.inputs.into_iter().map(|t| t.into()).collect(), - output: fp.output.map(|t| Box::new((*t).into())), - abi: fp.abi, - is_unsafe: fp.is_unsafe, - }), - Type::Generic(_) => { - // ResolvedPath doesn't support unassigned generic parameters. - unreachable!("UnassignedGeneric") - } - } - } -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub struct FQResolvedPathType { - pub path: Box, -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub struct FQReference { - pub is_mutable: bool, - pub lifetime: Lifetime, - pub inner: Box, -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub struct FQTuple { - pub elements: Vec, -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub struct FQSlice { - pub element: Box, -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub struct FQArray { - pub element: Box, - pub len: usize, -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub struct FQRawPointer { - pub is_mutable: bool, - pub inner: Box, -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub struct FQFunctionPointer { - pub inputs: Vec, - pub output: Option>, - pub abi: rustdoc_types::Abi, - pub is_unsafe: bool, -} - -impl PartialEq for FQPath { - fn eq(&self, other: &Self) -> bool { - // Using destructuring syntax to make sure we get a compiler error - // if a new field gets added, as a reminder to update this Hash implementation. - let Self { - segments, - qualified_self, - package_id, - } = self; - let Self { - segments: other_segments, - qualified_self: other_qualified_self, - package_id: other_package_id, - } = other; - let is_equal = package_id == other_package_id - && segments.len() == other_segments.len() - && qualified_self == other_qualified_self; - if is_equal { - // We want to ignore the first segment of the path, because dependencies can be - // renamed and this can lead to equivalent paths not being considered equal. - // Given that we already have the package id as part of the type, it is safe - // to disregard the first segment when determining equality. - let self_segments = segments.iter().skip(1); - let other_segments = other_segments.iter().skip(1); - for (self_segment, other_segment) in self_segments.zip_eq(other_segments) { - if self_segment != other_segment { - return false; - } - } - true - } else { - false - } - } -} - -impl Hash for FQPath { - fn hash(&self, state: &mut H) { - // Using destructuring syntax to make sure we get a compiler error - // if a new field gets added, as a reminder to update this Hash implementation. - let Self { - segments, - qualified_self, - package_id, - } = self; - package_id.hash(state); - qualified_self.hash(state); - // We want to ignore the first segment of the path, because dependencies can be - // renamed and this can lead to equivalent paths not being considered equal. - // Given that we already have the package id as part of the type, it is safe - // to disregard the first segment when determining equality. - let self_segments = segments.iter().skip(1); - for segment in self_segments { - segment.hash(state) - } - } -} - -impl FQPath { - /// Return the name of the crate that this type belongs to. - /// - /// E.g. `my_crate::my_module::MyType` will return `my_crate`. - pub fn crate_name(&self) -> &str { - // This unwrap never fails thanks to the validation done in `parse` - &self.segments.first().unwrap().ident - } - - pub fn render_path(&self, id2name: &BiHashMap, buffer: &mut String) { - let crate_name = id2name - .get_by_left(&self.package_id) - .with_context(|| { - format!( - "The package id '{}' is missing from the id<>name mapping for crates.", - self.package_id - ) - }) - .unwrap(); - let mut qself_closing_wedge_index = None; - if let Some(qself) = &self.qualified_self { - write!(buffer, "<").unwrap(); - qself.type_.render_path(id2name, buffer); - write!(buffer, " as ",).unwrap(); - qself_closing_wedge_index = Some(qself.position.saturating_sub(1)); - } - write!(buffer, "{crate_name}").unwrap(); - for (index, path_segment) in self.segments[1..].iter().enumerate() { - write!(buffer, "::{}", path_segment.ident).unwrap(); - let generic_arguments = &path_segment.generic_arguments; - if !generic_arguments.is_empty() { - write!(buffer, "::<").unwrap(); - let mut arguments = generic_arguments.iter().peekable(); - while let Some(argument) = arguments.next() { - argument.render_path(id2name, buffer); - if arguments.peek().is_some() { - write!(buffer, ", ").unwrap(); - } - } - write!(buffer, ">").unwrap(); - } - if Some(index + 1) == qself_closing_wedge_index { - write!(buffer, ">").unwrap(); - } - } - } - - /// A utility method to render the path for usage in error messages. - /// - /// It doesn't require a "package_id <> name" mapping. - pub fn render_for_error(&self, buffer: &mut String) { - let mut qself_closing_wedge_index = None; - if let Some(qself) = &self.qualified_self { - write!(buffer, "<").unwrap(); - qself.type_.render_for_error(buffer); - write!(buffer, " as ",).unwrap(); - qself_closing_wedge_index = Some(qself.position.saturating_sub(1)); - } - for (index, path_segment) in self.segments.iter().enumerate() { - if index != 0 { - buffer.push_str("::"); - } - buffer.push_str(&path_segment.ident); - let generic_arguments = &path_segment.generic_arguments; - if !generic_arguments.is_empty() { - write!(buffer, "::<").unwrap(); - let mut arguments = generic_arguments.iter().peekable(); - while let Some(argument) = arguments.next() { - argument.render_for_error(buffer); - if arguments.peek().is_some() { - write!(buffer, ", ").unwrap(); - } - } - write!(buffer, ">").unwrap(); - } - if Some(index + 1) == qself_closing_wedge_index { - write!(buffer, ">").unwrap(); - } - } - } -} - -impl FQPathType { - pub fn render_path(&self, id2name: &BiHashMap, buffer: &mut String) { - match self { - FQPathType::ResolvedPath(p) => p.render_path(id2name, buffer), - FQPathType::Reference(r) => r.render_path(id2name, buffer), - FQPathType::Tuple(t) => t.render_path(id2name, buffer), - FQPathType::ScalarPrimitive(s) => { - write!(buffer, "{s}").unwrap(); - } - FQPathType::Slice(s) => s.render_path(id2name, buffer), - FQPathType::Array(a) => a.render_path(id2name, buffer), - FQPathType::RawPointer(r) => r.render_path(id2name, buffer), - FQPathType::FunctionPointer(fp) => fp.render_path(id2name, buffer), - } - } - - pub fn render_for_error(&self, buffer: &mut String) { - match self { - FQPathType::ResolvedPath(p) => p.render_for_error(buffer), - FQPathType::Reference(r) => r.render_for_error(buffer), - FQPathType::Tuple(t) => t.render_for_error(buffer), - FQPathType::ScalarPrimitive(s) => { - write!(buffer, "{s}").unwrap(); - } - FQPathType::Slice(s) => s.render_for_error(buffer), - FQPathType::Array(a) => a.render_for_error(buffer), - FQPathType::RawPointer(r) => r.render_for_error(buffer), - FQPathType::FunctionPointer(fp) => fp.render_for_error(buffer), - } - } -} - -impl FQSlice { - pub fn render_path(&self, id2name: &BiHashMap, buffer: &mut String) { - write!(buffer, "[").unwrap(); - self.element.render_path(id2name, buffer); - write!(buffer, "]").unwrap(); - } - - pub fn render_for_error(&self, buffer: &mut String) { - write!(buffer, "[").unwrap(); - self.element.render_for_error(buffer); - write!(buffer, "]").unwrap(); - } -} - -impl FQArray { - pub fn render_path(&self, id2name: &BiHashMap, buffer: &mut String) { - write!(buffer, "[").unwrap(); - self.element.render_path(id2name, buffer); - write!(buffer, "; {}]", self.len).unwrap(); - } - - pub fn render_for_error(&self, buffer: &mut String) { - write!(buffer, "[").unwrap(); - self.element.render_for_error(buffer); - write!(buffer, "; {}]", self.len).unwrap(); - } -} - -impl FQGenericArgument { - pub fn render_path(&self, id2name: &BiHashMap, buffer: &mut String) { - match self { - FQGenericArgument::Type(t) => { - t.render_path(id2name, buffer); - } - FQGenericArgument::Lifetime(l) => match l { - ResolvedPathLifetime::Static => { - write!(buffer, "'static").unwrap(); - } - ResolvedPathLifetime::Named(_) | ResolvedPathLifetime::Inferred => { - // TODO: we should have proper name mapping for lifetimes here, but we know all our current - // usecases will work with lifetime elision. - write!(buffer, "'_").unwrap(); - } - }, - } - } - - pub fn render_for_error(&self, buffer: &mut String) { - match self { - FQGenericArgument::Type(t) => { - t.render_for_error(buffer); - } - FQGenericArgument::Lifetime(l) => match l { - ResolvedPathLifetime::Static => { - write!(buffer, "'static").unwrap(); - } - ResolvedPathLifetime::Named(_) | ResolvedPathLifetime::Inferred => { - // TODO: we should have proper name mapping for lifetimes here, but we know all our current - // usecases will work with lifetime elision. - write!(buffer, "'_").unwrap(); - } - }, - } - } -} - -impl FQResolvedPathType { - pub fn render_path(&self, id2name: &BiHashMap, buffer: &mut String) { - self.path.render_path(id2name, buffer); - } - - pub fn render_for_error(&self, buffer: &mut String) { - self.path.render_for_error(buffer); - } -} - -impl FQTuple { - pub fn render_path(&self, id2name: &BiHashMap, buffer: &mut String) { - write!(buffer, "(").unwrap(); - let mut types = self.elements.iter().peekable(); - while let Some(ty) = types.next() { - ty.render_path(id2name, buffer); - if types.peek().is_some() { - write!(buffer, ", ").unwrap(); - } - } - write!(buffer, ")").unwrap(); - } - - pub fn render_for_error(&self, buffer: &mut String) { - write!(buffer, "(").unwrap(); - let mut types = self.elements.iter().peekable(); - while let Some(ty) = types.next() { - ty.render_for_error(buffer); - if types.peek().is_some() { - write!(buffer, ", ").unwrap(); - } - } - write!(buffer, ")").unwrap(); - } -} - -impl FQReference { - pub fn render_path(&self, id2name: &BiHashMap, buffer: &mut String) { - write!(buffer, "&").unwrap(); - if self.is_mutable { - write!(buffer, "mut ").unwrap(); - } - self.inner.render_path(id2name, buffer); - } - - pub fn render_for_error(&self, buffer: &mut String) { - write!(buffer, "&").unwrap(); - if self.is_mutable { - write!(buffer, "mut ").unwrap(); - } - self.inner.render_for_error(buffer); - } -} - -impl FQRawPointer { - pub fn render_path(&self, id2name: &BiHashMap, buffer: &mut String) { - if self.is_mutable { - write!(buffer, "*mut ").unwrap(); - } else { - write!(buffer, "*const ").unwrap(); - } - self.inner.render_path(id2name, buffer); - } - - pub fn render_for_error(&self, buffer: &mut String) { - if self.is_mutable { - write!(buffer, "*mut ").unwrap(); - } else { - write!(buffer, "*const ").unwrap(); - } - self.inner.render_for_error(buffer); - } -} - -impl FQFunctionPointer { - pub fn render_path(&self, id2name: &BiHashMap, buffer: &mut String) { - write_fn_pointer_prefix(buffer, &self.abi, self.is_unsafe).unwrap(); - write!(buffer, "fn(").unwrap(); - let mut inputs = self.inputs.iter().peekable(); - while let Some(input) = inputs.next() { - input.render_path(id2name, buffer); - if inputs.peek().is_some() { - write!(buffer, ", ").unwrap(); - } - } - write!(buffer, ")").unwrap(); - if let Some(output) = &self.output { - write!(buffer, " -> ").unwrap(); - output.render_path(id2name, buffer); - } - } - - pub fn render_for_error(&self, buffer: &mut String) { - write_fn_pointer_prefix(buffer, &self.abi, self.is_unsafe).unwrap(); - write!(buffer, "fn(").unwrap(); - let mut inputs = self.inputs.iter().peekable(); - while let Some(input) = inputs.next() { - input.render_for_error(buffer); - if inputs.peek().is_some() { - write!(buffer, ", ").unwrap(); - } - } - write!(buffer, ")").unwrap(); - if let Some(output) = &self.output { - write!(buffer, " -> ").unwrap(); - output.render_for_error(buffer); - } - } -} - -impl Display for FQPath { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let last_segment_index = self.segments.len().saturating_sub(1); - let mut qself_closing_wedge_index = None; - if let Some(qself) = &self.qualified_self { - write!(f, "<{} as ", qself.type_)?; - qself_closing_wedge_index = Some(qself.position.saturating_sub(1)) - } - for (i, segment) in self.segments.iter().enumerate() { - write!(f, "{segment}")?; - if Some(i) == qself_closing_wedge_index { - write!(f, ">")?; - } - if i != last_segment_index { - write!(f, "::")?; - } - } - Ok(()) - } -} - -impl Display for FQPathType { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - FQPathType::ResolvedPath(p) => write!(f, "{p}"), - FQPathType::Reference(r) => write!(f, "{r}"), - FQPathType::Tuple(t) => write!(f, "{t}"), - FQPathType::ScalarPrimitive(s) => { - write!(f, "{s}") - } - FQPathType::Slice(s) => { - write!(f, "{s}") - } - FQPathType::Array(a) => { - write!(f, "{a}") - } - FQPathType::RawPointer(r) => { - write!(f, "{r}") - } - FQPathType::FunctionPointer(fp) => { - write!(f, "{fp}") - } - } - } -} - -impl Display for FQSlice { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "[{}]", self.element) - } -} - -impl Display for FQArray { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "[{}; {}]", self.element, self.len) - } -} - -impl Display for FQResolvedPathType { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.path) - } -} - -impl Display for FQReference { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "&")?; - if self.is_mutable { - write!(f, "mut ")?; - } - write!(f, "{}", self.inner) - } -} - -impl Display for FQRawPointer { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - if self.is_mutable { - write!(f, "*mut ")?; - } else { - write!(f, "*const ")?; - } - write!(f, "{}", self.inner) - } -} - -impl Display for FQFunctionPointer { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write_fn_pointer_prefix(f, &self.abi, self.is_unsafe)?; - write!(f, "fn(")?; - let last_input_index = self.inputs.len().saturating_sub(1); - for (i, input) in self.inputs.iter().enumerate() { - write!(f, "{input}")?; - if i != last_input_index && !self.inputs.is_empty() { - write!(f, ", ")?; - } - } - write!(f, ")")?; - if let Some(output) = &self.output { - write!(f, " -> {output}")?; - } - Ok(()) - } -} - -impl Display for FQTuple { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "(")?; - let last_element_index = self.elements.len().saturating_sub(1); - for (i, element) in self.elements.iter().enumerate() { - write!(f, "{element}")?; - if i != last_element_index { - write!(f, ", ")?; - } - } - write!(f, ")") - } -} - -impl Display for FQPathSegment { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.ident)?; - if !self.generic_arguments.is_empty() { - write!(f, "::<")?; - } - let last_argument_index = self.generic_arguments.len().saturating_sub(1); - for (j, argument) in self.generic_arguments.iter().enumerate() { - write!(f, "{argument}")?; - if j != last_argument_index { - write!(f, ", ")?; - } - } - if !self.generic_arguments.is_empty() { - write!(f, ">")?; - } - Ok(()) - } -} - -impl Display for FQGenericArgument { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - FQGenericArgument::Type(t) => { - write!(f, "{t}") - } - FQGenericArgument::Lifetime(l) => { - write!(f, "{l}") - } - } - } -} - -impl From for GenericLifetimeParameter { - fn from(l: ResolvedPathLifetime) -> Self { - match l { - ResolvedPathLifetime::Static => GenericLifetimeParameter::Static, - ResolvedPathLifetime::Named(n) => GenericLifetimeParameter::Named(n), - ResolvedPathLifetime::Inferred => GenericLifetimeParameter::Inferred, - } - } -} - -impl From for ResolvedPathLifetime { - fn from(l: GenericLifetimeParameter) -> Self { - match l { - GenericLifetimeParameter::Static => ResolvedPathLifetime::Static, - GenericLifetimeParameter::Named(n) => ResolvedPathLifetime::Named(n), - GenericLifetimeParameter::Inferred => ResolvedPathLifetime::Inferred, - } - } -} - -impl Display for ResolvedPathLifetime { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - ResolvedPathLifetime::Static => write!(f, "'static"), - ResolvedPathLifetime::Named(name) => write!(f, "'{}", name.as_str()), - ResolvedPathLifetime::Inferred => write!(f, "'_"), - } - } -} diff --git a/compiler/pavexc/src/language/fq_path_resolution.rs b/compiler/pavexc/src/language/fq_path_resolution.rs deleted file mode 100644 index d1934a002..000000000 --- a/compiler/pavexc/src/language/fq_path_resolution.rs +++ /dev/null @@ -1,525 +0,0 @@ -use std::fmt::{Display, Formatter}; -use std::ops::Deref; -use std::sync::Arc; - -use rustdoc_types::ItemEnum; - -use crate::language::callable_path::{CallPathGenericArgument, CallPathLifetime, CallPathType}; -use crate::language::krate_name::dependency_name2package_id; -use crate::language::resolved_type::{Array, FunctionPointer, GenericArgument, Slice}; -use crate::language::{CallPath, InvalidCallPath, RawPointer, Tuple, Type, TypeReference}; -use crate::rustdoc::{ - CannotGetCrateData, CrateCollection, CrateCollectionExt, GlobalItemId, ResolvedItem, -}; -use rustdoc_ext::RustdocKindExt; -use rustdoc_resolver::{GenericBindings, resolve_type}; - -use super::RawIdentifiers; -use super::fq_path::*; -use super::krate_name::CrateNameResolutionError; -use super::krate2package_id; -use super::resolved_type::GenericLifetimeParameter; - -#[derive(Debug, Clone, Copy)] -pub enum PathKind { - Callable, - Type, -} - -pub fn resolve_fq_path_type( - path_type: &FQPathType, - krate_collection: &CrateCollection, -) -> Result { - match path_type { - FQPathType::ResolvedPath(p) => { - let resolved_item = find_rustdoc_item_type(&p.path, 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 - .get_canonical_path_by_local_type_id(used_by_package_id, &item.id, None)?; - let mut generic_arguments = vec![]; - 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, \ - but I found `{}`, {}.", - base_type.join("::"), - other.kind() - ); - } - }; - - let last_segment = &p - .path - .segments - .last() - .expect("Type with an empty path, impossible!"); - for (i, param_def) in generic_param_def.iter().enumerate() { - let arg = if let Some(arg) = last_segment.generic_arguments.get(i) { - match arg { - FQGenericArgument::Type(t) => GenericArgument::TypeParameter( - resolve_fq_path_type(t, krate_collection)?, - ), - FQGenericArgument::Lifetime(l) => { - GenericArgument::Lifetime(l.clone().into()) - } - } - } else { - match ¶m_def.kind { - rustdoc_types::GenericParamDefKind::Lifetime { .. } => { - GenericArgument::Lifetime(GenericLifetimeParameter::from_name( - param_def.name.clone(), - )) - } - rustdoc_types::GenericParamDefKind::Type { default, .. } => { - let Some(default) = default else { - anyhow::bail!( - "Every generic parameter must either be explicitly assigned or have a default. \ - `{}` in `{}` is unassigned and without a default.", - param_def.name, - base_type.join("::") - ) - }; - let ty = resolve_type( - default, - &resolved_item.item_id.package_id, - krate_collection, - &GenericBindings::default(), - )?; - GenericArgument::TypeParameter(ty) - } - rustdoc_types::GenericParamDefKind::Const { .. } => { - anyhow::bail!( - "Const generics are not supported yet. I can't process `{}` in `{}`", - param_def.name, - base_type.join("::") - ) - } - } - }; - generic_arguments.push(arg); - } - - Ok(crate::language::resolved_type::PathType { - package_id: global_type_id.package_id().to_owned(), - rustdoc_id: Some(global_type_id.rustdoc_item_id), - base_type: base_type.to_vec(), - generic_arguments, - } - .into()) - } - FQPathType::Reference(r) => Ok(Type::Reference(TypeReference { - is_mutable: r.is_mutable, - lifetime: r.lifetime.clone(), - inner: Box::new(resolve_fq_path_type(&r.inner, krate_collection)?), - })), - FQPathType::Tuple(t) => { - let elements = t - .elements - .iter() - .map(|e| resolve_fq_path_type(e, krate_collection)) - .collect::, _>>()?; - Ok(Type::Tuple(Tuple { elements })) - } - FQPathType::ScalarPrimitive(s) => Ok(Type::ScalarPrimitive(s.clone())), - FQPathType::Slice(s) => { - let inner = resolve_fq_path_type(&s.element, krate_collection)?; - Ok(Type::Slice(Slice { - element_type: Box::new(inner), - })) - } - FQPathType::Array(a) => { - let inner = resolve_fq_path_type(&a.element, krate_collection)?; - Ok(Type::Array(Array { - element_type: Box::new(inner), - len: a.len, - })) - } - FQPathType::RawPointer(r) => { - let inner = resolve_fq_path_type(&r.inner, krate_collection)?; - Ok(Type::RawPointer(RawPointer { - is_mutable: r.is_mutable, - inner: Box::new(inner), - })) - } - FQPathType::FunctionPointer(fp) => { - let inputs = fp - .inputs - .iter() - .map(|e| resolve_fq_path_type(e, krate_collection)) - .collect::, _>>()?; - let output = fp - .output - .as_ref() - .map(|t| resolve_fq_path_type(t, krate_collection)) - .transpose()?; - Ok(Type::FunctionPointer(FunctionPointer { - inputs, - output: output.map(Box::new), - abi: fp.abi.clone(), - is_unsafe: fp.is_unsafe, - })) - } - } -} - -/// Parses a fully qualified path from the given identifiers. -/// -/// All paths must be fully qualified, meaning that they must start with a package name. -/// Using `crate::`/`super::`/`self::` is not allowed. -pub fn parse_fq_path( - identifiers: &RawIdentifiers, - graph: &guppy::graph::PackageGraph, - kind: PathKind, -) -> Result { - let path = match kind { - PathKind::Callable => CallPath::parse_callable_path(identifiers), - PathKind::Type => CallPath::parse_type_path(identifiers), - }?; - parse_call_path(&path, identifiers, graph) -} - -fn parse_call_path_generic_argument( - arg: &CallPathGenericArgument, - identifiers: &RawIdentifiers, - graph: &guppy::graph::PackageGraph, -) -> Result { - match arg { - CallPathGenericArgument::Type(t) => { - parse_call_path_type(t, identifiers, graph).map(FQGenericArgument::Type) - } - CallPathGenericArgument::Lifetime(l) => match l { - CallPathLifetime::Static => { - Ok(FQGenericArgument::Lifetime(ResolvedPathLifetime::Static)) - } - CallPathLifetime::Named(name) => Ok(FQGenericArgument::Lifetime( - ResolvedPathLifetime::from_name(name.to_owned()), - )), - }, - } -} - -fn parse_call_path_type( - type_: &CallPathType, - identifiers: &RawIdentifiers, - graph: &guppy::graph::PackageGraph, -) -> Result { - match type_ { - CallPathType::ResolvedPath(p) => { - let resolved_path = parse_call_path(p.path.deref(), identifiers, graph)?; - Ok(FQPathType::ResolvedPath(FQResolvedPathType { - path: Box::new(resolved_path), - })) - } - CallPathType::Reference(r) => Ok(FQPathType::Reference(FQReference { - is_mutable: r.is_mutable, - lifetime: r.lifetime.clone(), - inner: Box::new(parse_call_path_type(r.inner.deref(), identifiers, graph)?), - })), - CallPathType::Tuple(t) => { - let mut elements = Vec::with_capacity(t.elements.len()); - for element in t.elements.iter() { - elements.push(parse_call_path_type(element, identifiers, graph)?); - } - Ok(FQPathType::Tuple(FQTuple { elements })) - } - CallPathType::Slice(s) => { - let element_type = parse_call_path_type(s.element_type.deref(), identifiers, graph)?; - Ok(FQPathType::Slice(FQSlice { - element: Box::new(element_type), - })) - } - } -} - -fn parse_call_path( - path: &CallPath, - identifiers: &RawIdentifiers, - graph: &guppy::graph::PackageGraph, -) -> Result { - let mut segments = vec![]; - for raw_segment in &path.segments { - let generic_arguments = raw_segment - .generic_arguments - .iter() - .map(|arg| parse_call_path_generic_argument(arg, identifiers, graph)) - .collect::, _>>()?; - let segment = FQPathSegment { - ident: raw_segment.ident.to_string(), - generic_arguments, - }; - segments.push(segment); - } - - let qself = if let Some(qself) = &path.qualified_self { - Some(FQQualifiedSelf { - position: qself.position, - type_: parse_call_path_type(&qself.type_, identifiers, graph)?, - }) - } else { - None - }; - - let used_in = krate2package_id( - &identifiers.created_at.package_name, - &identifiers.created_at.package_version, - graph, - ) - .expect("Failed to resolve the created at coordinates to a package id"); - let package_id = - dependency_name2package_id(&path.leading_path_segment().to_string(), &used_in, graph) - .map_err(|source| PathMustBeAbsolute { - relative_path: path.to_string(), - source, - })?; - Ok(FQPath { - segments, - qualified_self: qself, - package_id, - }) -} - -/// Find the `rustdoc` items required to analyze the callable that `path` points to. -pub fn find_rustdoc_callable_items<'a>( - path: &FQPath, - krate_collection: &'a CrateCollection, -) -> Result, UnknownPath>, CannotGetCrateData> { - let krate = krate_collection.get_or_compute(&path.package_id)?; - - let path_without_generics: Vec<_> = path - .segments - .iter() - .map(|path_segment| path_segment.ident.to_string()) - .collect(); - 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, - }, - path.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 path.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( - path.clone(), - Arc::new(anyhow::anyhow!( - "{} is too short to be a method path, but there is no function at that path", - path - )), - ))); - } - - let qself = match path - .qualified_self - .as_ref() - .map(|qself| { - if let FQPathType::ResolvedPath(p) = &qself.type_ { - find_rustdoc_item_type(&p.path, krate_collection) - .map_err(|e| UnknownPath(path.to_owned(), Arc::new(e.into()))) - } else { - Err(UnknownPath( - path.clone(), - Arc::new(anyhow::anyhow!("Qualified self type is not a path")), - )) - } - }) - .transpose() - { - Ok(x) => x.map(|(item, resolved_path)| (resolved_path, item)), - Err(e) => return Ok(Err(e)), - }; - - let (method_name_segment, type_path_segments) = path.segments.split_last().unwrap(); - - // 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 = FQPath { - segments: type_path_segments.to_vec(), - qualified_self: None, - package_id: path.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(path.clone(), Arc::new(e.into())))); - } - }; - - // 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; - 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(&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) - && 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: impl_item_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: search_krate.core.package_id.clone(), - rustdoc_item_id: child_id.to_owned(), - }, - }); - break 'outer; - } - } - i => { - dbg!(i); - unreachable!() - } - } - } - - if let Some(next_parent) = parent_items.pop() { - parent_item = next_parent; - } else { - method = None; - break; - } - } - - let method_path = FQPath { - segments: method_owner_path - .segments - .iter() - .chain(std::iter::once(method_name_segment)) - .cloned() - .collect(), - qualified_self: path.qualified_self.clone(), - package_id: parent_item.item_id.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( - path.clone(), - Arc::new(anyhow::anyhow!( - "There was no method named `{}` attached to `{}`", - method_name_segment.ident, - method_owner_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>( - path: &FQPath, - krate_collection: &'a CrateCollection, -) -> Result<(FQPath, ResolvedItem<'a>), UnknownPath> { - krate_collection - .get_type_by_resolved_path(path.clone()) - .map_err(|e| UnknownPath(path.to_owned(), Arc::new(e.into())))? - .map_err(|e| UnknownPath(path.to_owned(), Arc::new(e.into()))) -} - -/// There are two key callables in Rust: functions and methods. -#[derive(Debug)] -pub enum CallableItem<'a> { - /// Functions are free-standing and map to a single `rustdoc` item. - Function(ResolvedItem<'a>, FQPath), - /// 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>, FQPath), - method: (ResolvedItem<'a>, FQPath), - /// The `self` type of the method. - /// It's only populated when working with trait methods. - qualified_self: Option<(ResolvedItem<'a>, FQPath)>, - }, -} - -#[derive(Debug, thiserror::Error, Clone)] -pub enum ParseError { - #[error(transparent)] - InvalidPath(#[from] InvalidCallPath), - #[error(transparent)] - PathMustBeAbsolute(#[from] PathMustBeAbsolute), -} - -#[derive(Debug, thiserror::Error, Clone)] -pub struct PathMustBeAbsolute { - pub(crate) relative_path: String, - #[source] - pub(crate) source: CrateNameResolutionError, -} - -impl Display for PathMustBeAbsolute { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "`{}` is not a fully-qualified import path.", - self.relative_path - ) - } -} - -#[derive(thiserror::Error, Debug, Clone)] -pub struct UnknownPath(pub FQPath, #[source] Arc); - -impl Display for UnknownPath { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let path = &self.0; - let krate = path.crate_name().to_string(); - write!( - f, - "I could not find '{path}' in the auto-generated documentation for '{krate}'." - ) - } -} diff --git a/compiler/pavexc/src/language/mod.rs b/compiler/pavexc/src/language/mod.rs index 9db0abf44..5bafc4bfe 100644 --- a/compiler/pavexc/src/language/mod.rs +++ b/compiler/pavexc/src/language/mod.rs @@ -1,31 +1,13 @@ -pub(crate) use callable_path::{CallPath, InvalidCallPath}; -pub(crate) use fq_path::{FQGenericArgument, FQPath, FQPathSegment, FQPathType, FQQualifiedSelf}; -pub(crate) use fq_path_resolution::{ - CallableItem, PathKind, UnknownPath, find_rustdoc_callable_items, find_rustdoc_item_type, - parse_fq_path, resolve_fq_path_type, -}; pub use krate_name::{ CrateNameResolutionError, UnknownCrate, UnknownDependency, dependency_name2package_id, krate2package_id, }; -use pavex_bp_schema::CreatedAt; pub(crate) use resolved_type::{ Callable, CallableInput, CanonicalType, EnumVariantConstructorPath, EnumVariantInit, FnHeader, - FreeFunction, FreeFunctionPath, Generic, GenericArgument, GenericLifetimeParameter, - InherentMethod, InherentMethodPath, Lifetime, PathType, PathTypeExt, RawPointer, - RustIdentifier, StructLiteralInit, TraitMethod, TraitMethodPath, Tuple, Type, TypeReference, + GenericArgument, GenericLifetimeParameter, InherentMethod, InherentMethodPath, Lifetime, + PathType, PathTypeExt, RustIdentifier, StructLiteralInit, TraitMethod, TraitMethodPath, Type, + TypeReference, }; -mod callable_path; -mod fq_path; -mod fq_path_resolution; mod krate_name; mod resolved_type; - -#[derive(Debug, Hash, Eq, PartialEq, Clone)] -pub struct RawIdentifiers { - /// Information on the location where the component is defined. - pub created_at: CreatedAt, - /// An unambiguous path to the type/callable. - pub import_path: String, -} diff --git a/compiler/pavexc/src/language/resolved_type.rs b/compiler/pavexc/src/language/resolved_type.rs index 8a37862f8..7fd2977a0 100644 --- a/compiler/pavexc/src/language/resolved_type.rs +++ b/compiler/pavexc/src/language/resolved_type.rs @@ -1,28 +1,10 @@ pub use rustdoc_ir::*; -use crate::language::{FQPath, FQPathSegment}; - pub(crate) trait PathTypeExt { - fn resolved_path(&self) -> FQPath; fn callable_struct_literal_path(&self) -> StructLiteralPath; } impl PathTypeExt for PathType { - fn resolved_path(&self) -> FQPath { - let mut segments = Vec::with_capacity(self.base_type.len()); - for segment in &self.base_type { - segments.push(FQPathSegment { - ident: segment.to_owned(), - generic_arguments: vec![], - }); - } - FQPath { - segments, - qualified_self: None, - package_id: self.package_id.clone(), - } - } - fn callable_struct_literal_path(&self) -> StructLiteralPath { // base_type is [crate_name, module1, ..., TypeName] let crate_name = self.base_type.first().cloned().unwrap_or_default(); diff --git a/compiler/pavexc/src/rustdoc/mod.rs b/compiler/pavexc/src/rustdoc/mod.rs index 78d1aca42..7e52a0a29 100644 --- a/compiler/pavexc/src/rustdoc/mod.rs +++ b/compiler/pavexc/src/rustdoc/mod.rs @@ -9,7 +9,7 @@ pub use rustdoc_processor::{ }; pub(crate) use annotations::{AnnotatedItem, AnnotationCoordinates, ImplInfo}; -pub use queries::{Crate, CrateCollection, CrateCollectionExt, GlobalItemId, ResolvedItem}; +pub use queries::{Crate, CrateCollection, CrateCollectionExt, GlobalItemId}; pub use rustdoc_processor::compute::CannotGetCrateData; mod annotations; diff --git a/compiler/pavexc/src/rustdoc/queries/collection.rs b/compiler/pavexc/src/rustdoc/queries/collection.rs index bdee325f6..b36e3c96b 100644 --- a/compiler/pavexc/src/rustdoc/queries/collection.rs +++ b/compiler/pavexc/src/rustdoc/queries/collection.rs @@ -1,14 +1,6 @@ -use std::borrow::Cow; -use std::sync::Arc; - -use rustdoc_types::{Item, ItemEnum}; - -use crate::language::{ - FQGenericArgument, FQPathType, UnknownCrate, krate2package_id, resolve_fq_path_type, -}; +use crate::language::UnknownCrate; +use crate::language::krate2package_id; use crate::rustdoc::CannotGetCrateData; -use rustdoc_ext::RustdocKindExt; -use rustdoc_resolver::{GenericBindings, resolve_type}; use super::super::AnnotatedItem; use super::super::annotations::AnnotationCoordinates; @@ -16,7 +8,7 @@ use super::super::indexer::PavexIndexer; use super::super::progress_reporter::ShellProgress; use super::CrateCollection; use rustdoc_processor::queries::Crate; -use rustdoc_processor::{GlobalItemId, UnknownItemPath}; +use rustdoc_processor::GlobalItemId; /// Extension trait adding Pavex-specific methods to `CrateCollection`. pub trait CrateCollectionExt { @@ -39,14 +31,6 @@ pub trait CrateCollectionExt { &self, c: &AnnotationCoordinates, ) -> Result, UnknownCrate>, CannotGetCrateData>; - - fn get_type_by_resolved_path( - &self, - resolved_path: crate::language::FQPath, - ) -> Result< - Result<(crate::language::FQPath, ResolvedItem<'_>), GetItemByResolvedPathError>, - CannotGetCrateData, - >; } impl CrateCollectionExt for CrateCollection { @@ -97,172 +81,4 @@ impl CrateCollectionExt for CrateCollection { .and_then(|a| a.get_by_annotation_id(&c.id)) .map(|item| (krate, item)))) } - - fn get_type_by_resolved_path( - &self, - mut resolved_path: crate::language::FQPath, - ) -> Result< - Result<(crate::language::FQPath, ResolvedItem<'_>), GetItemByResolvedPathError>, - CannotGetCrateData, - > { - let mut path_without_generics = resolved_path - .segments - .iter() - .map(|p| p.ident.clone()) - .collect::>(); - let krate = self.get_or_compute(&resolved_path.package_id)?; - // The path may come from a crate that depends on the one we are re-examining - // but with a rename in its `Cargo.toml`. We normalize the path to the original crate name - // in order to get a match in the index. - path_without_generics[0] = krate.crate_name(); - - let Ok(mut type_id) = krate.get_item_id_by_path(&path_without_generics, self)? else { - return Ok(Err(UnknownItemPath { - 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(), - }, - ))); - } - - // 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(&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(&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())); - }; - 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 = GenericBindings::default(); - for (path_arg, alias_generic) in path_args.iter().zip(alias_generics.iter()) { - match path_arg { - FQGenericArgument::Type(t) => { - let t = resolve_fq_path_type(t, self).unwrap(); - name2path_arg.types.insert(alias_generic.name.clone(), t); - } - FQGenericArgument::Lifetime(l) => { - name2path_arg - .lifetimes - .insert(alias_generic.name.clone(), l.to_binding_name()); - } - } - } - - let aliased = resolve_type( - &type_alias.type_, - type_id.package_id(), - self, - &name2path_arg, - ) - .unwrap(); - let aliased: FQPathType = aliased.into(); - let FQPathType::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; - } - - let resolved_item = ResolvedItem { - item, - item_id: type_id, - }; - Ok(Ok((resolved_path, resolved_item))) - } -} - -#[derive(Debug, Clone)] -pub struct ResolvedItem<'a> { - pub item: Cow<'a, Item>, - pub item_id: GlobalItemId, -} - -#[derive(thiserror::Error, Debug)] -pub enum GetItemByResolvedPathError { - #[error(transparent)] - UnknownItemPath(UnknownItemPath), - #[error(transparent)] - UnsupportedItemKind(UnsupportedItemKind), -} - -impl From for GetItemByResolvedPathError { - fn from(value: UnsupportedItemKind) -> Self { - Self::UnsupportedItemKind(value) - } -} - -impl From for GetItemByResolvedPathError { - fn from(value: UnknownItemPath) -> Self { - Self::UnknownItemPath(value) - } -} - -#[derive(thiserror::Error, Debug)] -pub struct UnsupportedItemKind { - pub path: Vec, - pub kind: String, -} - -impl std::fmt::Display for UnsupportedItemKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let path = self.path.join("::").replace(' ', ""); - write!( - f, - "'{path}' pointed at {} item. I don't know how to handle that (yet)", - self.kind - ) - } } diff --git a/compiler/pavexc/src/rustdoc/queries/mod.rs b/compiler/pavexc/src/rustdoc/queries/mod.rs index 6e7b16c1b..aebbc319b 100644 --- a/compiler/pavexc/src/rustdoc/queries/mod.rs +++ b/compiler/pavexc/src/rustdoc/queries/mod.rs @@ -3,6 +3,6 @@ mod collection; // Public API (consumed via `rustdoc/mod.rs` re-exports) pub type CrateCollection = rustdoc_processor::CrateCollection; -pub use collection::{CrateCollectionExt, ResolvedItem}; +pub use collection::CrateCollectionExt; pub use rustdoc_processor::GlobalItemId; pub use rustdoc_processor::queries::Crate; diff --git a/compiler/ui_tests/reflection/unions_are_supported/diagnostics.dot b/compiler/ui_tests/reflection/unions_are_supported/diagnostics.dot new file mode 100644 index 000000000..77c2f1561 --- /dev/null +++ b/compiler/ui_tests/reflection/unions_are_supported/diagnostics.dot @@ -0,0 +1,41 @@ +digraph "* * - 0" { + 0 [ label = "0| &pavex::router::AllowedMethods"] + 1 [ label = "1| crate::route_0::Next0(&'a pavex::router::AllowedMethods) -> crate::route_0::Next0<'a>"] + 2 [ label = "2| pavex::middleware::Next::new(crate::route_0::Next0<'a>) -> pavex::middleware::Next>"] + 3 [ label = "3| pavex::middleware::wrap_noop(pavex::middleware::Next>) -> pavex::Response"] + 4 [ label = "4| ::into_response(pavex::Response) -> pavex::Response"] + 2 -> 3 [ ] + 1 -> 2 [ ] + 3 -> 4 [ ] + 0 -> 1 [ ] +} + +digraph "* * - 1" { + 0 [ label = "0| &pavex::router::AllowedMethods"] + 1 [ label = "1| pavex::router::default_fallback(&pavex::router::AllowedMethods) -> pavex::Response"] + 2 [ label = "2| ::into_response(pavex::Response) -> pavex::Response"] + 1 -> 2 [ ] + 0 -> 1 [ ] +} + +digraph "GET / - 0" { + 0 [ label = "0| crate::route_1::Next0() -> crate::route_1::Next0"] + 1 [ label = "1| pavex::middleware::Next::new(crate::route_1::Next0) -> pavex::middleware::Next"] + 2 [ label = "2| pavex::middleware::wrap_noop(pavex::middleware::Next) -> pavex::Response"] + 3 [ label = "3| ::into_response(pavex::Response) -> pavex::Response"] + 1 -> 2 [ ] + 0 -> 1 [ ] + 2 -> 3 [ ] +} + +digraph "GET / - 1" { + 0 [ label = "0| app_25e5f03a::build_union() -> app_25e5f03a::MyUnion"] + 1 [ label = "1| app_25e5f03a::handler(app_25e5f03a::MyUnion) -> pavex::Response"] + 2 [ label = "2| ::into_response(pavex::Response) -> pavex::Response"] + 0 -> 1 [ ] + 1 -> 2 [ ] +} + +digraph app_state { + 0 [ label = "0| crate::ApplicationState() -> crate::ApplicationState"] +} From 7501125885d69afd3de4ce640f6c5e2366daa585 Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Thu, 12 Mar 2026 09:31:16 +0100 Subject: [PATCH 2/3] chore: Clean-up leftover modules --- .../analyses/application_state/cloning.rs | 2 +- .../application_state/thread_safety.rs | 2 +- .../call_graph/borrow_checker/clone.rs | 2 +- .../call_graph/borrow_checker/copy.rs | 2 +- .../pavexc/src/compiler/analyses/cloning.rs | 2 +- .../compiler/analyses/components/db/mod.rs | 10 +- .../src/compiler/analyses/framework_items.rs | 4 +- .../analyses/processing_pipeline/pipeline.rs | 2 +- .../pavexc/src/compiler/analyses/unused.rs | 2 +- .../user_components/annotations/diagnostic.rs | 2 +- .../user_components/annotations/mod.rs | 2 +- .../src/compiler/component/error_handler.rs | 2 +- .../{utils.rs => framework_rustdoc.rs} | 124 ++++++++---------- compiler/pavexc/src/compiler/mod.rs | 5 +- .../pavexc/src/compiler/path_parameters.rs | 2 +- compiler/pavexc/src/compiler/resolvers.rs | 38 ------ compiler/pavexc/src/language/mod.rs | 4 +- compiler/pavexc/src/language/resolved_type.rs | 52 ++++++++ .../pavexc/src/rustdoc/queries/collection.rs | 2 +- 19 files changed, 129 insertions(+), 132 deletions(-) rename compiler/pavexc/src/compiler/{utils.rs => framework_rustdoc.rs} (85%) delete mode 100644 compiler/pavexc/src/compiler/resolvers.rs diff --git a/compiler/pavexc/src/compiler/analyses/application_state/cloning.rs b/compiler/pavexc/src/compiler/analyses/application_state/cloning.rs index 21abc37ed..02782d988 100644 --- a/compiler/pavexc/src/compiler/analyses/application_state/cloning.rs +++ b/compiler/pavexc/src/compiler/analyses/application_state/cloning.rs @@ -11,8 +11,8 @@ use crate::{ processing_pipeline::RequestHandlerPipeline, }, computation::Computation, + framework_rustdoc::resolve_type_path, traits::assert_trait_is_implemented, - utils::resolve_type_path, }, diagnostic::{AnnotatedSource, CompilerDiagnostic, HelpWithSnippet}, language::Type, diff --git a/compiler/pavexc/src/compiler/analyses/application_state/thread_safety.rs b/compiler/pavexc/src/compiler/analyses/application_state/thread_safety.rs index efa0cd2dc..ffb6fb831 100644 --- a/compiler/pavexc/src/compiler/analyses/application_state/thread_safety.rs +++ b/compiler/pavexc/src/compiler/analyses/application_state/thread_safety.rs @@ -7,8 +7,8 @@ use crate::{ computations::ComputationDb, }, computation::Computation, + framework_rustdoc::resolve_type_path, traits::{MissingTraitImplementationError, assert_trait_is_implemented}, - utils::resolve_type_path, }, diagnostic::{CompilerDiagnostic, ComponentKind}, language::Type, diff --git a/compiler/pavexc/src/compiler/analyses/call_graph/borrow_checker/clone.rs b/compiler/pavexc/src/compiler/analyses/call_graph/borrow_checker/clone.rs index 55153a0ce..6c6a219e5 100644 --- a/compiler/pavexc/src/compiler/analyses/call_graph/borrow_checker/clone.rs +++ b/compiler/pavexc/src/compiler/analyses/call_graph/borrow_checker/clone.rs @@ -11,7 +11,7 @@ use crate::compiler::analyses::components::{ use crate::compiler::analyses::computations::ComputationDb; use crate::compiler::analyses::user_components::ScopeId; use crate::compiler::computation::Computation; -use crate::compiler::utils::resolve_type_path; +use crate::compiler::framework_rustdoc::resolve_type_path; use crate::language::{ Callable, CallableInput, FnHeader, Lifetime, PathType, RustIdentifier, TraitMethod, TraitMethodPath, Type, TypeReference, diff --git a/compiler/pavexc/src/compiler/analyses/call_graph/borrow_checker/copy.rs b/compiler/pavexc/src/compiler/analyses/call_graph/borrow_checker/copy.rs index 4e14ba25e..93aac4a45 100644 --- a/compiler/pavexc/src/compiler/analyses/call_graph/borrow_checker/copy.rs +++ b/compiler/pavexc/src/compiler/analyses/call_graph/borrow_checker/copy.rs @@ -7,8 +7,8 @@ use crate::{ call_graph::{CallGraphNode, RawCallGraph}, computations::ComputationDb, }, + framework_rustdoc::resolve_type_path, traits::assert_trait_is_implemented, - utils::resolve_type_path, }, language::{PathType, Type}, rustdoc::CrateCollection, diff --git a/compiler/pavexc/src/compiler/analyses/cloning.rs b/compiler/pavexc/src/compiler/analyses/cloning.rs index 5ffee7cc3..4968a46a2 100644 --- a/compiler/pavexc/src/compiler/analyses/cloning.rs +++ b/compiler/pavexc/src/compiler/analyses/cloning.rs @@ -6,8 +6,8 @@ use crate::{ components::{ComponentDb, ComponentId, HydratedComponent}, computations::ComputationDb, }, + framework_rustdoc::resolve_type_path, traits::{MissingTraitImplementationError, assert_trait_is_implemented}, - utils::resolve_type_path, }, diagnostic::{CompilerDiagnostic, ComponentKind}, language::Type, diff --git a/compiler/pavexc/src/compiler/analyses/components/db/mod.rs b/compiler/pavexc/src/compiler/analyses/components/db/mod.rs index 57ab502e4..90383b4fc 100644 --- a/compiler/pavexc/src/compiler/analyses/components/db/mod.rs +++ b/compiler/pavexc/src/compiler/analyses/components/db/mod.rs @@ -16,14 +16,14 @@ use crate::compiler::component::{ PostProcessingMiddleware, PreProcessingMiddleware, RequestHandler, WrappingMiddleware, }; use crate::compiler::computation::{Computation, MatchResult}; +use crate::compiler::framework_rustdoc::{ + resolve_framework_free_function, resolve_framework_inherent_method, + resolve_framework_trait_method, resolve_type_path, +}; use crate::compiler::interner::Interner; use crate::compiler::traits::assert_trait_is_implemented; -use crate::compiler::utils::{ - get_err_variant, get_ok_variant, resolve_framework_free_function, - resolve_framework_inherent_method, resolve_framework_trait_method, resolve_type_path, -}; use crate::diagnostic::{ParsedSourceFile, Registration, TargetSpan}; -use crate::language::{Callable, Lifetime, Type, TypeReference}; +use crate::language::{Callable, Lifetime, Type, TypeReference, get_err_variant, get_ok_variant}; use crate::rustdoc::{CrateCollection, CrateCollectionExt}; use ahash::{HashMap, HashMapExt, HashSet}; use guppy::graph::PackageGraph; diff --git a/compiler/pavexc/src/compiler/analyses/framework_items.rs b/compiler/pavexc/src/compiler/analyses/framework_items.rs index d1c0859e4..11899d329 100644 --- a/compiler/pavexc/src/compiler/analyses/framework_items.rs +++ b/compiler/pavexc/src/compiler/analyses/framework_items.rs @@ -3,7 +3,9 @@ use bimap::BiHashMap; use proc_macro2::Ident; use quote::format_ident; -use crate::{compiler::utils::resolve_type_path, language::Type, rustdoc::CrateCollection}; +use crate::{ + compiler::framework_rustdoc::resolve_type_path, language::Type, rustdoc::CrateCollection, +}; use pavex_bp_schema::{CloningPolicy, Lifecycle}; /// The id for a framework item inside [`FrameworkItemDb`]. diff --git a/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs b/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs index eda51c039..47e4a4331 100644 --- a/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs +++ b/compiler/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs @@ -21,8 +21,8 @@ use crate::compiler::analyses::constructibles::ConstructibleDb; use crate::compiler::analyses::framework_items::FrameworkItemDb; use crate::compiler::app::GENERATED_APP_PACKAGE_ID; use crate::compiler::computation::Computation; -use crate::compiler::utils::LifetimeGenerator; use crate::diagnostic::{AnnotatedSource, CompilerDiagnostic, HelpWithSnippet}; +use crate::language::LifetimeGenerator; use crate::language::{ Callable, CallableInput, CanonicalType, GenericArgument, GenericLifetimeParameter, Lifetime, PathType, PathTypeExt, RustIdentifier, StructLiteralInit, Type, TypeReference, diff --git a/compiler/pavexc/src/compiler/analyses/unused.rs b/compiler/pavexc/src/compiler/analyses/unused.rs index 17efd4253..470543c59 100644 --- a/compiler/pavexc/src/compiler/analyses/unused.rs +++ b/compiler/pavexc/src/compiler/analyses/unused.rs @@ -3,8 +3,8 @@ use crate::compiler::analyses::components::HydratedComponent; use crate::compiler::analyses::components::{ComponentDb, ComponentId}; use crate::compiler::analyses::computations::ComputationDb; use crate::compiler::analyses::processing_pipeline::RequestHandlerPipeline; -use crate::compiler::utils::get_ok_variant; use crate::diagnostic::{CompilerDiagnostic, DiagnosticSink}; +use crate::language::get_ok_variant; use indexmap::IndexSet; use miette::Severity; use pavex_bp_schema::{Lint, LintSetting}; diff --git a/compiler/pavexc/src/compiler/analyses/user_components/annotations/diagnostic.rs b/compiler/pavexc/src/compiler/analyses/user_components/annotations/diagnostic.rs index 4240c5ac8..621f1ec58 100644 --- a/compiler/pavexc/src/compiler/analyses/user_components/annotations/diagnostic.rs +++ b/compiler/pavexc/src/compiler/analyses/user_components/annotations/diagnostic.rs @@ -2,7 +2,7 @@ use crate::{ compiler::{ analyses::user_components::{UserComponentId, imports::UnresolvedImport}, component::{ConfigTypeValidationError, PrebuiltTypeValidationError}, - resolvers::{TypeResolutionError, UnsupportedConstGeneric}, + framework_rustdoc::{TypeResolutionError, UnsupportedConstGeneric}, }, diagnostic::{ self, CallableDefSource, ComponentKind, DiagnosticSink, OptionalLabeledSpanExt, diff --git a/compiler/pavexc/src/compiler/analyses/user_components/annotations/mod.rs b/compiler/pavexc/src/compiler/analyses/user_components/annotations/mod.rs index f768bac4b..f4ca76055 100644 --- a/compiler/pavexc/src/compiler/analyses/user_components/annotations/mod.rs +++ b/compiler/pavexc/src/compiler/analyses/user_components/annotations/mod.rs @@ -23,7 +23,7 @@ use crate::{ compiler::{ analyses::{computations::ComputationDb, prebuilt_types::PrebuiltTypeDb}, component::{ConfigType, DefaultStrategy, PrebuiltType}, - resolvers::CallableResolutionError, + framework_rustdoc::CallableResolutionError, }, diagnostic::{ComponentKind, DiagnosticSink, Registration}, language::Type, diff --git a/compiler/pavexc/src/compiler/component/error_handler.rs b/compiler/pavexc/src/compiler/component/error_handler.rs index ba17cd850..ba3a49500 100644 --- a/compiler/pavexc/src/compiler/component/error_handler.rs +++ b/compiler/pavexc/src/compiler/component/error_handler.rs @@ -1,7 +1,7 @@ use std::fmt::{Display, Formatter}; use crate::compiler::component::CannotTakeMutReferenceError; -use crate::compiler::utils::get_err_variant; +use crate::language::get_err_variant; use ahash::HashMap; use indexmap::IndexSet; use itertools::Itertools; diff --git a/compiler/pavexc/src/compiler/utils.rs b/compiler/pavexc/src/compiler/framework_rustdoc.rs similarity index 85% rename from compiler/pavexc/src/compiler/utils.rs rename to compiler/pavexc/src/compiler/framework_rustdoc.rs index 1431a2a3c..4eb42fab0 100644 --- a/compiler/pavexc/src/compiler/utils.rs +++ b/compiler/pavexc/src/compiler/framework_rustdoc.rs @@ -1,10 +1,12 @@ +//! Resolution of framework types and callables from rustdoc. + use std::ops::Deref; use guppy::PackageId; use rustdoc_types::ItemEnum; use crate::language::{Callable, GenericArgument, PathType, Type}; -use crate::rustdoc::CrateCollection; +use crate::rustdoc::{CannotGetCrateData, CrateCollection}; use rustdoc_ext::GlobalItemId; use rustdoc_ir::{CallableInput, FnHeader, RustIdentifier, TraitMethod, TraitMethodPath}; use rustdoc_processor::queries::Crate; @@ -12,26 +14,39 @@ use rustdoc_resolver::{GenericBindings, resolve_type}; use super::app::PAVEX_VERSION; -pub(crate) fn get_ok_variant(t: &Type) -> &Type { - debug_assert!(t.is_result()); - let Type::Path(t) = t else { - unreachable!(); - }; - let GenericArgument::TypeParameter(t) = &t.generic_arguments[0] else { - unreachable!() - }; - t +// Re-export types from `rustdoc_resolver` so that downstream code +// within pavexc can keep importing them from this module. +pub use rustdoc_resolver::{ + InputParameterResolutionError, OutputTypeResolutionError, SelfResolutionError, + TypeResolutionError, UnsupportedConstGeneric, +}; + +#[derive(thiserror::Error, Debug, Clone)] +pub(crate) enum CallableResolutionError { + #[error(transparent)] + SelfResolutionError(#[from] SelfResolutionError), + #[error(transparent)] + InputParameterResolutionError(#[from] InputParameterResolutionError), + #[error(transparent)] + OutputTypeResolutionError(#[from] OutputTypeResolutionError), + #[error(transparent)] + CannotGetCrateData(#[from] CannotGetCrateData), } -pub(crate) fn get_err_variant(t: &Type) -> &Type { - debug_assert!(t.is_result()); - let Type::Path(t) = t else { - unreachable!(); - }; - let GenericArgument::TypeParameter(t) = &t.generic_arguments[1] else { - unreachable!() - }; - t +impl From for CallableResolutionError { + fn from(e: rustdoc_resolver::CallableResolutionError) -> Self { + match e { + rustdoc_resolver::CallableResolutionError::SelfResolutionError(e) => { + CallableResolutionError::SelfResolutionError(e) + } + rustdoc_resolver::CallableResolutionError::InputParameterResolutionError(e) => { + CallableResolutionError::InputParameterResolutionError(e) + } + rustdoc_resolver::CallableResolutionError::OutputTypeResolutionError(e) => { + CallableResolutionError::OutputTypeResolutionError(e) + } + } + } } /// Get the `PackageId` for the `pavex` crate. @@ -71,6 +86,12 @@ fn strip_generics_from_path(raw_path: &str) -> Vec { /// /// Generic arguments in the path (e.g. `Foo::<'a, T>`) are stripped for the /// rustdoc lookup; the resolved type's generics come from the type definition. +/// +/// The path must resolve to a struct, enum, or trait. +/// +/// # Panics +/// +/// Panics if the resolved item is not a struct, enum, or trait. pub(crate) fn resolve_type_path(raw_path: &str, krate_collection: &CrateCollection) -> Type { // Strip generic arguments from the path for rustdoc lookup. // E.g. "pavex::request::path::RawPathParams::<'server, 'request>" @@ -115,26 +136,20 @@ pub(crate) fn resolve_type_path(raw_path: &str, krate_collection: &CrateCollecti ItemEnum::Struct(s) => &s.generics.params, ItemEnum::Enum(e) => &e.generics.params, ItemEnum::Trait(t) => &t.generics.params, - _ => { - // No generics for primitives, etc. - return PathType { - package_id: canonical_global_id.package_id().to_owned(), - rustdoc_id: Some(canonical_global_id.rustdoc_item_id), - base_type: base_type.to_vec(), - generic_arguments: vec![], - } - .into(); + other => { + panic!( + "Expected `{raw_path}` to resolve to a struct, enum, or trait, \ + but it resolved to a {other:?}" + ); } }; let mut generic_arguments = vec![]; for generic_def in generic_defs { let arg = match &generic_def.kind { - rustdoc_types::GenericParamDefKind::Lifetime { .. } => { - GenericArgument::Lifetime(rustdoc_ir::GenericLifetimeParameter::from_name( - &generic_def.name, - )) - } + rustdoc_types::GenericParamDefKind::Lifetime { .. } => GenericArgument::Lifetime( + rustdoc_ir::GenericLifetimeParameter::from_name(&generic_def.name), + ), rustdoc_types::GenericParamDefKind::Type { default, .. } => { if let Some(default) = default { let default = resolve_type( @@ -307,13 +322,12 @@ pub(crate) fn resolve_framework_trait_method( let mut inputs = Vec::new(); let mut takes_self_as_ref = false; for (parameter_index, (param_name, parameter_type)) in fn_item.sig.inputs.iter().enumerate() { - if parameter_index == 0 { - if let rustdoc_types::Type::BorrowedRef { type_, .. } = parameter_type - && let rustdoc_types::Type::Generic(g) = type_.deref() - && g == "Self" - { - takes_self_as_ref = true; - } + if parameter_index == 0 + && let rustdoc_types::Type::BorrowedRef { type_, .. } = parameter_type + && let rustdoc_types::Type::Generic(g) = type_.deref() + && g == "Self" + { + takes_self_as_ref = true; } let resolved = resolve_type( parameter_type, @@ -396,33 +410,3 @@ pub(crate) fn resolve_framework_trait_method( takes_self_as_ref, })) } - -/// A generator of unique lifetime names. -#[derive(Debug, Clone)] -pub struct LifetimeGenerator { - next: usize, -} - -impl LifetimeGenerator { - const ALPHABET: [char; 26] = [ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', - 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - ]; - - pub fn new() -> Self { - Self { next: 0 } - } - - /// Generates a new lifetime name. - pub fn next(&mut self) -> String { - let next = self.next; - self.next += 1; - let round = next / Self::ALPHABET.len(); - let letter = Self::ALPHABET[next % Self::ALPHABET.len()]; - if round == 0 { - format!("{letter}") - } else { - format!("{letter}{round}") - } - } -} diff --git a/compiler/pavexc/src/compiler/mod.rs b/compiler/pavexc/src/compiler/mod.rs index cd893d3da..a5410243b 100644 --- a/compiler/pavexc/src/compiler/mod.rs +++ b/compiler/pavexc/src/compiler/mod.rs @@ -8,11 +8,8 @@ mod codegen; mod codegen_utils; mod component; mod computation; +mod framework_rustdoc; mod generated_app; mod interner; mod path_parameters; -// HACK: breaking encapsulation because resolver logic is split across this module -// and `resolved_path` in `language`. -pub mod resolvers; mod traits; -mod utils; diff --git a/compiler/pavexc/src/compiler/path_parameters.rs b/compiler/pavexc/src/compiler/path_parameters.rs index f57d7d070..d04bcadbe 100644 --- a/compiler/pavexc/src/compiler/path_parameters.rs +++ b/compiler/pavexc/src/compiler/path_parameters.rs @@ -13,7 +13,7 @@ use crate::compiler::analyses::processing_pipeline::RequestHandlerPipeline; use crate::compiler::analyses::router::Router; use crate::compiler::component::Constructor; use crate::compiler::computation::{Computation, MatchResultVariant}; -use crate::compiler::utils::resolve_type_path; +use crate::compiler::framework_rustdoc::resolve_type_path; use crate::diagnostic::CompilerDiagnostic; use crate::diagnostic::DiagnosticSink; use crate::language::{GenericArgument, Type}; diff --git a/compiler/pavexc/src/compiler/resolvers.rs b/compiler/pavexc/src/compiler/resolvers.rs deleted file mode 100644 index 6ef77ad80..000000000 --- a/compiler/pavexc/src/compiler/resolvers.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Resolution of callables and types from rustdoc information. - -use crate::rustdoc::CannotGetCrateData; - -// Re-export types that moved to `rustdoc_resolver` so that downstream code -// within pavexc can keep importing them from this module. -pub use rustdoc_resolver::{ - InputParameterResolutionError, OutputTypeResolutionError, SelfResolutionError, - TypeResolutionError, UnsupportedConstGeneric, -}; - -#[derive(thiserror::Error, Debug, Clone)] -pub(crate) enum CallableResolutionError { - #[error(transparent)] - SelfResolutionError(#[from] SelfResolutionError), - #[error(transparent)] - InputParameterResolutionError(#[from] InputParameterResolutionError), - #[error(transparent)] - OutputTypeResolutionError(#[from] OutputTypeResolutionError), - #[error(transparent)] - CannotGetCrateData(#[from] CannotGetCrateData), -} - -impl From for CallableResolutionError { - fn from(e: rustdoc_resolver::CallableResolutionError) -> Self { - match e { - rustdoc_resolver::CallableResolutionError::SelfResolutionError(e) => { - CallableResolutionError::SelfResolutionError(e) - } - rustdoc_resolver::CallableResolutionError::InputParameterResolutionError(e) => { - CallableResolutionError::InputParameterResolutionError(e) - } - rustdoc_resolver::CallableResolutionError::OutputTypeResolutionError(e) => { - CallableResolutionError::OutputTypeResolutionError(e) - } - } - } -} diff --git a/compiler/pavexc/src/language/mod.rs b/compiler/pavexc/src/language/mod.rs index 5bafc4bfe..df2b01d64 100644 --- a/compiler/pavexc/src/language/mod.rs +++ b/compiler/pavexc/src/language/mod.rs @@ -5,8 +5,8 @@ pub use krate_name::{ pub(crate) use resolved_type::{ Callable, CallableInput, CanonicalType, EnumVariantConstructorPath, EnumVariantInit, FnHeader, GenericArgument, GenericLifetimeParameter, InherentMethod, InherentMethodPath, Lifetime, - PathType, PathTypeExt, RustIdentifier, StructLiteralInit, TraitMethod, TraitMethodPath, Type, - TypeReference, + LifetimeGenerator, PathType, PathTypeExt, RustIdentifier, StructLiteralInit, TraitMethod, + TraitMethodPath, Type, TypeReference, get_err_variant, get_ok_variant, }; mod krate_name; diff --git a/compiler/pavexc/src/language/resolved_type.rs b/compiler/pavexc/src/language/resolved_type.rs index 7fd2977a0..888e7ba6d 100644 --- a/compiler/pavexc/src/language/resolved_type.rs +++ b/compiler/pavexc/src/language/resolved_type.rs @@ -1,5 +1,57 @@ pub use rustdoc_ir::*; +pub(crate) fn get_ok_variant(t: &Type) -> &Type { + debug_assert!(t.is_result()); + let Type::Path(t) = t else { + unreachable!(); + }; + let GenericArgument::TypeParameter(t) = &t.generic_arguments[0] else { + unreachable!() + }; + t +} + +pub(crate) fn get_err_variant(t: &Type) -> &Type { + debug_assert!(t.is_result()); + let Type::Path(t) = t else { + unreachable!(); + }; + let GenericArgument::TypeParameter(t) = &t.generic_arguments[1] else { + unreachable!() + }; + t +} + +/// A generator of unique lifetime names. +#[derive(Debug, Clone)] +pub struct LifetimeGenerator { + next: usize, +} + +impl LifetimeGenerator { + const ALPHABET: [char; 26] = [ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + ]; + + pub fn new() -> Self { + Self { next: 0 } + } + + /// Generates a new lifetime name. + pub fn next(&mut self) -> String { + let next = self.next; + self.next += 1; + let round = next / Self::ALPHABET.len(); + let letter = Self::ALPHABET[next % Self::ALPHABET.len()]; + if round == 0 { + format!("{letter}") + } else { + format!("{letter}{round}") + } + } +} + pub(crate) trait PathTypeExt { fn callable_struct_literal_path(&self) -> StructLiteralPath; } diff --git a/compiler/pavexc/src/rustdoc/queries/collection.rs b/compiler/pavexc/src/rustdoc/queries/collection.rs index b36e3c96b..770a061a1 100644 --- a/compiler/pavexc/src/rustdoc/queries/collection.rs +++ b/compiler/pavexc/src/rustdoc/queries/collection.rs @@ -7,8 +7,8 @@ use super::super::annotations::AnnotationCoordinates; use super::super::indexer::PavexIndexer; use super::super::progress_reporter::ShellProgress; use super::CrateCollection; -use rustdoc_processor::queries::Crate; use rustdoc_processor::GlobalItemId; +use rustdoc_processor::queries::Crate; /// Extension trait adding Pavex-specific methods to `CrateCollection`. pub trait CrateCollectionExt { From e51b85f52e0f52decb3dba05f80f010dceb4516f Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:58:24 +0100 Subject: [PATCH 3/3] ci: Don't treat 429 as link checking errors --- .github/ci_generator/templates/job_steps/build_docs.jinja | 1 + .github/workflows/docs.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/ci_generator/templates/job_steps/build_docs.jinja b/.github/ci_generator/templates/job_steps/build_docs.jinja index 7e90cd62b..025b496de 100644 --- a/.github/ci_generator/templates/job_steps/build_docs.jinja +++ b/.github/ci_generator/templates/job_steps/build_docs.jinja @@ -34,6 +34,7 @@ lycheeVersion: v0.16.1 args: | --base site + --accept 200,429 --exclude-loopback --exclude-path="site/api_reference/pavex/http" --exclude-path="site/api_reference/pavex/time" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ce8a8caf2..00587961c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1742,6 +1742,7 @@ jobs: lycheeVersion: v0.16.1 args: | --base site + --accept 200,429 --exclude-loopback --exclude-path="site/api_reference/pavex/http" --exclude-path="site/api_reference/pavex/time"