The Game Panel specification

ADR-0014: Class-level attributes are memoised as presence flags

Context

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.

Decision

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.

Alternatives

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

Consequences

Easier:

Harder:

Constrained:

Sources