@@ -349,28 +349,44 @@ The problem is needing them for every runtime analysis thread.
349349- No stack overflows on the full test suite or the largest available
350350 project.
351351
352- #### Phase 4g: Carry ` Atom ` in ` PhpType::Named `
353-
354- ** Impact: Low-Medium. Effort: Medium.**
355-
356- Deferred from Phase 4d. With the FQN now cached, the residual
357- per-substitution string cost is ` PhpType::Named(cls.fqn().to_string()) `
358- (and the equivalent patterns in ` template_subs.rs ` , ` return_types.rs ` ,
359- ` inheritance/mod.rs ` , ` forward_walk/scope_state.rs ` ,
360- ` rhs_resolution/mod.rs ` , and the Laravel builder providers). Each of
361- these turns a cached ` Atom ` back into an owned ` String ` because
362- ` PhpType::Named ` holds a ` String ` .
363-
364- Changing ` PhpType::Named ` (and the analogous per-member cache keys such
365- as ` format!("{}::{}", class_fqn, method.name) ` in ` target_cache.rs ` /
366- ` property_access.rs ` ) to carry ` Atom ` would let the resolution pipeline
367- thread interned names end-to-end without re-allocating. This touches a
368- core enum used throughout the type engine, so it is a standalone
369- refactor rather than part of the 4d fqn-cache change.
352+ #### Phase 4g: Carry ` Atom ` in ` PhpType::Named ` ✓
353+
354+ Changed ` PhpType::Named ` to hold an ` Atom ` (interned string) instead of
355+ a ` String ` . ` Named ` values are drawn from a bounded set (class names,
356+ keywords, template parameters, ` $this ` ) and are compared and cloned
357+ throughout the substitution hot path, so pointer-sized equality and
358+ free (` Copy ` ) cloning pay off; literal scalar values live in
359+ ` PhpType::Literal ` , so the interner is never fed unbounded free text.
360+ The substitution sites the FQN cache (Phase 4d) fed back into a fresh
361+ ` String ` — ` PhpType::Named(cls.fqn().to_string()) ` in ` template_subs.rs ` ,
362+ ` return_types.rs ` , ` inheritance/mod.rs ` , ` rhs_resolution/mod.rs ` , and the
363+ Laravel providers — now thread the cached ` Atom ` end-to-end
364+ (` PhpType::Named(cls.fqn()) ` ) with no reallocation. ` Atom ` 's
365+ ` Deref<Target = str> ` means the ~ 160 read sites (comparisons, keyword
366+ checks, ` base_name ` extraction) were unaffected; only genuine
367+ ` String ` -typed sinks (` Vec<String> ` keys, ` Generic ` 's ` String ` field)
368+ needed an explicit ` .to_string() ` .
369+
370+ #### Phase 4h: Intern composite per-member cache keys
371+
372+ ** Impact: Low. Effort: Low.**
373+
374+ ` target_cache.rs ` (` BODY_INFER_MEMO ` , the re-entry set) and
375+ ` property_access.rs ` key thread-local caches on
376+ ` format!("{}::{}", class_fqn, method) ` . These allocate a ` String ` per
377+ lookup. Interning them into a single ` Atom ` key would remove the
378+ allocation, but the key space is the ` FQN × member ` cartesian product,
379+ which is effectively unbounded — feeding it to the global ` ustr `
380+ interner would leak one entry per distinct pair for the process
381+ lifetime (the exact "constructed once, never compared" case
382+ ` atom.rs ` warns against). Any fix here should therefore use a
383+ composite ` (Atom, Atom) ` tuple key (both halves already interned,
384+ no new interner entries) rather than interning the joined string.
370385
371386** Success criteria:**
372- - ` PhpType::Named ` carries an interned name (no ` String `
373- reallocation on the substitution hot path).
387+ - The ` "FQN::member" ` cache keys no longer allocate a ` String ` per
388+ lookup.
389+ - No net growth in the global interner.
374390- No test regressions.
375391
376392---
0 commit comments