[compiler] Add Realm.asProgram() facade for chained mutation pipelines#10693
[compiler] Add Realm.asProgram() facade for chained mutation pipelines#10693FionaBronwen wants to merge 1 commit into
Conversation
Today the mutator framework returns a Realm. Realm holds cloned types and a
layered state map but does not implement Program, so it cannot be fed back
into the next mutation stage or consumed by emitters that expect a Program.
This change makes a mutated realm consumable as a Program:
- Realm gains an optional cloned global namespace slot (#globalNamespace)
with a public getter and an internal setter (setGlobalNamespace). The
mutator engine records the cloned global on the realm when it runs a
namespace-rooted mutation.
- Realm.asProgram() returns a Program-shaped facade. Pass-through members
(compilerOptions, host, checker, resolver, sourceFiles, etc.) come from
the parent program. Realm-aware overrides reflect realm-local state:
* getGlobalNamespaceType -> the cloned global if recorded, otherwise
the parent's global
* stateMap, stateSet -> realm-layered views (writes are realm-local;
reads fall back to the parent and to original types via the clone
back-link)
* stateMaps, stateSets -> proxy views that route per-key lookups
through the layered stateMap/stateSet helpers
- StateMapRealmView and the new StateSetRealmView fall back to the parent's
state keyed by the original type when reading on a clone. This is
achieved through a non-enumerable Symbol-for back-link recorded at clone
time (Realm.cloneIntoRealm), so decorator state set on the original
remains visible under the clone without an eager copy.
Multiple stages can now be chained as:
const stage1 = mutateSubgraphWithNamespace(program, ms1, program.getGlobalNamespaceType());
const p1 = stage1.realm?.asProgram() ?? program;
const stage2 = mutateSubgraphWithNamespace(p1, ms2, p1.getGlobalNamespaceType());
...
A new test file (test/experimental/realm-as-program.test.ts) exercises the
facade end-to-end: getGlobalNamespaceType returns the cloned tree,
navigateProgram on the facade walks the mutated tree, and a two-stage chain
sees both stage 1 and stage 2 mutations. All existing experimental tests
continue to pass.
There was a problem hiding this comment.
an intresting approach, I had some branch where I experimented with something that I believe also solve the same issue you are trying to solve here but introducing the concept of type graph. A program can then have multiple type graphs which can have realms. But the idea unlike realm which are mixed a type graph is a completely independent version. It felt like it worked well for what my pr was doing(adding typespec emitter options instead of json schema) but also was opening the door for having those type graph mutator that would take a type graph and return a whole new one
There was a problem hiding this comment.
@timotheeguerin ooh I like this! Do you have any plans to land the TypeGraph approach? As you mentioned, the completely independent TypeGraphs within a program would be super useful for how we'd like to use mutators.
Overview
Today the mutator framework returns a
Realm.Realmholds cloned types and a layered state map but does not implementProgram, so it cannot be fed back into a subsequent mutation stage or consumed by emitters that expect aProgram.This change makes a mutated realm consumable as a
Program:Realmgains an optional cloned global namespace slot (#globalNamespace) with a public getter and an internal setter (setGlobalNamespace). The mutator engine records the cloned global on the realm when it runs a namespace-rooted mutation.Realm.asProgram()returns aProgram-shaped facade. Pass-through members (compilerOptions,host,checker,resolver,sourceFiles, etc.) come from the parent program. Realm-aware overrides reflect realm-local state:getGlobalNamespaceType-> the cloned global if recorded, otherwise the parent's globalstateMap,stateSet-> realm-layered views (writes are realm-local; reads fall back to the parent and to original types via the clone back-link)stateMaps,stateSets-> proxy views that route per-key lookups through the layeredstateMap/stateSethelpersStateMapRealmViewand the newStateSetRealmViewfall back to the parent's state keyed by the original type when reading on a clone. This is achieved through a non-enumerable Symbol-for back-link recorded at clone time (Realm.cloneIntoRealm), so decorator state set on the original remains visible under the clone without an eager copy.Multiple mutation stages can now be chained as:
A new test file (
test/experimental/realm-as-program.test.ts) exercises the facade end-to-end:getGlobalNamespaceTypereturns the cloned tree,navigateProgramon the facade walks the mutated tree, and a two-stage chain sees both stage 1 and stage 2 mutations. All existing experimental tests continue to pass.