@sap/cds-compiler 4.2.2 → 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 +32 -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 +2 -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 +18 -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
@@ -26,7 +26,7 @@ const {
26
26
  const {
27
27
  dictAdd, dictAddArray, dictFirst, dictForEach,
28
28
  } = require('../base/dictionaries');
29
- const { weakLocation } = require('../base/messages');
29
+ const { weakLocation } = require('../base/location');
30
30
  const { CompilerAssertion } = require('../base/error');
31
31
 
32
32
  const { kindProperties } = require('./base');
@@ -37,7 +37,6 @@ const {
37
37
  annotationIsFalse,
38
38
  annotationLocation,
39
39
  augmentPath,
40
- splitIntoPath,
41
40
  linkToOrigin,
42
41
  setMemberParent,
43
42
  proxyCopyMembers,
@@ -104,7 +103,7 @@ function populate( model ) {
104
103
 
105
104
  forEachDefinition( model, traverseElementEnvironments );
106
105
  while (newAutoExposed.length) {
107
- // console.log( newAutoExposed.map( a => a.name.absolute ) )
106
+ // console.log( newAutoExposed.map( a => a.name.id ) )
108
107
  const all = newAutoExposed;
109
108
  newAutoExposed = [];
110
109
  all.forEach( traverseElementEnvironments );
@@ -251,7 +250,7 @@ function populate( model ) {
251
250
  if (leading._effectiveType !== undefined) {
252
251
  // You cannot refer to a query of another artifact:
253
252
  throw new CompilerAssertion(
254
- `Unexpected _effectiveType on leading query of ${ art.name.absolute }`
253
+ `Unexpected _effectiveType on leading query of ${ art.name.id }`
255
254
  );
256
255
  }
257
256
  // TODO: try just return (effectiveType( leading )) === 0 ? 0 : art;
@@ -443,7 +442,7 @@ function populate( model ) {
443
442
  // TODO: make linkToOrigin() work for returns, kind/name?
444
443
  const location = weakLocation( origin.returns.location );
445
444
  art.returns = {
446
- name: Object.assign( {}, art.name, { id: '', param: '', location } ),
445
+ name: Object.assign( {}, art.name, { id: '', location } ),
447
446
  kind: 'param',
448
447
  location,
449
448
  $inferred: 'expanded',
@@ -685,15 +684,18 @@ function populate( model ) {
685
684
  if (!col.value && !col.expand && !(col.target && col.type))
686
685
  continue; // error should have been reported by parser
687
686
  if (col.inline) {
687
+ const q = userQuery( query );
688
+ q.$inlines.push( col );
688
689
  col.kind = '$inline';
689
- col.name = {};
690
+ col.name = { id: `.${ q.$inlines.length }`, $inferred: '$internal' };
691
+ // TODO: really use $inferred: '$internal', not '$inline' ? Re-check.
690
692
  // a name for this internal symtab entry (e.g. '.2' to avoid clashes
691
693
  // with real elements) is only relevant for `cdsc -R`/debugging
692
- const q = userQuery( query );
693
- q.$inlines.push( col );
694
+ // TODO: use number = column position if "top-level", negative numbers otherwise
695
+ // (is also relevant for the semantic location - only use positive)
694
696
  dependsOnSilent( q, col );
695
697
  // or use userQuery( query ) in the following, too?
696
- setMemberParent( col, `.${ q.$inlines.length }`, query );
698
+ setMemberParent( col, null, query );
697
699
  initFromColumns( query, col.inline, col );
698
700
  }
699
701
  else if (!col.$replacement) {
@@ -835,7 +837,7 @@ function populate( model ) {
835
837
  }
836
838
  else if (Array.isArray( navElem )) {
837
839
  const names = navElem.filter( e => !e.$duplicates )
838
- .map( e => `${ e.name.alias }.${ e.name.element }` );
840
+ .map( e => `${ e._parent.name.id }.${ e.name.id }` );
839
841
  if (names.length) {
840
842
  error( 'wildcard-ambiguous', [ location, query ], { id: name, names },
841
843
  'Ambiguous wildcard, select $(ID) explicitly with $(NAMES)' );
@@ -982,7 +984,7 @@ function populate( model ) {
982
984
  if (target !== assocTarget)
983
985
  setExpandStatus( elem, 'target' ); // (might) also set in rewriteCondition
984
986
  elem.target = {
985
- path: [ { id: target.name.absolute, location } ],
987
+ path: [ { id: target.name.id, location } ],
986
988
  scope: 'global',
987
989
  location,
988
990
  $inferred: (target !== assocTarget ? 'IMPLICIT' : 'rewrite' ),
@@ -1000,7 +1002,7 @@ function populate( model ) {
1000
1002
  }
1001
1003
 
1002
1004
  function redirectImplicitlyDo( elem, assoc, target, service ) {
1003
- // console.log('ES:',elem.name.absolute,elem.name.element);
1005
+ // console.log('ES:',elem.name.id,elem.name.element);
1004
1006
  if (assoc._main === target && elem._main?.kind === 'entity' &&
1005
1007
  elem._main?._ancestors?.includes( target )) {
1006
1008
  // source and target of the model association are the same entity, and
@@ -1017,10 +1019,10 @@ function populate( model ) {
1017
1019
  target = createAutoExposed( origTarget, service, elemScope );
1018
1020
  const desc = origTarget._descendants ||
1019
1021
  setLink( origTarget, '_descendants', Object.create( null ) );
1020
- if (!desc[service.name.absolute]) // could be the target itself (no repeated msgs)!
1021
- desc[service.name.absolute] = [ target ];
1022
+ if (!desc[service.name.id]) // could be the target itself (no repeated msgs)!
1023
+ desc[service.name.id] = [ target ];
1022
1024
  else
1023
- desc[service.name.absolute].push( target );
1025
+ desc[service.name.id].push( target );
1024
1026
  }
1025
1027
  else if (exposed.length === 1) {
1026
1028
  return exposed[0];
@@ -1079,7 +1081,7 @@ function populate( model ) {
1079
1081
  // auto-exposed entity when successful, or `target` otherwise (no/failed autoexposure)
1080
1082
  function minimalExposure( target, service, elemScope ) {
1081
1083
  const descendants = scopedExposure( target._descendants &&
1082
- target._descendants[service.name.absolute] ||
1084
+ target._descendants[service.name.id] ||
1083
1085
  [],
1084
1086
  elemScope, target );
1085
1087
  const preferred = descendants.filter( d => annotationVal( d['@cds.redirection.target'] ) );
@@ -1121,7 +1123,7 @@ function populate( model ) {
1121
1123
  return false; // all (there could be no scoped autoexposed)
1122
1124
  // scoped target in model:
1123
1125
  const exposed = minimalExposure( targetScope, service, false );
1124
- // console.log('PES:',elem.name.absolute,elem.name.element,exposed.map(e=>e.name.absolute))
1126
+ // console.log('PES:',elem.name.id,elem.name.element,exposed.map(e=>e.name.id))
1125
1127
  if (exposed.length === 1) // unique redirection for target scope: use that
1126
1128
  return exposed[0];
1127
1129
  // TODO: warning if exposed.length >= 2? Probably not
@@ -1134,7 +1136,7 @@ function populate( model ) {
1134
1136
  return autoScope;
1135
1137
  const { location } = service.name;
1136
1138
  const nullScope = {
1137
- kind: 'namespace', name: { absolute: autoScopeName, location }, location,
1139
+ kind: 'namespace', name: { id: autoScopeName, location }, location,
1138
1140
  };
1139
1141
  model.definitions[autoScopeName] = nullScope;
1140
1142
  initArtifact( nullScope );
@@ -1216,33 +1218,33 @@ function populate( model ) {
1216
1218
  }
1217
1219
  // no @cds.autoexpose or @cds.autoexpose:null
1218
1220
  // TODO: introduce deprecated._noInheritedAutoexposeViaComposition
1219
- art.$autoexpose = model.$compositionTargets[art.name.absolute]
1221
+ art.$autoexpose = model.$compositionTargets[art.name.id]
1220
1222
  ? autoexposeViaComposition
1221
1223
  : null;
1222
1224
  return true; // still check for inherited @cds.autoexpose
1223
1225
  }
1224
1226
 
1225
1227
  function autoExposedName( target, service, elemScope ) {
1226
- const { absolute } = target.name;
1228
+ const absolute = target.name.id;
1227
1229
  if (isDeprecatedEnabled( options, '_shortAutoexposed' )) {
1228
1230
  const parent = definitionScope( target )._parent;
1229
- const name = (parent) ? absolute.substring( parent.name.absolute.length + 1 ) : absolute;
1231
+ const name = (parent) ? absolute.substring( parent.name.id.length + 1 ) : absolute;
1230
1232
  // no need for dedot here (as opposed to deprecated._longAutoexposed), as
1231
1233
  // the name for dependent entities have already been created using `_` then
1232
- return `${ service.name.absolute }.${ name }`;
1234
+ return `${ service.name.id }.${ name }`;
1233
1235
  }
1234
1236
  if (isDeprecatedEnabled( options, '_longAutoexposed' ))
1235
- return `${ service.name.absolute }.${ absolute }`;
1237
+ return `${ service.name.id }.${ absolute }`;
1236
1238
  const base = definitionScope( target );
1237
1239
  if (base === target)
1238
- return `${ service.name.absolute }.${ absolute.substring( absolute.lastIndexOf( '.' ) + 1 ) }`;
1240
+ return `${ service.name.id }.${ absolute.substring( absolute.lastIndexOf( '.' ) + 1 ) }`;
1239
1241
  // for scoped (e.g. calculated) entities, use exposed name of base:
1240
1242
  const exposed = minimalExposure( base, service, elemScope );
1241
- // console.log(exposed.map( a => a.name.absolute ));
1243
+ // console.log(exposed.map( a => a.name.id ));
1242
1244
  const sbasename = (exposed.length === 1 && exposed[0] !== base) // same with no/failed expose
1243
- ? exposed[0].name.absolute
1245
+ ? exposed[0].name.id
1244
1246
  : autoExposedName( base, service, elemScope );
1245
- return sbasename + absolute.slice( base.name.absolute.length );
1247
+ return sbasename + absolute.slice( base.name.id.length );
1246
1248
  }
1247
1249
 
1248
1250
 
@@ -1268,7 +1270,7 @@ function populate( model ) {
1268
1270
  !annotationVal( autoexposed['@cds.autoexposed'] )) {
1269
1271
  // existing def not auto-exposed, or un-scoped auto-exposed: should not happen
1270
1272
  if (options.testMode)
1271
- throw new CompilerAssertion( `Tried to auto-expose ${ target.name.absolute } twice`);
1273
+ throw new CompilerAssertion( `Tried to auto-expose ${ target.name.id } twice`);
1272
1274
  }
1273
1275
  return autoexposed;
1274
1276
  }
@@ -1292,10 +1294,10 @@ function populate( model ) {
1292
1294
  }
1293
1295
  // console.log(absolute)
1294
1296
  const { location } = target.name;
1295
- const from = augmentPath( location, target.name.absolute );
1297
+ const from = augmentPath( location, target.name.id );
1296
1298
  let art = {
1297
1299
  kind: 'entity',
1298
- name: { location, path: splitIntoPath( location, absolute ), absolute },
1300
+ name: { location, id: absolute },
1299
1301
  location: target.location,
1300
1302
  query: { location, op: { val: 'SELECT', location }, from },
1301
1303
  $syntax: 'projection',
@@ -238,6 +238,8 @@ function propagate( model ) {
238
238
  return;
239
239
  if (prop === 'params' && target.$inferred !== 'proxy' && target.$inferred !== 'include')
240
240
  return;
241
+ if (prop === 'foreignKeys' && target.on)
242
+ return; // e.g. published associations with filters
241
243
  const location = target.type && !target.type.$inferred && target.type.location ||
242
244
  target.location ||
243
245
  target._outer && target._outer.location;
@@ -46,8 +46,7 @@ const {
46
46
  isDeprecatedEnabled,
47
47
  } = require('../base/model');
48
48
  const { dictAdd } = require('../base/dictionaries');
49
- const { dictLocation } = require('../base/location');
50
- const { weakLocation } = require('../base/messages');
49
+ const { dictLocation, weakLocation } = require('../base/location');
51
50
  const { combinedLocation } = require('../base/location');
52
51
  const { typeParameters } = require('./builtins');
53
52
 
@@ -414,7 +413,7 @@ function resolve( model ) {
414
413
  obj.items = items;
415
414
  obj.$expand = 'origin';
416
415
  }
417
- if (obj.items) { // TODO: make this a while in v2 (also items proxy)
416
+ if (obj.items) { // TODO: make this a while in v5 (also items proxy)
418
417
  obj = obj.items || obj; // the object which has type properties
419
418
  effectiveType( obj );
420
419
  }
@@ -440,11 +439,11 @@ function resolve( model ) {
440
439
  }
441
440
 
442
441
  // Check if relational type is missing its target or if it's used directly.
443
- if (elemtype.category === 'relation' && obj.type.path.length > 0 &&
442
+ if (elemtype.category === 'relation' &&
444
443
  !obj.target && !obj.targetAspect) {
445
444
  const isCsn = (obj._block && obj._block.$frontend === 'json');
446
445
  error( 'type-missing-target', [ obj.type.location, obj ],
447
- { '#': isCsn ? 'csn' : 'std', type: elemtype.name.absolute }, {
446
+ { '#': isCsn ? 'csn' : 'std', type: elemtype }, {
448
447
  // We don't say "use 'association to <target>" because the type could be used
449
448
  // in action parameters, etc. as well.
450
449
  std: 'The type $(TYPE) can\'t be used directly because it\'s compiler internal',
@@ -589,20 +588,23 @@ function resolve( model ) {
589
588
  if (specifiedElement.foreignKeys) {
590
589
  const sKeys = Object.keys( specifiedElement.foreignKeys );
591
590
  /** @type {any} */
592
- let iKeys = inferredElement;
591
+ let iAssoc = inferredElement;
593
592
  if (inferredElement._effectiveType !== 0) {
594
- while (iKeys._origin && !iKeys.foreignKeys)
595
- iKeys = iKeys._origin;
593
+ while (iAssoc._origin && !iAssoc.foreignKeys && !iAssoc.on)
594
+ iAssoc = iAssoc._origin;
596
595
  }
597
- iKeys = Object.keys( iKeys.foreignKeys || {} );
598
- if (sKeys.length !== iKeys.length || sKeys.some( fkey => !iKeys.includes( fkey ) )) {
599
- error( 'query-mismatched-element', [
600
- specifiedElement.foreignKeys.location || specifiedElement.location, user,
601
- ], {
602
- '#': 'foreignKeys',
603
- name: user.name.id,
604
- target: specifiedElement.target,
605
- art: inferredElement.target,
596
+ const iKeys = Object.keys( iAssoc.foreignKeys || {} );
597
+ const loc = [
598
+ specifiedElement.foreignKeys[$location] || specifiedElement.location, user,
599
+ ];
600
+ if (iAssoc.on) {
601
+ error( 'query-mismatched-element', loc, {
602
+ '#': 'unmanagedToManaged', name: user.name.id,
603
+ } );
604
+ }
605
+ else if (sKeys.length !== iKeys.length || sKeys.some( fkey => !iKeys.includes( fkey ) )) {
606
+ error( 'query-mismatched-element', loc, {
607
+ '#': 'foreignKeys', name: user.name.id,
606
608
  } );
607
609
  }
608
610
  }
@@ -960,7 +962,7 @@ function resolve( model ) {
960
962
  } );
961
963
  // TODO: also warning if inside structure
962
964
  }
963
- else {
965
+ else { // if (obj.target._artifact)
964
966
  // TODO: extra with $inferred (to avoid messages)?
965
967
  resolveExpr( obj.on, art.kind === 'mixin' ? 'mixin-on' : 'on', art );
966
968
  }
@@ -1187,9 +1189,7 @@ function resolve( model ) {
1187
1189
  const nav = elem._main && elem._main.query && elem.value && pathNavigation( elem.value );
1188
1190
  if (nav && nav.item !== elem.value.path[elem.value.path.length - 1]) {
1189
1191
  if (!elem.on && origType.on) {
1190
- error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
1191
- // TODO: Better text ?
1192
- 'The ON-condition is not rewritten here - provide an explicit ON-condition' );
1192
+ error( 'rewrite-not-supported', [ elem.target.location, elem ] );
1193
1193
  return;
1194
1194
  }
1195
1195
  }
@@ -1285,10 +1285,10 @@ function resolve( model ) {
1285
1285
 
1286
1286
  // eslint-disable-next-line no-shadow
1287
1287
  function findOrig( chain, alias, art ) {
1288
- if (!art || dict[art.name.absolute])
1288
+ if (!art || dict[art.name.id])
1289
1289
  // some include ref or query source cannot be found, or cyclic ref
1290
1290
  return true;
1291
- dict[art.name.absolute] = true;
1291
+ dict[art.name.id] = true;
1292
1292
 
1293
1293
  if (art.includes) {
1294
1294
  news.push( {
@@ -1378,14 +1378,18 @@ function resolve( model ) {
1378
1378
  art;
1379
1379
  if (step.args)
1380
1380
  resolveParams( step.args, art, entity, expected, user, step.location );
1381
- if (entity) {
1381
+
1382
+ // Publishing an association with filters is ok in column
1383
+ const publishAssoc = art.kind === 'entity' && expected === 'column';
1384
+
1385
+ if (entity || publishAssoc) {
1382
1386
  if (step.where) {
1383
1387
  setLink( step, '_user', user._user || user );
1384
1388
  const inCalc = (expected === 'calc' || expected === 'calc-filter');
1385
1389
  resolveExpr( step.where, (inCalc ? 'calc-filter' : 'filter'), step );
1386
1390
  }
1387
1391
  }
1388
- else if (step.where || step.cardinality ) {
1392
+ else if (step.where || step.cardinality) {
1389
1393
  const location = combinedLocation( step.where, step.cardinality );
1390
1394
  let variant = alias ? 'tableAlias' : 'std';
1391
1395
  if (expected === 'from')
@@ -227,7 +227,9 @@ function fns( model ) {
227
227
  dynamic: parentElements,
228
228
  navigation: assocOnNavigation,
229
229
  notFound: undefinedParentElement,
230
+ rewriteProjectionToSelf: true,
230
231
  }),
232
+ rewriteProjectionToSelf: true,
231
233
  },
232
234
  'mixin-on': {
233
235
  lexical: tableAliasesAndSelf,
@@ -313,10 +315,9 @@ function fns( model ) {
313
315
 
314
316
  // Return absolute name for unchecked path `ref`. We first try searching for
315
317
  // the path root starting from `env`. If it exists, return its absolute name
316
- // appended with the name of the rest of the path and set `ref.absolute` to
317
- // the return value. Otherwise, complain if `unchecked` is false, and set
318
- // `ref.absolute` to the path name of `ref`.
319
- // Used for collecting artifact extension, and annotation assignments.
318
+ // appended with the name of the rest of the path. Otherwise, complain if
319
+ // `unchecked` is false, and set `ref.absolute` to the path name of `ref`.
320
+ // Used for collecting artifact extension.
320
321
  //
321
322
  // Return '' if the ref is good, but points to an element.
322
323
  function resolveUncheckedPath( ref, refCtx, user ) {
@@ -325,6 +326,11 @@ function fns( model ) {
325
326
  return undefined;
326
327
 
327
328
  const semantics = referenceSemantics[refCtx];
329
+ if (!semantics.isMainRef)
330
+ throw new CompilerAssertion( `resolveUncheckedPath() called for reference ctx '${ refCtx }'` );
331
+ if (!definedViaCdl( user ))
332
+ return (path.length === 1) ? path[0].id : '';
333
+
328
334
  let art = getPathRoot( ref, semantics, user );
329
335
  if (ref.scope && ref.scope !== 'global')
330
336
  return ''; // TYPE OF, Main:elem
@@ -333,9 +339,9 @@ function fns( model ) {
333
339
  art = art[0];
334
340
  if (!art)
335
341
  return (semantics.dynamic !== modelDefinitions) ? art : pathName( path );
336
- if (path.length === 1)
337
- return art.name.absolute; // TODO: name.id
338
- return `${ art.name.absolute }.${ pathName( ref.path.slice(1) ) }`;
342
+
343
+ const first = (art.kind === 'using' ? art.extern : art.name).id;
344
+ return (path.length === 1) ? first : `${ first }.${ pathName( ref.path.slice(1) ) }`;
339
345
  }
340
346
 
341
347
  /**
@@ -502,12 +508,12 @@ function fns( model ) {
502
508
  }
503
509
  }
504
510
 
505
- // Search in $special (ex $self/$projection) and dynamic environment:
511
+ // Search in $special (excluding $self/$projection) and dynamic environment:
506
512
  const dynamicDict = semantics.dynamic( ruser, user._user && user._artifact );
507
513
  if (!dynamicDict) // avoid consequential errors
508
514
  return setArtifactLink( head, null );
509
515
  const isVar = (semantics.dollar && head.id.charAt( 0 ) === '$');
510
- const dict = (isVar) ? model.$magicVariables.artifacts : dynamicDict;
516
+ const dict = (isVar) ? model.$magicVariables.elements : dynamicDict;
511
517
  const r = dict[head.id];
512
518
  if (r)
513
519
  return setArtifactLink( head, r );
@@ -515,7 +521,7 @@ function fns( model ) {
515
521
  if (!semantics.dollar)
516
522
  valid.push( dynamicDict );
517
523
  else
518
- valid.push( model.$magicVariables.artifacts, removeDollarNames( dynamicDict ) );
524
+ valid.push( model.$magicVariables.elements, removeDollarNames( dynamicDict ) );
519
525
  // TODO: streamline function arguments (probably: user, path, semantics )
520
526
  const undef = semantics.notFound( ruser, head, valid, dynamicDict,
521
527
  !isMainRef && user._user && user._artifact, path );
@@ -590,11 +596,16 @@ function fns( model ) {
590
596
  function acceptLexical( art, path, semantics, user ) {
591
597
  if (semantics.isMainRef || !art)
592
598
  return !!art;
599
+
593
600
  // Non-global lexical are table aliases, mixins and $self, $projection, $parameters,
594
601
  // Do not accept a lonely table alias and `$projection`
595
602
  // TODO: test table alias and mixin named `$projection`
596
- if (path.length !== 1 || user.expand || user.inline)
603
+ if (path.length !== 1 || user.expand || user.inline) {
604
+ // Rewrite $projection to $self
605
+ if (semantics.rewriteProjectionToSelf && art.kind === '$self' && path[0].id === '$projection')
606
+ path[0].id = '$self';
597
607
  return art.name?.$inferred !== '$internal'; // not a compiler-generated internal alias
608
+ }
598
609
 
599
610
  // allow mixins, $self, and `up_` in anonymous target aspect (is $navElement):
600
611
  return art.kind === 'mixin' ||
@@ -612,7 +623,7 @@ function fns( model ) {
612
623
 
613
624
  switch (art.kind) {
614
625
  case 'using': {
615
- const def = model.definitions[art.name.absolute];
626
+ const def = model.definitions[art.extern.id];
616
627
  if (!def)
617
628
  return def;
618
629
  if (def.$duplicates)
@@ -663,7 +674,7 @@ function fns( model ) {
663
674
  const uniqueNames = arr.filter( e => !e.$duplicates );
664
675
  if (uniqueNames.length) {
665
676
  const names = uniqueNames.filter( e => e._parent.$inferred !== '$internal' )
666
- .map( e => `${ e.name.alias }.${ e.name.element }` );
677
+ .map( e => `${ e._parent.name.id }.${ e.name.id }` );
667
678
  let variant = names.length === uniqueNames.length ? 'std' : 'few';
668
679
  if (names.length === 0)
669
680
  variant = 'none';
@@ -929,7 +940,7 @@ function fns( model ) {
929
940
  if (id.charAt( 0 ) === '$') {
930
941
  const tableAlias = dynamicDict[id]?._parent;
931
942
  // TODO: probably better to pass param `semantics` and calculate dynamic dict explicitly
932
- const alias = tableAlias?.kind === '$tableAlias' ? tableAlias.name?.alias : null;
943
+ const alias = tableAlias?.kind === '$tableAlias' ? tableAlias.name?.id : null;
933
944
  // TODO: mention $self without query
934
945
  signalNotFound( 'ref-undefined-var', [ head.location, user ], valid,
935
946
  { '#': (alias ? 'alias' : 'std'), alias, id } );
@@ -966,10 +977,10 @@ function fns( model ) {
966
977
  const { id } = head;
967
978
  const src = id.charAt( 0 ) !== '$' && user._combined?.[id];
968
979
  if (src && !Array.isArray( src )) {
969
- path.$prefix = src.name.alias; // pushing it to path directly could be problematic
980
+ path.$prefix = src._parent.name.id; // pushing it to path directly could be problematic
970
981
  // configurable error:
971
982
  signalNotFound( 'ref-deprecated-orderby', [ head.location, user ], valid,
972
- { id: head.id, newcode: `${ src.name.alias }.${ head.id }` } );
983
+ { id: head.id, newcode: `${ path.$prefix }.${ head.id }` } );
973
984
  return src;
974
985
  }
975
986
  undefinedParentElement( user, head, valid, dynamicDict );
@@ -984,7 +995,8 @@ function fns( model ) {
984
995
  }
985
996
 
986
997
  function undefinedItemElement( user, item, valid, _dict, art, path ) {
987
- if (art.name && art.name.select && art.name.select > 1) {
998
+ const query = userQuery( art );
999
+ if (query?.name?.id > 1) {
988
1000
  // TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
989
1001
  // and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
990
1002
  // both as text variants to ref-undefined-element
@@ -996,21 +1008,35 @@ function fns( model ) {
996
1008
  valid, { art: art._main, id: item.id } );
997
1009
  }
998
1010
  else if (art.kind === 'builtin') { // magic variable / replacement variable
999
- const id = (item === path[path.length - 1])
1000
- ? item.id
1001
- : pathName( path.slice( path.indexOf( item ) ) );
1011
+ // $magic.{ var } is a configurable error,
1012
+ // TODO: if it becomes non-configurable, we can omit this warning
1013
+ let id = pathName( path );
1014
+ let head = path[0]._artifact || { _parent: art };
1015
+ // eslint-disable-next-line no-cond-assign
1016
+ while ((head = head?._parent) && head.kind === 'builtin')
1017
+ id = `${ head.name.id }.${ id }`;
1002
1018
  signalNotFound( ( art.$uncheckedElements ? 'ref-unknown-var' : 'ref-undefined-var'),
1003
- [ item.location, user ], valid,
1004
- { id: `${ art.name.element }.${ id }` } );
1019
+ [ item.location, user ], valid, { id } );
1005
1020
  }
1006
- else {
1007
- const variant = art.kind === 'aspect' && !art.name && 'aspect';
1021
+ else if (art.kind === 'aspect' && !art.name) { // anonymous target aspect
1008
1022
  signalNotFound( 'ref-undefined-element', [ item.location, user ],
1009
- valid, {
1010
- '#': variant,
1011
- art: (variant ? '' : searchName( art, item.id, 'element' )),
1012
- id: item.id,
1013
- } );
1023
+ valid, { '#': 'aspect', id: item.id } );
1024
+ }
1025
+ else {
1026
+ const target = art._effectiveType?.target;
1027
+ if (target?._artifact) {
1028
+ signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
1029
+ { '#': 'target', art: target, id: item.id } );
1030
+ }
1031
+ else if (!target) {
1032
+ const variant = art.kind === 'aspect' && !art.name && 'aspect';
1033
+ signalNotFound( 'ref-undefined-element', [ item.location, user ],
1034
+ valid, {
1035
+ '#': variant,
1036
+ art: (variant ? '' : searchName( art, item.id, 'element' )),
1037
+ id: item.id,
1038
+ } );
1039
+ }
1014
1040
  }
1015
1041
  return null;
1016
1042
  }
@@ -1364,7 +1390,7 @@ function fns( model ) {
1364
1390
  return false; // param ref `:$self` is not $self
1365
1391
  // $self in entity (TODO: mixin? new assoc in col? in aspect?)
1366
1392
  const kind = right._artifact?.kind;
1367
- if (kind !== 'entity' && kind !== 'aspect' && kind !== 'select')
1393
+ if (kind !== 'entity' && kind !== 'aspect' && kind !== 'select' && kind !== 'event')
1368
1394
  return false; // (ok, this would also return `false` for `:$self`)
1369
1395
  const { path } = left;
1370
1396
  // TODO: we might return true and issue an extra error here
@@ -1436,7 +1462,7 @@ function fns( model ) {
1436
1462
 
1437
1463
  for (let set = query._main.query; set.op; set = set.args[0]) {
1438
1464
  const right = set.args[1];
1439
- if (query.name.select >= (right._leadingQuery || right).name.select)
1465
+ if (query.name.id >= (right._leadingQuery || right).name.id)
1440
1466
  return { txt: 'setQuery', op: set.op.val };
1441
1467
  }
1442
1468
  throw new CompilerAssertion( 'Did we pass the leading query as argument?' );