ulo
A macro that cannot see a type's impl block should emit the same call for every type and let method resolution choose the definition, rather than detect what the type defines.
What a macro can see
A procedural macro attached to a struct receives the struct's tokens and nothing else. The impl block beside it, where a NestJS-style provider's constructor and lifecycle hooks are written, is not in its input. NestJS reads those at runtime by reflection. Rust has no reflection.
Dispatch instead of detection
- Detecting whether a type has a constructor, or a given hook, is not possible: the impl is not in the input, and specialization, the feature that would let generated code branch on what a type implements, is unstable.
- A blanket trait gives every type a no-op default for each hook.
- The attribute on a user-written hook emits an inherent method of the same name that forwards to it.
- Generated startup code calls that name on every provider. Where the inherent method exists it wins method resolution; otherwise the no-op runs.
- Five hooks are called on every provider whether it defines them or not. The same mechanism, returning an Option, decides which enhancer roles a type registers.
The concrete-type rule
- The blanket impl also covers Arc<Struct>, and the provider wrapper holds an Arc. Method syntax on the wrapper binds the no-op at the Arc layer; the hook never fires and nothing reports it.
- Generated code uses fully qualified call syntax.
- Inside a generic function the bound is erased and the dispatch always takes the fallback.
Boot-time wiring
- The container is a registry keyed by each type's name, built in per-module topological order.
- A missing provider or a dependency cycle fails at boot with the chain named in the error, not at compile time.
- For a while two code paths built those keys in two formats, and an alias worked where the written generic did not. One function now produces every key.
Where it stops holding
The position holds while the generated call is made where the concrete type is known. Through a smart pointer with method syntax, or inside generic code, the dispatch returns the no-op and reports nothing. A framework that must call hooks from generic code needs either the detection it cannot have or a trait the user implements by hand.