You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ExportGeneratorX derives the Java package of all generated artifacts (ExportedNamesProvider, ResourceDescriptionManager/Strategy/Constants, FingerprintComputer, FragmentProvider) from the .export file's URI segments. Meanwhile ExportFragment2 (ExportFragment2.java:53-62) binds the Guice contract for those same types from the grammar: getRuntimeBasePackage(grammar) + ".naming"/".resource" + GrammarUtil.getSimpleName(grammar) + suffix.
The two only agree by convention — when the .export file sits beside its grammar under src/<grammar-package>/ with the grammar's simple name as its file stem. Any other layout (Maven src/main/java/... roots, moved files, renamed stems) produces bindings that point at classes that don't exist.
The code has acknowledged this gap since the beginning, in two generations of TODO comments that this issue formalizes and supersedes:
TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) — on all six accessors, introduced with the builder participant itself (852664f8b, 2017-11-24), when per-model generation moved from language-build time into the incremental builder and lost access to the grammar-derived naming context the MWE2 fragment kept using.
TODO we still need to add a package to the models. Extension models already have a name in contrast to cases above — on getExportFeatureExtension, introduced with export extensions (724fd1e9b, 2018-02-07), which added the optional export extension name for grammar header: a simple name for extensions, still no package.
The PRs implementing this issue should remove these TODOs in favor of a reference here.
A third failure mode is visible in every test run today: getGrammar()'s sibling-file lookup uses a throwing demand-load (resourceSet.getResource(..., true)), so building the headerless test .export files logs a BuilderParticipant ERROR (Resource '/TEST/<name>.xtext' does not exist, via ExportedNamesProviderGenerator) and silently aborts generation for that file — ~75 such entries in one current full-test log. Notably, the caller is already written null-tolerant (grammar?.name ?: ...); only the resolver throws instead of returning null.
#1281 hardens the URI derivation (no crash on short URIs, no invalid/empty packages) but deliberately does not change the identity model.
Design: role-aware identity ladder
Declared model identity (future, optional syntax — see open questions): qualifier → base package, simple name → class stem. The only mechanism that makes an export model location-independent, and the only correct option for contributor-owned extension models that must not squat in the host language's namespace.
Grammar package — default for the primary (!model.extension) model: qualifier of the resolved grammar's qualified name. This is not a new policy; it aligns the generator with what ExportFragment2 already promises Guice.
Make getGrammar() a defensive resolver: guard a null resource set, catch demand-load failures, reject unresolved proxies, return null instead of throwing; audit the few callers for null-tolerance (the names provider already is). This fixes the test-log ERROR noise and lets generation actually execute over test fixtures, and it de-risks step (2) of the ladder: resolution failure must select the fallback, never fail generation — and must not make output identity oscillate between builds.
Constraints established during design review (two independent reviews)
Never apply (2) to extension models: they live in contributor bundles; host-grammar packages there create OSGi split packages and wrong ownership. Semantic target (for MyGrammar) ≠ code ownership.
Documented behavior change: under (2), src/main/java/<pkg>/... layouts change from main.java.<pkg> to <pkg> — a fix relative to the Guice bindings, but it supersedes the legacy URI contract that fix: derive a valid package for shallow export model URIs in ExportGeneratorX #1281's tests intentionally preserve; those tests and their comments move together with this change.
Simple-name alignment (follow-up candidate): class stems still come from the file name while the fragment expects the grammar's simple name — same split-brain, second axis.
End-to-end generation: a real .export + grammar pair asserting the generated source itself (today nothing asserts generator output; generation aborts silently in tests pre-phase-0).
Testing note:ExportGeneratorXTest (added in #1281, extended in #1459) pins the current URI-derivation shape table — crash-free single-segment handling, valid-project-name/generated fallbacks, and byte-for-byte preservation of the ≥5-segment derivation. When the identity ladder lands, those expectations describe only the URI-fallback rung: keep them as the fallback-rung spec, and add cases for the new rungs (declared model identity; grammar package for primary non-extension sources; extensions must not inherit host-grammar packages). The test's synthetic-resource technique (no fixtures, getTestSourceFileName() -> null) is the intended pattern for the new cases.
Open questions
Syntax for (1): widen name to a dotted rule (Scope-style DottedID; note this grammar's existing QualifiedID is ::-separated), or a separate optional package clause that leaves existing extension class names untouched? Needs maintainer input; requires MWE2 regen (verified viable: ~12 files, export bundles only, no ecore/genmodel movement since both rules return EString).
Validation rules for declared identity (valid Java package + identifier, ambiguity with file location).
Sequencing
#1281 ships first as the floor (independently proven). Phase 0 is a small generator-only PR (in preparation). Step (2) is a further small generator-only PR. Step (1) is language surface and can wait for a concrete modularization consumer.
Problem
ExportGeneratorXderives the Java package of all generated artifacts (ExportedNamesProvider, ResourceDescriptionManager/Strategy/Constants, FingerprintComputer, FragmentProvider) from the.exportfile's URI segments. MeanwhileExportFragment2(ExportFragment2.java:53-62) binds the Guice contract for those same types from the grammar:getRuntimeBasePackage(grammar) + ".naming"/".resource"+GrammarUtil.getSimpleName(grammar)+ suffix.The two only agree by convention — when the
.exportfile sits beside its grammar undersrc/<grammar-package>/with the grammar's simple name as its file stem. Any other layout (Mavensrc/main/java/...roots, moved files, renamed stems) produces bindings that point at classes that don't exist.The code has acknowledged this gap since the beginning, in two generations of TODO comments that this issue formalizes and supersedes:
TODO this is a hack; to support modularization we should probably add name to export models (as with scope models)— on all six accessors, introduced with the builder participant itself (852664f8b, 2017-11-24), when per-model generation moved from language-build time into the incremental builder and lost access to the grammar-derived naming context the MWE2 fragment kept using.TODO we still need to add a package to the models. Extension models already have a name in contrast to cases above— ongetExportFeatureExtension, introduced with export extensions (724fd1e9b, 2018-02-07), which added the optionalexport extension name for grammarheader: a simple name for extensions, still no package.The PRs implementing this issue should remove these TODOs in favor of a reference here.
A third failure mode is visible in every test run today:
getGrammar()'s sibling-file lookup uses a throwing demand-load (resourceSet.getResource(..., true)), so building the headerless test.exportfiles logs aBuilderParticipantERROR (Resource '/TEST/<name>.xtext' does not exist, viaExportedNamesProviderGenerator) and silently aborts generation for that file — ~75 such entries in one current full-test log. Notably, the caller is already written null-tolerant (grammar?.name ?: ...); only the resolver throws instead of returning null.#1281 hardens the URI derivation (no crash on short URIs, no invalid/empty packages) but deliberately does not change the identity model.
Design: role-aware identity ladder
!model.extension) model: qualifier of the resolved grammar's qualified name. This is not a new policy; it aligns the generator with whatExportFragment2already promises Guice.Phase 0 — independently shippable: non-throwing grammar resolution
Make
getGrammar()a defensive resolver: guard a null resource set, catch demand-load failures, reject unresolved proxies, return null instead of throwing; audit the few callers for null-tolerance (the names provider already is). This fixes the test-log ERROR noise and lets generation actually execute over test fixtures, and it de-risks step (2) of the ladder: resolution failure must select the fallback, never fail generation — and must not make output identity oscillate between builds.Constraints established during design review (two independent reviews)
for MyGrammar) ≠ code ownership.src/main/java/<pkg>/...layouts change frommain.java.<pkg>to<pkg>— a fix relative to the Guice bindings, but it supersedes the legacy URI contract that fix: derive a valid package for shallow export model URIs in ExportGeneratorX #1281's tests intentionally preserve; those tests and their comments move together with this change.Test matrix
ResourceImpl, missing sibling grammar, unresolvedtargetGrammarproxy, resolution changing between rebuilds..export+ grammar pair asserting the generated source itself (today nothing asserts generator output; generation aborts silently in tests pre-phase-0).Testing note:
ExportGeneratorXTest(added in #1281, extended in #1459) pins the current URI-derivation shape table — crash-free single-segment handling, valid-project-name/generatedfallbacks, and byte-for-byte preservation of the ≥5-segment derivation. When the identity ladder lands, those expectations describe only the URI-fallback rung: keep them as the fallback-rung spec, and add cases for the new rungs (declared model identity; grammar package for primary non-extension sources; extensions must not inherit host-grammar packages). The test's synthetic-resource technique (no fixtures,getTestSourceFileName() -> null) is the intended pattern for the new cases.Open questions
nameto a dotted rule (Scope-styleDottedID; note this grammar's existingQualifiedIDis::-separated), or a separate optionalpackageclause that leaves existing extension class names untouched? Needs maintainer input; requires MWE2 regen (verified viable: ~12 files, export bundles only, no ecore/genmodel movement since both rules returnEString).Sequencing
#1281 ships first as the floor (independently proven). Phase 0 is a small generator-only PR (in preparation). Step (2) is a further small generator-only PR. Step (1) is language surface and can wait for a concrete modularization consumer.
🤖 Filed by Claude Code on João's behalf.