@typemf/generator 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typemf/generator",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -30,9 +30,9 @@
30
30
  "provenance": true
31
31
  },
32
32
  "dependencies": {
33
- "@typemf/core": "0.6.0",
34
- "@typemf/node": "0.6.0",
35
- "@typemf/xmi": "0.6.0",
33
+ "@typemf/core": "0.8.0",
34
+ "@typemf/node": "0.8.0",
35
+ "@typemf/xmi": "0.8.0",
36
36
  "nunjucks": "^3.2.4",
37
37
  "prettier": "^3.9.8"
38
38
  },
@@ -43,7 +43,7 @@ export interface {{ eClass.getName() }} extends {{ superClass.getName() if super
43
43
  {% else %}
44
44
  {% set _ = useImport("EObjectImpl", "impl", {foundational: true}) %}
45
45
  {% endif %}
46
- {% set _ = useImport("BasicEList", "impl", {foundational: true}) %}
46
+ {% if options['generate-ecore'] or hasStoredManyValuedFeature(ownFeatures) %}{% set _ = useImport("BasicEList", "impl", {foundational: true}) %}{% endif %}
47
47
  {% for feature in ownFeatures %}
48
48
  {% if feature.isMany() %}
49
49
  {% set _ = useImport("EList", "types", {foundational: true}) %}
@@ -115,72 +115,54 @@ export class {{ eClass.getName() }}Impl extends {{ (superClass.getName() + "Impl
115
115
  {% endif %}
116
116
  {% endfor %}
117
117
 
118
- {% if eClass.getName() == "EClassifier" %}
119
- {#- classifierID is real, internal dispatch bookkeeping (matching
120
- @typemf/core's own hand-written EClassifierImpl), not a modeled
121
- Ecore feature - so it's declared here, once, on EClassifier itself
122
- (inherited by every descendant: EClass, EDataType, EEnum, ...)
123
- rather than emitted by the ordinary feature-driven generation
124
- above, which has no feature to generate it from. Confirmed
125
- necessary by actually running self-hosted bootstrap code (which
126
- constructs real EClassifier-derived metaclass shells and needs to
127
- set their classifierID), not assumed - see NOTES.md. #}
118
+ {% if options['generate-ecore'] and eClass.getName() == "EClassifier" %}
119
+ {#- Ecore only: the classifier ID is bookkeeping, not a modeled feature, inherited by every
120
+ classifier. #}
128
121
  private classifierId = -1;
129
122
 
130
123
  getClassifierID(): number {
131
124
  return this.classifierId;
132
125
  }
133
126
 
127
+ /** Sets the classifier's ID; called by generated packages. */
134
128
  setClassifierID(id: number): void {
135
129
  this.classifierId = id;
136
130
  }
137
131
  {% endif %}
138
- {% if eClass.getName() == "EStructuralFeature" %}
139
- {#- featureID: the exact same real gap as classifierID above, one level
140
- down the hierarchy - internal dispatch bookkeeping (matching
141
- @typemf/core's own hand-written EStructuralFeatureImpl), not a
142
- modeled Ecore feature, declared once here on EStructuralFeature
143
- itself (inherited by EAttribute, EReference, ...). Found the same
144
- way: running self-hosted bootstrap code, which constructs real
145
- EStructuralFeature-derived metaclass shells (EAttributeImpl,
146
- EReferenceImpl) and needs to set their featureID - see NOTES.md. #}
132
+ {% if options['generate-ecore'] and eClass.getName() == "EStructuralFeature" %}
133
+ {#- Ecore only: the feature ID (bookkeeping) and the container class (getter modeled as an
134
+ operation, setter only on the implementation, used during bootstrap). #}
147
135
  private featureId = -1;
148
136
 
149
137
  getFeatureID(): number {
150
138
  return this.featureId;
151
139
  }
152
140
 
141
+ /** Sets the feature's ID; called by generated packages. */
153
142
  setFeatureID(id: number): void {
154
143
  this.featureId = id;
155
144
  }
156
- {#- containerClass: real EMF's own asymmetric design (see EStructuralFeature.getContainerClass()'s
157
- doc comment in @typemf/core) - PUBLIC getter (a real, modeled, reflectively-callable operation),
158
- IMPL-ONLY setter (never on the interface, set once during bootstrap - see epackage.njk). Not
159
- internal bookkeeping like featureID (it IS a real declared operation), but shares the same
160
- "special-cased field the normal per-operation loop must skip" shape, so isBookkeepingOperation
161
- treats it the same way (see typescript-filters.ts). #}
145
+
162
146
  private containerClass: {{ useImport("TypeScriptClass", "types", {foundational: true}) }}<unknown> | undefined;
163
147
 
164
148
  getContainerClass(): {{ useImport("TypeScriptClass", "types", {foundational: true}) }}<unknown> | undefined {
165
149
  return this.containerClass;
166
150
  }
167
151
 
152
+ /** Sets the implementation class of the declaring class; called by generated packages. */
168
153
  setContainerClass(cls: {{ useImport("TypeScriptClass", "types", {foundational: true}) }}<unknown> | undefined): void {
169
154
  this.containerClass = cls;
170
155
  }
171
156
  {% endif %}
172
- {% if eClass.getName() == "EOperation" %}
173
- {#- operationID: the exact same real gap as classifierID/featureID above - internal dispatch
174
- bookkeeping (matching @typemf/core's own hand-written EOperationImpl), not a modeled Ecore
175
- feature, declared once here on EOperation itself. Same discovery path: self-hosted bootstrap
176
- code constructs real EOperationImpl instances and needs to set their operationID - see NOTES.md
177
- (point 4). #}
157
+ {% if options['generate-ecore'] and eClass.getName() == "EOperation" %}
158
+ {#- Ecore only: the operation ID is bookkeeping, not a modeled feature. #}
178
159
  private operationId = -1;
179
160
 
180
161
  getOperationID(): number {
181
162
  return this.operationId;
182
163
  }
183
164
 
165
+ /** Sets the operation's ID; called by generated packages. */
184
166
  setOperationID(id: number): void {
185
167
  this.operationId = id;
186
168
  }
@@ -196,11 +178,12 @@ export class {{ eClass.getName() }}Impl extends {{ (superClass.getName() + "Impl
196
178
  {% if feature.isMany() %}
197
179
  {% if getter %}
198
180
  {% set _featureImports = registerBodyImports(feature) %}
181
+ {#- Computed: the cached list is read-only, since changing it would never reach the model. #}
199
182
  get{{ ucfirst(feature.getName()) }}(): EList<{{ tsScalarType(feature) }}> {
200
183
  if (this._{{ feature.getName() }}CacheGeneration !== EObjectImpl.getModelGeneration()) {
201
- this._{{ feature.getName() }}Cache = (() => {
184
+ this._{{ feature.getName() }}Cache = new {{ useImport("UnmodifiableEList", "impl", {foundational: true}) }}((() => {
202
185
  {{ getter | indent(8) }}
203
- })();
186
+ })());
204
187
  this._{{ feature.getName() }}CacheGeneration = EObjectImpl.getModelGeneration();
205
188
  }
206
189
  return this._{{ feature.getName() }}Cache!;
@@ -339,6 +322,7 @@ export class {{ eClass.getName() }}Impl extends {{ (superClass.getName() + "Impl
339
322
  {% endfor %}
340
323
 
341
324
  eGet(feature: EStructuralFeature): unknown {
325
+ this.requireOwnFeature(feature);
342
326
  switch (feature.getFeatureID()) {
343
327
  {% for feature in ownFeatures %}
344
328
  {% if featureGetter(feature) or trivialDerivedFormula(feature) %}
@@ -363,6 +347,7 @@ export class {{ eClass.getName() }}Impl extends {{ (superClass.getName() + "Impl
363
347
  }
364
348
 
365
349
  eSet(feature: EStructuralFeature, value: unknown): void {
350
+ this.requireOwnFeature(feature);
366
351
  switch (feature.getFeatureID()) {
367
352
  {% for feature in ownFeatures %}
368
353
  {% if feature.isMany() %}
@@ -445,18 +430,9 @@ export class {{ eClass.getName() }}Impl extends {{ (superClass.getName() + "Impl
445
430
  }
446
431
  }
447
432
 
448
- {#- The many-valued analog of eBasicSetValue, for the identical reason:
449
- bootstrap code needs to add to a list (eStructuralFeatures,
450
- eAnnotations, eSuperTypes, ...) before the feature's own metaclass
451
- exists to back the ordinary getter's lazy construction. Constructs
452
- the cache with no feature reference when called this way - a real,
453
- narrow, deliberate limitation: containment/opposite maintenance on
454
- a list built this way is unavailable, since there's no feature to
455
- derive it from. Acceptable specifically because only the metamodel's
456
- own self-describing bootstrap instances (EClassImpl, EAttributeImpl,
457
- etc., as constructed by {Pkg}PackageImpl) ever take this path -
458
- ordinary domain instances always go through the normal, fully-
459
- wired getter instead, never this one. #}
433
+ {% if options['generate-ecore'] %}
434
+ {#- Ecore only: the bootstrap adds to lists before the metaclasses their getters need exist. #}
435
+ /** @internal The list of the many-valued feature `featureId`, for bootstrapping the package. */
460
436
  eBootstrapList(featureId: number): BasicEList<unknown> {
461
437
  switch (featureId) {
462
438
  {% for feature in ownFeatures %}
@@ -474,8 +450,10 @@ export class {{ eClass.getName() }}Impl extends {{ (superClass.getName() + "Impl
474
450
  {% endif %}
475
451
  }
476
452
  }
453
+ {% endif %}
477
454
 
478
455
  eIsSet(feature: EStructuralFeature): boolean {
456
+ this.requireOwnFeature(feature);
479
457
  switch (feature.getFeatureID()) {
480
458
  {% for feature in ownFeatures %}
481
459
  {% if feature.isMany() %}
@@ -526,6 +504,7 @@ export class {{ eClass.getName() }}Impl extends {{ (superClass.getName() + "Impl
526
504
  }
527
505
 
528
506
  eUnset(feature: EStructuralFeature): void {
507
+ this.requireOwnFeature(feature);
529
508
  switch (feature.getFeatureID()) {
530
509
  {% for feature in ownFeatures %}
531
510
  {% if feature.isMany() and not featureGetter(feature) %}
@@ -1,4 +1,5 @@
1
1
  {% set eFactoryClassifier = findEClassByName(package, "EFactory") %}
2
+ {% set pkgClass = packageClassName(package) %}
2
3
  {% file factoryClassName(package) + ".ts" %}
3
4
  export interface {{ factoryClassName(package) }} extends {{ useImport("EFactory", "types", {foundational: true}) }} {
4
5
  {% for classifier in concreteEClassesOf(package) %}
@@ -25,6 +26,14 @@ export interface {{ factoryClassName(package) }} extends {{ useImport("EFactory"
25
26
  double-import the old, pre-refactor version of this template had to
26
27
  guard against explicitly. #}
27
28
  export class {{ factoryClassName(package) }}Impl extends {{ useImport("EFactoryImpl", "impl", {foundational: true}) }} implements {{ useImport(factoryClassName(package), "root", {foundational: false}) }} {
29
+ /**
30
+ * The factory instance, as EMF's `{{ factoryClassName(package) }}.eINSTANCE` (on the class, since
31
+ * TypeScript interfaces have no statics). Reading it constructs the package if necessary.
32
+ */
33
+ static get eINSTANCE(): {{ factoryClassName(package) }} {
34
+ return {{ useImport("get" + pkgClass + "Ref", "impl", {file: pkgClass + "Ref", foundational: false}) }}().getEFactoryInstance() as {{ factoryClassName(package) }};
35
+ }
36
+
28
37
  {% for classifier in concreteEClassesOf(package) %}
29
38
  create{{ classifier.getName() }}(): {{ useImport(classifier.getName(), "types", {foundational: false}) }} {
30
39
  return new {{ useImport(classifier.getName() + "Impl", "impl", {foundational: false}) }}();
@@ -4,13 +4,7 @@
4
4
  {% set _ = useImport(name, "types", {foundational: true}) %}
5
5
  {% endfor %}
6
6
 
7
- /**
8
- * Classifier/feature IDs, as pure numeric literals - safe to import from
9
- * anywhere (Factory, Switch) with zero risk of the circular-import hazard
10
- * that made a lazy, explicit init() necessary for the singleton itself
11
- * (see {{ pkgClass }}Impl.ts) - these are just numbers, never require
12
- * constructing anything.
13
- */
7
+ /** The classifier, feature and operation IDs; plain numbers, safe to import from anywhere. */
14
8
  export const Ids = {
15
9
  {% for classifier in package.getEClassifiers() %}
16
10
  {{ classifier.getName() }}: { self: {{ classifier.getClassifierID() }}{% if isEClass(classifier) %}{% for feature in classifier.getEStructuralFeatures() %}, {{ feature.getName() }}: {{ feature.getFeatureID() }}{% endfor %}{% for op in classifier.getEOperations() %}, op{{ loop.index0 }}: {{ op.getOperationID() }}{% endfor %}{% endif %} },
@@ -60,27 +54,27 @@ export interface {{ pkgClass }} extends {{ useImport("EPackage", "types", {found
60
54
  {% set needsEnum = false %}
61
55
  {% set needsEnumLiteral = false %}
62
56
  {% set needsAnnotation = false %}
63
- {% if package.getEAnnotations().size() > 0 %}{% set needsAnnotation = true %}{% endif %}
57
+ {% if runtimeAnnotations(package).length > 0 %}{% set needsAnnotation = true %}{% endif %}
64
58
  {% for classifier in package.getEClassifiers() %}
65
- {% if classifier.getEAnnotations().size() > 0 %}{% set needsAnnotation = true %}{% endif %}
59
+ {% if runtimeAnnotations(classifier).length > 0 %}{% set needsAnnotation = true %}{% endif %}
66
60
  {% if isEClass(classifier) %}
67
61
  {% for feature in classifier.getEStructuralFeatures() %}
68
62
  {% if isEReference(feature) %}{% set needsReference = true %}{% else %}{% set needsAttribute = true %}{% endif %}
69
- {% if feature.getEAnnotations().size() > 0 %}{% set needsAnnotation = true %}{% endif %}
63
+ {% if runtimeAnnotations(feature).length > 0 %}{% set needsAnnotation = true %}{% endif %}
70
64
  {% endfor %}
71
65
  {% for op in classifier.getEOperations() %}
72
66
  {% set needsOperation = true %}
73
- {% if op.getEAnnotations().size() > 0 %}{% set needsAnnotation = true %}{% endif %}
67
+ {% if runtimeAnnotations(op).length > 0 %}{% set needsAnnotation = true %}{% endif %}
74
68
  {% if op.getEParameters().size() > 0 %}{% set needsParameter = true %}{% endif %}
75
69
  {% for param in op.getEParameters() %}
76
- {% if param.getEAnnotations().size() > 0 %}{% set needsAnnotation = true %}{% endif %}
70
+ {% if runtimeAnnotations(param).length > 0 %}{% set needsAnnotation = true %}{% endif %}
77
71
  {% endfor %}
78
72
  {% endfor %}
79
73
  {% elif isEEnum(classifier) %}
80
74
  {% set needsEnum = true %}
81
75
  {% for literal in classifier.getELiterals() %}
82
76
  {% set needsEnumLiteral = true %}
83
- {% if literal.getEAnnotations().size() > 0 %}{% set needsAnnotation = true %}{% endif %}
77
+ {% if runtimeAnnotations(literal).length > 0 %}{% set needsAnnotation = true %}{% endif %}
84
78
  {% endfor %}
85
79
  {% else %}
86
80
  {% set needsDataType = true %}
@@ -104,10 +98,12 @@ export interface {{ pkgClass }} extends {{ useImport("EPackage", "types", {found
104
98
  {% set _ = useImport(factoryClassName(package), "root", {foundational: false}) %}
105
99
  {% set _ = useImport(factoryClassName(package) + "Impl", "impl", {foundational: false}) %}
106
100
  {% set _ = useImport("set" + pkgClass + "Ref", "impl", {file: pkgClass + "Ref", foundational: false}) %}
101
+ {% set _ = useImport("register" + pkgClass + "Initializer", "impl", {file: pkgClass + "Ref", foundational: false}) %}
107
102
 
108
103
  {#-
109
- Emits code reconstructing `element`'s real EAnnotations (source +
110
- details) onto the runtime object built at `targetExpr`. Scoped to
104
+ Emits code reconstructing `element`'s EAnnotations (source + details) onto
105
+ the runtime object built at `targetExpr`, except the generator-only ones
106
+ (see runtimeAnnotations). Scoped to
111
107
  package/classifier/feature/enum-literal only - EOperations have no
112
108
  runtime representation in the converted metamodel yet, so there's
113
109
  nowhere for an operation-level annotation to attach (see NOTES.md).
@@ -155,7 +151,7 @@ export interface {{ pkgClass }} extends {{ useImport("EPackage", "types", {found
155
151
  everywhere else in this file (the metaclass for EStringToStringMapEntry doesn't exist yet at the
156
152
  point this code runs during self-construction); the ordinary branch has no such ordering problem and
157
153
  uses the real, public API (setDetailValue) directly. #}
158
- {% for annotation in element.getEAnnotations() %}
154
+ {% for annotation in runtimeAnnotations(element) %}
159
155
  {
160
156
  const annotation = new EAnnotationImpl();
161
157
  {% if annotation.getSource() %}
@@ -207,14 +203,9 @@ export class {{ pkgClass }}Impl extends EPackageImpl implements {{ pkgClass }} {
207
203
  private static _instance: {{ pkgClass }}Impl | undefined;
208
204
 
209
205
  /**
210
- * The one explicit entry point that constructs the singleton - call it
211
- * yourself, with your own factory, before anything else in your program
212
- * touches {{ pkgClass }}Impl.eINSTANCE, to use a custom factory (e.g. one
213
- * overriding createX() to return your own derived classes). Whichever
214
- * caller gets here FIRST wins; a later call with a *different* factory
215
- * is logged and otherwise ignored, rather than silently replacing an
216
- * already-in-use singleton or throwing on a caller who didn't cause the
217
- * conflict.
206
+ * Constructs the package, if that hasn't happened yet, and returns it. To use your own factory
207
+ * (e.g. one returning subclasses), call this with it before anything reads `eINSTANCE`; a
208
+ * factory passed after construction is ignored with a warning.
218
209
  */
219
210
  static init(factory?: {{ factoryClassName(package) }}): {{ pkgClass }}Impl {
220
211
  if (!{{ pkgClass }}Impl._instance) {
@@ -235,10 +226,7 @@ export class {{ pkgClass }}Impl extends EPackageImpl implements {{ pkgClass }} {
235
226
 
236
227
  private constructor() {
237
228
  super();
238
- // Set immediately, before Pass 1 constructs any classifier (each of
239
- // which may need this reference - see {{ pkgClass }}Ref.ts's own doc
240
- // comment for why this is a late-bound reference rather than a
241
- // direct import of {{ pkgClass }}Impl.
229
+ // Set first: constructing the classifiers below already needs the package reference.
242
230
  set{{ pkgClass }}Ref(this);
243
231
 
244
232
  // Pass 1: create every classifier (empty shell - no features/supertypes
@@ -266,10 +254,7 @@ export class {{ pkgClass }}Impl extends EPackageImpl implements {{ pkgClass }} {
266
254
  {{ bootstrapSet("literal", "setLiteral", "Ids.EEnumLiteral.literal", jsString(literal.getLiteral())) }}
267
255
  {{ emitAnnotations("literal", literal) }}
268
256
  {% if options['generate-ecore'] %}
269
- // No addLiteral() here - this generated EEnumImpl doesn't have that
270
- // hand-written convenience method (it's not a reflective feature),
271
- // so the list-add and the eEnum backpointer (a real, modeled,
272
- // non-changeable opposite - see NOTES.md) are wired manually.
257
+ // eEnum is not changeable, so both ends of the opposite are set directly.
273
258
  this._{{ classifier.getName() }}.eBootstrapList(Ids.EEnum.eLiterals).add(literal);
274
259
  literal.eBasicSetValue(Ids.EEnumLiteral.eEnum, this._{{ classifier.getName() }});
275
260
  {% else %}
@@ -325,6 +310,9 @@ export class {{ pkgClass }}Impl extends EPackageImpl implements {{ pkgClass }} {
325
310
  {{ bootstrapSet(fieldExpr, "setChangeable", "Ids.EStructuralFeature.changeable", feature.isChangeable()) }}
326
311
  {{ bootstrapSet(fieldExpr, "setOrdered", "Ids.ETypedElement.ordered", feature.isOrdered()) }}
327
312
  {{ bootstrapSet(fieldExpr, "setUnique", "Ids.ETypedElement.unique", feature.isUnique()) }}
313
+ {% if feature.getDefaultValueLiteral() !== undefined %}
314
+ {{ bootstrapSet(fieldExpr, "setDefaultValueLiteral", "Ids.EStructuralFeature.defaultValueLiteral", jsString(feature.getDefaultValueLiteral())) }}
315
+ {% endif %}
328
316
  {{ fieldExpr }}.setFeatureID(Ids.{{ classifier.getName() }}.{{ feature.getName() }});
329
317
  {#- containerClass: the concrete implementation class the feature is declared on - the closest
330
318
  TypeScript has to Java's own containerClass semantics (Class<?>, e.g. EClassifier.class in the
@@ -345,11 +333,8 @@ export class {{ pkgClass }}Impl extends EPackageImpl implements {{ pkgClass }} {
345
333
  {% endfor %}
346
334
  {% endfor %}
347
335
 
348
- // Pass 2.5: operations and their parameters - real EOperation/EParameter objects, not just the
349
- // generated METHODS (which already existed) - eOperations used to be permanently empty at runtime;
350
- // this is what actually populates it, the same way pass 2 populates eStructuralFeatures. Kept as
351
- // its own pass, after every classifier and feature exists (an operation's return type or a
352
- // parameter's type can reference either), rather than folded into the loop above.
336
+ // Pass 2.5: operations and their parameters, after every classifier and feature exists, since
337
+ // their types can refer to any of them.
353
338
  {% for classifier in eClassesOf(package) %}
354
339
  {% for op in classifier.getEOperations() %}
355
340
  {% set opIndex = loop.index0 %}
@@ -406,21 +391,9 @@ export class {{ pkgClass }}Impl extends EPackageImpl implements {{ pkgClass }} {
406
391
  {% endfor %}
407
392
  {% endfor %}
408
393
 
409
- // Pass 5 (recompute EAll* caches, ordinary mode only) removed - stale post-swap.
410
- // @typemf/core's own EClassImpl is now itself generated code (see NOTES.md's point 6/7
411
- // write-ups), using the same automatic, get-bodied caching (EObjectImpl.getModelGeneration(),
412
- // point 1) as self-hosted output always did - there is no recomputeAllLists() method to call
413
- // anymore, in either mode, and none is needed.
414
394
 
415
- // Pass 6: set the package's own name/nsURI/nsPrefix/annotations last,
416
- // deliberately - these go through the ordinary reflective setters
417
- // (this.setName(), etc.), which need the "name"/etc. feature's own
418
- // metaclass object to already exist (it's built above, in the passes
419
- // this follows). Calling them earlier - before those metaclasses
420
- // exist - is a genuine bootstrap-ordering bug, confirmed directly by
421
- // reproducing it: this line order isn't a style choice, the metaclass
422
- // shells and feature wiring above are a real, load-bearing
423
- // precondition for the reflective setter calls below to work at all.
395
+ // Pass 5: the package's own name, nsURI, nsPrefix and annotations, last: the setters used here
396
+ // need the features built above.
424
397
  this.setName({{ jsString(package.getName()) }});
425
398
  this.setNsURI({{ jsString(package.getNsURI()) }});
426
399
  this.setNsPrefix({{ jsString(package.getNsPrefix()) }});
@@ -476,4 +449,9 @@ export class {{ pkgClass }}Impl extends EPackageImpl implements {{ pkgClass }} {
476
449
  {% endif %}
477
450
  {% endfor %}
478
451
  }
452
+
453
+ // Lets get{{ pkgClass }}Ref() construct the package on first use.
454
+ register{{ pkgClass }}Initializer(() => {
455
+ void {{ pkgClass }}Impl.eINSTANCE;
456
+ });
479
457
  {% endfile %}
@@ -2,43 +2,41 @@
2
2
  import type { {{ packageClassName(package) }} } from '../{{ packageClassName(package) }}.js';
3
3
 
4
4
  /**
5
- * A late-bound reference to the {{ packageClassName(package) }} singleton,
6
- * set once by {{ packageClassName(package) }}Impl's own constructor after
7
- * it finishes constructing itself.
8
- *
9
- * Every generated class needs a reference to the package singleton (for
10
- * eClass(), for feature-ID lookups in field initializers/lazy getters,
11
- * etc.) - and {{ packageClassName(package) }}Impl itself constructs
12
- * (nearly) every one of those classes to build the runtime metamodel. For
13
- * an ordinary generated package that mutual reference is harmless: the
14
- * package singleton already exists in full by the time anything else is
15
- * ever constructed. But self-hosting a metamodel that describes itself
16
- * (as with real Ecore.ecore) makes this a genuine, confirmed hazard: a
17
- * real class along the package's own superclass chain, or a class that is
18
- * ALSO the superclass of something the package constructs directly (the
19
- * "EFactory unification" / "EPackage unification" cases), can end up
20
- * needing this reference while {{ packageClassName(package) }}Impl is
21
- * still in the middle of constructing itself - a genuine circular
22
- * dependency, not merely an ordering preference, confirmed directly
23
- * against a real failing bundle ("Class extends value undefined is not a
24
- * constructor or null") while building this generator, not assumed.
25
- *
26
- * This module has only a type-only import (erased at compile time, zero
27
- * runtime footprint) - so importing FROM this module can never itself
28
- * participate in a circular dependency, unlike importing
29
- * {{ packageClassName(package) }}Impl (or anything re-exporting it)
30
- * directly.
5
+ * A reference to the {{ packageClassName(package) }} singleton that every generated class of the package
6
+ * reads instead of importing {{ packageClassName(package) }}Impl, which would be a circular import: the
7
+ * package constructs those classes while it constructs itself. This module only has a type
8
+ * import, so it can be imported from anywhere.
31
9
  */
32
10
  let ref: {{ packageClassName(package) }} | undefined;
33
11
 
12
+ /** Constructs the package on first use; registered when {{ packageClassName(package) }}Impl.ts is evaluated. */
13
+ let initializer: (() => void) | undefined;
14
+ let initializing = false;
15
+
34
16
  export function set{{ packageClassName(package) }}Ref(pkg: {{ packageClassName(package) }}): void {
35
17
  ref = pkg;
36
18
  }
37
19
 
20
+ export function register{{ packageClassName(package) }}Initializer(init: () => void): void {
21
+ initializer = init;
22
+ }
23
+
38
24
  export function get{{ packageClassName(package) }}Ref(): {{ packageClassName(package) }} {
25
+ // `initializing` turns a reentrant call during construction into the error below.
26
+ if (!ref && initializer && !initializing) {
27
+ initializing = true;
28
+ try {
29
+ initializer();
30
+ } finally {
31
+ initializing = false;
32
+ }
33
+ }
39
34
  if (!ref) {
40
35
  throw new Error(
41
- 'get{{ packageClassName(package) }}Ref() called before the {{ packageClassName(package) }} singleton finished constructing itself.'
36
+ 'get{{ packageClassName(package) }}Ref() called before the {{ packageClassName(package) }} singleton finished constructing itself, and no ' +
37
+ 'initializer was registered to construct it on demand - this means {{ packageClassName(package) }}Impl.ts\'s own module ' +
38
+ 'was never evaluated at all (an unusual bundler/tree-shaking setup). Call {{ packageClassName(package) }}Impl.eINSTANCE ' +
39
+ 'or .init() yourself first, or check that this package is marked side-effectful.'
42
40
  );
43
41
  }
44
42
  return ref;
@@ -1,22 +1,8 @@
1
1
  {% file "util/" + typeGuardsClassName(package) + ".ts" %}
2
2
  /**
3
- * Runtime type guards, one per classifier declared in types/ - the
4
- * reflective counterpart to a hand-written structural guard like
5
- *
6
- * function isReference(feature: EStructuralFeature): feature is EReference {
7
- * return "isContainment" in feature;
8
- * }
9
- *
10
- * Structural (duck-typing) checks like that one work when a single
11
- * classifier has some genuinely distinguishing own member - but nothing
12
- * guarantees an arbitrary generated classifier has one (a subclass might
13
- * add no own features at all, inheriting everything). These guards check
14
- * the object's REAL runtime EClass against the metamodel's own
15
- * eAllSuperTypes reflectively instead (via isSuperTypeOf()), which is
16
- * correct for every classifier unconditionally, including abstract ones
17
- * (correctly true for any instance of a concrete subclass) and dynamic
18
- * instances (no dependency on the object's actual JS class/prototype at
19
- * all, unlike `instanceof`).
3
+ * Type guards for the classifiers of the package. They check an object's class with
4
+ * `isSuperTypeOf()`, so they also work for abstract classes, interfaces and dynamic objects,
5
+ * unlike `instanceof`.
20
6
  */
21
7
  function hasEClass(obj: unknown): obj is {{ useImport("EObject", "types", {foundational: true}) }} {
22
8
  return obj !== null && typeof obj === 'object' && typeof (obj as { eClass?: unknown }).eClass === 'function';