Introduce pmp-macro-misc crate: define shared types, traits, and metadata for macro registration and handling
This commit is contained in:
11
crates/pmp-macro/crates/pmp-macro-misc/Cargo.toml
Normal file
11
crates/pmp-macro/crates/pmp-macro-misc/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "pmp-macro-misc"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
license-file.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bitflags = "^2"
|
||||||
|
inventory = "^0"
|
||||||
45
crates/pmp-macro/crates/pmp-macro-misc/src/context.rs
Normal file
45
crates/pmp-macro/crates/pmp-macro-misc/src/context.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// ---------------------------------------------------------------------------------------------- //
|
||||||
|
// Mago-facing placeholders //
|
||||||
|
// //
|
||||||
|
// These types will wrap or re-export mago's AST / symbol / source types once the //
|
||||||
|
// integration surface is understood. For now they are empty structs so the trait //
|
||||||
|
// and derive can be written against concrete type names without a mago dependency. //
|
||||||
|
// ---------------------------------------------------------------------------------------------- //
|
||||||
|
|
||||||
|
/// Global preprocessor state passed to every handler invocation.
|
||||||
|
///
|
||||||
|
/// Will hold references to mago's parsed file set, resolved symbol table,
|
||||||
|
/// and any cross-file index needed for macros like `#[Sealed]` that must
|
||||||
|
/// scan the entire project.
|
||||||
|
///
|
||||||
|
/// TODO: replace with real mago types.
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct MacroContext;
|
||||||
|
|
||||||
|
/// The PHP AST node that carries the attribute being processed, together
|
||||||
|
/// with the specific attribute instance (which macro it is, its position).
|
||||||
|
///
|
||||||
|
/// TODO: replace with real mago node types.
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct AttributedNode;
|
||||||
|
|
||||||
|
/// The parsed and type-resolved arguments supplied to the attribute.
|
||||||
|
///
|
||||||
|
/// e.g. for `#[Sealed([Success::class, Failure::class])]` this would
|
||||||
|
/// expose the two resolved class names as typed Rust values.
|
||||||
|
///
|
||||||
|
/// TODO: replace with real resolved argument types.
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct ResolvedAttr;
|
||||||
|
|
||||||
|
/// A single source-level diagnostic emitted by a macro handler.
|
||||||
|
///
|
||||||
|
/// TODO: decide between a custom type or wrapping mago's / miette's diagnostic type.
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Diagnostic;
|
||||||
|
|
||||||
|
/// A rewrite instruction produced by `PmpMacro::transform`.
|
||||||
|
///
|
||||||
|
/// TODO: decide between text-span replacement or typed AST mutation depending on what mago exposes.
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Rewrite;
|
||||||
36
crates/pmp-macro/crates/pmp-macro-misc/src/descriptor.rs
Normal file
36
crates/pmp-macro/crates/pmp-macro-misc/src/descriptor.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
use crate::target::Target;
|
||||||
|
|
||||||
|
/// Describes a single constructor parameter of a PHP macro attribute.
|
||||||
|
///
|
||||||
|
/// All fields are `&'static str` so the descriptor can be embedded as a
|
||||||
|
/// `static` and referenced from the inventory without allocation.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ParamDescriptor {
|
||||||
|
/// The parameter name as it appears in the PHP constructor.
|
||||||
|
pub name: &'static str,
|
||||||
|
/// A human-readable PHP type hint, e.g. `"class-string[]"` or `"bool"`.
|
||||||
|
pub php_type: &'static str,
|
||||||
|
/// Whether the parameter must be supplied by the caller.
|
||||||
|
pub required: bool,
|
||||||
|
/// The PHP source representation of the default value, e.g. `"[]"` or `"false"`.
|
||||||
|
/// `None` when `required` is `true`.
|
||||||
|
pub default: Option<&'static str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Static metadata for a registered PHP macro attribute.
|
||||||
|
///
|
||||||
|
/// Generated by `#[derive(PmpMacro)]` and stored as a `&'static MacroDescriptor`
|
||||||
|
/// in the inventory. Entirely allocation-free.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MacroDescriptor {
|
||||||
|
/// The PHP attribute name, e.g. `"Sealed"`. Used to match against
|
||||||
|
/// attribute usages in parsed PHP files.
|
||||||
|
pub name: &'static str,
|
||||||
|
/// Which PHP constructs this attribute is permitted to annotate.
|
||||||
|
pub target: Target,
|
||||||
|
/// Human-readable description of what this macro does.
|
||||||
|
/// Used for error messages and generated documentation.
|
||||||
|
pub docs: &'static str,
|
||||||
|
/// The constructor parameters this attribute accepts.
|
||||||
|
pub params: &'static [ParamDescriptor],
|
||||||
|
}
|
||||||
20
crates/pmp-macro/crates/pmp-macro-misc/src/lib.rs
Normal file
20
crates/pmp-macro/crates/pmp-macro-misc/src/lib.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//! Shared types, traits, and registration machinery for `pmp-macro`.
|
||||||
|
//!
|
||||||
|
//! # Crate layout
|
||||||
|
//!
|
||||||
|
//! | Module | Contents |
|
||||||
|
//! |--------------|-----------------------------------------------------------------|
|
||||||
|
//! | `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`]|
|
||||||
|
|
||||||
|
mod target;
|
||||||
|
mod traits;
|
||||||
|
mod context;
|
||||||
|
mod descriptor;
|
||||||
|
|
||||||
|
pub use target::Target;
|
||||||
|
pub use descriptor::{ MacroDescriptor, ParamDescriptor };
|
||||||
|
pub use traits::{ PmpMacro, MacroRegistry, MacroRegistration, };
|
||||||
|
pub use context::{ Rewrite, ResolvedAttr, Diagnostic, MacroContext, AttributedNode };
|
||||||
39
crates/pmp-macro/crates/pmp-macro-misc/src/target.rs
Normal file
39
crates/pmp-macro/crates/pmp-macro-misc/src/target.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
use bitflags::bitflags;
|
||||||
|
|
||||||
|
bitflags!
|
||||||
|
{
|
||||||
|
/// The PHP construct(s) that a macro attribute is permitted to annotate.
|
||||||
|
///
|
||||||
|
/// Mirrors PHP's `Attribute::TARGET_*` constants. A macro may target
|
||||||
|
/// multiple sites by OR-ing flags together:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let target = Target::CLASS | Target::INTERFACE;
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Target: u16
|
||||||
|
{
|
||||||
|
const CLASS = 1 << 0;
|
||||||
|
const INTERFACE = 1 << 1;
|
||||||
|
const TRAIT = 1 << 2;
|
||||||
|
const ENUM = 1 << 3;
|
||||||
|
const METHOD = 1 << 4;
|
||||||
|
const FUNCTION = 1 << 5;
|
||||||
|
const PROPERTY = 1 << 6;
|
||||||
|
const CONSTANT = 1 << 7;
|
||||||
|
const PARAMETER = 1 << 8;
|
||||||
|
|
||||||
|
/// Convenience: any class-like construct.
|
||||||
|
const ANY_CLASS_LIKE = Self::CLASS.bits()
|
||||||
|
| Self::INTERFACE.bits()
|
||||||
|
| Self::TRAIT.bits()
|
||||||
|
| Self::ENUM.bits();
|
||||||
|
|
||||||
|
/// Convenience: any callable construct.
|
||||||
|
const ANY_CALLABLE = Self::METHOD.bits()
|
||||||
|
| Self::FUNCTION.bits();
|
||||||
|
|
||||||
|
/// Convenience: all targets.
|
||||||
|
const ALL = u16::MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
143
crates/pmp-macro/crates/pmp-macro-misc/src/traits.rs
Normal file
143
crates/pmp-macro/crates/pmp-macro-misc/src/traits.rs
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
use std::{
|
||||||
|
sync::OnceLock,
|
||||||
|
collections::HashMap
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
descriptor::MacroDescriptor,
|
||||||
|
context::{ Rewrite, ResolvedAttr, Diagnostic, MacroContext, AttributedNode }
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------- //
|
||||||
|
|
||||||
|
/// The core trait every PHP macro attribute handler must implement.
|
||||||
|
///
|
||||||
|
/// The `descriptor` method is generated by `#[derive(PmpMacro)]` and should
|
||||||
|
/// not be written by hand. 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
|
||||||
|
/// `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 ----------------------------------------------------- //
|
||||||
|
|
||||||
|
/// Analyse the attributed PHP node and emit diagnostics.
|
||||||
|
///
|
||||||
|
/// Called during the analysis pass. Returning an empty `Vec` means no
|
||||||
|
/// issues were found. The default implementation is a no-op.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn analyze(
|
||||||
|
&self,
|
||||||
|
ctx: &MacroContext,
|
||||||
|
attr: &ResolvedAttr,
|
||||||
|
node: &AttributedNode,
|
||||||
|
) -> Vec<Diagnostic> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produce rewrite instructions for the attributed PHP node.
|
||||||
|
///
|
||||||
|
/// Called during the transform pass, after all analysis is complete.
|
||||||
|
/// The default implementation is a no-op.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn transform(
|
||||||
|
&self,
|
||||||
|
ctx: &MacroContext,
|
||||||
|
attr: &ResolvedAttr,
|
||||||
|
node: &AttributedNode,
|
||||||
|
) -> Vec<Rewrite> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------- //
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
pub struct MacroRegistration {
|
||||||
|
pub handler: &'static dyn PmpMacro,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MacroRegistration
|
||||||
|
{
|
||||||
|
pub const fn new(handler: &'static dyn PmpMacro) -> Self {
|
||||||
|
Self { handler }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
pub struct MacroRegistry {
|
||||||
|
/// Handlers keyed by attribute name for O(1) lookup during processing.
|
||||||
|
handlers: HashMap<&'static str, &'static dyn PmpMacro>,
|
||||||
|
}
|
||||||
|
|
||||||
|
static REGISTRY: OnceLock<MacroRegistry> = OnceLock::new();
|
||||||
|
|
||||||
|
impl MacroRegistry
|
||||||
|
{
|
||||||
|
/// Returns the global macro registry, validating and building it on first call.
|
||||||
|
///
|
||||||
|
/// Deduplication is checked here — if two macros share the same attribute name
|
||||||
|
/// this will panic with a descriptive message. Subsequent calls are free
|
||||||
|
/// (just an atomic load from the `OnceLock`).
|
||||||
|
///
|
||||||
|
/// This is the only intended way to access registered macros. Direct use of
|
||||||
|
/// `inventory::iter::<MacroRegistration>` bypasses dedup and should be avoided.
|
||||||
|
pub fn registry() -> &'static MacroRegistry {
|
||||||
|
REGISTRY.get_or_init(MacroRegistry::build)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build() -> Self
|
||||||
|
{
|
||||||
|
let mut handlers: HashMap<&'static str, &'static dyn PmpMacro> = HashMap::new();
|
||||||
|
|
||||||
|
for reg in inventory::iter::<MacroRegistration>
|
||||||
|
{
|
||||||
|
let name = reg.handler.descriptor().name;
|
||||||
|
|
||||||
|
if handlers.insert(name, reg.handler).is_some()
|
||||||
|
{
|
||||||
|
panic!(
|
||||||
|
"duplicate PmpMacro registration: attribute name \"{name}\" is \
|
||||||
|
registered more than once - check your macro definitions"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { handlers }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look up a registered macro handler by its PHP attribute name.
|
||||||
|
pub fn get(&self, name: &str) -> Option<&'static dyn PmpMacro> {
|
||||||
|
self.handlers.get(name).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all registered handlers.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &'static dyn PmpMacro> {
|
||||||
|
self.handlers.values().copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of registered macros.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.handlers.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.handlers.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user