@sap/cds-compiler 4.4.4 → 4.6.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 (82) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/bin/cdsc.js +18 -11
  3. package/bin/cdsv2m.js +7 -5
  4. package/doc/CHANGELOG_BETA.md +22 -0
  5. package/lib/api/main.js +306 -144
  6. package/lib/api/options.js +18 -6
  7. package/lib/api/validate.js +1 -1
  8. package/lib/base/message-registry.js +45 -10
  9. package/lib/base/messages.js +33 -16
  10. package/lib/base/model.js +4 -0
  11. package/lib/base/optionProcessorHelper.js +45 -176
  12. package/lib/checks/annotationsOData.js +49 -0
  13. package/lib/checks/elements.js +32 -34
  14. package/lib/checks/enricher.js +39 -3
  15. package/lib/checks/validator.js +8 -7
  16. package/lib/compiler/assert-consistency.js +40 -17
  17. package/lib/compiler/builtins.js +30 -53
  18. package/lib/compiler/checks.js +46 -14
  19. package/lib/compiler/cycle-detector.js +1 -4
  20. package/lib/compiler/define.js +35 -10
  21. package/lib/compiler/extend.js +21 -7
  22. package/lib/compiler/generate.js +3 -0
  23. package/lib/compiler/populate.js +5 -1
  24. package/lib/compiler/propagator.js +46 -9
  25. package/lib/compiler/resolve.js +94 -35
  26. package/lib/compiler/shared.js +60 -33
  27. package/lib/compiler/tweak-assocs.js +188 -92
  28. package/lib/compiler/utils.js +11 -1
  29. package/lib/edm/annotations/edmJson.js +41 -66
  30. package/lib/edm/annotations/genericTranslation.js +27 -9
  31. package/lib/edm/annotations/preprocessAnnotations.js +2 -3
  32. package/lib/edm/csn2edm.js +28 -11
  33. package/lib/edm/edmInboundChecks.js +58 -15
  34. package/lib/edm/edmPreprocessor.js +12 -16
  35. package/lib/edm/edmUtils.js +5 -2
  36. package/lib/gen/Dictionary.json +10 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +15 -2
  39. package/lib/gen/language.tokens +1 -0
  40. package/lib/gen/languageParser.js +6557 -5618
  41. package/lib/json/from-csn.js +4 -5
  42. package/lib/json/to-csn.js +29 -4
  43. package/lib/language/antlrParser.js +19 -1
  44. package/lib/language/errorStrategy.js +28 -7
  45. package/lib/language/genericAntlrParser.js +118 -24
  46. package/lib/language/textUtils.js +16 -0
  47. package/lib/main.d.ts +28 -3
  48. package/lib/main.js +3 -0
  49. package/lib/model/csnRefs.js +4 -1
  50. package/lib/model/csnUtils.js +20 -14
  51. package/lib/model/revealInternalProperties.js +5 -2
  52. package/lib/optionProcessor.js +23 -22
  53. package/lib/render/manageConstraints.js +13 -29
  54. package/lib/render/toCdl.js +47 -26
  55. package/lib/render/toHdbcds.js +63 -42
  56. package/lib/render/toRename.js +6 -10
  57. package/lib/render/toSql.js +71 -117
  58. package/lib/render/utils/common.js +41 -6
  59. package/lib/transform/.eslintrc.json +9 -1
  60. package/lib/transform/addTenantFields.js +228 -0
  61. package/lib/transform/db/applyTransformations.js +57 -4
  62. package/lib/transform/db/assertUnique.js +4 -4
  63. package/lib/transform/db/backlinks.js +13 -1
  64. package/lib/transform/db/cdsPersistence.js +1 -1
  65. package/lib/transform/db/expansion.js +24 -3
  66. package/lib/transform/db/flattening.js +70 -71
  67. package/lib/transform/db/killAnnotations.js +37 -0
  68. package/lib/transform/db/rewriteCalculatedElements.js +46 -6
  69. package/lib/transform/db/temporal.js +1 -1
  70. package/lib/transform/draft/db.js +2 -16
  71. package/lib/transform/draft/odata.js +3 -3
  72. package/lib/transform/effective/associations.js +3 -5
  73. package/lib/transform/effective/main.js +6 -9
  74. package/lib/transform/forOdata.js +26 -55
  75. package/lib/transform/forRelationalDB.js +38 -18
  76. package/lib/transform/odata/toFinalBaseType.js +3 -3
  77. package/lib/transform/odata/typesExposure.js +14 -5
  78. package/lib/transform/transformUtils.js +47 -34
  79. package/lib/transform/translateAssocsToJoins.js +45 -11
  80. package/lib/transform/universalCsn/coreComputed.js +1 -1
  81. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  82. package/package.json +7 -6
@@ -38,6 +38,7 @@ const {
38
38
  annotationLocation,
39
39
  linkToOrigin,
40
40
  setMemberParent,
41
+ dependsOn,
41
42
  proxyCopyMembers,
42
43
  setExpandStatus,
43
44
  setExpandStatusAnnotate,
@@ -855,6 +856,9 @@ function populate( model ) {
855
856
  const elemLocation = !query._main.$inferred && location;
856
857
  const origin = envParent ? navElem : navElem._origin;
857
858
  const elem = linkToOrigin( origin, name, query, null, elemLocation );
859
+ if (origin.$calcDepElement) // TODO: this will be changed in the next PR
860
+ dependsOn( elem, origin.$calcDepElement, location );
861
+
858
862
  // TODO: check assocToMany { * }
859
863
  dictAdd( elements, name, elem, ( _name, loc ) => {
860
864
  // there can be a definition from a previous inline with the same name:
@@ -1183,7 +1187,7 @@ function populate( model ) {
1183
1187
  function isDirectProjection( proj, base ) {
1184
1188
  return proj.kind === 'entity' && // not event
1185
1189
  // direct proj (TODO: or should we add them to another list?)
1186
- // TODO: delete ENTITY._from
1190
+ // TODO: delete ENTITY._from - maybe not...
1187
1191
  proj.query && proj.query.op && proj.query.op.val === 'SELECT' &&
1188
1192
  proj._from && proj._from.length === 1 &&
1189
1193
  base === resolvePath( proj._from[0], 'from', proj.query );
@@ -21,7 +21,7 @@ const {
21
21
  viewFromPrimary,
22
22
  } = require('./utils');
23
23
  const $inferred = Symbol.for( 'cds.$inferred' );
24
- // const { refString } = require( '../base/messages')
24
+ // const { ref } = require( '../model/revealInternalProperties' )
25
25
 
26
26
  // Note that propagation here is also used for deep-copying (function `onlyViaParent`)
27
27
  function propagate( model ) {
@@ -59,7 +59,7 @@ function propagate( model ) {
59
59
  srid: always,
60
60
  localized: always,
61
61
  target: notWithExpand,
62
- targetAspect: notWithExpand,
62
+ targetAspect,
63
63
  cardinality: notWithExpand,
64
64
  on: notWithExpand,
65
65
  foreignKeys: expensive, // includes "notWithExpand", dictionary copy
@@ -68,6 +68,7 @@ function propagate( model ) {
68
68
  enum: expensive,
69
69
  params: expensive, // actually only with parent action
70
70
  returns,
71
+ $filtered: annotation,
71
72
  };
72
73
  const { options } = model;
73
74
  // eslint-disable-next-line max-len
@@ -90,7 +91,7 @@ function propagate( model ) {
90
91
  runMembers( art );
91
92
  return;
92
93
  }
93
- // console.log('RUN:', art.name, art.elements ? Object.keys(art.elements) : 0)
94
+ // if (!art.builtin)console.log('RUN:', ref(art))
94
95
 
95
96
  const chain = [];
96
97
  let targets = [ art ];
@@ -109,6 +110,7 @@ function propagate( model ) {
109
110
  if (target.value?._artifact.$inferred !== 'include') {
110
111
  // If the referred to element is not inferred, it is a new one and not the original.
111
112
  // The new one was not originally referred to => error;
113
+ // TODO: no messages in propagator.js
112
114
  warning( 'ref-unexpected-override', [ target.name.location, target ],
113
115
  { id: target.name.id, target: target.value?._artifact },
114
116
  'Calculated element $(ID) does not originally refer to $(TARGET)' );
@@ -134,11 +136,11 @@ function propagate( model ) {
134
136
  chain.reverse();
135
137
  chain.forEach( step );
136
138
  runMembers( art );
137
- // console.log('DONE:', art.name, art.elements ? Object.keys(art.elements) : 0);
139
+ // if(!art.builtin)console.log('DONE:',ref(art),art.elements?Object.keys(art.elements):0);
138
140
  }
139
141
 
140
142
  function runMembers( art ) {
141
- // console.log('MEMBERS:',refString(art), art.elements ? Object.keys(art.elements) : 0)
143
+ // if(!art.builtin)console.log('MEMBERS:',ref(art))
142
144
  forEachMember( art, run ); // after propagation in parent!
143
145
  // propagate to sub query elements even if not requested:
144
146
  if (art.$queries)
@@ -150,16 +152,29 @@ function propagate( model ) {
150
152
  }
151
153
  if (obj.items)
152
154
  run( obj.items );
155
+ obj = obj.targetAspect;
156
+ // if(obj)console.log('TA:',ref(art),!!getOrigin( obj ))
157
+ if (obj && isAnonymousAspect( obj ))
158
+ run( obj );
153
159
  setLink( art, '_status', 'propagated' );
154
160
  }
155
161
 
162
+ function isAnonymousAspect( aspect ) {
163
+ while (aspect) {
164
+ if (aspect.elements)
165
+ return true;
166
+ aspect = getOrigin( aspect );
167
+ }
168
+ return false;
169
+ }
170
+
156
171
  function step({ target, source }) {
157
- // console.log('PROPS:',source&&source.name,'->',target.name)
158
172
  const viaType = target.type && // TODO: falsy $inferred value instead of 'cast'?
159
173
  (!target.type.$inferred || target.type.$inferred === 'cast');
160
174
  const keys = Object.keys( source );
175
+ // console.log('PROPS:',ref(source),'->',ref(target),keys.join('+'))
161
176
  for (const prop of keys) {
162
- // TODO: warning with competing props from multi-includes
177
+ // TODO: warning with competing props from multi-includes, but not in propagator.js
163
178
  if (target[prop] !== undefined || source[prop] === undefined)
164
179
  continue;
165
180
  const transformer = props[prop] || props[prop.charAt(0)];
@@ -227,7 +242,7 @@ function propagate( model ) {
227
242
  // * `enum`: an enum cannot be used with `expand`
228
243
  // * `keys`: should also not be propagated with `expand`
229
244
  function expensive( prop, target, source ) {
230
- // console.log(prop,source.name,'->',target.kind,target.name);
245
+ // console.log('EXP:',prop,ref(source),'->',ref(target));
231
246
  if (source.kind === 'builtin')
232
247
  return;
233
248
  if (target.expand) // do not propagate `keys` with expand
@@ -245,8 +260,12 @@ function propagate( model ) {
245
260
  target._outer && target._outer.location;
246
261
  const dict = source[prop];
247
262
  target[prop] = Object.create( null ); // also propagate empty elements
263
+ const propagateKey = target.kind === 'aspect'; // anonymous aspect
248
264
  for (const name in dict) {
249
- const member = linkToOrigin( dict[name], name, target, prop, location );
265
+ const origin = dict[name];
266
+ const member = linkToOrigin( origin, name, target, prop, location );
267
+ if (propagateKey && origin.key)
268
+ member.key = Object.assign( { $inferred: 'expanded' }, origin.key );
250
269
  member.$inferred = 'proxy';
251
270
  if (prop === 'foreignKeys')
252
271
  setLink( member, '_effectiveType', member );
@@ -262,6 +281,24 @@ function propagate( model ) {
262
281
  always( prop, target, source );
263
282
  }
264
283
 
284
+ function targetAspect( prop, target, source ) {
285
+ if (target.targetAspect)
286
+ return;
287
+ const ta = source.targetAspect;
288
+ if (!ta.elements && !ta._origin) { // _origin set for elements in source
289
+ notWithExpand( prop, target, source );
290
+ }
291
+ else {
292
+ const tat = { location: ta.location, $inferred: 'prop', kind: 'aspect' };
293
+ setLink( tat, '_origin', ta );
294
+ setLink( tat, '_outer', target );
295
+ setLink( tat, '_parent', target._parent );
296
+ setLink( tat, '_main', null );
297
+ target.targetAspect = tat;
298
+ // console.log('TAC:',ref(tat),'via',ref(ta))
299
+ }
300
+ }
301
+
265
302
  function notWithExpand( prop, target, source ) {
266
303
  if (!target.expand || prop === 'type' && source.elements)
267
304
  always( prop, target, source );
@@ -44,6 +44,7 @@ const {
44
44
  forEachGeneric,
45
45
  forEachInOrder,
46
46
  isDeprecatedEnabled,
47
+ isBetaEnabled,
47
48
  } = require('../base/model');
48
49
  const { dictAdd } = require('../base/dictionaries');
49
50
  const { dictLocation, weakLocation } = require('../base/location');
@@ -132,7 +133,9 @@ function resolve( model ) {
132
133
  if (location) {
133
134
  model.$assert = null;
134
135
  const msg = semanticLoc && 'target';
135
- error( 'ref-cyclic', [ location, semanticLoc || user ], { art, '#': msg }, {
136
+ error( 'ref-cyclic', [ location, semanticLoc || user ], {
137
+ art, '#': msg,
138
+ }, {
136
139
  std: 'Illegal circular reference to $(ART)',
137
140
  element: 'Illegal circular reference to element $(MEMBER) of $(ART)',
138
141
  target: 'Illegal circular reference to target $(ART)',
@@ -516,8 +519,8 @@ function resolve( model ) {
516
519
  if (art.value) {
517
520
  if (art.$syntax === 'calc')
518
521
  checkCalculatedElement( art );
519
- else if (art.type && !art.$inferred )
520
- checkStructureCast( art );
522
+ else if (art.type && !art.$inferred)
523
+ checkColumnTypeCast( art );
521
524
  }
522
525
 
523
526
  resolveExprInAnnotations( art );
@@ -554,10 +557,12 @@ function resolve( model ) {
554
557
 
555
558
  // Check explicit types: If either side has one, so must the other.
556
559
  const sType = specifiedElement.type?._artifact;
557
- const iType = getInferredPropFromOrigin( 'type' )?._artifact || inferredElement;
560
+ const iTypeArt = getInferredPropFromOrigin( 'type' )?._artifact;
561
+ const iType = iTypeArt || inferredElement;
558
562
 
559
563
  // xor: could be missing a type;
560
564
  // FIXME: The coding above returns incorrect iType for expand on associations
565
+
561
566
  if (!specifiedElement.type && inferredElement.type) {
562
567
  error( 'query-mismatched-element', [ specifiedElement.location, user ], {
563
568
  '#': !specifiedElement.type ? 'missing' : 'extra', name: user.name.id, prop: 'type',
@@ -565,8 +570,12 @@ function resolve( model ) {
565
570
  return;
566
571
  }
567
572
  // If specified type is `null`, type could not be resolved.
568
- else if (sType && sType !== iType) {
569
- const typeName = iType?.name && sType?.name ? 'typeName' : 'type';
573
+ else if (sType && sType !== iType &&
574
+ // Special case for $recompilation: allow one level of type indirection. See #12113.
575
+ (!model.options.$recompile || sType !== iType.type?._artifact)) {
576
+ const typeName = !iTypeArt && 'typeExtra' || // no inferred type prop
577
+ iType?.name && sType?.name && 'typeName' || // both types are named
578
+ 'type'; // unknown type names
570
579
  const othertype = typeName !== 'type' && iType || '';
571
580
  error( 'query-mismatched-element', [
572
581
  specifiedElement.type.location || specifiedElement.location, user,
@@ -638,8 +647,11 @@ function resolve( model ) {
638
647
 
639
648
  // If cardinality is not specified, the compiler uses the inferred one.
640
649
  if (specifiedElement.cardinality) {
650
+ // Users can change the origin's cardinality via filter: We can't rely on the origin.
651
+ const ref = inferredElement.value?.path;
652
+ const assocFilterCardinality = ref?.[ref.length - 1]?.cardinality;
641
653
  const sCardinality = specifiedElement.cardinality;
642
- const iCardinality = getInferredPropFromOrigin( 'cardinality' );
654
+ const iCardinality = assocFilterCardinality || getInferredCardinality();
643
655
  if (!iCardinality) {
644
656
  error( 'query-mismatched-element', [
645
657
  sCardinality.location || specifiedElement.location, user,
@@ -747,6 +759,20 @@ function resolve( model ) {
747
759
  }
748
760
  return element[prop];
749
761
  }
762
+
763
+ function getInferredCardinality() {
764
+ let element = inferredElement;
765
+ if (element._effectiveType !== 0) {
766
+ while (getOrigin( element )) {
767
+ const ref = element.value?.path;
768
+ if (element.cardinality || ref?.[ref.length - 1]?.cardinality)
769
+ break;
770
+ element = getOrigin( element );
771
+ }
772
+ }
773
+ const ref = element.value?.path;
774
+ return element.cardinality || ref?.[ref.length - 1]?.cardinality;
775
+ }
750
776
  }
751
777
 
752
778
 
@@ -795,24 +821,20 @@ function resolve( model ) {
795
821
  else if (!allowedInKind.includes( parent.kind )) {
796
822
  error( 'def-invalid-calc-elem', loc, { '#': parent.kind } );
797
823
  }
798
- else if (effectiveType( art )?.elements) {
824
+ else if (effectiveType( art )?.elements && !art.$inferred) {
799
825
  // For inferred (e.g. included) calc elements, this error is already emitted at the origin.
800
- if (!art.$inferred) {
801
- if (art.type)
802
- error( 'type-unexpected-structure', [ art.type.location, art ], { '#': 'calc' } );
803
- else
804
- error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
805
- }
826
+ if (art.type)
827
+ error( 'type-unexpected-structure', [ art.type.location, art ], { '#': 'calc' } );
828
+ else
829
+ error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
806
830
  }
807
- else if (effectiveType( art )?.items) {
831
+ else if (effectiveType( art )?.items && !art.$inferred) {
808
832
  // For inferred (e.g. included) calc elements, this error is already emitted at the origin.
809
- if (!art.$inferred) {
810
- const isCast = art.type?.$inferred === 'cast';
811
- error( 'type-unexpected-many', [ (art.type || art.value).location, art ], {
812
- '#': (!art.type && 'calc-implicit') || (isCast && 'calc-cast') || 'calc',
813
- elemref: art.type ? undefined : { ref: art.value.path },
814
- } );
815
- }
833
+ const isCast = art.type?.$inferred === 'cast';
834
+ error( 'type-unexpected-many', [ (art.type || art.value).location, art ], {
835
+ '#': (!art.type && 'calc-implicit') || (isCast && 'calc-cast') || 'calc',
836
+ elemref: art.type ? undefined : { ref: art.value.path },
837
+ } );
816
838
  }
817
839
  else {
818
840
  const noTruthyAllowed = [ 'localized', 'key', 'virtual' ];
@@ -827,7 +849,7 @@ function resolve( model ) {
827
849
  }
828
850
  }
829
851
 
830
- function checkStructureCast( art ) {
852
+ function checkColumnTypeCast( art ) {
831
853
  const elem = (art.value.op?.val === 'cast')
832
854
  ? art.value.args[0]?._artifact
833
855
  : art.value._artifact;
@@ -836,6 +858,9 @@ function resolve( model ) {
836
858
  error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'to-structure' } );
837
859
  else if (elem.elements) // TODO: calc elements
838
860
  error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'from-structure' } );
861
+ else if (elem.target && !art.type._artifact?.target)
862
+ // allow cast to association -> that is already checked and denied elsewhere
863
+ error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'from-assoc' } );
839
864
  }
840
865
  }
841
866
 
@@ -1353,18 +1378,50 @@ function resolve( model ) {
1353
1378
  }
1354
1379
  }
1355
1380
 
1356
- function resolveAnnoExpr( expr, art ) {
1357
- if (expr.$tokenTexts)
1358
- resolveExpr( expr, 'annotation', art );
1359
- else if (expr.literal === 'array')
1360
- expr.val.forEach( val => resolveAnnoExpr( val, art ) );
1361
- else if (expr.literal === 'struct')
1362
- Object.values( expr.struct ).forEach( val => resolveAnnoExpr( val, art ) );
1381
+ function resolveAnnoExpr( expr, art, anno = expr ) {
1382
+ if (expr.$tokenTexts) {
1383
+ if (!anno.kind)
1384
+ initAnnotationForExpression( anno, art );
1385
+ resolveExpr( expr, 'annotation', anno );
1386
+ reportUnsupportedAnnoExpr( expr );
1387
+ }
1388
+ else if (expr.literal === 'array') {
1389
+ expr.val.forEach( val => resolveAnnoExpr( val, art, anno ) );
1390
+ }
1391
+ else if (expr.literal === 'struct') {
1392
+ Object.values( expr.struct ).forEach( val => resolveAnnoExpr( val, art, anno ) );
1393
+ }
1394
+ }
1395
+
1396
+ function reportUnsupportedAnnoExpr( expr ) {
1397
+ if (isBetaEnabled( model.options, 'annotationExpressions' ))
1398
+ return;
1399
+ const alreadyReported = model.$messageFunctions.messages
1400
+ .find(msg => msg.messageId === 'anno-experimental-expressions');
1401
+ if (!alreadyReported) {
1402
+ info( 'anno-experimental-expressions', [ expr.location ], {
1403
+ option: 'annotationExpressions',
1404
+ // eslint-disable-next-line max-len
1405
+ }, 'Expressions in annotation values are a beta feature. Use at your own risk. (This message can be suppressed with beta flag $(OPTION))' );
1406
+ }
1407
+ }
1408
+
1409
+ // for faster processing, mark artifacts and annotations which contain anno expressions
1410
+ function initAnnotationForExpression( anno, art ) {
1411
+ anno.kind = '$annotation';
1412
+ setLink( anno, '_outer', art );
1413
+ while (!art.$contains?.$annotation) {
1414
+ art.$contains ??= {};
1415
+ art.$contains.$annotation = true; // TODO: extra values for elem and $self refs
1416
+ if (!art._main)
1417
+ break;
1418
+ art = art._parent; // TODO: really go up?
1419
+ }
1363
1420
  }
1364
1421
 
1365
1422
  function resolveExpr( expr, exprCtx, user ) {
1366
1423
  // console.log(expr?.location.line,exprCtx)
1367
- traverseExpr( expr, exprCtx, user, (e, c, u) => resolveExprItem( e, c, u ) );
1424
+ traverseExpr( expr, exprCtx, user, resolveExprItem );
1368
1425
  }
1369
1426
 
1370
1427
  function resolveExprItem( expr, expected, user ) {
@@ -1374,8 +1431,10 @@ function resolve( model ) {
1374
1431
  if (expr.path) {
1375
1432
  // TODO: re-think this $expected: 'exists' thing
1376
1433
  if (expr.$expected === 'exists') {
1377
- error( 'expr-unexpected-exists', [ expr.location, user ], {},
1378
- 'An EXISTS predicate is not expected here' );
1434
+ if (expected !== 'annotation') { // `exists e[…]` allowed in annotation expressions
1435
+ error( 'expr-unexpected-exists', [ expr.location, user ], {},
1436
+ 'An EXISTS predicate is not expected here' );
1437
+ }
1379
1438
  // We complain about the EXISTS before, as EXISTS subquery is also not supported
1380
1439
  // TODO: location of EXISTS, TODO: really do this in define.js
1381
1440
  expr.$expected = 'approved-exists'; // only complain once
@@ -1414,8 +1473,8 @@ function resolve( model ) {
1414
1473
  if (step.args)
1415
1474
  resolveParams( step.args, art, entity, expected, user, step.location );
1416
1475
 
1417
- // Publishing an association with filters is ok in column
1418
- const publishAssoc = art.kind === 'entity' && expected === 'column';
1476
+ // Publishing an association with filters is ok in columns and also ok in calculated elements.
1477
+ const publishAssoc = art.kind === 'entity' && (expected === 'column' || expected === 'calc');
1419
1478
 
1420
1479
  if (entity || publishAssoc) {
1421
1480
  if (step.where) {
@@ -105,7 +105,7 @@ function fns( model ) {
105
105
  dynamic: modelBuiltinsOrDefinitions,
106
106
  navigation: environment,
107
107
  notFound: undefinedDefinition,
108
- accept: acceptEntityOrAssoc,
108
+ accept: acceptQuerySource,
109
109
  noDep: '', // dependency special for from
110
110
  },
111
111
  type: {
@@ -286,7 +286,6 @@ function fns( model ) {
286
286
  'ref-undefined-element': 'anno-undefined-element',
287
287
  'ref-undefined-param': 'anno-undefined-param',
288
288
  },
289
- // check: checkAssocOn,
290
289
  param: paramSemantics,
291
290
  nestedColumn: () => ({ // in expand and inline - TODO
292
291
  lexical: justDollarSelf,
@@ -297,6 +296,17 @@ function fns( model ) {
297
296
  rewriteProjectionToSelf: true,
298
297
  }),
299
298
  },
299
+ // TODO: introduce some kind of inheritance
300
+ annoRewrite: { // annotation assignments
301
+ lexical: justDollarSelf, // TODO: forbid $projection
302
+ dollar: true,
303
+ dynamic: parentElements,
304
+ navigation: assocOnNavigation,
305
+ noDep: true,
306
+ notFound: null, // no error, just falsy links
307
+ param: paramSemantics,
308
+ // nestedColumn: // in expand and inline - TODO
309
+ },
300
310
  };
301
311
 
302
312
  Object.assign( model.$functions, {
@@ -314,25 +324,23 @@ function fns( model ) {
314
324
  // Expression traversal function ----------------------------------------------
315
325
  function traverseExpr( expr, exprCtx, user, callback ) {
316
326
  if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
317
- return;
327
+ return null;
318
328
 
319
329
  if (expr.path) {
320
- callback( expr, exprCtx, user );
321
330
  // TODO: move arguments and filter traversal to here
322
- return;
323
- }
324
- else if (expr.type || expr.query) {
325
- callback( expr, exprCtx, user );
331
+ return callback( expr, exprCtx, user );
326
332
  }
333
+ let first = (expr.type || expr.query) && callback( expr, exprCtx, user );
327
334
 
328
- if (expr.args) {
335
+ if (expr.args && !first) {
329
336
  const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
330
337
  // TODO: re-think $expected
331
- if (!callback.traverse?.( args, exprCtx, user, callback ))
332
- args.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
338
+ first = args.find( e => traverseExpr( e, exprCtx, user, callback ) );
333
339
  }
334
- if (expr.suffix) // fn( … ) OVER …
335
- expr.suffix.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
340
+
341
+ first ??= expr.suffix && // fn( ) OVER
342
+ expr.suffix.find( e => traverseExpr( e, exprCtx, user, callback ) );
343
+ return first;
336
344
  }
337
345
 
338
346
  // Return absolute name for unchecked path `ref`. We first try searching for
@@ -395,7 +403,6 @@ function fns( model ) {
395
403
  return setArtifactLink( ref, root );
396
404
 
397
405
  // how many path items are for artifacts (rest: elements)
398
- // console.log(expected, ref.path.map(a=>a.id),artItemsCount)
399
406
  let art = getPathItem( ref, semantics, user );
400
407
  if (!art)
401
408
  return setArtifactLink( ref, art );
@@ -413,14 +420,19 @@ function fns( model ) {
413
420
  const target = art._effectiveType?.target?._artifact;
414
421
  if (target)
415
422
  dependsOn( user._main, target, location, user );
423
+ if (target?.$calcDepElement)
424
+ dependsOn( user._main, target.$calcDepElement, location, user );
416
425
  }
417
426
  else if (art._main && art.kind !== 'select' || path[0]._navigation?.kind !== '$self') {
418
427
  // no real dependency to bare $self (or actually: the underlying query)
419
428
  dependsOn( user, art, location );
429
+ if (art.$calcDepElement)
430
+ dependsOn( user, art.$calcDepElement, location );
420
431
  // Without on-demand resolve, we can simply signal 'undefined "x"'
421
- // instead of 'illegal cycle' in the following case:
422
- // element elem: type of elem.x;
432
+ // instead of 'illegal cycle' in the following case:
433
+ // element elem: type of elem.x;
423
434
  }
435
+
424
436
  // TODO: really write dependency with expand/inline? write test
425
437
  // (removing it is not incompatible => not urgent)
426
438
  }
@@ -498,7 +510,9 @@ function fns( model ) {
498
510
  return undefined; // parse error
499
511
  if (head._artifact !== undefined)
500
512
  return head._artifact;
501
- const ruser = user._user || user; // TODO: nicer name if we keep this
513
+ let ruser = user._user || user; // TODO: nicer name if we keep this
514
+ if (ruser.kind === '$annotation')
515
+ ruser = ruser._outer;
502
516
 
503
517
  // Handle expand/inline, `type of`, :param, global (internally for CDL):
504
518
  if (user._pathHead && !semantics.isMainRef) // in expand/inline
@@ -545,8 +559,9 @@ function fns( model ) {
545
559
  else
546
560
  valid.push( model.$magicVariables.elements, removeDollarNames( dynamicDict ) );
547
561
  // TODO: streamline function arguments (probably: user, path, semantics )
548
- const undef = semantics.notFound( ruser, head, valid, dynamicDict,
549
- !isMainRef && user._user && user._artifact, path, semantics );
562
+ const undef = semantics.notFound?.( user._user || user, head, valid, dynamicDict,
563
+ !isMainRef && user._user && user._artifact,
564
+ path, semantics );
550
565
  return setArtifactLink( head, undef || null );
551
566
  }
552
567
 
@@ -913,9 +928,8 @@ function fns( model ) {
913
928
  if (target && assocSpec && user) {
914
929
  if (assocSpec !== 'calc')
915
930
  dependsOn( user._main || user, target, location || user.location, user );
916
- else // (TODO: users of) calc elements must depend on navigation target
917
- dependsOn( user, target, location || user.location );
918
- // TODO: have some _delayedDeps for calc elements
931
+ else
932
+ dependsOn( user.$calcDepElement, target, location || user.location, user );
919
933
  }
920
934
  const effectiveTarget = Functions.effectiveType( target );
921
935
  // if (effectiveTarget === 0 && location)
@@ -960,11 +974,11 @@ function fns( model ) {
960
974
  }
961
975
 
962
976
  function undefinedTargetElement( user, head, valid, _dict, pathItemArtifact ) {
963
- // is only called if there is a target, targetElements() returns falsy otherwise
964
- const { target } = pathItemArtifact?._effectiveType || user._parent;
977
+ // `art.target` may not set in case target entities `myEntity[unknown > 2]`
978
+ const art = pathItemArtifact?._effectiveType || user._parent;
965
979
  // TODO: better with $refs in filter conditions
966
980
  signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
967
- { '#': 'target', art: target, id: head.id } );
981
+ { '#': 'target', art: art.target || art, id: head.id } );
968
982
  }
969
983
 
970
984
  function undefinedVariable( user, head, valid ) {
@@ -1033,14 +1047,16 @@ function fns( model ) {
1033
1047
  return null;
1034
1048
  }
1035
1049
 
1036
- function undefinedNestedElement( user, head, valid, _dict, _art, path ) {
1050
+ function undefinedNestedElement( user, head, valid, _dict, _art, path, semantics ) {
1037
1051
  const art = user._pathHead._origin;
1038
1052
  if (!art)
1039
1053
  return null; // no consequential error
1040
- return undefinedItemElement( user, head, valid, null, art, path );
1054
+ return undefinedItemElement( user, head, valid, null, art, path, semantics );
1041
1055
  }
1042
1056
 
1043
1057
  function undefinedItemElement( user, item, valid, _dict, art, path, semantics ) {
1058
+ if (semantics.notFound === null)
1059
+ return;
1044
1060
  const query = userQuery( art );
1045
1061
  if (query?.name?.id > 1) {
1046
1062
  const root = userQuery( user ) !== query && path[0]._navigation;
@@ -1084,7 +1100,6 @@ function fns( model ) {
1084
1100
  { '#': variant, art: a, id: item.id }, semantics );
1085
1101
  }
1086
1102
  }
1087
- return null;
1088
1103
  }
1089
1104
 
1090
1105
  // Functions called via semantics.accept: -------------------------------------
@@ -1264,15 +1279,17 @@ function fns( model ) {
1264
1279
  return false;
1265
1280
  }
1266
1281
 
1267
- function acceptEntityOrAssoc( art, user, ref ) { // for FROM
1282
+ function acceptQuerySource( art, user, ref ) { // for FROM
1268
1283
  const { path, scope } = ref;
1269
1284
  // see getPathItem(): how many path items are for the main artifact ref?
1270
1285
  const artItemsCount = (typeof scope === 'number' && scope) || (scope ? 1 : path.length);
1271
- // at least the last main definition should be an entity
1286
+ // at least the last main definition should be an entity or an
1287
+ // event (if the user is also an event)
1272
1288
  // an additional check for target would need effectiveType()
1273
1289
  const source = path[artItemsCount - 1]._artifact;
1274
- if (source.kind !== 'entity') {
1275
- signalNotFound( 'ref-invalid-source', [ ref.location, user ], null );
1290
+ const variant = (user._main.kind === 'event') ? 'event' : 'std';
1291
+ if (source.kind !== 'entity' && !acceptEventProjectionSource( source, user )) {
1292
+ signalNotFound( 'ref-invalid-source', [ ref.location, user ], null, { '#': variant } );
1276
1293
  return false;
1277
1294
  }
1278
1295
  if (source === art)
@@ -1280,10 +1297,20 @@ function fns( model ) {
1280
1297
  const assoc = Functions.effectiveType( art );
1281
1298
  if (assoc.target)
1282
1299
  return art; // TODO: use target here
1283
- signalNotFound( 'ref-invalid-source', [ ref.location, user ], null );
1300
+ signalNotFound( 'ref-invalid-source', [ ref.location, user ], null, { '#': variant } );
1284
1301
  return false;
1285
1302
  }
1286
1303
 
1304
+ function acceptEventProjectionSource( source, user ) {
1305
+ if (user._main.kind !== 'event' || (source.kind !== 'event' && source.kind !== 'type'))
1306
+ return false;
1307
+ const effectiveType = Functions.effectiveType( source );
1308
+ if (!effectiveType)
1309
+ return false;
1310
+ const { kind } = effectiveType;
1311
+ return (kind === 'entity' || kind === 'event' || (kind === 'type' && effectiveType.elements));
1312
+ }
1313
+
1287
1314
  function acceptTypeOrElement( art, user, ref ) { // for type
1288
1315
  // was ['action', 'function'].includes( user._parent?.kind ))
1289
1316
  while (user._outer)