Prototype resolution-dependent DSL compiler boundary for #958 - #1030
Draft
trippyogi wants to merge 2 commits into
Draft
Prototype resolution-dependent DSL compiler boundary for #958#1030trippyogi wants to merge 2 commits into
trippyogi wants to merge 2 commits into
Conversation
Add a non-authoritative DeferredCall path to test semantic compiler selection during resolution, exact alias-path invalidation, and Struct.new expansion into existing Operations. Includes lifecycle, identity, incremental invalidation, and in-stream resolution oracles. Generated operations remain non-authoritative pending guidance on the ordered execution model.
Open
…sion. Prove that substituting the generated Operations at the original stream position lets Foo participate in ordinary ancestor-based constant resolution (Bar::SOME_REF -> Struct::SOME_REF).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This is a draft architecture prototype for #958.
I wanted to test one question before implementing the registration API: how should Rubydex handle a DSL-shaped call when the compiler cannot be selected correctly until the receiver has semantic identity?
The prototype keeps normal
OperationBuilderbehavior authoritative, records eligible calls as deferred compiler candidates, resolves the receiver through normal constant resolution and aliases, and then emits ordinary existingOperations from a testStruct.newcompiler.I stopped before making those generated operations authoritative. That execution boundary overlaps with the ordered IR / Chunk / VM direction discussed in #798 and the incremental work in #960.
flowchart LR A[RubyOperationBuilder] --> B[Known Operation] A --> C[Deferred compiler call] C --> D[Resolver: owner + aliases] D --> E[Existing Operations] B --> F[Semantic graph] E -. authoritative execution is open .-> FCurrent prototype scope:
Example:
The normal
DefineConstant(Foo)still wins, so graph semantics do not change. The deferred call is only an observation used to test compiler selection and generated IR.This only exercises the OperationBuilder path and temporarily changes its result from
Vec<Operation>toVec<CompiledItem>to preserve ordering. I'm not proposing that public Rust API shape as settled.What this shows
Compiler selection belongs after semantic resolution
Receiver text is not enough.
The tests cover direct and root-qualified
Struct, one/multi-hop aliases, lexical shadows, wrong declaration kinds, reopened owners, multiple resolved alias targets, ambiguity removal, class/module/class transitions, and alias cycles.The reliable boundary is the resolved
DeclarationIdafter normal constant resolution and alias normalization.The existing worklist model can carry the experiment
Deferred candidates use the existing
NameDependent,Unit, andpending_workmachinery.Dependencies are limited to the receiver plus the alias path actually traversed:
Unrelated aliases/references do not wake the candidate.
The prototype duplicates a small amount of alias traversal to capture dependency provenance. I would expect a production version to expose that from the canonical resolver, especially with #960 in flight.
Existing Operations can model the tested Struct effect
For:
the test compiler emits existing operations equivalent to:
A test-only substitution at the original stream position proves that this can affect normal resolution:
Foobecomes a class with superclassStruct, owns the generated attribute, andBar < Fooresolves normally.Using the dependency chain from #958, the same in-stream oracle also resolves an ordinary
SOME_REFinsideBar < Foothrough the DSL-generatedFoo < Structchain toStruct::SOME_REF.The same oracle works for
Outer::Foo/Outer::Barwhile keeping the generated class non-lexical.This is an IR expressiveness oracle, not a production-complete implementation of
Struct.new.Where I stopped
The unresolved part is how selected compiler output becomes authoritative.
OperationApplierconsumes an ordered stream with transient state such as owner, lexical scope, visibility, preceding references, and instruction position. #1024 now preserves exact lexical constant-resolution context from a namespaceDefinition/NameId, but generic DSL execution can still depend on additional stream state.The narrow Struct expansion can replay pre-merge because it establishes most of the state it needs itself. I don't think that is enough evidence to make out-of-band replay the general model.
Applying generated semantics after
LocalGraphhas merged would also require graph surgery and reintroduce the replacement/ownership problems explored by earlier approaches.The prototype also exposes an ordering limit:
Rubydex still has a class-shaped
Structdeclaration, so the prototype selects the Struct compiler. I left source-order runtime-value tracking out of #958; it appears tied to ordered IR execution rather than compiler matching.Cost
I benchmarked the
OperationBuilderbackend against the same baseline on synthetic corpora plusruby-lsp, Rails, Tapioca, and Discourse.Real-world candidate counts for this narrow shape were low:
Real-world RSS deltas were roughly 1ΓÇô2%, with ~0.6% on candidate-free corpora. Stress tests showed cost scaling with candidate count and actual alias depth.
Windows CPU timings were noisy, so I would not draw performance conclusions from those yet.
Questions
Before taking this further, I'd like guidance on two things:
Where should a resolution-dependent compiler invocation live?
The prototype uses an ordered
CompiledItem::DeferredCall. Would this fit better as an IR instruction interpreted by the future VM / Chunk model from Add operation-based indexing pipeline #798?What ordered execution state should authoritative expansion retain?
Should execution pause/resume at the original stream position when semantic dependencies are unresolved, or should owner/visibility/reference/block state become explicitly replayable?
Those answers also affect fallback-vs-compiler ownership, reverse/unload behavior, compiler-owner bootstrap, and how dependency provenance should integrate with #960.
Deliberately out of scope
This draft does not add the public compiler registry, Ruby/C/FFI registration, Ruby callbacks, block or implicit-receiver DSLs, authoritative replay, reverse operations, or additional DSL compilers.
I want to settle the execution boundary before adding those layers.
Validation
Current branch is rebased on
main.cargo fmt --checkΓÇö passcargo clippy --lib --all-featuresΓÇö passcargo testΓÇö 1188 library + 9 CLI + 2 doctests passedoperation::deferredtests ΓÇö 33 passedThis draft is for architecture review, not as a complete fix for #958.