@sap/cds-compiler 4.2.4 → 4.3.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/bin/cdsc.js +8 -0
  3. package/bin/cdshi.js +3 -3
  4. package/doc/CHANGELOG_BETA.md +7 -0
  5. package/lib/api/main.js +19 -0
  6. package/lib/base/location.js +16 -0
  7. package/lib/base/message-registry.js +47 -16
  8. package/lib/base/messages.js +49 -38
  9. package/lib/base/model.js +1 -1
  10. package/lib/checks/checkPathsInStoredCalcElement.js +83 -0
  11. package/lib/checks/existsExpressionsOnlyForeignKeys.js +71 -0
  12. package/lib/checks/existsMustEndInAssoc.js +27 -0
  13. package/lib/checks/onConditions.js +47 -1
  14. package/lib/checks/validator.js +10 -1
  15. package/lib/compiler/assert-consistency.js +23 -15
  16. package/lib/compiler/base.js +31 -14
  17. package/lib/compiler/builtins.js +21 -20
  18. package/lib/compiler/checks.js +36 -49
  19. package/lib/compiler/define.js +71 -91
  20. package/lib/compiler/extend.js +27 -25
  21. package/lib/compiler/finalize-parse-cdl.js +1 -1
  22. package/lib/compiler/generate.js +67 -87
  23. package/lib/compiler/kick-start.js +7 -5
  24. package/lib/compiler/populate.js +32 -30
  25. package/lib/compiler/propagator.js +2 -0
  26. package/lib/compiler/resolve.js +29 -25
  27. package/lib/compiler/shared.js +57 -31
  28. package/lib/compiler/tweak-assocs.js +203 -22
  29. package/lib/compiler/utils.js +0 -18
  30. package/lib/gen/Dictionary.json +10 -4
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/languageParser.js +3 -3
  33. package/lib/inspect/inspectPropagation.js +2 -1
  34. package/lib/json/from-csn.js +63 -28
  35. package/lib/json/to-csn.js +23 -13
  36. package/lib/language/antlrParser.js +1 -1
  37. package/lib/language/errorStrategy.js +5 -1
  38. package/lib/language/genericAntlrParser.js +67 -61
  39. package/lib/main.d.ts +26 -1
  40. package/lib/main.js +2 -1
  41. package/lib/model/csnRefs.js +1 -0
  42. package/lib/model/csnUtils.js +28 -0
  43. package/lib/model/revealInternalProperties.js +3 -9
  44. package/lib/optionProcessor.js +17 -1
  45. package/lib/render/toCdl.js +1 -1
  46. package/lib/transform/db/associations.js +3 -4
  47. package/lib/transform/db/backlinks.js +293 -0
  48. package/lib/transform/db/expansion.js +9 -7
  49. package/lib/transform/db/flattening.js +3 -2
  50. package/lib/transform/db/rewriteCalculatedElements.js +1 -67
  51. package/lib/transform/db/transformExists.js +3 -58
  52. package/lib/transform/db/views.js +8 -14
  53. package/lib/transform/effective/.eslintrc.json +4 -0
  54. package/lib/transform/effective/associations.js +101 -0
  55. package/lib/transform/effective/main.js +88 -0
  56. package/lib/transform/effective/misc.js +61 -0
  57. package/lib/transform/effective/queries.js +42 -0
  58. package/lib/transform/effective/types.js +121 -0
  59. package/lib/transform/forRelationalDB.js +12 -235
  60. package/lib/transform/localized.js +22 -3
  61. package/lib/transform/parseExpr.js +7 -3
  62. package/lib/transform/transformUtils.js +5 -22
  63. package/lib/transform/translateAssocsToJoins.js +42 -38
  64. package/lib/transform/universalCsn/universalCsnEnricher.js +17 -1
  65. package/package.json +1 -2
  66. package/lib/language/language.g4 +0 -3260
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+
3
+ const { requireForeignKeyAccess } = require('../checks/onConditions');
4
+
5
+ /**
6
+ * Check that associations in filters (in an exists expression) are only fk-accesses. Everything else is forbidden.
7
+ *
8
+ * @param {CSN.Artifact} parent
9
+ * @param {string} name
10
+ * @param {Array} expr
11
+ */
12
+ function forbidAssocInExists( parent, name, expr ) {
13
+ for (let i = 0; i < expr.length - 1; i++) {
14
+ if (expr[i] === 'exists' && expr[i + 1].ref) {
15
+ i++;
16
+ const current = expr[i];
17
+
18
+ const { _links } = expr[i];
19
+
20
+ const assocs = _links.filter(link => link.art?.target).map(link => current.ref[link.idx]);
21
+
22
+ checkForInvalidAssoc.call(this, assocs);
23
+ }
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Check that associations in filters (in an exists expression) are only fk-accesses. Everything else is forbidden.
29
+ *
30
+ * @param {object[]} assocs Array of refs of assocs - possibly with a .where to check
31
+ */
32
+ function checkForInvalidAssoc( assocs ) {
33
+ for (const assoc of assocs) {
34
+ if (assoc.where) {
35
+ for (let i = 0; i < assoc.where.length; i++) {
36
+ const part = assoc.where[i];
37
+
38
+ if (part._links && !(assoc.where[i - 1] && assoc.where[i - 1] === 'exists')) {
39
+ for (const link of part._links) {
40
+ if (link.art && link.art.target) {
41
+ if (link.art.keys) { // managed - allow FK access
42
+ const next = part._links[link.idx + 1];
43
+ if (next !== undefined) { // there is a next path step - check if it is a fk
44
+ requireForeignKeyAccess(part, i, (errorIndex) => {
45
+ const { ref } = assoc.where[part.$path[part.$path.length - 1]];
46
+ this.error('ref-expecting-foreign-key', part.$path, { alias: ref[errorIndex], id: assoc.id, name: ref[link.idx] });
47
+ });
48
+ }
49
+ else { // no traversal, ends on managed
50
+ this.error('ref-unexpected-assoc', part.$path, { '#': 'managed-filter', id: assoc.id, name: assoc.where[part.$path[part.$path.length - 1]].ref[link.idx] });
51
+ }
52
+ }
53
+ else { // unmanaged - always wrong
54
+ this.error('ref-unexpected-assoc', part.$path, { '#': 'unmanaged-filter', id: assoc.id, name: assoc.where[part.$path[part.$path.length - 1]].ref[link.idx] });
55
+ }
56
+ // Recursively drill down if the assoc-step has a filter
57
+ if (part.ref[link.idx].where)
58
+ checkForInvalidAssoc.call(this, [ part.ref[link.idx] ]);
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ module.exports = {
68
+ having: forbidAssocInExists,
69
+ where: forbidAssocInExists,
70
+ xpr: forbidAssocInExists,
71
+ };
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * A path following an “exists” predicate must always end in an association.
5
+ *
6
+ * @param {object} parent
7
+ * @param {string} prop
8
+ * @param {Array} expression
9
+ * @param {CSN.Path} path
10
+ */
11
+ function existsMustEndInAssoc( parent, prop, expression, path ) {
12
+ for (let i = 0; i < expression?.length - 1; i++) {
13
+ if (expression[i] === 'exists') {
14
+ const next = expression[i + 1];
15
+ const { _art } = next;
16
+ const errorPath = path.concat([ prop, i ]);
17
+ if (!next.SELECT && !_art?.target)
18
+ this.error('ref-expecting-assoc', errorPath, { elemref: next, type: _art.type });
19
+ }
20
+ }
21
+ }
22
+
23
+ module.exports = {
24
+ having: existsMustEndInAssoc,
25
+ where: existsMustEndInAssoc,
26
+ xpr: existsMustEndInAssoc,
27
+ };
@@ -172,8 +172,16 @@ function validateOnCondition( member, memberName, property, path ) {
172
172
  * If a fk-step is missing, `errorIndex` will be `> parent.ref.length`.
173
173
  */
174
174
  function requireForeignKeyAccess( parent, refIndex, noForeignKeyCallback ) {
175
- const { ref, _links } = parent;
175
+ const { _links } = parent;
176
+ const ref = [ ...parent.ref ]; // copy so the original ref stays untouched
176
177
  const assoc = _links[refIndex].art;
178
+ const nextLink = _links[refIndex + 1]?.art;
179
+
180
+ if (nextLink?.value) {
181
+ const resolved = resolveCalculatedElementRef(nextLink);
182
+ if (resolved)
183
+ ref.splice(refIndex + 1, 1, ...resolved);
184
+ }
177
185
 
178
186
  const next = pathId(ref[refIndex + 1]);
179
187
  let possibleKeys = next && assoc.keys.filter(r => r.ref[0] === next);
@@ -219,4 +227,42 @@ function validateMixinOnCondition( query, path ) {
219
227
  forEachGeneric( query.SELECT, 'mixin', validateOnCondition.bind(this), path );
220
228
  }
221
229
 
230
+ /**
231
+ * As calculated elements are only resolved in a later transformation step,
232
+ * we must provide a way to check whether a calc element references e.g.
233
+ * a foreign key somewhere down the line.
234
+ *
235
+ * In the following example, `G:indirect` is eventually a foreign key of `G:toG`,
236
+ * hence it is allowed to be used in e.g. an infix filter:
237
+ * @example
238
+ * ```
239
+ * entity G {
240
+ * key id : Integer;
241
+ * idx : Integer;
242
+ * toG: Association to G { idx };
243
+ * cidx = idx;
244
+ * indirect = cidx;
245
+ * }
246
+ *
247
+ * view V as select from G where exists toG[toG.indirect = 1];
248
+ * ^^^^^^^^
249
+ * ```
250
+ *
251
+ * @param {CSN.Element} calculatedElement
252
+ * @returns {CSN.ArtifactReference} the resolved element or the calculated element itself if it is complex
253
+ */
254
+ function resolveCalculatedElementRef( calculatedElement ) {
255
+ if (calculatedElement.value.ref) {
256
+ const { _links } = calculatedElement.value;
257
+ const leaf = _links[_links.length - 1];
258
+ // TODO: once #11538 is available, checking the leaf for `.value`
259
+ // is not enough anymore.
260
+ if (leaf.art.value)
261
+ return resolveCalculatedElementRef(leaf.art);
262
+ return calculatedElement.value.ref;
263
+ }
264
+
265
+ return null;
266
+ }
267
+
222
268
  module.exports = { validateOnCondition, validateMixinOnCondition, requireForeignKeyAccess };
@@ -39,6 +39,9 @@ const { validateAssociationsInItems } = require('./arrayOfs');
39
39
  const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
40
40
  const checkExplicitlyNullableKeys = require('./nullableKeys');
41
41
  const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
42
+ const existsMustEndInAssoc = require('./existsMustEndInAssoc');
43
+ const forbidAssocInExists = require('./existsExpressionsOnlyForeignKeys');
44
+ const checkPathsInStoredCalcElement = require('./checkPathsInStoredCalcElement');
42
45
  const managedWithoutKeys = require('./managedWithoutKeys');
43
46
  const {
44
47
  checkSqlAnnotationOnArtifact,
@@ -72,7 +75,13 @@ const forRelationalDBArtifactValidators
72
75
  checkSqlAnnotationOnArtifact,
73
76
  ];
74
77
 
75
- const forRelationalDBCsnValidators = [ nonexpandableStructuredInExpression, navigationIntoMany ];
78
+ const forRelationalDBCsnValidators = [
79
+ existsMustEndInAssoc,
80
+ forbidAssocInExists,
81
+ nonexpandableStructuredInExpression,
82
+ navigationIntoMany,
83
+ checkPathsInStoredCalcElement,
84
+ ];
76
85
  /**
77
86
  * @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
78
87
  */
@@ -192,17 +192,17 @@ function assertConsistency( model, stage ) {
192
192
  $magicVariables: {
193
193
  // $magicVariables contains "builtin" artifacts that differ from
194
194
  // "normal artifacts" and therefore have a custom schema
195
- requires: [ 'kind', 'artifacts' ],
195
+ requires: [ 'kind', 'elements' ],
196
196
  schema: {
197
197
  kind: { test: isString, enum: [ '$magicVariables' ] },
198
- artifacts: {
198
+ elements: {
199
199
  // Do not use "normal" definitions spec because of these artifacts
200
200
  // are missing the location property
201
201
  test: isDictionary( definition ),
202
202
  requires: [ 'kind', 'name' ],
203
203
  optional: [
204
204
  'elements', '$autoElement', '$uncheckedElements', '_origin', '_extensions',
205
- '$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps',
205
+ '$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps', '_parent',
206
206
  ],
207
207
  schema: {
208
208
  kind: { test: isString, enum: [ 'builtin' ] },
@@ -239,7 +239,9 @@ function assertConsistency( model, stage ) {
239
239
  optional: [ 'name', 'extern', 'usings', 'fileDep' ],
240
240
  },
241
241
  extern: {
242
- requires: [ 'location', 'path' ],
242
+ kind: [ 'using' ],
243
+ requires: [ 'location' ],
244
+ optional: [ 'location', 'path', 'id' ],
243
245
  schema: { path: { inherits: 'path', optional: [ '$delimited' ] } },
244
246
  },
245
247
  elements: { kind: true, inherits: 'definitions', also: [ 0 ] }, // 0 for cyclic expansions
@@ -344,9 +346,9 @@ function assertConsistency( model, stage ) {
344
346
  $inlines: { test: TODO },
345
347
  type: {
346
348
  kind: true,
347
- requires: [ 'location', 'path' ],
349
+ requires: [ 'location' ],
348
350
  optional: [
349
- 'scope', '_artifact', '$inferred', '$parens',
351
+ 'path', 'scope', '_artifact', '$inferred', '$parens',
350
352
  ],
351
353
  },
352
354
  targetAspect: {
@@ -516,13 +518,13 @@ function assertConsistency( model, stage ) {
516
518
  kind: true,
517
519
  instanceOf: 'ignore', // TODO: XsnName,
518
520
  schema: {
519
- select: { test: TODO },
521
+ id: { test: isStringOrNumber },
522
+ select: { test: TODO }, // TODO: remove
520
523
  }, // TODO: rename query prop in name
521
524
  requires: [ 'location' ],
522
525
  optional: [
523
526
  'path', 'id', '$delimited', 'variant', // TODO: req path, opt id for main, req id for member
524
527
  '_artifact', '$inferred',
525
- 'absolute', 'select', 'alias', 'element', 'action', 'param',
526
528
  ],
527
529
  },
528
530
  absolute: { test: isString },
@@ -665,6 +667,7 @@ function assertConsistency( model, stage ) {
665
667
  '$internal', // compiler internal; must not reach CSN output
666
668
  '*', // inferred from query wildcard
667
669
  'as', // query alias name
670
+ 'path-prefix', // using declaration for `entity path.prefix.E`
668
671
  'aspect-composition',
669
672
  'autoexposed', // for auto-exposed entities (they can't be referred to)
670
673
  'cast', // type from cast() function
@@ -676,7 +679,7 @@ function assertConsistency( model, stage ) {
676
679
  'keys',
677
680
  'localized', // e.g. compiler-generated elements for localized: `text` assoc, etc.
678
681
  'localized-entity', // `.texts` entity
679
- 'nav', // only used for MASKED, TODO(v4): Remove
682
+ 'nav', // only used for MASKED, TODO(v5): Remove
680
683
  'none', // only used in ensureColumnName(): Used in object representing empty alias
681
684
  'query', // inferred query properties, e.g. `key`
682
685
  'rewrite', // on-conditions or FKeys are rewritten
@@ -759,7 +762,7 @@ function assertConsistency( model, stage ) {
759
762
  if (parent.kind !== 'namespace')
760
763
  throw new InternalConsistencyError(`Property '${ prop }' must be inside artifact that is a namespace but was '${ parent.kind }'${ at( [ node, parent ], prop, name ) }` );
761
764
 
762
- const parentName = parent.name && parent.name.absolute;
765
+ const parentName = parent.name?.id;
763
766
  if (parentName !== 'cds' && parentName !== 'localized')
764
767
  throw new InternalConsistencyError(`Property '${ prop }' must be inside namespace 'cds' or 'localized' but was '${ parentName }'${ at( [ node, parent ], prop, name ) }` );
765
768
  }
@@ -968,6 +971,11 @@ function assertConsistency( model, stage ) {
968
971
  };
969
972
  }
970
973
 
974
+ function isStringOrNumber( node, parent, prop, spec ) {
975
+ if (typeof node !== 'number')
976
+ isString( node, parent, prop, spec );
977
+ }
978
+
971
979
  function isString( node, parent, prop, spec ) {
972
980
  if (typeof node !== 'string')
973
981
  throw new InternalConsistencyError( `Expected string but found ${ typeof node }${ at( [ node, parent ], prop ) }` );
@@ -1005,12 +1013,12 @@ function assertConsistency( model, stage ) {
1005
1013
  if (prop === 'artifacts')
1006
1014
  standard( art, parent, prop, spec, name );
1007
1015
  }
1008
- else if (!art.name.absolute ||
1009
- !model.definitions[art.name.absolute] &&
1010
- !(model.vocabularies && model.vocabularies[art.name.absolute])) {
1016
+ else if (!art.name.id ||
1017
+ !model.definitions[art.name.id] &&
1018
+ !model.vocabularies?.[art.name.id]) {
1011
1019
  // TODO: sign ignored artifacts with $inferred = 'IGNORED'
1012
- if (parent.kind === 'source' ||
1013
- art.name.absolute && art.name.absolute.startsWith( 'localized.' ))
1020
+ if (parent.kind === 'source' || art.kind === 'using' ||
1021
+ art.name.id?.startsWith?.( 'localized.' ))
1014
1022
  standard( art, parent, prop, spec, name );
1015
1023
  else
1016
1024
  throw new InternalConsistencyError( `Expected definition${ at( [ art, parent ], prop, name ) }` );
@@ -61,8 +61,8 @@ const kindProperties = {
61
61
  annotate: {
62
62
  noDep: 'special', elements: true, enum: true, actions: true, params: true,
63
63
  },
64
- builtin: {}, // = CURRENT_DATE, TODO: improve
65
- $parameters: {}, // $parameters in query entities
64
+ builtin: { normalized: 'element' }, // = $now, $user.id,
65
+ $parameters: {}, // $parameters in query entities - TODO: normalized: 'alias'?
66
66
  };
67
67
 
68
68
  function propExists( prop, parent ) {
@@ -79,26 +79,43 @@ function propExists( prop, parent ) {
79
79
  * @returns {XSN.Name}
80
80
  */
81
81
  function getArtifactName( art ) {
82
- if (!art.name || art.name.absolute) // no name or “old style”
83
- return art.name;
84
- // extend and annotate statements as members already have "sparse" names → calculate old one
82
+ const { name } = art;
83
+ if (!name) // no name
84
+ return name;
85
+ if (!art.kind) // annotation assignments
86
+ return { ...art.name, absolute: art.name.id };
87
+ if (art.kind === 'using')
88
+ return { ...art.name, absolute: art.extern.id };
89
+
85
90
  const namePath = [];
86
- let parent = art;
87
- while (parent._main) { // until we hit the main artifact
88
- namePath.push( parent );
91
+ let parent = art._outer || art;
92
+ while (parent._main || parent.kind === 'builtin') { // until we hit the main artifact
93
+ if (parent.name.$inferred !== '$internal' || parent.kind === '$inline')
94
+ namePath.push( parent );
95
+ if (parent.kind === 'select')
96
+ break;
89
97
  parent = parent._parent;
98
+ parent = parent._outer || parent; // for anonymous aspect and items
90
99
  }
91
100
  namePath.reverse();
92
101
  // start with id/location of art.name, and absolute of art._main
93
- const name = { id: art.name.id, location: art.name.location, absolute: parent.name.absolute };
102
+ const dot = (art._main || typeof name.id !== 'string') ? -1 : name.id.lastIndexOf( '.' );
103
+ const rname = (!parent?.name) ? { id: name.id } : {
104
+ id: (dot < 0 ? name.id : name.id.substring( dot + 1)),
105
+ location: name.location,
106
+ absolute: (parent._main || parent).name.id,
107
+ };
108
+ if (name.path !== undefined)
109
+ rname.path = name.path;
94
110
  for (const np of namePath) {
95
111
  const prop = getMemberNameProp( np, np.kind );
96
- name[prop] = (name[prop]) ? `${ name[prop] }.${ np.name.id }` : np.name.id;
112
+ rname[prop] = (rname[prop]) ? `${ rname[prop] }.${ np.name.id }` : np.name.id;
113
+ }
114
+ if (name._artifact !== undefined) {
115
+ Object.defineProperty( rname, '_artifact',
116
+ { value: name._artifact, configurable: true, writable: true } );
97
117
  }
98
- const link = art.name._artifact;
99
- if (link !== undefined)
100
- Object.defineProperty( name, '_artifact', { value: link, configurable: true, writable: true } );
101
- return name;
118
+ return rname;
102
119
  }
103
120
 
104
121
  // TODO: probably store this prop in name
@@ -119,7 +119,7 @@ const specialFunctions = compileFunctions( {
119
119
  MAX: 'COUNT',
120
120
  SUM: 'COUNT',
121
121
  AVG: 'COUNT',
122
- STDDDEV: 'COUNT',
122
+ STDDEV: 'COUNT',
123
123
  VAR: 'COUNT',
124
124
  LOCATE_REGEXPR: [
125
125
  {
@@ -441,7 +441,7 @@ function initBuiltins( model ) {
441
441
  const art = {
442
442
  kind: 'namespace',
443
443
  // builtin namespaces don't have a cds file, so no location available
444
- name: { absolute: name, location: builtinLocation() },
444
+ name: { id: name, location: builtinLocation() },
445
445
  blocks: [],
446
446
  builtin,
447
447
  location: builtinLocation(),
@@ -462,10 +462,10 @@ function initBuiltins( model ) {
462
462
  function env( builtins, prefix, parent ) {
463
463
  const artifacts = Object.create( null );
464
464
  for (const name of Object.keys( builtins )) {
465
- const absolute = prefix + name;
465
+ const id = prefix + name;
466
466
  // TODO: reconsider whether to set a type to itself - looks wrong
467
467
  const art = {
468
- kind: 'type', builtin: true, name: { absolute },
468
+ kind: 'type', builtin: true, name: { id },
469
469
  };
470
470
  if (parent)
471
471
  parent._subArtifacts[name] = art;
@@ -475,21 +475,23 @@ function initBuiltins( model ) {
475
475
  Object.assign( art, builtins[name] );
476
476
  if (!art.internal)
477
477
  artifacts[name] = art;
478
- model.definitions[absolute] = art;
478
+ model.definitions[id] = art;
479
479
  }
480
480
  return artifacts;
481
481
  }
482
482
 
483
483
  function setMagicVariables( builtins ) {
484
- const artifacts = Object.create( null );
485
- for (const name in builtins) {
486
- const magic = builtins[name];
484
+ const elements = Object.create( null );
485
+ model.$magicVariables = { kind: '$magicVariables', elements };
486
+ for (const id in builtins) {
487
+ const magic = builtins[id];
487
488
  // TODO: rename to $builtinFunction
488
489
  const art = {
489
- kind: 'builtin',
490
- name: { id: name, absolute: '', element: name },
490
+ kind: 'builtin', // TODO: $var
491
+ name: { id },
491
492
  };
492
- artifacts[name] = art;
493
+ elements[id] = art;
494
+ setProp( art, '_parent', model.$magicVariables );
493
495
 
494
496
  if (magic.$autoElement)
495
497
  art.$autoElement = magic.$autoElement;
@@ -499,11 +501,10 @@ function initBuiltins( model ) {
499
501
  art.$requireElementAccess = magic.$requireElementAccess;
500
502
 
501
503
  createMagicElements( art, magic.elements );
502
- if (options.variableReplacements?.[name])
503
- createMagicElements( art, options.variableReplacements[name] );
504
+ if (options.variableReplacements?.[id])
505
+ createMagicElements( art, options.variableReplacements[id] );
504
506
  // setProp( art, '_effectiveType', art );
505
507
  }
506
- model.$magicVariables = { kind: '$magicVariables', artifacts };
507
508
  }
508
509
 
509
510
  function createMagicElements( art, elements ) {
@@ -514,20 +515,20 @@ function initBuiltins( model ) {
514
515
  if (names.length > 0 && !art.elements)
515
516
  art.elements = Object.create( null );
516
517
 
517
- for (const n of names) {
518
+ for (const id of names) {
518
519
  const magic = {
519
- kind: 'builtin', // TODO: '$variable'
520
- name: { id: n, absolute: '', element: `${ art.name.element }.${ n }` },
520
+ kind: 'builtin', // TODO: '$var'
521
+ name: { id },
521
522
  };
522
523
  // Propagate this property so that it is available for sub-elements.
523
524
  if (art.$uncheckedElements)
524
525
  magic.$uncheckedElements = art.$uncheckedElements;
525
526
  setProp( magic, '_parent', art );
526
527
  // setProp( magic, '_effectiveType', magic );
527
- if (elements[n] && typeof elements[n] === 'object')
528
- createMagicElements( magic, elements[n] );
528
+ if (elements[id] && typeof elements[id] === 'object')
529
+ createMagicElements( magic, elements[id] );
529
530
 
530
- art.elements[n] = magic;
531
+ art.elements[id] = magic;
531
532
  }
532
533
  }
533
534
  }