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

@@ -7,7 +7,7 @@
//! | `target` | [`Target`] bitflags for PHP attribute target sites |
//! | `descriptor` | [`MacroDescriptor`] and [`ParamDescriptor`] - static metadata |
//! | `context` | [`MacroContext`], [`AttributedNode`], [`ResolvedAttr`], etc. |
//! | `traits` | [`PmpMacro`] trait, [`MacroRegistration`], [`validate_registry`]|
//! | `traits` | [`PmpMacro`] trait, [`MacroRegistration`], [`MacroRegistry`] |
mod target;
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
/// not be written by hand. The two handler methods have default no-op
/// The two handler methods have default no-op
/// implementations so that analysis-only and transform-only macros only need
/// 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.
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 ----------------------------------------------------- //
pub trait PmpMacro: Send + Sync
{
/// Analyse the attributed PHP node and emit diagnostics.
///
/// Called during the analysis pass. Returning an empty `Vec` means no
@@ -38,9 +30,7 @@ pub trait PmpMacro: Send + Sync {
ctx: &MacroContext,
attr: &ResolvedAttr,
node: &AttributedNode,
) -> Vec<Diagnostic> {
vec![]
}
) -> Vec<Diagnostic> { vec![] }
/// Produce rewrite instructions for the attributed PHP node.
///
@@ -52,25 +42,40 @@ pub trait PmpMacro: Send + Sync {
ctx: &MacroContext,
attr: &ResolvedAttr,
node: &AttributedNode,
) -> Vec<Rewrite> {
vec![]
}
) -> Vec<Rewrite> { 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!`.
///
/// Wraps a `&'static dyn PmpMacro` so the inventory iterator can hand out
/// references to every registered handler without allocation.
/// Stores static metadata alongside fn pointers that delegate to the
/// handler struct's `PmpMacro` impl, avoiding the need for `dyn` dispatch.
///
/// Generated via `#[derive(PmpMacro)]`
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
{
pub const fn new(handler: &'static dyn PmpMacro) -> Self {
Self { handler }
pub const fn new(
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.
///
/// Built once on first access and then cached for the lifetime of the process.
/// Built once on first access via [`registry()`] and cached for the process lifetime.
pub struct MacroRegistry {
/// 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();
@@ -104,14 +108,13 @@ impl MacroRegistry
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>
{
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!(
"duplicate PmpMacro registration: attribute name \"{name}\" is \
registered more than once - check your macro definitions"
@@ -122,13 +125,13 @@ impl MacroRegistry
Self { handlers }
}
/// Look up a registered macro handler by its PHP attribute name.
pub fn get(&self, name: &str) -> Option<&'static dyn PmpMacro> {
/// Look up a registered macro by its PHP attribute name.
pub fn get(&self, name: &str) -> Option<&'static MacroRegistration> {
self.handlers.get(name).copied()
}
/// Iterate over all registered handlers.
pub fn iter(&self) -> impl Iterator<Item = &'static dyn PmpMacro> {
/// Iterate over all registered macros.
pub fn iter(&self) -> impl Iterator<Item = &'static MacroRegistration> {
self.handlers.values().copied()
}