@sap/cds-compiler 4.0.2 → 4.2.2

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 (101) hide show
  1. package/CHANGELOG.md +200 -5
  2. package/bin/cdsc.js +18 -15
  3. package/doc/CHANGELOG_BETA.md +16 -0
  4. package/doc/CHANGELOG_DEPRECATED.md +15 -0
  5. package/lib/api/main.js +33 -13
  6. package/lib/api/options.js +2 -2
  7. package/lib/api/validate.js +25 -25
  8. package/lib/base/location.js +6 -7
  9. package/lib/base/message-registry.js +123 -42
  10. package/lib/base/messages.js +18 -10
  11. package/lib/base/model.js +43 -10
  12. package/lib/checks/defaultValues.js +6 -6
  13. package/lib/checks/elements.js +11 -10
  14. package/lib/checks/foreignKeys.js +0 -5
  15. package/lib/checks/manyNavigations.js +33 -0
  16. package/lib/checks/onConditions.js +22 -14
  17. package/lib/checks/queryNoDbArtifacts.js +132 -73
  18. package/lib/checks/selectItems.js +4 -55
  19. package/lib/checks/sql-snippets.js +15 -4
  20. package/lib/checks/types.js +3 -3
  21. package/lib/checks/utils.js +4 -3
  22. package/lib/checks/validator.js +3 -1
  23. package/lib/compiler/.eslintrc.json +2 -1
  24. package/lib/compiler/assert-consistency.js +71 -40
  25. package/lib/compiler/base.js +7 -2
  26. package/lib/compiler/builtins.js +40 -41
  27. package/lib/compiler/checks.js +415 -367
  28. package/lib/compiler/classes.js +62 -0
  29. package/lib/compiler/cycle-detector.js +9 -9
  30. package/lib/compiler/define.js +124 -90
  31. package/lib/compiler/extend.js +115 -88
  32. package/lib/compiler/finalize-parse-cdl.js +26 -25
  33. package/lib/compiler/generate.js +57 -49
  34. package/lib/compiler/index.js +56 -56
  35. package/lib/compiler/kick-start.js +10 -7
  36. package/lib/compiler/moduleLayers.js +1 -1
  37. package/lib/compiler/populate.js +180 -144
  38. package/lib/compiler/propagator.js +10 -9
  39. package/lib/compiler/resolve.js +321 -246
  40. package/lib/compiler/shared.js +812 -433
  41. package/lib/compiler/tweak-assocs.js +114 -50
  42. package/lib/compiler/utils.js +241 -46
  43. package/lib/edm/.eslintrc.json +40 -1
  44. package/lib/edm/annotations/genericTranslation.js +721 -707
  45. package/lib/edm/annotations/preprocessAnnotations.js +88 -77
  46. package/lib/edm/csn2edm.js +389 -378
  47. package/lib/edm/edm.js +679 -770
  48. package/lib/edm/edmAnnoPreprocessor.js +132 -146
  49. package/lib/edm/edmInboundChecks.js +29 -27
  50. package/lib/edm/edmPreprocessor.js +689 -648
  51. package/lib/edm/edmUtils.js +279 -300
  52. package/lib/gen/Dictionary.json +34 -10
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +1 -1
  55. package/lib/gen/languageParser.js +2857 -2856
  56. package/lib/json/from-csn.js +77 -51
  57. package/lib/json/to-csn.js +15 -15
  58. package/lib/language/antlrParser.js +2 -1
  59. package/lib/language/genericAntlrParser.js +52 -43
  60. package/lib/language/language.g4 +61 -64
  61. package/lib/language/multiLineStringParser.js +2 -0
  62. package/lib/main.d.ts +65 -0
  63. package/lib/model/csnRefs.js +37 -19
  64. package/lib/model/csnUtils.js +51 -18
  65. package/lib/model/revealInternalProperties.js +30 -22
  66. package/lib/modelCompare/compare.js +149 -41
  67. package/lib/modelCompare/utils/filter.js +55 -25
  68. package/lib/optionProcessor.js +21 -9
  69. package/lib/render/manageConstraints.js +20 -17
  70. package/lib/render/toCdl.js +63 -23
  71. package/lib/render/toHdbcds.js +2 -2
  72. package/lib/render/toRename.js +4 -9
  73. package/lib/render/toSql.js +82 -35
  74. package/lib/render/utils/common.js +11 -9
  75. package/lib/render/utils/unique.js +52 -0
  76. package/lib/transform/db/applyTransformations.js +62 -21
  77. package/lib/transform/db/assertUnique.js +7 -8
  78. package/lib/transform/db/associations.js +2 -2
  79. package/lib/transform/db/cdsPersistence.js +9 -9
  80. package/lib/transform/db/constraints.js +47 -17
  81. package/lib/transform/db/expansion.js +138 -68
  82. package/lib/transform/db/flattening.js +98 -30
  83. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  84. package/lib/transform/db/temporal.js +1 -1
  85. package/lib/transform/db/transformExists.js +8 -7
  86. package/lib/transform/db/views.js +73 -33
  87. package/lib/transform/draft/db.js +11 -9
  88. package/lib/transform/draft/odata.js +1 -1
  89. package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
  90. package/lib/transform/forRelationalDB.js +148 -136
  91. package/lib/transform/localized.js +92 -54
  92. package/lib/transform/odata/toFinalBaseType.js +3 -3
  93. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
  94. package/lib/transform/translateAssocsToJoins.js +14 -28
  95. package/lib/utils/file.js +7 -7
  96. package/lib/utils/moduleResolve.js +210 -121
  97. package/lib/utils/objectUtils.js +1 -1
  98. package/package.json +5 -5
  99. package/share/messages/check-proper-type-of.md +1 -1
  100. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  101. package/share/messages/message-explanations.json +1 -1
@@ -62,14 +62,14 @@ const {
62
62
  testExpr,
63
63
  targetMaxNotOne,
64
64
  traverseQueryPost,
65
- traverseExpr,
65
+ linkToOrigin,
66
66
  } = require('./utils');
67
67
 
68
68
  const detectCycles = require('./cycle-detector');
69
69
 
70
- const $location = Symbol.for('cds.$location');
70
+ const $location = Symbol.for( 'cds.$location' );
71
71
 
72
- const $inferred = Symbol.for('cds.$inferred');
72
+ const $inferred = Symbol.for( 'cds.$inferred' );
73
73
 
74
74
  // TODO: make this part of specExpected in shared.js
75
75
  // (standard: not possible on last if !ref.$expected → ref.scope: '$exists')
@@ -87,18 +87,20 @@ function resolve( model ) {
87
87
  } = model.$messageFunctions;
88
88
  const {
89
89
  resolvePath,
90
+ traverseExpr,
90
91
  createRemainingAnnotateStatements,
91
92
  effectiveType,
92
93
  getOrigin,
93
- resolveType,
94
+ getInheritedProp,
94
95
  resolveTypeArgumentsUnchecked,
95
96
  } = model.$functions;
96
97
  Object.assign( model.$functions, {
97
98
  resolveExpr,
99
+ addForeignKeyNavigations,
98
100
  } );
99
101
 
100
102
  const ignoreSpecifiedElements
101
- = isDeprecatedEnabled(model.options, 'ignoreSpecifiedQueryElements');
103
+ = isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' );
102
104
 
103
105
  return doResolve();
104
106
 
@@ -125,14 +127,21 @@ function resolve( model ) {
125
127
  // create “super” ANNOTATE statements for annotations on unknown artifacts:
126
128
  createRemainingAnnotateStatements();
127
129
  // report cyclic dependencies:
128
- detectCycles( model.definitions, ( user, art, location ) => {
130
+ detectCycles( model.definitions, ( user, art, location, semanticLoc ) => {
129
131
  if (location) {
130
- error( 'ref-cyclic', [ location, user ], { art }, {
132
+ model.$assert = null;
133
+ const msg = semanticLoc && 'target';
134
+ error( 'ref-cyclic', [ location, semanticLoc || user ], { art, '#': msg }, {
131
135
  std: 'Illegal circular reference to $(ART)',
132
136
  element: 'Illegal circular reference to element $(MEMBER) of $(ART)',
133
- });
137
+ target: 'Illegal circular reference to target $(ART)',
138
+ } );
134
139
  }
135
- });
140
+ } );
141
+ if (model.$assert) {
142
+ error( '$internal-expecting-cyclic', null, {},
143
+ 'INTERNAL: the compiler should have issued an Error[ref-cyclic]' );
144
+ }
136
145
  return model;
137
146
  }
138
147
 
@@ -149,22 +158,52 @@ function resolve( model ) {
149
158
  return;
150
159
  // TODO: what about elements where _origin is set without value?
151
160
  // TODO: or should we push elems with `expand` sibling to extra list for
152
- // better messages? (Whatever that means exactly.)
161
+ // better messages? (Whatever that means exactly.)
153
162
  const nav = pathNavigation( elem.value );
154
163
  const { path } = elem.value;
155
- const item = path[path.length - 1];
156
- if (nav.navigation && nav.item === item) {
157
- // sourceElem, alias.sourceElem, mixin:
158
- // redirectImplicitly( elem, origin );
159
- pushLink( nav.navigation, '_projections', elem );
160
- }
161
- else if (elem._pathHead?.kind === '$inline' && path.length === 1) {
164
+
165
+ if (elem._pathHead?.kind === '$inline' && path.length === 1) {
166
+ const item = path[0];
162
167
  const hpath = elem._pathHead.value?.path;
163
168
  const head = hpath?.length === 1 && hpath[0]._navigation;
164
169
  // Alias .{ elem } - but not with Alias.{ $magic }: consider for ON-rewrite
165
170
  if (head?.kind === '$tableAlias' && item.id.charAt(0) !== '$')
166
171
  pushLink( head.elements[item.id], '_projections', elem );
167
172
  }
173
+ else if (nav.navigation) { // not set for $self.…
174
+ // Path could start with table alias; get start index
175
+ let index = path.indexOf(nav.item);
176
+ if (index === -1)
177
+ return;
178
+
179
+ let navItem = nav.navigation;
180
+ if (path[index].where || path[index].args)
181
+ return;
182
+ ++index;
183
+ while (navItem && index < path.length) {
184
+ const step = path[index];
185
+ if (!step?.id || step.where || step.args)
186
+ break;
187
+ if (!navItem.elements?.[step.id]) {
188
+ const elements = navItem._origin?.elements ||
189
+ navItem._origin?.target?._artifact?.elements;
190
+ if (!elements)
191
+ break;
192
+ // Only link available path steps (navigation tree).
193
+ const origin = elements[step.id];
194
+ const member = linkToOrigin( origin, step.id, navItem, 'elements',
195
+ navItem.path?.location, true );
196
+ member.$inferred = 'expanded';
197
+ member.kind = '$navElement';
198
+ }
199
+ navItem = navItem.elements[step.id];
200
+ setLink( step, '_navigation', navItem );
201
+ ++index;
202
+ }
203
+ // Last path step, if found, is a simple projection
204
+ if (index === path.length && navItem)
205
+ pushLink( navItem, '_projections', elem );
206
+ }
168
207
  } );
169
208
  }
170
209
  }
@@ -319,13 +358,32 @@ function resolve( model ) {
319
358
  const parent = art._parent;
320
359
  const allowedInMain = [ 'entity', 'aspect', 'event' ].includes( adHocOrMainKind( art ) );
321
360
  const isTopLevelElement = parent && (parent.kind !== 'element' || parent.targetAspect);
322
- if (art.key?.val && !art.key.$inferred && !(allowedInMain && isTopLevelElement)) {
323
- warning( 'unexpected-key', [ art.key.location, art ],
324
- { '#': allowedInMain ? 'sub' : 'std', keyword: 'key' }, {
325
- std: '$(KEYWORD) is only supported for elements in an entity or an aspect',
326
- sub: '$(KEYWORD) is only supported for top-level elements',
327
- });
361
+
362
+ // Check KEY (TODO: make this an extra function)
363
+ const { key } = art;
364
+ if (key?.val && !key.$inferred) {
365
+ // With unmanaged/composition as key, we complain at the `key` keyword, not
366
+ // the `on` condition / the aspect, because the easiest fix would be to
367
+ // simply remove the keyword. Text and message-id are accordingly.
368
+ // This fits nicely with exposing unmanaged/composition with explicit `key`.
369
+ // We do not complain about unmanaged/composition inside struct keys.
370
+ // (Actually, aspect compositions are not supported as sub elements anyway.)
371
+ if (getInheritedProp( art, 'targetAspect' )) {
372
+ error( 'def-invalid-key', [ key.location, art ], { '#': 'composition' } );
373
+ // TODO: test with managed composition exposed with explicit KEY
374
+ }
375
+ else if (art.target && getInheritedProp( art, 'on' )) {
376
+ error( 'def-invalid-key', [ key.location, art ], { '#': 'unmanaged' } );
377
+ }
378
+ else if (!allowedInMain || !isTopLevelElement) {
379
+ warning( 'def-unsupported-key', [ art.key.location, art ],
380
+ { '#': allowedInMain ? 'sub' : 'std', keyword: 'key' }, {
381
+ std: '$(KEYWORD) is only supported for elements in an entity or an aspect',
382
+ sub: '$(KEYWORD) is only supported for top-level elements',
383
+ } );
384
+ }
328
385
  }
386
+
329
387
  if (art.targetAspect && !(allowedInMain && isTopLevelElement)) {
330
388
  message( 'type-managed-composition', [ art.targetAspect.location, art ],
331
389
  { '#': allowedInMain ? 'sub' : 'std' } );
@@ -334,7 +392,7 @@ function resolve( model ) {
334
392
  for (const include of art.includes) {
335
393
  const struct = include._artifact;
336
394
  if (struct && struct.kind !== 'type' && struct.elements &&
337
- Object.values( struct.elements ).some( e => e.targetAspect)) {
395
+ Object.values( struct.elements ).some( e => e.targetAspect )) {
338
396
  message( 'type-managed-composition', [ include.location, art ],
339
397
  { '#': struct.kind, art: struct } );
340
398
  }
@@ -358,7 +416,7 @@ function resolve( model ) {
358
416
  }
359
417
  if (obj.items) { // TODO: make this a while in v2 (also items proxy)
360
418
  obj = obj.items || obj; // the object which has type properties
361
- effectiveType(obj);
419
+ effectiveType( obj );
362
420
  }
363
421
  if (obj.type) { // TODO: && !obj.type.$inferred ?
364
422
  if (obj !== (art.returns || art)) // not already checked
@@ -385,13 +443,13 @@ function resolve( model ) {
385
443
  if (elemtype.category === 'relation' && obj.type.path.length > 0 &&
386
444
  !obj.target && !obj.targetAspect) {
387
445
  const isCsn = (obj._block && obj._block.$frontend === 'json');
388
- error('type-missing-target', [ obj.type.location, obj ],
389
- { '#': isCsn ? 'csn' : 'std', type: elemtype.name.absolute }, {
390
- // We don't say "use 'association to <target>" because the type could be used
391
- // in action parameters, etc. as well.
392
- std: 'The type $(TYPE) can\'t be used directly because it\'s compiler internal',
393
- csn: 'Type $(TYPE) is missing a target',
394
- });
446
+ error( 'type-missing-target', [ obj.type.location, obj ],
447
+ { '#': isCsn ? 'csn' : 'std', type: elemtype.name.absolute }, {
448
+ // We don't say "use 'association to <target>" because the type could be used
449
+ // in action parameters, etc. as well.
450
+ std: 'The type $(TYPE) can\'t be used directly because it\'s compiler internal',
451
+ csn: 'Type $(TYPE) is missing a target',
452
+ } );
395
453
  }
396
454
  }
397
455
  }
@@ -426,21 +484,26 @@ function resolve( model ) {
426
484
  forEachGeneric( obj.targetAspect, 'elements', elem => dependsOnSilent( art, elem ) );
427
485
  }
428
486
  if (obj.foreignKeys) { // silent dependencies
429
- forEachGeneric( obj, 'foreignKeys', (elem) => {
430
- dependsOnSilent( art, elem );
431
- } );
487
+ // Avoid strange ref-cyclic if managed composition is key (check comes later)
488
+ // TODO: the following is already done by addImplicitForeignKeys()!
489
+ if (obj.$inferred !== 'aspect-composition') {
490
+ forEachGeneric( obj, 'foreignKeys', (elem) => {
491
+ dependsOnSilent( art, elem );
492
+ } );
493
+ }
432
494
  addForeignKeyNavigations( art );
433
495
  }
434
496
 
435
497
  resolveExpr( art.default, 'default', art );
436
- resolveExpr( art.value, (art.$syntax === 'calc' ? 'calc' : 'expr'), art );
498
+ // TODO: distinguish not by $syntax (it is semantics), but whether in query
499
+ resolveExpr( art.value, (art.$syntax === 'calc' ? 'calc' : 'column'), art );
437
500
  if (art.type?.$inferred === 'cast')
438
501
  inferTypePropertiesFromCast( art );
439
502
  if (art.value) {
440
503
  if (art.$syntax === 'calc')
441
- checkCalculatedElement(art);
504
+ checkCalculatedElement( art );
442
505
  else if (art.type && !art.$inferred )
443
- checkStructureCast(art);
506
+ checkStructureCast( art );
444
507
  }
445
508
 
446
509
  forEachMember( art, resolveRefs, art.targetAspect );
@@ -450,14 +513,16 @@ function resolve( model ) {
450
513
 
451
514
  if (!ignoreSpecifiedElements && art.elements$ && art.elements) {
452
515
  for (const id in art.elements$) {
453
- resolveRefs(art.elements$[id]);
454
- checkSpecifiedElement(art.elements[id], art.elements$[id]);
516
+ resolveRefs( art.elements$[id] );
517
+ checkSpecifiedElement( art.elements[id], art.elements$[id] );
455
518
  }
456
519
  }
457
520
 
458
521
  /**
459
522
  * Check whether the signature of the specified element matches that of the inferred one.
460
523
  *
524
+ * TODO: resolveRefs() is already too long → do not add sub functions
525
+ *
461
526
  * TODO:
462
527
  * - This function has a lot of quite similar code blocks; it should be refactored to
463
528
  * combine them.
@@ -474,96 +539,97 @@ function resolve( model ) {
474
539
 
475
540
  // Check explicit types: If either side has one, so must the other.
476
541
  const sType = specifiedElement.type?._artifact;
477
- const iType = getInferredPropFromOrigin('type')?._artifact || inferredElement;
542
+ const iType = getInferredPropFromOrigin( 'type' )?._artifact || inferredElement;
478
543
 
479
544
  // xor: could be missing a type;
480
545
  // FIXME: The coding above returns incorrect iType for expand on associations
481
546
  if (!specifiedElement.type && inferredElement.type) {
482
- error('query-mismatched-element', [ specifiedElement.location, user ], {
547
+ error( 'query-mismatched-element', [ specifiedElement.location, user ], {
483
548
  '#': !specifiedElement.type ? 'missing' : 'extra', name: user.name.id, prop: 'type',
484
- });
549
+ } );
485
550
  return;
486
551
  }
487
552
  // If specified type is `null`, type could not be resolved.
488
553
  else if (sType && sType !== iType) {
489
554
  const typeName = iType?.name && sType?.name ? 'typeName' : 'type';
490
- error('query-mismatched-element', [
555
+ const othertype = typeName !== 'type' && iType || '';
556
+ error( 'query-mismatched-element', [
491
557
  specifiedElement.type.location || specifiedElement.location, user,
492
558
  ], {
493
559
  '#': typeName,
494
560
  name: user.name.id,
495
561
  type: sType,
496
- othertype: iType,
497
- });
562
+ othertype,
563
+ } );
498
564
  return;
499
565
  }
500
566
 
501
567
  // This relies on (element) expansion! Check that both sides have the following properties.
502
568
  // On the inferred side, they are likely expanded.
503
- if (!hasXorPropMismatch('elements') && !hasXorPropMismatch('items') &&
504
- !hasXorPropMismatch('target') && !hasXorPropMismatch('enum')) {
569
+ if (!hasXorPropMismatch( 'elements' ) && !hasXorPropMismatch( 'items' ) &&
570
+ !hasXorPropMismatch( 'target' ) && !hasXorPropMismatch( 'enum' )) {
505
571
  // Element are already traversed via elements$ merging.
506
572
 
507
573
  // only check items, if the specified one is not expanded/inferred
508
574
  if (specifiedElement.items && !specifiedElement.items.$inferred)
509
- checkSpecifiedElement(inferredElement.items, specifiedElement.items, specifiedElement);
575
+ checkSpecifiedElement( inferredElement.items, specifiedElement.items, specifiedElement );
510
576
 
511
- if (specifiedElement.target &&
577
+ if (specifiedElement.target?._artifact && inferredElement.target?._artifact &&
512
578
  specifiedElement.target._artifact !== inferredElement.target._artifact) {
513
- error('query-mismatched-element', [
579
+ error( 'query-mismatched-element', [
514
580
  specifiedElement.target.location || specifiedElement.location, user,
515
581
  ], {
516
582
  '#': 'target',
517
583
  name: user.name.id,
518
584
  target: specifiedElement.target,
519
585
  art: inferredElement.target,
520
- });
586
+ } );
521
587
  }
522
588
 
523
589
  if (specifiedElement.foreignKeys) {
524
- const sKeys = Object.keys(specifiedElement.foreignKeys);
590
+ const sKeys = Object.keys( specifiedElement.foreignKeys );
525
591
  /** @type {any} */
526
592
  let iKeys = inferredElement;
527
593
  if (inferredElement._effectiveType !== 0) {
528
594
  while (iKeys._origin && !iKeys.foreignKeys)
529
595
  iKeys = iKeys._origin;
530
596
  }
531
- iKeys = Object.keys(iKeys.foreignKeys || {});
532
- if (sKeys.length !== iKeys.length || sKeys.some(key => !iKeys.includes(key))) {
533
- error('query-mismatched-element', [
597
+ iKeys = Object.keys( iKeys.foreignKeys || {} );
598
+ if (sKeys.length !== iKeys.length || sKeys.some( fkey => !iKeys.includes( fkey ) )) {
599
+ error( 'query-mismatched-element', [
534
600
  specifiedElement.foreignKeys.location || specifiedElement.location, user,
535
601
  ], {
536
602
  '#': 'foreignKeys',
537
603
  name: user.name.id,
538
604
  target: specifiedElement.target,
539
605
  art: inferredElement.target,
540
- });
606
+ } );
541
607
  }
542
608
  }
543
609
 
544
610
  if (specifiedElement.virtual) {
545
- const iVirtual = getInferredPropFromOrigin('virtual')?.val || false;
611
+ const iVirtual = getInferredPropFromOrigin( 'virtual' )?.val || false;
546
612
  if (!specifiedElement.virtual.val !== !iVirtual) {
547
- error('query-mismatched-element', [
613
+ error( 'query-mismatched-element', [
548
614
  specifiedElement.virtual.location || specifiedElement.location, user,
549
615
  ], {
550
616
  '#': 'prop', prop: 'virtual', name: user.name.id,
551
- });
617
+ } );
552
618
  }
553
619
  }
554
620
 
555
621
  // If cardinality is not specified, the compiler uses the inferred one.
556
622
  if (specifiedElement.cardinality) {
557
623
  const sCardinality = specifiedElement.cardinality;
558
- const iCardinality = getInferredPropFromOrigin('cardinality');
624
+ const iCardinality = getInferredPropFromOrigin( 'cardinality' );
559
625
  if (!iCardinality) {
560
- error('query-mismatched-element', [
626
+ error( 'query-mismatched-element', [
561
627
  sCardinality.location || specifiedElement.location, user,
562
628
  ], {
563
629
  '#': 'extra',
564
630
  prop: 'cardinality',
565
631
  name: user.name.id,
566
- });
632
+ } );
567
633
  }
568
634
  else {
569
635
  // Note: Cardinality does not have sourceMin (CSN "srcmin").
@@ -575,7 +641,7 @@ function resolve( model ) {
575
641
  for (const prop in props) {
576
642
  if (sCardinality[prop]?.val === iCardinality[prop]?.val)
577
643
  continue;
578
- error('query-mismatched-element', [
644
+ error( 'query-mismatched-element', [
579
645
  sCardinality[prop]?.location || sCardinality.location || specifiedElement.location,
580
646
  user,
581
647
  ], {
@@ -583,53 +649,59 @@ function resolve( model ) {
583
649
  '#': !sCardinality[prop] ? 'missing' : (iCardinality[prop] ? 'prop' : 'extra'),
584
650
  prop: `cardinality.${ props[prop] }`,
585
651
  name: user.name.id,
586
- });
652
+ } );
587
653
  }
588
654
  }
589
655
  }
590
656
 
591
657
  if (specifiedElement.value) {
592
- error('query-unexpected-property', [
658
+ error( 'query-unexpected-property', [
593
659
  specifiedElement.value.location || specifiedElement.location, user,
594
660
  ], {
595
661
  '#': 'calculatedElement', prop: 'value', name: user.name.id,
596
- });
662
+ } );
597
663
  }
598
664
 
599
665
  if (specifiedElement.key) { // TODO: `|| inferredElement.key?.val`, once to.sql is fixed
600
666
  // TODO: Do not use _origin chain for key; has been propagated in propagateKeyProps().
601
- const iKey = getInferredPropFromOrigin('key')?.val;
667
+ const iKey = getInferredPropFromOrigin( 'key' )?.val;
602
668
  // If "key" is specified or truthy in the inferred element, the values must match.
603
669
  if (!iKey !== !specifiedElement.key?.val) {
604
- error('query-mismatched-element', [
670
+ error( 'query-mismatched-element', [
605
671
  specifiedElement.key?.location || specifiedElement.location, user,
606
672
  ], {
607
673
  '#': specifiedElement.key ? 'prop' : 'missing', prop: 'key', name: user.name.id,
608
- });
674
+ } );
609
675
  }
610
676
  }
611
677
 
612
- if (specifiedElement.enum) {
613
- const iEnum = inferredElement.enum || inferredElement.value?.enum;
614
- forEachGeneric(specifiedElement, 'enum', (sVal, name) => {
615
- const iVal = iEnum[name];
616
- if (!iVal) {
617
- error('query-mismatched-element', [ specifiedElement.location, user ], {
618
- '#': 'std',
619
- name: user.name.id,
620
- prop: 'enum',
621
- });
678
+ if (specifiedElement.enum && !specifiedElement.$expand) {
679
+ // TODO: ".value" is necessary due to recompilation: The compiler does not copy
680
+ // "enum" out of ".value", i.e. casts, only "type", changing the _effectiveType.
681
+ const iEnumValues = inferredElement.enum || inferredElement.value?.enum;
682
+ const sEnumValues = specifiedElement.enum;
683
+ for (const name in specifiedElement.enum) {
684
+ // TODO: See TODO above; issue is cast()
685
+ const sEnumEntry = sEnumValues[name];
686
+ const iEnumEntry = iEnumValues[name]?._effectiveType || iEnumValues[name];
687
+ if (!iEnumEntry) {
688
+ error( 'query-mismatched-element', [ specifiedElement.location, user ], {
689
+ '#': 'enumExtra', name: user.name.id, id: name,
690
+ } );
691
+ break;
622
692
  }
623
-
624
- // TODO: Get $expanded enum values
625
- /* if (iVal.value?.val !== sVal.value?.val || iVal.value?.['#'] !== sVal.value?.['#']) {
626
- error('query-mismatched-element', [ specifiedElement.location, user ], {
627
- '#': 'std',
628
- name: user.name.id,
629
- prop: 'enum',
630
- });
631
- } */
632
- });
693
+ else {
694
+ // We allow implicit `val: "<name>"`.
695
+ const iVal = iEnumEntry.value?.val || iEnumEntry.value?.['#'] || name;
696
+ const sVal = sEnumEntry.value?.val || sEnumEntry.value?.['#'] || name;
697
+ if (iVal !== sVal) {
698
+ error( 'query-mismatched-element', [ specifiedElement.location, user ], {
699
+ '#': 'enumVal', name: user.name.id, id: name,
700
+ } );
701
+ break;
702
+ }
703
+ }
704
+ }
633
705
  }
634
706
  }
635
707
 
@@ -642,7 +714,7 @@ function resolve( model ) {
642
714
  !inferredElement.value?.[prop] !== !specifiedElement[prop]) {
643
715
  error( 'query-mismatched-element', [ specifiedElement.location, specifiedElement ], {
644
716
  '#': specifiedElement[prop] ? 'extra' : 'missing', name: user.name.id, prop,
645
- });
717
+ } );
646
718
  return true;
647
719
  }
648
720
  return false;
@@ -652,8 +724,8 @@ function resolve( model ) {
652
724
  // Inferred property via _origin chain (0 === circular).
653
725
  let element = inferredElement;
654
726
  if (element._effectiveType !== 0) {
655
- while (getOrigin(element) && !element[prop])
656
- element = getOrigin(element);
727
+ while (getOrigin( element ) && !element[prop])
728
+ element = getOrigin( element );
657
729
  }
658
730
  return element[prop];
659
731
  }
@@ -691,21 +763,21 @@ function resolve( model ) {
691
763
  while (parent.kind === 'element')
692
764
  parent = parent._parent;
693
765
 
694
- if (!allowedInKind.includes(art._main.kind)) {
766
+ if (!allowedInKind.includes( art._main.kind )) {
695
767
  if (art.$inferred === 'include') {
696
768
  // even for include-chains, we find the correct ref due to element-expansion.
697
- const include = art._main.includes.find(i => i._artifact === art._origin._main);
698
- error('ref-invalid-calc-elem', [ include.location || art.value.location, art ],
699
- { '#': art._main.kind });
769
+ const include = art._main.includes.find( i => i._artifact === art._origin._main );
770
+ error( 'ref-invalid-calc-elem', [ include.location || art.value.location, art ],
771
+ { '#': art._main.kind } );
700
772
  }
701
773
  else {
702
774
  error( 'def-invalid-calc-elem', loc, { '#': art._main.kind } );
703
775
  }
704
776
  }
705
- else if (!allowedInKind.includes(parent.kind)) {
777
+ else if (!allowedInKind.includes( parent.kind )) {
706
778
  error( 'def-invalid-calc-elem', loc, { '#': parent.kind } );
707
779
  }
708
- else if (effectiveType(art)?.elements) {
780
+ else if (effectiveType( art )?.elements) {
709
781
  // For inferred (e.g. included) calc elements, this error is already emitted at the origin.
710
782
  if (!art.$inferred) {
711
783
  if (art.type)
@@ -714,7 +786,7 @@ function resolve( model ) {
714
786
  error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
715
787
  }
716
788
  }
717
- else if (effectiveType(art)?.items) {
789
+ else if (effectiveType( art )?.items) {
718
790
  // For inferred (e.g. included) calc elements, this error is already emitted at the origin.
719
791
  if (!art.$inferred) {
720
792
  const isCast = art.type?.$inferred === 'cast';
@@ -730,7 +802,7 @@ function resolve( model ) {
730
802
  if (art[prop]?.val) {
731
803
  // probably better than a parse error (which is good for DEFAULT vs calc),
732
804
  // also appears with parse-cdl:
733
- error('def-invalid-calc-elem', loc, { '#': prop });
805
+ error( 'def-invalid-calc-elem', loc, { '#': prop } );
734
806
  return; // one error is enough
735
807
  }
736
808
  }
@@ -742,22 +814,19 @@ function resolve( model ) {
742
814
  ? art.value.args[0]?._artifact
743
815
  : art.value._artifact;
744
816
  if (elem && art.type) { // has explicit type
745
- if (art.type._artifact?.elements) {
746
- error('type-cast-to-structured', [ art.type.location, art ], {},
747
- 'Can\'t cast to structured element');
748
- }
749
- else if (elem.elements) { // TODO: calc elements
750
- error('type-cast-structured', [ art.type.location, art ], {},
751
- 'Structured elements can\'t be cast to a different type');
752
- }
817
+ if (art.type._artifact?.elements)
818
+ error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'to-structure' } );
819
+ else if (elem.elements) // TODO: calc elements
820
+ error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'from-structure' } );
753
821
  }
754
822
  }
755
823
 
756
- // Return type containing the assoc spec (keys, on); note that no
757
- // propagation/rewrite has been done yet, cyclic dependency must have been
758
- // checked before!
824
+ /**
825
+ * Return type containing the assoc spec (keys, on); note that no
826
+ * propagation/rewrite has been done yet, cyclic dependency must have been
827
+ * checked before!
828
+ */
759
829
  function getAssocSpec( type ) {
760
- // only to be called without cycles
761
830
  let unmanaged = null;
762
831
  while (type) {
763
832
  if (type.on) // if unmanaged, continue trying to find targetAspect
@@ -793,7 +862,7 @@ function resolve( model ) {
793
862
  } );
794
863
  // if (!query.$inlines) console.log('RQ:',query)
795
864
  for (const col of query.$inlines)
796
- resolveExpr( col.value, 'expr', col );
865
+ resolveExpr( col.value, 'column', col );
797
866
  // for (const col of query.$inlines)
798
867
  // if (!col.value.path) throw new CompilerAssertion(col.name.element)
799
868
  if (query !== query._main._leadingQuery) // will be done later
@@ -801,22 +870,27 @@ function resolve( model ) {
801
870
  if (query.from)
802
871
  resolveJoinOn( query.from );
803
872
  if (query.where)
804
- resolveExpr( query.where, 'expr', query ); // TODO: extra 'where'?
873
+ resolveExpr( query.where, 'where', query );
805
874
  if (query.groupBy)
806
- resolveBy( query.groupBy, 'expr', 'expr' ); // TODO: extra 'groupBy'?
807
- resolveExpr( query.having, 'expr', query ); // TODO: extra 'having' or 'where'?
875
+ resolveBy( query.groupBy, 'groupBy', 'groupBy' );
876
+ resolveExpr( query.having, 'having', query );
808
877
  if (query.$orderBy) // ORDER BY from UNION:
809
878
  // TODO clarify: can I access the tab alias of outer queries? If not:
810
879
  // 4th arg query._main instead query._parent.
811
- resolveBy( query.$orderBy, 'order-by-set-ref', 'order-by-set-expr' );
880
+ resolveBy( query.$orderBy, 'orderBy-set-ref', 'orderBy-set-expr' );
812
881
  if (query.orderBy) { // ORDER BY
813
882
  // search in `query.elements` after having checked table aliases of the current query
814
- resolveBy( query.orderBy, 'order-by-ref', 'order-by-expr' );
883
+ resolveBy( query.orderBy, 'orderBy-ref', 'orderBy-expr' );
815
884
  // TODO: disallow resulting element ref if in expression!
816
885
  // Necessary to check it in the compiler as it might work with other semantics on DB!
817
886
  // (we could downgrade it to a warning if name is equal to unique source element name)
818
887
  // TODO: Some helping text mentioning an alias name would be useful
819
888
  }
889
+ for (const limit of query.$limit || []) // LIMIT from UNION:
890
+ resolveLimit( limit );
891
+ if (query.limit)
892
+ resolveLimit( query.limit );
893
+
820
894
  return;
821
895
 
822
896
  function resolveJoinOn( join ) {
@@ -824,27 +898,35 @@ function resolve( model ) {
824
898
  for (const j of join.args)
825
899
  resolveJoinOn( j );
826
900
  if (join.on)
827
- resolveExpr( join.on, 'joinOn', query );
828
- // TODO: check restrictions according to join "query"
901
+ resolveExpr( join.on, 'join-on', join );
829
902
  }
830
903
  }
831
904
 
832
- // Note the strange name resolution (dynamic part) for ORDER BY: the same
833
- // as for select items if it is an expression, but first look at select
834
- // item alias (i.e. like `$projection.NAME` if it is a path. If it is an
835
- // ORDER BY of an UNION, do not allow any dynamic path in an expression,
836
- // and only allow the elements of the leading query if it is a path.
837
- //
838
- // This seems to be similar, but different in SQLite 3.22.0: ORDER BY seems
839
- // to bind stronger than UNION (see <SQLite>/src/parse.y), and the name
840
- // resolution seems to use select item aliases from all SELECTs of the
841
- // UNION (see <SQLite>/test/tkt2822.test).
905
+ /**
906
+ * Note the strange name resolution (dynamic part) for ORDER BY: the same
907
+ * as for select items if it is an expression, but first look at select
908
+ * item alias (i.e. like `$projection.NAME` if it is a path. If it is an
909
+ * ORDER BY of an UNION, do not allow any dynamic path in an expression,
910
+ * and only allow the elements of the leading query if it is a path.
911
+ *
912
+ * This seems to be similar, but different in SQLite 3.22.0: ORDER BY seems
913
+ * to bind stronger than UNION (see <SQLite>/src/parse.y), and the name
914
+ * resolution seems to use select item aliases from all SELECTs of the
915
+ * UNION (see <SQLite>/test/tkt2822.test).
916
+ */
842
917
  function resolveBy( array, refMode, exprMode ) {
843
918
  for (const value of array ) {
844
919
  if (value)
845
920
  resolveExpr( value, (value.path ? refMode : exprMode), query );
846
921
  }
847
922
  }
923
+
924
+ function resolveLimit( limit ) {
925
+ if (limit.rows)
926
+ resolveExpr( limit.rows, 'limit-rows', query );
927
+ if (limit.offset)
928
+ resolveExpr( limit.offset, 'limit-offset', query );
929
+ }
848
930
  }
849
931
 
850
932
  function resolveTarget( art, obj ) {
@@ -875,7 +957,7 @@ function resolve( model ) {
875
957
  { '#': isComposition ? 'comp' : 'std' }, {
876
958
  std: 'An unmanaged association can\'t be defined as type',
877
959
  comp: 'An unmanaged composition can\'t be defined as type',
878
- });
960
+ } );
879
961
  // TODO: also warning if inside structure
880
962
  }
881
963
  else {
@@ -933,7 +1015,7 @@ function resolve( model ) {
933
1015
  const serviceKeys = keyElementNames( issue.target.elements );
934
1016
  const modelKeys = keyElementNames( modelTarget.elements );
935
1017
  if (modelKeys.length !== serviceKeys.length) {
936
- issue.id = modelKeys.find( id => !serviceKeys.includes( id ));
1018
+ issue.id = modelKeys.find( id => !serviceKeys.includes( id ) );
937
1019
  issue['#'] = 'missing';
938
1020
  }
939
1021
  else if (!modelKeys.every( (id, index) => id === serviceKeys[index] )) {
@@ -986,7 +1068,7 @@ function resolve( model ) {
986
1068
  }
987
1069
 
988
1070
  function addImplicitForeignKeys( art, obj, target ) {
989
- obj.foreignKeys = Object.create(null);
1071
+ obj.foreignKeys = Object.create( null );
990
1072
  forEachInOrder( target, 'elements', ( elem, name ) => {
991
1073
  if (elem.key && elem.key.val) {
992
1074
  const { location } = obj.target;
@@ -1002,22 +1084,59 @@ function resolve( model ) {
1002
1084
  // the following should be done automatically, since we run resolveRefs after that
1003
1085
  setArtifactLink( key.targetElement, elem );
1004
1086
  setArtifactLink( key.targetElement.path[0], elem );
1005
- setLink( key, '_effectiveType', effectiveType(elem) );
1006
- dependsOn(key, elem, location);
1007
- dependsOnSilent(art, key);
1087
+ setLink( key, '_effectiveType', effectiveType( elem ) );
1088
+ dependsOn( key, elem, location );
1089
+ // TODO TMP: instead, make managed composition of aspects and unmanaged
1090
+ // assocs not depend on their `on` condition (empty `_deps` after resolve)
1091
+ if (art.$inferred !== 'aspect-composition')
1092
+ dependsOnSilent( art, key );
1008
1093
  }
1009
- });
1094
+ } );
1010
1095
  obj.foreignKeys[$inferred] = 'keys';
1011
1096
  }
1012
1097
 
1013
- function addForeignKeyNavigations( art ) {
1014
- art.$keysNavigation = Object.create(null);
1098
+ /**
1099
+ * Add reference tree from foreign key reference back to foreign key of association.
1100
+ *
1101
+ * For `type T: Association to Target { foo as bar, elem.sub }`, this function adds:
1102
+ *
1103
+ * '$keysNavigation': {
1104
+ * foo: { _artifact: 'type:“T”/key:“bar”' },
1105
+ * elem: {
1106
+ * '$keysNavigation': { sub: { _artifact: 'type:“T”/key:“sub”' } }
1107
+ * }
1108
+ *
1109
+ * This function complains if two foreign keys point to the same target element
1110
+ * (`Association to Target { foo as bar, foo }`) or overlapping target elements
1111
+ * (`Association to Target { elem.sub, elem }`). In `resolvePath`, the compiler
1112
+ * already forbids to follow associations in foreign key refs.
1113
+ *
1114
+ * This ref tree could also be used in a core-compiler check which is now part
1115
+ * of to.sql: refs in the `on` condition of unmanaged associations cannot follow
1116
+ * associations other to foreign key refs.
1117
+ *
1118
+ * This ref tree is only created for originally defined managed associations
1119
+ * (including those created by the compiler, like the `up_` association), not
1120
+ * for derived association like for `type DerivedT: T`, or exposed ones.
1121
+ */
1122
+ function addForeignKeyNavigations( art, silent = false ) {
1123
+ art.$keysNavigation = Object.create( null );
1124
+ const keys = [];
1125
+ // Basically sort foreign keys according to length of target element ref.
1126
+ // This way, we complain about ref to sub element (`elem.sub`) even if it
1127
+ // comes earlier than the ref to structure element (`elem`).
1015
1128
  forEachGeneric( art, 'foreignKeys', ( key ) => {
1016
- if (!key.targetElement || !key.targetElement.path)
1017
- return;
1129
+ const path = key.targetElement?.path;
1130
+ if (path) {
1131
+ const arr = keys[path.length] || (keys[path.length] = []);
1132
+ arr.push( key );
1133
+ }
1134
+ } );
1135
+ for (const key of keys.flat()) {
1018
1136
  let dict = art.$keysNavigation;
1019
- const last = key.targetElement.path[key.targetElement.path.length - 1];
1020
- for (const item of key.targetElement.path) {
1137
+ const { path } = key.targetElement;
1138
+ const last = path[path.length - 1];
1139
+ for (const item of path) {
1021
1140
  let nav = dict[item.id];
1022
1141
  if (!nav) {
1023
1142
  nav = {};
@@ -1025,16 +1144,24 @@ function resolve( model ) {
1025
1144
  if (item === last)
1026
1145
  setArtifactLink( nav, key );
1027
1146
  else
1028
- nav.$keysNavigation = Object.create(null);
1147
+ nav.$keysNavigation = Object.create( null );
1029
1148
  }
1030
1149
  else if (item === last || nav._artifact) {
1031
- error( 'duplicate-key-ref', [ item.location, key ], {},
1032
- 'The same target reference has already been used in a key definition' );
1033
- return;
1150
+ if (silent)
1151
+ break;
1152
+ const name = nav._artifact?.name.id;
1153
+ const text = (item !== last) ? 'sub' : 'std';
1154
+ error( 'duplicate-key-ref', [ item.location, key ], { '#': text, name }, {
1155
+ std: 'Foreign key $(NAME) already refers to the same target element',
1156
+ // eslint-disable-next-line max-len
1157
+ sub: 'Foreign key $(NAME) already refers to the target element whose sub element is again referred to here',
1158
+ // TODO: please add ideas for a better text, e.g. to (closed) PR #11325
1159
+ } );
1160
+ break;
1034
1161
  }
1035
1162
  dict = nav.$keysNavigation;
1036
1163
  }
1037
- } );
1164
+ }
1038
1165
  }
1039
1166
 
1040
1167
  // TODO: add this somehow to tweak-assocs.js ?
@@ -1067,6 +1194,8 @@ function resolve( model ) {
1067
1194
  }
1068
1195
  }
1069
1196
  const origTarget = origType.target._artifact;
1197
+ // console.log(require('../model/revealInternalProperties').ref(elem),
1198
+ // !!origTarget,!!origType._effectiveType,!!origType.target)
1070
1199
  if (!origTarget || !target)
1071
1200
  return;
1072
1201
 
@@ -1100,7 +1229,7 @@ function resolve( model ) {
1100
1229
  target: 'The redirected target $(ART) is a complex view',
1101
1230
  // eslint-disable-next-line max-len
1102
1231
  targetOp: 'The redirected target $(ART) is a complex view with $(KEYWORD)',
1103
- });
1232
+ } );
1104
1233
  break;
1105
1234
  }
1106
1235
  target = from._artifact;
@@ -1116,7 +1245,7 @@ function resolve( model ) {
1116
1245
  let redirected = null;
1117
1246
  chain.reverse();
1118
1247
  let news = [ { chain, sources: [ target ] } ];
1119
- const dict = Object.create(null);
1248
+ const dict = Object.create( null );
1120
1249
  while (news.length) {
1121
1250
  const outer = news;
1122
1251
  news = [];
@@ -1194,65 +1323,9 @@ function resolve( model ) {
1194
1323
 
1195
1324
  // Resolve the type and its arguments if applicable.
1196
1325
  function resolveTypeExpr( art, user ) {
1197
- const typeArt = resolveType( art.type, user );
1198
- if (typeArt) {
1326
+ const typeArt = resolvePath( art.type, 'type', user );
1327
+ if (typeArt)
1199
1328
  resolveTypeArgumentsUnchecked( art, typeArt, user );
1200
- checkTypeArguments( art, typeArt );
1201
- }
1202
- }
1203
-
1204
- /**
1205
- * Check the type arguments on `artWithType`.
1206
- * If the effective type is an array or structured type, an error is emitted.
1207
- */
1208
- function checkTypeArguments( artWithType, typeArt ) {
1209
- // Note: `_effectiveType` point to `artWithType` itself, if it is an enum type,
1210
- // descend to the origin in this case.
1211
- // TODO: this function is not complete(!): parallel `elements` and `length`, … - rework function
1212
- // TODO: check relationship with resolveTypeArgumentsUnchecked()
1213
- let effectiveTypeArt = effectiveType( typeArt );
1214
- while (effectiveTypeArt?.enum)
1215
- effectiveTypeArt = effectiveType( getOrigin( effectiveTypeArt ) );
1216
- if (!effectiveTypeArt)
1217
- return; // e.g. illegal definition references, cycles, ...
1218
-
1219
- const params = effectiveTypeArt.parameters &&
1220
- effectiveTypeArt.parameters.map(p => p.name || p) || [];
1221
-
1222
- for (const param of typeParameters.list) {
1223
- if (artWithType[param] !== undefined) {
1224
- if (!params.includes(param)) {
1225
- // Whether the type ref itself is a builtin or a custom type with a builtin as base.
1226
- const type = getOrigin(artWithType);
1227
-
1228
- let variant;
1229
- if (type.builtin)
1230
- // `.type` is already a builtin: use a nicer message.
1231
- variant = 'builtin';
1232
- else if (effectiveTypeArt.builtin)
1233
- // base type is a builtin, i.e. a scalar
1234
- variant = 'type';
1235
- else
1236
- // effectiveType is not a builtin -> array or structured
1237
- variant = 'non-scalar';
1238
-
1239
- // console.log(typeArt.name,artWithType.name,effectiveTypeArt.name)
1240
- error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
1241
- '#': variant, prop: param, art: artWithType.type, type: effectiveTypeArt,
1242
- });
1243
- break; // Avoid spam: Only emit the first error.
1244
- }
1245
- else if (!typeParameters.expectedLiteralsFor[param].includes(artWithType[param].literal)) {
1246
- error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
1247
- '#': 'incorrect-type',
1248
- prop: param,
1249
- code: artWithType[param].literal,
1250
- names: typeParameters.expectedLiteralsFor[param],
1251
- });
1252
- break; // Avoid spam: Only emit the first error.
1253
- }
1254
- }
1255
- }
1256
1329
  }
1257
1330
 
1258
1331
  function resolveExpr( expr, exprCtx, user ) {
@@ -1264,6 +1337,7 @@ function resolve( model ) {
1264
1337
  resolveTypeExpr( expr, user._user || user );
1265
1338
 
1266
1339
  if (expr.path) {
1340
+ // TODO: re-think this $expected: 'exists' thing
1267
1341
  if (expr.$expected === 'exists') {
1268
1342
  error( 'expr-unexpected-exists', [ expr.location, user ], {},
1269
1343
  'An EXISTS predicate is not expected here' );
@@ -1296,7 +1370,7 @@ function resolve( model ) {
1296
1370
  function resolveParamsAndWhere( step, expected, user, isLast ) {
1297
1371
  const alias = (step._navigation?.kind === '$tableAlias') ? step._navigation : null;
1298
1372
  const type = alias || effectiveType( step._artifact );
1299
- const art = (type && type.target) ? type.target._artifact : type;
1373
+ const art = type?.target ? type.target._artifact : type;
1300
1374
  if (!art)
1301
1375
  return;
1302
1376
  const entity = art.kind === 'entity' &&
@@ -1317,7 +1391,7 @@ function resolve( model ) {
1317
1391
  if (expected === 'from')
1318
1392
  variant = 'from';
1319
1393
  // XSN TODO: filter$location including […]
1320
- message( 'expr-no-filter', [ location, user ], { '#': variant }, {
1394
+ message( 'expr-unexpected-filter', [ location, user ], { '#': variant }, {
1321
1395
  std: 'A filter can only be provided when navigating along associations',
1322
1396
  // to help users for `… from E:toF { toF[…].x }`
1323
1397
  // eslint-disable-next-line max-len
@@ -1329,26 +1403,26 @@ function resolve( model ) {
1329
1403
 
1330
1404
  function resolveParams( dict, art, entity, expected, user, stepLocation ) {
1331
1405
  if (!entity || !entity.params) {
1332
- let first = dict[Object.keys(dict)[0]];
1333
- if (Array.isArray(first))
1406
+ let first = dict[Object.keys( dict )[0]];
1407
+ if (Array.isArray( first ))
1334
1408
  first = first[0];
1335
- message( 'args-no-params',
1336
- [ dict[$location] ||
1337
- dictLocation( dict, first && first.name && first.name.location || stepLocation),
1409
+ error( 'expr-unexpected-argument',
1410
+ [ dict[$location] || dictLocation( dict, first?.name?.location || stepLocation ),
1338
1411
  user ],
1339
- { art, '#': (entity ? 'entity' : expected ) },
1340
- {
1341
- std: 'Parameters can only be provided when navigating along associations',
1342
- from: 'Parameters can only be provided for the source entity or associations',
1343
- // or extra message id for entity?
1344
- entity: 'Entity $(ART) has no parameters',
1345
- } );
1412
+ { art, '#': (entity ? 'entity' : expected ) },
1413
+ {
1414
+ std: 'Parameters can only be provided when navigating along associations',
1415
+ from: 'Parameters can only be provided for the source entity or associations',
1416
+ // or extra message id for entity?
1417
+ entity: 'Unexpected arguments for entity $(ART) without parameters',
1418
+ } );
1346
1419
  return;
1347
1420
  }
1348
- const exp = (expected === 'from') ? 'expr' : expected;
1349
- if (Array.isArray(dict)) {
1350
- message( 'args-expected-named', [ dict[0] && dict[0].location || stepLocation, user ], {},
1351
- 'Named parameters must be provided for the entity' );
1421
+ const exp = (expected === 'from') ? 'from-args' : expected;
1422
+ if (Array.isArray( dict )) {
1423
+ const loc = [ dict[0] && dict[0].location || stepLocation, user ];
1424
+ error( 'expr-expected-named-argument', loc, {},
1425
+ 'Expected named parameters for the entity' );
1352
1426
  for (const a of dict)
1353
1427
  resolveExpr( a, exp, user );
1354
1428
  return;
@@ -1357,33 +1431,34 @@ function resolve( model ) {
1357
1431
  for (const name in dict) {
1358
1432
  const param = art.params[name];
1359
1433
  const arg = dict[name];
1360
- for (const a of Array.isArray(arg) ? arg : [ arg ]) {
1434
+ for (const a of Array.isArray( arg ) ? arg : [ arg ]) {
1361
1435
  setArtifactLink( a.name, param );
1362
1436
  if (!param) {
1363
- message( 'args-undefined-param', [ a.name.location, user ], { art, id: name },
1364
- 'Entity $(ART) has no parameter $(ID)' );
1437
+ error( 'expr-undefined-param', [ a.name.location, user ], { art, id: name },
1438
+ 'Entity $(ART) has no parameter $(ID)' );
1365
1439
  }
1366
- // TODO: Also for other parameters?
1367
- resolveExpr( a, (expected === 'from') ? 'param-only' : exp, user );
1440
+ resolveExpr( a, exp, user );
1368
1441
  }
1369
1442
  }
1370
1443
  }
1371
1444
  }
1372
1445
 
1373
- // Return condensed info about reference in select item
1374
- // - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
1375
- // - sourceElem (in query) -> { navigation: navElem, item: path[0], tableAlias }
1376
- // - mixinElem -> { navigation: mixinElement, item: path[0] }
1377
- // - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
1378
- // - $self -> { item: undefined, tableAlias: $self }
1379
- // - $parameters.P, :P -> {}
1380
- // - $now, current_date -> {}
1381
- // - undef, redef -> {}
1382
- // With 'navigation': store that navigation._artifact is projected
1383
- // With 'navigation': rewrite its ON condition
1384
- // With navigation: Do KEY propagation
1385
- //
1386
- // TODO: re-think this function, copied in populate.js and tweak-assocs.js
1446
+ /**
1447
+ * Return condensed info about reference in select item
1448
+ * - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
1449
+ * - sourceElem (in query) -> { navigation: navElem, item: path[0], tableAlias }
1450
+ * - mixinElem -> { navigation: mixinElement, item: path[0] }
1451
+ * - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
1452
+ * - $self -> { item: undefined, tableAlias: $self }
1453
+ * - $parameters.P, :P -> {}
1454
+ * - $now, current_date -> {}
1455
+ * - undef, redef -> {}
1456
+ * With 'navigation': store that navigation._artifact is projected
1457
+ * With 'navigation': rewrite its ON condition
1458
+ * With navigation: Do KEY propagation
1459
+ *
1460
+ * TODO: re-think this function, copied in populate.js and tweak-assocs.js
1461
+ */
1387
1462
  function pathNavigation( ref ) {
1388
1463
  // currently, indirectly projectable elements are not included - we might
1389
1464
  // keep it this way! If we want them to be included - be aware: cycles