The container consults its instance cache before it reflects on a class, so it cannot tell whether a class is
liminal at the point it checks the cache. A class carrying #[Liminal] is stored in the liminal cache but looked up
in the ordinary one, and never produces a cache hit. Fixing that requires the cache check to know class-level
liminality, and reflecting on every resolution to find out would defeat the purpose of checking the cache first.
The three class-level attributes from RFC-0001, NoResolution,
Lazy and Liminal, are markers. Resolution only tests them for presence and never reads a property from any of
them, so no instance is needed. ReflectionAttribute::getName() does not instantiate the attribute, which a probe
attribute with a counting constructor confirms. That matters once the module system lands and third-party classes
carry attributes from unrelated libraries that the container has no business constructing.
The change is a correctness prerequisite for fixing liminal cache misses, not a performance change.
The container memoises class-level attribute lookups for NoResolution, Lazy and Liminal as a map of booleans
keyed by class-string, populated by a single unfiltered getAttributes() pass matched on getName(). No attribute
is instantiated, and no reflection happens on the cache-check path after the first resolution of a class.
newInstance() runs user code, and third-party classes will carry
attributes the container never asked about, which can throw and can be slow.ReflectionClass instances. It saves about 0.4 µs from a 98.6 µs resolution, roughly 0.4%, because
parsed class metadata is already shared and the reflector is a thin handle over it. It is written down here so
that it is not revisited.Measured on a Root(Mid, Leaf, Mid) and Mid(Leaf, Leaf) graph of 8 resolve calls and 7 constructor parameters:
| Operation | Cost |
|---|---|
Three filtered getAttributeInstance() calls per resolution, before this decision |
1.750 µs |
One unfiltered scan matched on getName() |
0.692 µs |
| A memoised array hit | 0.277 µs |
Easier:
Harder:
createDependency() will remain the actual hot path, at about 3 µs per
constructor parameter with 64% of that in its four getAttributeInstance() calls. Caching the whole resolution
plan measures about fifteen times cheaper there, cutting roughly 20% from a representative resolution, and is left
to be considered separately.Constrained:
created and decided.