@sap/cds-compiler 2.4.4 → 2.10.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 (106) hide show
  1. package/CHANGELOG.md +241 -1
  2. package/bin/.eslintrc.json +17 -0
  3. package/bin/cds_update_identifiers.js +8 -7
  4. package/bin/cdsc.js +180 -132
  5. package/bin/cdshi.js +18 -11
  6. package/bin/cdsse.js +38 -32
  7. package/bin/cdsv2m.js +8 -7
  8. package/doc/CHANGELOG_BETA.md +36 -1
  9. package/lib/api/main.js +81 -100
  10. package/lib/api/options.js +17 -11
  11. package/lib/api/validate.js +12 -8
  12. package/lib/backends.js +0 -81
  13. package/lib/base/keywords.js +32 -2
  14. package/lib/base/location.js +2 -2
  15. package/lib/base/message-registry.js +66 -4
  16. package/lib/base/messages.js +84 -27
  17. package/lib/base/model.js +2 -61
  18. package/lib/checks/arrayOfs.js +0 -1
  19. package/lib/checks/defaultValues.js +27 -2
  20. package/lib/checks/elements.js +1 -6
  21. package/lib/checks/enricher.js +8 -2
  22. package/lib/checks/foreignKeys.js +0 -6
  23. package/lib/checks/managedWithoutKeys.js +17 -0
  24. package/lib/checks/nonexpandableStructured.js +38 -0
  25. package/lib/checks/onConditions.js +9 -45
  26. package/lib/checks/queryNoDbArtifacts.js +27 -9
  27. package/lib/checks/selectItems.js +25 -2
  28. package/lib/checks/types.js +26 -2
  29. package/lib/checks/unknownMagic.js +38 -0
  30. package/lib/checks/utils.js +61 -0
  31. package/lib/checks/validator.js +66 -13
  32. package/lib/compiler/assert-consistency.js +24 -12
  33. package/lib/compiler/builtins.js +2 -0
  34. package/lib/compiler/checks.js +6 -4
  35. package/lib/compiler/definer.js +101 -39
  36. package/lib/compiler/index.js +88 -59
  37. package/lib/compiler/resolver.js +455 -209
  38. package/lib/compiler/shared.js +57 -33
  39. package/lib/edm/annotations/genericTranslation.js +183 -187
  40. package/lib/edm/csn2edm.js +128 -99
  41. package/lib/edm/edm.js +18 -21
  42. package/lib/edm/edmPreprocessor.js +361 -127
  43. package/lib/edm/edmUtils.js +103 -33
  44. package/lib/gen/Dictionary.json +74 -28
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +18 -4
  47. package/lib/gen/language.tokens +124 -118
  48. package/lib/gen/languageLexer.interp +13 -1
  49. package/lib/gen/languageLexer.js +870 -839
  50. package/lib/gen/languageLexer.tokens +116 -111
  51. package/lib/gen/languageParser.js +5894 -5614
  52. package/lib/json/from-csn.js +152 -67
  53. package/lib/json/to-csn.js +334 -135
  54. package/lib/language/antlrParser.js +4 -3
  55. package/lib/language/errorStrategy.js +1 -0
  56. package/lib/language/genericAntlrParser.js +24 -14
  57. package/lib/language/language.g4 +188 -128
  58. package/lib/main.d.ts +435 -0
  59. package/lib/main.js +31 -7
  60. package/lib/model/api.js +78 -0
  61. package/lib/model/csnRefs.js +463 -187
  62. package/lib/model/csnUtils.js +280 -136
  63. package/lib/model/enrichCsn.js +75 -4
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/modelCompare/compare.js +70 -25
  66. package/lib/optionProcessor.js +13 -10
  67. package/lib/render/.eslintrc.json +4 -1
  68. package/lib/render/DuplicateChecker.js +8 -5
  69. package/lib/render/toCdl.js +123 -40
  70. package/lib/render/toHdbcds.js +156 -65
  71. package/lib/render/toSql.js +87 -11
  72. package/lib/render/utils/common.js +55 -9
  73. package/lib/render/utils/sql.js +3 -3
  74. package/lib/sql-identifier.js +6 -1
  75. package/lib/transform/{sql → db}/.eslintrc.json +0 -0
  76. package/lib/transform/{sql → db}/assertUnique.js +7 -8
  77. package/lib/transform/{sql → db}/constraints.js +35 -20
  78. package/lib/transform/db/draft.js +353 -0
  79. package/lib/transform/db/expansion.js +582 -0
  80. package/lib/transform/db/flattening.js +325 -0
  81. package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
  82. package/lib/transform/{sql → db}/helpers.js +0 -0
  83. package/lib/transform/{sql → db}/transformExists.js +256 -60
  84. package/lib/transform/forHanaNew.js +216 -765
  85. package/lib/transform/forOdataNew.js +60 -56
  86. package/lib/transform/localized.js +48 -26
  87. package/lib/transform/odata/attachPath.js +19 -4
  88. package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
  89. package/lib/transform/odata/generateForeignKeyElements.js +13 -12
  90. package/lib/transform/odata/referenceFlattener.js +60 -36
  91. package/lib/transform/odata/sortByAssociationDependency.js +4 -4
  92. package/lib/transform/odata/structuralPath.js +76 -0
  93. package/lib/transform/odata/structureFlattener.js +21 -22
  94. package/lib/transform/odata/toFinalBaseType.js +5 -5
  95. package/lib/transform/odata/typesExposure.js +27 -17
  96. package/lib/transform/odata/utils.js +2 -2
  97. package/lib/transform/transformUtilsNew.js +141 -77
  98. package/lib/transform/translateAssocsToJoins.js +17 -14
  99. package/lib/transform/universalCsnEnricher.js +67 -0
  100. package/lib/utils/file.js +0 -11
  101. package/lib/utils/moduleResolve.js +6 -8
  102. package/lib/utils/timetrace.js +6 -1
  103. package/package.json +2 -1
  104. package/lib/base/deepCopy.js +0 -66
  105. package/lib/json/walker.js +0 -26
  106. package/lib/utils/string.js +0 -17
@@ -45,9 +45,7 @@ const {
45
45
  dictAdd, dictAddArray,
46
46
  } = require('../base/dictionaries');
47
47
  const { dictLocation } = require('../base/location');
48
- const {
49
- makeMessageFunction, searchName, weakLocation,
50
- } = require('../base/messages');
48
+ const { searchName, weakLocation } = require('../base/messages');
51
49
  const { combinedLocation } = require('../base/location');
52
50
  const { pushLink } = require('./utils');
53
51
  const {
@@ -84,7 +82,7 @@ function resolve( model ) {
84
82
  } = fns( model, environment );
85
83
  const {
86
84
  info, warning, error, message,
87
- } = makeMessageFunction( model, model.options, 'compile' );
85
+ } = model.$messageFunctions;
88
86
  const {
89
87
  initArtifact,
90
88
  lateExtensions,
@@ -95,6 +93,8 @@ function resolve( model ) {
95
93
 
96
94
  // behavior depending on option `deprecated`:
97
95
  const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
96
+ // TODO: we should get rid of noElementsExpansion soon; both
97
+ // beta.nestedProjections and beta.universalCsn do not work with it.
98
98
  const scopedRedirections
99
99
  = enableExpandElements &&
100
100
  !isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ) &&
@@ -253,9 +253,11 @@ function resolve( model ) {
253
253
  }
254
254
  else {
255
255
  setProp( art, '_effectiveType', art );
256
+ if (art.expand && !art.value && !art.elements)
257
+ initFromColumns( art, art.expand );
256
258
  // When not blocked (by future origin = false) and not REDIRECTED TO and not MIXIN
257
259
  // try to implicitly redirect explicitly provided target:
258
- if (art.target && art._origin == null && !art.value && art.kind !== 'mixin')
260
+ else if (art.target && art._origin == null && !art.value && art.kind !== 'mixin')
259
261
  redirectImplicitly( art, art );
260
262
  }
261
263
  }
@@ -268,8 +270,13 @@ function resolve( model ) {
268
270
  let eType = art;
269
271
  if (eType._outer)
270
272
  eType = effectiveType( eType._outer );
273
+ // collect the "latest" cardinality (calculate lazyly if necessary)
274
+ let cardinality = art.cardinality ||
275
+ art._effectiveType && (() => getCardinality( art._effectiveType ));
271
276
  for (const a of chain) {
272
- if (a.expand && initFromColumns( a, a.expand, a ) || // before redirectImplicitly()!
277
+ if (a.cardinality)
278
+ cardinality = a.cardinality;
279
+ if (a.expand && expandFromColumns( a, art, cardinality ) ||
273
280
  art.target && redirectImplicitly( a, art ) ||
274
281
  art.elements && expandElements( a, art, eType ))
275
282
  art = a;
@@ -279,21 +286,55 @@ function resolve( model ) {
279
286
  return art;
280
287
  }
281
288
 
289
+ function expandFromColumns( elem, assoc, cardinality ) {
290
+ const path = elem.value && elem.value.path;
291
+ if (!path || path.broken)
292
+ return null;
293
+ if (!assoc.target)
294
+ return initFromColumns( elem, elem.expand );
295
+ const { targetMax } = path[path.length - 1].cardinality ||
296
+ (cardinality instanceof Function ? cardinality() : cardinality);
297
+ if (targetMax && (targetMax.val === '*' || targetMax.val > 1))
298
+ elem.items = { location: dictLocation( elem.expand ) };
299
+ return initFromColumns( elem, elem.expand );
300
+ }
301
+
302
+ function getCardinality( type ) {
303
+ // only to be called without cycles
304
+ while (type) {
305
+ if (type.cardinality)
306
+ return type.cardinality;
307
+ type = directType( type );
308
+ }
309
+ return {};
310
+ }
311
+
312
+ function userQuery( user ) {
313
+ // TODO: we need _query links set by the definer
314
+ while (user._main) {
315
+ if (user.kind === 'select' || user.kind === '$join')
316
+ return user;
317
+ user = user._parent;
318
+ }
319
+ return null;
320
+ }
321
+
322
+ // TODO: test it in combination with top-level CAST function
282
323
  function directType( art ) {
283
324
  // Be careful when using it with art.target or art.enum or art.elements
284
- if (art._origin)
325
+ if (art._origin || art.builtin)
285
326
  return art._origin;
286
327
  if (art.type)
287
328
  return resolveType( art.type, art );
288
329
  // console.log( 'EXPR-IN', art.kind, refString(art.name) )
289
- if (!art._main)
330
+ if (!art._main || !art.value || !art.value.path)
290
331
  return undefined;
291
332
  if (art._pathHead && art.value) {
292
333
  setProp( art, '_origin', resolvePath( art.value, 'expr', art, null ) );
293
334
  return art._origin;
294
335
  }
295
- const query = art._parent._leadingQuery || art._parent; // TODO: re-check leading
296
- if (query.kind !== 'select' || !art.value.path)
336
+ const query = userQuery( art ) || art._parent;
337
+ if (query.kind !== 'select')
297
338
  return undefined;
298
339
  // Reached an element in a query which is a simple ref -> return referred artifact
299
340
  // TODO: remember that we still have to resolve path arguments and filters
@@ -384,10 +425,10 @@ function resolve( model ) {
384
425
  info( 'query-missing-element', [ ielem.name.location, view ], { id },
385
426
  'Element $(ID) is missing in specified elements' );
386
427
  }
387
- else if (!Array.isArray( ielem ) && !Array.isArray( selem )) {
428
+ else {
388
429
  for (const prop in selem) {
389
- // just annotation assignments for the moment
390
- if (prop.charAt(0) === '@')
430
+ // just annotation assignments and doc comments for the moment
431
+ if (prop.charAt(0) === '@' || prop === 'doc')
391
432
  ielem[prop] = selem[prop];
392
433
  }
393
434
  selem.$replacement = true;
@@ -395,7 +436,7 @@ function resolve( model ) {
395
436
  }
396
437
  for (const id in view.elements$) {
397
438
  const selem = view.elements$[id]; // specified element
398
- if (!Array.isArray( selem ) && !selem.$replacement) {
439
+ if (!selem.$replacement) {
399
440
  error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
400
441
  'Element $(ID) does not result from the query' );
401
442
  }
@@ -405,8 +446,7 @@ function resolve( model ) {
405
446
  function traverseElementEnvironments( art ) {
406
447
  populateView( art );
407
448
  environment( art );
408
- // TODO: but we have no proxy copy of sub elements yet - no redirection in ValueHelpList.cds
409
- forEachGeneric( art, 'elements', traverseElementEnvironments );
449
+ forEachMember( art, traverseElementEnvironments );
410
450
  }
411
451
 
412
452
  function populateQuery( query ) {
@@ -414,14 +454,16 @@ function resolve( model ) {
414
454
  // already done or $join query or parse error
415
455
  return;
416
456
  setProp( query, '_combined', Object.create(null) );
457
+ query.$inlines = [];
417
458
  forEachGeneric( query, '$tableAliases', resolveTabRef );
418
459
 
419
460
  initFromColumns( query, query.columns );
461
+ // TODO: already in definer: complain about EXCLUDING with no wildcard
462
+ // (would have been automatically with a good CDL syntax: `* without (...)`)
420
463
  if (query.excludingDict) {
421
464
  for (const name in query.excludingDict)
422
- resolveExcluding( name );
465
+ resolveExcluding( name, query._combined, query.excludingDict, query );
423
466
  }
424
- forEachGeneric( query, 'elements', initElem );
425
467
  return;
426
468
 
427
469
  function resolveTabRef( alias ) {
@@ -455,46 +497,36 @@ function resolve( model ) {
455
497
  dictAddArray( query._combined, name, elem, null ); // not dictAdd()
456
498
  });
457
499
  }
500
+ }
458
501
 
459
- function resolveExcluding( name ) {
460
- if (query._combined[name])
461
- return;
462
- /** @type {object} */
463
- const compileMessageRef = info(
464
- 'ref-undefined-excluding', [ query.excludingDict[name].location, query ], { name },
465
- 'Element $(NAME) has not been found'
466
- );
467
- attachAndEmitValidNames(compileMessageRef, query._combined);
468
- }
469
-
470
- function initElem( elem ) {
471
- if (elem.type && !elem.type.$inferred)
472
- return; // explicit type -> enough or directType()
473
- if (elem.$inferred) {
474
- // redirectImplicitly( elem, elem._origin );
475
- return;
476
- }
477
- if (!elem.value || !elem.value.path) // TODO: test $inferred
478
- return; // no value ref or $inferred
479
- // TODO: what about SELECT from E { $projection.a as a1, a } !!!!!!
480
-
481
- const origin = setProp( elem, '_origin',
482
- resolvePath( elem.value, 'expr', elem, query._combined ) );
483
- // console.log( message( null, elem.location, elem, {art:query}, 'Info','RED').toString(),
484
- // elem.value)
485
- // TODO: make this resolvePath() also part of directType() ?!
486
- if (!origin)
487
- return;
502
+ function initElem( elem, query ) {
503
+ if (elem.type && !elem.type.$inferred)
504
+ return; // explicit type -> enough or directType()
505
+ if (elem.$inferred) {
506
+ // redirectImplicitly( elem, elem._origin );
507
+ return;
508
+ }
509
+ if (!elem.value || !elem.value.path) // TODO: test $inferred
510
+ return; // no value ref or $inferred
511
+ // TODO: what about SELECT from E { $projection.a as a1, a } !!!!!!
512
+
513
+ const env = columnEnv( elem._pathHead, query );
514
+ const origin = setProp( elem, '_origin',
515
+ resolvePath( elem.value, 'expr', elem, env ) );
516
+ // console.log( message( null, elem.location, elem, {art:query}, 'Info','RED').toString(),
517
+ // elem.value)
518
+ // TODO: make this resolvePath() also part of directType() ?!
519
+ if (!origin)
520
+ return;
521
+ if (elem.foreignKeys) { // REDIRECTED with explicit foreign keys
522
+ forEachGeneric( elem, 'foreignKeys', (key, name) => initKey( key, name, elem ) );
523
+ }
488
524
 
489
- // now set things which are necessary for later sub phases:
490
- const nav = pathNavigation( elem.value );
491
- if (nav.navigation && nav.item === elem.value.path[elem.value.path.length - 1]) {
492
- // redirectImplicitly( elem, origin );
493
- pushLink( nav.navigation, '_projections', elem );
494
- }
495
- if (elem.foreignKeys) { // REDIRECTED with explicit foreign keys
496
- forEachGeneric( elem, 'foreignKeys', (key, name) => initKey( key, name, elem ) );
497
- }
525
+ // now set things which are necessary for later sub phases:
526
+ const nav = pathNavigation( elem.value );
527
+ if (nav.navigation && nav.item === elem.value.path[elem.value.path.length - 1]) {
528
+ // redirectImplicitly( elem, origin );
529
+ pushLink( nav.navigation, '_projections', elem );
498
530
  }
499
531
  }
500
532
 
@@ -528,8 +560,12 @@ function resolve( model ) {
528
560
  // or should we use orig.location? - TODO: try to find test to see message
529
561
  .$inferred = 'expand-element';
530
562
  }
531
- art.$expand = 'origin'; // if value stays, elements won't appear in CSN
532
- // TODO: should we also set 'virtual' here?
563
+ // Set elements expansion status (the if condition is always true, as no
564
+ // elements expansion will take place on artifact with existing other
565
+ // member property):
566
+ if (!art.$expand)
567
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
568
+ // TODO: have some art.elements[SYM.$inferred] = 'expand-elements';
533
569
  return true;
534
570
  }
535
571
 
@@ -555,14 +591,45 @@ function resolve( model ) {
555
591
  return false;
556
592
  }
557
593
 
558
- function setExpandStatus( elem ) {
594
+ // About Helper property $expand for faster the XSN-to-CSN transformation
595
+ // - null/undefined: artifact, member, items does not contain expanded members
596
+ // - 'origin': all expanded (sub) elements have no new target/on and no new annotations
597
+ // that value is only on elements, types, and params -> no other members
598
+ // when set, only on elem/art with expanded elements
599
+ // - 'target': all expanded (sub) elements might only have new target/on, but
600
+ // no indivual annotations on any (sub) member
601
+ // when set, traverse all parents where the value has been 'origin' before
602
+ // - 'annotate': at least one inferred (sub) member has an individual annotation,
603
+ // not counting propagated ones; set up to the definition (main artifact)
604
+ // (only set with anno on $inferred elem)
605
+ // Usage according to CSN flavor:
606
+ // - gensrc: do not render enferred elements (including expanded elements),
607
+ // collect annotate statements with value 'annotate'
608
+ // - client: do not render expanded sub elements if artifact/member is no type, has a type,
609
+ // has $expand = 'origin', and all its _origin also have $expand = 'origin'
610
+ // (might sometimes render the elements unnecessarily, which is not wrong)
611
+ // - universal: do not render expanded sub elements if $expand = 'origin'
612
+ function setExpandStatus( elem, status ) {
613
+ // set on element
559
614
  while (elem._main) {
560
615
  elem = elem._parent;
561
- if (!elem.$expand)
616
+ if (elem.$expand !== 'origin')
562
617
  return;
563
- elem.$expand = 'target'; // meaning: expanded, containing assocs
618
+ elem.$expand = status; // meaning: expanded, containing assocs
564
619
  for (let line = elem.items; line; line = line.items)
565
- line.$expand = 'target'; // to-csn just uses the innermost $expand
620
+ line.$expand = status; // to-csn just uses the innermost $expand
621
+ }
622
+ }
623
+ function setExpandStatusAnnotate( elem, status ) {
624
+ for (;;) {
625
+ if (elem.$expand === status)
626
+ return; // already set
627
+ elem.$expand = status; // meaning: expanded, containing annos
628
+ for (let line = elem.items; line; line = line.items)
629
+ line.$expand = status; // to-csn just uses the innermost $expand
630
+ if (!elem._main)
631
+ return;
632
+ elem = elem._parent;
566
633
  }
567
634
  }
568
635
 
@@ -574,7 +641,7 @@ function resolve( model ) {
574
641
  // PRE: elem has no target, assoc has target prop
575
642
  if (elem.kind === '$tableAlias')
576
643
  return false;
577
- setExpandStatus( elem );
644
+ setExpandStatus( elem, 'target' );
578
645
  let target = resolvePath( assoc.target, 'target', assoc );
579
646
  // console.log( info( null, [ elem.location, elem ], {target,art:assoc,name:''+assoc.target},
580
647
  // 'RED').toString())
@@ -635,7 +702,8 @@ function resolve( model ) {
635
702
  setLink( elem, origin, '_origin' );
636
703
  setLink( origin, elem._parent, '_parent' );
637
704
  if (elem._main) // remark: the param `elem` can also be a type
638
- setLink( origin, elem._main, '_parent', '_main' );
705
+ setLink( origin, elem._main, '_main' );
706
+ setLink( origin, origin, '_effectiveType' );
639
707
  setLink( origin, elem._block, '_block' );
640
708
  if (elem.foreignKeys) {
641
709
  origin.foreignKeys = elem.foreignKeys;
@@ -686,6 +754,7 @@ function resolve( model ) {
686
754
  const nullScope = {
687
755
  kind: 'namespace', name: { absolute: autoScopeName, location }, location,
688
756
  };
757
+ model.definitions[autoScopeName] = nullScope;
689
758
  initArtifact( nullScope );
690
759
  return nullScope;
691
760
  }
@@ -894,146 +963,215 @@ function resolve( model ) {
894
963
  return art;
895
964
  }
896
965
 
966
+ // TODO: probably do this already in definer.js
967
+ function ensureColumnName( col, query ) {
968
+ if (col.name)
969
+ return col.name.id;
970
+ if (col.inline || col.val === '*')
971
+ return '';
972
+ const path = col.value &&
973
+ (col.value.path || !col.value.args && col.value.func && col.value.func.path);
974
+ if (path) {
975
+ const last = !path.broken && path.length && path[path.length - 1];
976
+ if (last) {
977
+ col.name = { id: last.id, location: last.location, $inferred: 'as' };
978
+ return col.name.id;
979
+ }
980
+ }
981
+ else if (col.value || col.expand) {
982
+ error( 'query-req-name', [ col.value && col.value.location || col.location, query ], {},
983
+ 'Alias name is required for this select item' );
984
+ }
985
+ // invent a name for code completion in expression
986
+ col.name = {
987
+ id: '',
988
+ location: col.value && col.value.location || col.location,
989
+ $inferred: 'none',
990
+ };
991
+ return '';
992
+ }
993
+
897
994
  // TODO: make this function shorter - make part of this (e.g. setting
898
995
  // parent/name) also be part of definer.js
899
- // TODO: query is actually the elemParent
900
- function initFromColumns( query, columns, colParent = undefined, elements = undefined ) {
901
- if (!elements) {
902
- elements = Object.create(null);
903
- query.elements = Object.create(null);
996
+ // TODO: query is actually the elemParent, where the new elements are added to
997
+ // top-level: just query, columns
998
+ // inline: + elements (TODO: remove), colParent
999
+ // expand: just query (which is a column/element), columns=array of expand
1000
+ function initFromColumns( query, columns, inlineHead = undefined ) {
1001
+ const elemsParent = query.items || query;
1002
+ if (!inlineHead) {
1003
+ elemsParent.elements = Object.create(null);
904
1004
  if (query._main._leadingQuery === query) // never the case for 'expand'
905
- query._main.elements = query.elements;
1005
+ query._main.elements = elemsParent.elements;
906
1006
  }
907
- let wildcard = false;
908
- let inline = 0;
909
1007
 
910
1008
  for (const col of columns || [ { val: '*' } ]) {
911
1009
  if (col.val === '*') {
912
- wildcard = col.location || query.from && query.from.location || query.location;
913
- continue;
1010
+ const siblings = wildcardSiblings( columns, query );
1011
+ expandWildcard( col, siblings, inlineHead, query );
914
1012
  }
915
- col.kind = 'element';
916
1013
  if ((col.expand || col.inline) && !isBetaEnabled( options, 'nestedProjections' )) {
917
1014
  error( null, [ col.location, query ], { prop: (col.expand ? 'expand' : 'inline') },
918
1015
  'Unsupported nested $(PROP)' );
919
1016
  }
920
- if (!col.value)
1017
+ if (!col.value && !col.expand)
921
1018
  continue; // error should have been reported by parser
922
1019
  if (col.inline) {
923
1020
  col.kind = '$inline';
924
1021
  col.name = {};
925
1022
  // a name for this internal symtab entry (e.g. '.2' to avoid clashes
926
1023
  // with real elements) is only relevant for for `cdsc -R`/debugging
927
- setMemberParent( col, `.${ ++inline }`, query );
928
- initFromColumns( query, col.inline, col, elements );
1024
+ const q = userQuery( query );
1025
+ q.$inlines.push( col );
1026
+ // or use userQuery( query ) in the following, too?
1027
+ setMemberParent( col, `.${ q.$inlines.length }`, query );
1028
+ initFromColumns( query, col.inline, col );
929
1029
  continue;
930
1030
  }
931
- if (!col.name) {
932
- const path = col.value.path || !col.value.args && col.value.func && col.value.func.path;
933
- if (!path) {
934
- error( 'query-req-name', [ col.value.location || col.location, query ], {},
935
- 'Alias name is required for this select item' );
936
- }
937
- else if (path.length && !path.broken) {
938
- const last = path[path.length - 1];
939
- if (last)
940
- col.name = { id: last.id, location: last.location, $inferred: 'as' };
941
- }
942
- if (!col.name) {
943
- // invent a name for code completion in expression
944
- col.name = { id: '', location: col.value.location, $inferred: 'none' };
945
- }
946
- }
947
- const { id } = col.name;
948
- dictAdd( elements, id, col, ( name, location ) => {
949
- error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
950
- });
951
- setMemberParent( col, id, query );
952
- if (!wildcard) {
953
- if (col.$duplicates !== true)
954
- dictAdd( query.elements, id, col );
955
- col.$replacement = true;
1031
+ else if (!col.$replacement) {
1032
+ const id = ensureColumnName( col, query );
1033
+ col.kind = 'element';
1034
+ dictAdd( elemsParent.elements, id, col, ( name, location ) => {
1035
+ error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
1036
+ });
1037
+ setMemberParent( col, id, query );
956
1038
  }
957
1039
  }
958
- if (wildcard)
959
- expandWildcard( elements, wildcard );
1040
+ forEachGeneric( query, 'elements', e => initElem( e, query ) );
960
1041
  return true;
1042
+ }
961
1043
 
962
- // TODO: make this function shorter, probably outside initFromColumns
963
- // `elements` are the element explicitly provided via `columns`
964
- // eslint-disable-next-line no-shadow
965
- function expandWildcard( elements, location ) {
966
- const inferred = query._main.$inferred;
967
- const excludingDict = query.excludingDict || Object.create(null);
968
- const env = colParent
969
- ? environment( colParent._origin || colParent )
970
- : query._combined;
971
- // TODO: paths in excludingDict? - Did I mention it? EXCLUDING is ill-defined...
972
- for (const name in env) {
973
- const navElem = env[name];
974
- // TODO: if it is an array, filter out those with masked
975
- if (excludingDict[name] || navElem.masked && navElem.masked.val)
976
- continue;
977
- const selElem = elements[name];
978
- if (selElem) { // is explicitly provided
979
- if (!selElem.name) // no name with parse error or repeated def
980
- continue;
981
- const path = selElem.value && selElem.value.path;
982
- // TODO: to bring the message below for ParentElem.Assoc, we should move this
983
- // check to resolveElem for elems with $replacement, by comparing the
984
- // name with the name.element of the _origin.
985
- // We cannot check path.length === 1, as we want to allow Alias.Elem.
986
-
987
- // TODO: bring this less often (only if shadowed elem does not appear
988
- // in expr and if not projected as other name)
989
- if (!inferred && (
990
- selElem.$replacement !== 'silent' && (!selElem.target || selElem.target.$inferred ) ||
991
- path && path[path.length - 1].id !== selElem.name.id)) {
992
- if (Array.isArray(navElem)) {
993
- info( 'wildcard-excluding-many', [ selElem.name.location, query ], { id: name },
994
- 'This select item replaces $(ID) from two or more sources' );
995
- }
996
- else {
997
- info( 'wildcard-excluding-one', [ selElem.name.location, query ],
998
- { id: name, alias: navElem._parent.name.id },
999
- 'This select item replaces $(ID) from table alias $(ALIAS)' );
1000
- }
1001
- }
1002
- if (!selElem.$replacement || selElem.$replacement === 'silent') {
1003
- selElem.$replacement = true;
1004
- dictAdd( query.elements, name, selElem );
1005
- }
1006
- else {
1007
- selElem.$inferred = 'query';
1008
- }
1044
+ // col ($replacement set before *)
1045
+ // false if two cols have same name
1046
+ function wildcardSiblings( columns, query ) {
1047
+ const siblings = Object.create(null);
1048
+ if (!columns)
1049
+ return siblings;
1050
+
1051
+ let seenWildcard = null;
1052
+ for (const col of columns) {
1053
+ const id = ensureColumnName( col, query );
1054
+ if (id) {
1055
+ col.$replacement = !seenWildcard;
1056
+ siblings[id] = !(id in siblings) && col;
1057
+ }
1058
+ else if (col.val === '*') {
1059
+ seenWildcard = true;
1060
+ }
1061
+ }
1062
+ return siblings;
1063
+ }
1064
+
1065
+ // TODO: make struct.* are to be added at place, not sub-wildcards first,
1066
+ // see test3/Queries/ExpandInlineCreate/Excluding.cds
1067
+ // TODO: disallow $self.elem.* and $self.*, toSelf.* (circular dependency)
1068
+ function expandWildcard( wildcard, siblingElements, colParent, query ) {
1069
+ const { elements } = query.items || query;
1070
+ let location = wildcard.location || query.from && query.from.location || query.location;
1071
+ const inferred = query._main.$inferred;
1072
+ const excludingDict = (colParent || query).excludingDict || Object.create(null);
1073
+
1074
+ const envParent = wildcard._pathHead; // TODO: rename _pathHead to _pathEnv
1075
+ // console.log('S1:',location.line,location.col,
1076
+ // envParent&&!!envParent._origin&&envParent._origin.name)
1077
+ const env = columnEnv( envParent, query );
1078
+ // console.log('S2:',location.line,location.col,
1079
+ // envParent&&!!envParent._origin&&envParent._origin.name,
1080
+ // Object.keys(env),Object.keys(elements))
1081
+ for (const name in env) {
1082
+ const navElem = env[name];
1083
+ // TODO: if it is an array, filter out those with masked
1084
+ if (excludingDict[name] || navElem.masked && navElem.masked.val)
1085
+ continue;
1086
+ const sibling = siblingElements[name];
1087
+ if (sibling) { // is explicitly provided (without duplicate)
1088
+ if (!inferred && !envParent) // not yet for expand/inline
1089
+ reportReplacement( sibling, navElem, query );
1090
+ if (!sibling.$replacement) {
1091
+ sibling.$replacement = true;
1092
+ sibling.kind = 'element';
1093
+ dictAdd( elements, name, sibling, ( _name, loc ) => {
1094
+ // there can be a definition from a previous inline with the same name:
1095
+ error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
1096
+ });
1097
+ setMemberParent( sibling, name, query );
1009
1098
  }
1010
- else if (Array.isArray(navElem)) {
1011
- const names = navElem.filter( e => !e.$duplicates)
1012
- .map( e => `${ e.name.alias }.${ e.name.element }` );
1013
- if (names.length) {
1014
- error( 'wildcard-ambiguous', [ location, query ], { id: name, names },
1015
- 'Ambiguous wildcard, select $(ID) explicitly with $(NAMES)' );
1016
- }
1017
- }
1018
- else {
1019
- location = weakLocation( location );
1020
- const origin = colParent ? navElem : navElem._origin;
1021
- const elem = linkToOrigin( origin, name, query, 'elements', location );
1022
- elem.$inferred = '*';
1023
- elem.name.$inferred = '*';
1024
- if (colParent)
1025
- setWildcardExpandInline( elem, colParent, origin, name, location );
1026
- else
1027
- setElementOrigin( elem, navElem, name, location );
1099
+ // else {
1100
+ // sibling.$inferred = 'query';
1101
+ // }
1102
+ }
1103
+ else if (Array.isArray(navElem)) {
1104
+ const names = navElem.filter( e => !e.$duplicates)
1105
+ .map( e => `${ e.name.alias }.${ e.name.element }` );
1106
+ if (names.length) {
1107
+ error( 'wildcard-ambiguous', [ location, query ], { id: name, names },
1108
+ 'Ambiguous wildcard, select $(ID) explicitly with $(NAMES)' );
1028
1109
  }
1029
1110
  }
1030
- forEachInOrder( { elements }, 'elements', (elem, name) => {
1031
- if (!elem.$replacement)
1032
- dictAdd( query.elements, name, elem );
1033
- });
1111
+ else {
1112
+ location = weakLocation( location );
1113
+ const origin = envParent ? navElem : navElem._origin;
1114
+ const elem = linkToOrigin( origin, name, query, null, location );
1115
+ // TODO: check assocToMany { * }
1116
+ dictAdd( elements, name, elem, ( _name, loc ) => {
1117
+ // there can be a definition from a previous inline with the same name:
1118
+ error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
1119
+ });
1120
+ elem.$inferred = '*';
1121
+ elem.name.$inferred = '*';
1122
+ if (envParent)
1123
+ setWildcardExpandInline( elem, envParent, origin, name, location );
1124
+ else
1125
+ setElementOrigin( elem, navElem, name, location );
1126
+ }
1127
+ }
1128
+ if (envParent || query.kind !== 'select') {
1129
+ // already done in populateQuery (TODO: change that and check whether
1130
+ // `*` is allowed at all in definer)
1131
+ const user = colParent || query;
1132
+ for (const name in user.excludingDict)
1133
+ resolveExcluding( name, env, excludingDict, query );
1034
1134
  }
1035
1135
  }
1036
1136
 
1137
+ function reportReplacement( sibling, navElem, query ) {
1138
+ // TODO: bring this much less often = only if shadowed elem does not appear
1139
+ // in expr and if not projected as other name.
1140
+ // Probably needs to be reported at a later phase
1141
+ const path = sibling.value && sibling.value.path;
1142
+ if (!sibling.target || sibling.target.$inferred || // not explicit REDIRECTED TO
1143
+ path && path[path.length - 1].id !== sibling.name.id) { // or renamed
1144
+ const { id } = sibling.name;
1145
+ if (Array.isArray(navElem)) {
1146
+ info( 'wildcard-excluding-many', [ sibling.name.location, query ], { id },
1147
+ 'This select item replaces $(ID) from two or more sources' );
1148
+ }
1149
+ else {
1150
+ info( 'wildcard-excluding-one', [ sibling.name.location, query ],
1151
+ { id, alias: navElem._parent.name.id },
1152
+ 'This select item replaces $(ID) from table alias $(ALIAS)' );
1153
+ }
1154
+ }
1155
+ }
1156
+
1157
+ function columnEnv( envParent, query ) { // etc. wildcard._pathHead;
1158
+ return (envParent)
1159
+ ? environment( directType( envParent ) || envParent )
1160
+ : userQuery( query )._combined;
1161
+ }
1162
+
1163
+ function resolveExcluding( name, env, excludingDict, user ) {
1164
+ if (env[name])
1165
+ return;
1166
+ /** @type {object} */
1167
+ // console.log(name,Object.keys(env),Object.keys(excludingDict))
1168
+ const compileMessageRef = info(
1169
+ 'ref-undefined-excluding', [ excludingDict[name].location, user ], { name },
1170
+ 'Element $(NAME) has not been found'
1171
+ );
1172
+ attachAndEmitValidNames( compileMessageRef, env );
1173
+ }
1174
+
1037
1175
  function setElementOrigin( queryElem, navElem, name, location ) {
1038
1176
  const sourceElem = navElem._origin;
1039
1177
  const alias = navElem._parent;
@@ -1276,6 +1414,7 @@ function resolve( model ) {
1276
1414
  }
1277
1415
  }
1278
1416
  if (obj.target) {
1417
+ // console.log(obj.name,obj._origin.name)
1279
1418
  if (obj._origin && obj._origin.$inferred === 'REDIRECTED')
1280
1419
  resolveTarget( art, obj._origin );
1281
1420
  // console.log(message( null, obj.location, obj, {target:obj.target}, 'Info','TARGET')
@@ -1301,6 +1440,7 @@ function resolve( model ) {
1301
1440
  }
1302
1441
  // Resolve projections/views
1303
1442
  // if (art.query)console.log( info( null, [art.query.location,art.query], 'VQ:' ).toString() );
1443
+ // TODO: here, any order should be ok, i.e. just loop over $queries
1304
1444
  traverseQueryPost( art.query, false, resolveQuery );
1305
1445
 
1306
1446
  if (obj.type || obj._origin || obj.value && obj.value.path || obj.elements) // typed artifacts
@@ -1320,7 +1460,7 @@ function resolve( model ) {
1320
1460
  }
1321
1461
 
1322
1462
  resolveExpr( art.default, 'default', art );
1323
- resolveExpr( art.value, 'expr', art );
1463
+ resolveExpr( art.value, 'expr', art, undefined, art.expand || art.inline );
1324
1464
  if (art.value && !art.type && !art.target && !art.elements)
1325
1465
  inferTypeFromCast( art );
1326
1466
 
@@ -1446,14 +1586,23 @@ function resolve( model ) {
1446
1586
  }
1447
1587
  }
1448
1588
  if (art && art._annotate) {
1449
- if (art.$expand && art._annotate.elements)
1450
- art.$expand = 'annotate'; // do not omit expanded elements when they are annotated
1451
- // TODO: returns
1452
- let obj = art.returns || art; // why the extra `returns` for actions?
1453
- obj = obj.items || obj;
1589
+ const aor = art.returns || art;
1590
+ const obj = aor.items || aor.targetAspect || aor;
1591
+ // Currently(?), effectiveType() does not calculate the effective type of
1592
+ // its line item:
1593
+ effectiveType( obj );
1594
+ if (art._annotate.elements)
1595
+ setExpandStatusAnnotate( aor, 'annotate' );
1454
1596
  annotate( obj, 'element', 'elements', 'enum', art );
1455
1597
  annotate( art, 'action', 'actions' );
1456
1598
  annotate( art, 'param', 'params' );
1599
+ // const { returns } = art._annotate;
1600
+ // if (returns) {
1601
+ // const dict = returns.elements;
1602
+ // const env = obj.returns && obj.returns.elements || null;
1603
+ // for (const n in dict)
1604
+ // annotateMembers( env && env[n], dict[n], 'elements', n, parent, 'element' );
1605
+ // }
1457
1606
  }
1458
1607
  return;
1459
1608
 
@@ -1509,6 +1658,8 @@ function resolve( model ) {
1509
1658
  ext.kind = 'annotate'; // after setMemberParent()!
1510
1659
  setProp( art, '_extension', ext );
1511
1660
  setProp( ext.name, '_artifact', art );
1661
+ if (art.returns)
1662
+ ext.$syntax = 'returns';
1512
1663
  return ext;
1513
1664
  }
1514
1665
 
@@ -1528,8 +1679,13 @@ function resolve( model ) {
1528
1679
  function chooseAssignment( annoName, art ) {
1529
1680
  // TODO: getPath an all names
1530
1681
  const anno = art[annoName];
1531
- if (!Array.isArray(anno)) // just one assignment -> use it
1682
+ if (!Array.isArray(anno)) { // just one assignment -> use it
1683
+ if (removeEllipsis( anno )) {
1684
+ error( 'anno-unexpected-ellipsis',
1685
+ [ anno.name.location, art ], { code: '...' } );
1686
+ }
1532
1687
  return;
1688
+ }
1533
1689
  // sort assignment according to layer
1534
1690
  const layerAnnos = Object.create(null);
1535
1691
  for (const a of anno) {
@@ -1541,33 +1697,107 @@ function resolve( model ) {
1541
1697
  else
1542
1698
  layerAnnos[name] = { layer, annos: [ a ] };
1543
1699
  }
1700
+ mergeArrayInSCCs();
1701
+ art[annoName] = mergeLayeredArrays( findLayerCandidate( ) );
1702
+ return;
1703
+
1704
+ function mergeArrayInSCCs( ) {
1705
+ let pos = 0;
1706
+ Object.values( layerAnnos ).forEach( (layer) => {
1707
+ const mergeSource
1708
+ = layer.annos.find(v => (v.$priority === undefined ||
1709
+ annotationPriorities[v.$priority] === annotationPriorities.define));
1710
+ if (mergeSource) {
1711
+ if (removeEllipsis( mergeSource )) {
1712
+ error( 'anno-unexpected-ellipsis',
1713
+ [ mergeSource.name.location, art ], { code: '...' } );
1714
+ }
1715
+ // merge source into elipsis array annotates
1716
+ layer.annos.forEach( (mergeTarget) => {
1717
+ if (mergeTarget.$priority &&
1718
+ annotationPriorities[mergeTarget.$priority] > annotationPriorities.define) {
1719
+ pos = findEllipsis( mergeTarget );
1720
+ if (pos > -1) {
1721
+ if (mergeSource.literal !== 'array') {
1722
+ error( 'anno-mismatched-ellipsis',
1723
+ [ mergeSource.name.location, art ], { code: '...' } );
1724
+ return;
1725
+ }
1726
+ mergeTarget.val.splice(pos, 1, ...mergeSource.val);
1727
+ }
1728
+ }
1729
+ });
1730
+ }
1731
+ });
1732
+ }
1733
+
1734
+ function mergeLayeredArrays( mergeTarget ) {
1735
+ if (mergeTarget.literal === 'array') {
1736
+ let layer = layers.layer( mergeTarget._block );
1737
+ delete layerAnnos[(layer) ? layer.realname : ''];
1738
+ let pos = findEllipsis( mergeTarget );
1739
+ while (pos > -1 && Object.keys( layerAnnos ).length ) {
1740
+ const mergeSource = findLayerCandidate();
1741
+ if (mergeSource.literal !== 'array') {
1742
+ error( 'anno-mismatched-ellipsis',
1743
+ [ mergeSource.name.location, art ], { code: '...' } );
1744
+ return mergeTarget;
1745
+ }
1746
+ mergeTarget.val.splice(pos, 1, ...mergeSource.val);
1747
+ layer = layers.layer( mergeSource._block );
1748
+ delete layerAnnos[(layer) ? layer.realname : ''];
1749
+ pos = findEllipsis( mergeTarget );
1750
+ }
1751
+ // remove excess ellipsis
1752
+ removeEllipsis( mergeTarget, pos );
1753
+ }
1754
+ return mergeTarget;
1755
+ }
1756
+
1757
+ function removeEllipsis(a, pos = findEllipsis( a )) {
1758
+ let count = 0;
1759
+ while (a.literal === 'array' && pos > -1) {
1760
+ count++;
1761
+ a.val.splice(pos, 1);
1762
+ pos = findEllipsis( a );
1763
+ }
1764
+ return count;
1765
+ }
1766
+
1767
+ function findEllipsis(a) {
1768
+ return (a.literal === 'array' && a.val)
1769
+ ? a.val.findIndex(v => v.literal === 'token' && v.val === '...') : -1;
1770
+ }
1771
+
1772
+ function findLayerCandidate() {
1544
1773
  // collect assignments of upper layers (are in no _layerExtends)
1545
- const exts = Object.keys( layerAnnos ).map( layerExtends );
1546
- const allExtends = Object.assign( Object.create(null), ...exts );
1547
- const collected = [];
1548
- for (const name in layerAnnos) {
1549
- if (!(name in allExtends))
1550
- collected.push( prioritizedAnnos( layerAnnos[name].annos ) );
1551
- }
1552
- // inspect collected assignments - choose the one or signal error
1553
- const justOnePerLayer = collected.every( annos => annos.length === 1);
1554
- if (!justOnePerLayer || collected.length > 1) {
1555
- for (const annos of collected) {
1556
- for (const a of annos ) {
1774
+ const exts = Object.keys( layerAnnos ).map( layerExtends );
1775
+ const allExtends = Object.assign( Object.create(null), ...exts );
1776
+ const collected = [];
1777
+ for (const name in layerAnnos) {
1778
+ if (!(name in allExtends))
1779
+ collected.push( prioritizedAnnos( layerAnnos[name].annos ) );
1780
+ }
1781
+ // inspect collected assignments - choose the one or signal error
1782
+ const justOnePerLayer = collected.every( annos => annos.length === 1);
1783
+ if (!justOnePerLayer || collected.length > 1) {
1784
+ for (const annos of collected) {
1785
+ for (const a of annos ) {
1557
1786
  // Only the message ID is different.
1558
- if (justOnePerLayer) {
1559
- message( 'anno-duplicate-unrelated-layer', [ a.name.location, art ], { anno: annoName },
1560
- 'Duplicate assignment with $(ANNO)' );
1561
- }
1562
- else {
1563
- message( 'anno-duplicate', [ a.name.location, art ], { anno: annoName },
1564
- 'Duplicate assignment with $(ANNO)' );
1787
+ if (justOnePerLayer) {
1788
+ message( 'anno-duplicate-unrelated-layer',
1789
+ [ a.name.location, art ], { anno: annoName },
1790
+ 'Duplicate assignment with $(ANNO)' );
1791
+ }
1792
+ else {
1793
+ message( 'anno-duplicate', [ a.name.location, art ], { anno: annoName },
1794
+ 'Duplicate assignment with $(ANNO)' );
1795
+ }
1565
1796
  }
1566
1797
  }
1567
1798
  }
1799
+ return collected[0][0]; // just choose any one with error
1568
1800
  }
1569
- art[annoName] = collected[0][0]; // just choose any one with error
1570
- return;
1571
1801
 
1572
1802
  function layerExtends( name ) {
1573
1803
  const { layer } = layerAnnos[name];
@@ -1605,8 +1835,11 @@ function resolve( model ) {
1605
1835
  // pure path has been resolved, resolve args and filter now:
1606
1836
  resolveExpr( alias, 'from', query._parent );
1607
1837
  } );
1838
+ for (const col of query.$inlines)
1839
+ resolveExpr( col.value, 'expr', col, undefined, true );
1840
+ // for (const col of query.$inlines)
1841
+ // if (!col.value.path) throw Error(col.name.element)
1608
1842
  if (query !== query._main._leadingQuery) // will be done later
1609
- // TODO: rethink elements(view) === elements(view._leadingQuery)
1610
1843
  forEachGeneric( query, 'elements', resolveRefs );
1611
1844
  if (query.from)
1612
1845
  resolveJoinOn( query.from );
@@ -1630,7 +1863,7 @@ function resolve( model ) {
1630
1863
  return;
1631
1864
 
1632
1865
  function resolveJoinOn( join ) {
1633
- if (join.args) { // JOIN
1866
+ if (join && join.args) { // JOIN
1634
1867
  for (const j of join.args)
1635
1868
  resolveJoinOn( j );
1636
1869
  if (join.on)
@@ -1667,7 +1900,10 @@ function resolve( model ) {
1667
1900
  }
1668
1901
  const target = resolvePath( obj.target, 'target', art );
1669
1902
  if (obj.on) {
1670
- if (!art._main || !art._parent.elements && !art._parent.items) {
1903
+ if (!art._main || !art._parent.elements && !art._parent.items && !art._parent.targetAspect) {
1904
+ // TODO: test of .items a bit unclear - we should somehow restrict the
1905
+ // use of unmanaged assocs in MANY, at least with $self
1906
+ // TODO: $self usage in anonymous aspects to be corrected in Core Compiler
1671
1907
  const isComposition = obj.type && obj.type.path && obj.type.path[0] &&
1672
1908
  obj.type.path[0].id === 'cds.Composition';
1673
1909
  message( 'assoc-as-type', [ obj.on.location, art ],
@@ -2108,6 +2344,9 @@ function resolve( model ) {
2108
2344
 
2109
2345
  // TODO: there is no need to rewrite the on condition of non-leading queries,
2110
2346
  // i.e. we could just have on = {…}
2347
+ // TODO: re-check $self rewrite (with managed composition of aspects),
2348
+ // and actually also $self inside anonymous aspect definitions
2349
+ // (not entirely urgent as we do not analyse it further, at least sole "$self")
2111
2350
  function rewriteCondition( elem, assoc ) {
2112
2351
  if (enableExpandElements && elem._parent && elem._parent.kind === 'element') {
2113
2352
  // managed association as sub element not supported yet
@@ -2311,7 +2550,8 @@ function resolve( model ) {
2311
2550
  resolveTypeArguments( art, typeArt, user );
2312
2551
  }
2313
2552
 
2314
- function resolveExpr( expr, expected, user, extDict) {
2553
+ function resolveExpr( expr, expected, user, extDict, expandOrInline) {
2554
+ // TODO: extra "expected" 'expand'/'inline' instead o param `expandOrInline`
2315
2555
  if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
2316
2556
  return;
2317
2557
  if (Array.isArray(expr)) {
@@ -2337,7 +2577,7 @@ function resolve( model ) {
2337
2577
  }
2338
2578
  resolvePath( expr, expected, user, extDict );
2339
2579
 
2340
- const last = expr.path[expr.path.length - 1];
2580
+ const last = !expandOrInline && expr.path[expr.path.length - 1];
2341
2581
  for (const step of expr.path) {
2342
2582
  if (step && (step.args || step.where || step.cardinality) &&
2343
2583
  step._artifact && !Array.isArray( step._artifact ) )
@@ -2358,12 +2598,18 @@ function resolve( model ) {
2358
2598
  const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
2359
2599
  args.forEach( e => e && resolveExpr( e, e.$expected || expected, user, extDict ) );
2360
2600
  }
2601
+ if (expr.suffix && !isBetaEnabled( options, 'windowFunctions' )) {
2602
+ const { location } = expr.suffix[0] || expr;
2603
+ error( null, [ location, user ], 'Window functions are not supported' );
2604
+ }
2605
+ if (expr.suffix)
2606
+ expr.suffix.forEach( s => s && resolveExpr( s, expected, user, extDict ) );
2361
2607
  }
2362
2608
 
2363
2609
  function resolveParamsAndWhere( step, expected, user, extDict, isLast ) {
2364
2610
  const alias = step._navigation && step._navigation.kind === '$tableAlias' && step._navigation;
2365
2611
  const type = alias || effectiveType( step._artifact );
2366
- const art = type && type.target && type.target._artifact || type;
2612
+ const art = (type && type.target) ? type.target._artifact : type;
2367
2613
  if (!art)
2368
2614
  return;
2369
2615
  const entity = (art.kind === 'entity') &&
@@ -2374,7 +2620,7 @@ function resolve( model ) {
2374
2620
  if (step.where)
2375
2621
  resolveExpr( step.where, 'filter', user, environment( type ) );
2376
2622
  }
2377
- else if (step.where || step.cardinality) {
2623
+ else if (step.where && step.where.location || step.cardinality ) {
2378
2624
  const location = combinedLocation( step.where, step.cardinality );
2379
2625
  // XSN TODO: filter$location including […]
2380
2626
  message( 'expr-no-filter', [ location, user ], { '#': expected },