Refactor PmpMacro trait and macro registry: replace dyn dispatch with function pointers, streamline handler struct registration, and enhance documentation

This commit is contained in:
2026-03-04 19:36:25 +01:00
parent 577c02c9a4
commit d8b4550392
3 changed files with 39 additions and 37 deletions

View File

@@ -10,7 +10,6 @@ license-file.workspace = true
proc-macro = true proc-macro = true
[dependencies] [dependencies]
syn = { version = "^2", features = ["full"] } syn = { version = "^2", features = ["full"] }
quote = "^1" quote = "^1"
darling = "^0" darling = "^0"

View File

@@ -7,7 +7,7 @@
//! | `target` | [`Target`] bitflags for PHP attribute target sites | //! | `target` | [`Target`] bitflags for PHP attribute target sites |
//! | `descriptor` | [`MacroDescriptor`] and [`ParamDescriptor`] - static metadata | //! | `descriptor` | [`MacroDescriptor`] and [`ParamDescriptor`] - static metadata |
//! | `context` | [`MacroContext`], [`AttributedNode`], [`ResolvedAttr`], etc. | //! | `context` | [`MacroContext`], [`AttributedNode`], [`ResolvedAttr`], etc. |
//! | `traits` | [`PmpMacro`] trait, [`MacroRegistration`], [`validate_registry`]| //! | `traits` | [`PmpMacro`] trait, [`MacroRegistration`], [`MacroRegistry`] |
mod target; mod target;
mod traits; mod traits;

View File

@@ -10,24 +10,16 @@ use crate::{
// --------------------------------------------------------------------------------------------- // // --------------------------------------------------------------------------------------------- //
/// The core trait every PHP macro attribute handler must implement. /// The handler trait implemented by each PHP macro attribute struct.
/// ///
/// The `descriptor` method is generated by `#[derive(PmpMacro)]` and should /// The two handler methods have default no-op
/// not be written by hand. The two handler methods have default no-op
/// implementations so that analysis-only and transform-only macros only need /// implementations so that analysis-only and transform-only macros only need
/// to override what they actually do. /// to override what they actually do.
/// ///
/// The trait is object-safe (`&self`, no generics) so the registry can store /// The trait is object-safe (`&self`, no generics), so the registry can store
/// `Box<dyn PmpMacro>` without knowing concrete types. /// `Box<dyn PmpMacro>` without knowing concrete types.
pub trait PmpMacro: Send + Sync { pub trait PmpMacro: Send + Sync
// --- generated by #[derive(PmpMacro)] --------------------------------------------------- // {
/// Returns the static metadata for this macro.
/// Must not be implemented by hand - use `#[derive(PmpMacro)]`.
fn descriptor(&self) -> &'static MacroDescriptor;
// --- user-implemented handler hooks ----------------------------------------------------- //
/// Analyse the attributed PHP node and emit diagnostics. /// Analyse the attributed PHP node and emit diagnostics.
/// ///
/// Called during the analysis pass. Returning an empty `Vec` means no /// Called during the analysis pass. Returning an empty `Vec` means no
@@ -38,9 +30,7 @@ pub trait PmpMacro: Send + Sync {
ctx: &MacroContext, ctx: &MacroContext,
attr: &ResolvedAttr, attr: &ResolvedAttr,
node: &AttributedNode, node: &AttributedNode,
) -> Vec<Diagnostic> { ) -> Vec<Diagnostic> { vec![] }
vec![]
}
/// Produce rewrite instructions for the attributed PHP node. /// Produce rewrite instructions for the attributed PHP node.
/// ///
@@ -52,25 +42,40 @@ pub trait PmpMacro: Send + Sync {
ctx: &MacroContext, ctx: &MacroContext,
attr: &ResolvedAttr, attr: &ResolvedAttr,
node: &AttributedNode, node: &AttributedNode,
) -> Vec<Rewrite> { ) -> Vec<Rewrite> { vec![] }
vec![]
}
} }
// --------------------------------------------------------------------------------------------- // // --------------------------------------------------------------------------------------------- //
/// Type alias for the analyze handler function pointer.
pub type AnalyzeFn = fn(&MacroContext, &ResolvedAttr, &AttributedNode) -> Vec<Diagnostic>;
/// Type alias for the transform handler function pointer.
pub type TransformFn = fn(&MacroContext, &ResolvedAttr, &AttributedNode) -> Vec<Rewrite>;
/// An entry in the global macro registry, submitted via `inventory::submit!`. /// An entry in the global macro registry, submitted via `inventory::submit!`.
/// ///
/// Wraps a `&'static dyn PmpMacro` so the inventory iterator can hand out /// Stores static metadata alongside fn pointers that delegate to the
/// references to every registered handler without allocation. /// handler struct's `PmpMacro` impl, avoiding the need for `dyn` dispatch.
///
/// Generated via `#[derive(PmpMacro)]`
pub struct MacroRegistration { pub struct MacroRegistration {
pub handler: &'static dyn PmpMacro, /// Static metadata for this macro — attribute name, targets, params, docs.
pub descriptor: &'static MacroDescriptor,
/// Delegates to `<Handler as PmpMacro>::analyze`.
pub analyze: AnalyzeFn,
/// Delegates to `<Handler as PmpMacro>::transform`.
pub transform: TransformFn,
} }
impl MacroRegistration impl MacroRegistration
{ {
pub const fn new(handler: &'static dyn PmpMacro) -> Self { pub const fn new(
Self { handler } descriptor: &'static MacroDescriptor,
analyze: AnalyzeFn,
transform: TransformFn,
) -> Self
{
Self { descriptor, analyze, transform }
} }
} }
@@ -79,11 +84,10 @@ inventory::collect!(MacroRegistration);
// --------------------------------------------------------------------------------------------- // // --------------------------------------------------------------------------------------------- //
/// Validated, deduplicated view of the global macro registry. /// Validated, deduplicated view of the global macro registry.
/// /// Built once on first access via [`registry()`] and cached for the process lifetime.
/// Built once on first access and then cached for the lifetime of the process.
pub struct MacroRegistry { pub struct MacroRegistry {
/// Handlers keyed by attribute name for O(1) lookup during processing. /// Handlers keyed by attribute name for O(1) lookup during processing.
handlers: HashMap<&'static str, &'static dyn PmpMacro>, handlers: HashMap<&'static str, &'static MacroRegistration>,
} }
static REGISTRY: OnceLock<MacroRegistry> = OnceLock::new(); static REGISTRY: OnceLock<MacroRegistry> = OnceLock::new();
@@ -104,14 +108,13 @@ impl MacroRegistry
fn build() -> Self fn build() -> Self
{ {
let mut handlers: HashMap<&'static str, &'static dyn PmpMacro> = HashMap::new(); let mut handlers: HashMap<&'static str, &'static MacroRegistration> = HashMap::new();
for reg in inventory::iter::<MacroRegistration> for reg in inventory::iter::<MacroRegistration>
{ {
let name = reg.handler.descriptor().name; let name = reg.descriptor.name;
if handlers.insert(name, reg.handler).is_some() if handlers.insert(name, reg).is_some() {
{
panic!( panic!(
"duplicate PmpMacro registration: attribute name \"{name}\" is \ "duplicate PmpMacro registration: attribute name \"{name}\" is \
registered more than once - check your macro definitions" registered more than once - check your macro definitions"
@@ -122,13 +125,13 @@ impl MacroRegistry
Self { handlers } Self { handlers }
} }
/// Look up a registered macro handler by its PHP attribute name. /// Look up a registered macro by its PHP attribute name.
pub fn get(&self, name: &str) -> Option<&'static dyn PmpMacro> { pub fn get(&self, name: &str) -> Option<&'static MacroRegistration> {
self.handlers.get(name).copied() self.handlers.get(name).copied()
} }
/// Iterate over all registered handlers. /// Iterate over all registered macros.
pub fn iter(&self) -> impl Iterator<Item = &'static dyn PmpMacro> { pub fn iter(&self) -> impl Iterator<Item = &'static MacroRegistration> {
self.handlers.values().copied() self.handlers.values().copied()
} }