@sap/cds-compiler 2.7.0 → 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 (63) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/lib/api/main.js +8 -10
  3. package/lib/api/options.js +13 -9
  4. package/lib/api/validate.js +11 -8
  5. package/lib/base/keywords.js +32 -2
  6. package/lib/base/message-registry.js +16 -0
  7. package/lib/base/messages.js +2 -0
  8. package/lib/base/model.js +1 -0
  9. package/lib/checks/onConditions.js +5 -0
  10. package/lib/checks/types.js +26 -2
  11. package/lib/checks/unknownMagic.js +38 -0
  12. package/lib/checks/validator.js +7 -2
  13. package/lib/compiler/assert-consistency.js +11 -5
  14. package/lib/compiler/builtins.js +2 -0
  15. package/lib/compiler/checks.js +3 -1
  16. package/lib/compiler/definer.js +87 -29
  17. package/lib/compiler/resolver.js +75 -16
  18. package/lib/compiler/shared.js +29 -9
  19. package/lib/edm/annotations/genericTranslation.js +182 -186
  20. package/lib/edm/csn2edm.js +93 -98
  21. package/lib/edm/edm.js +16 -20
  22. package/lib/edm/edmPreprocessor.js +274 -83
  23. package/lib/edm/edmUtils.js +29 -10
  24. package/lib/gen/language.checksum +1 -1
  25. package/lib/gen/language.interp +12 -1
  26. package/lib/gen/language.tokens +57 -53
  27. package/lib/gen/languageLexer.interp +10 -1
  28. package/lib/gen/languageLexer.js +770 -744
  29. package/lib/gen/languageLexer.tokens +49 -46
  30. package/lib/gen/languageParser.js +4727 -4323
  31. package/lib/json/from-csn.js +52 -23
  32. package/lib/json/to-csn.js +185 -71
  33. package/lib/language/errorStrategy.js +1 -0
  34. package/lib/language/genericAntlrParser.js +9 -0
  35. package/lib/language/language.g4 +90 -31
  36. package/lib/main.js +4 -0
  37. package/lib/model/api.js +78 -0
  38. package/lib/model/csnRefs.js +7 -1
  39. package/lib/model/csnUtils.js +5 -4
  40. package/lib/optionProcessor.js +7 -1
  41. package/lib/render/.eslintrc.json +3 -1
  42. package/lib/render/toCdl.js +45 -9
  43. package/lib/render/toHdbcds.js +100 -34
  44. package/lib/render/toSql.js +12 -4
  45. package/lib/render/utils/common.js +5 -9
  46. package/lib/sql-identifier.js +6 -1
  47. package/lib/transform/db/draft.js +6 -4
  48. package/lib/transform/db/expansion.js +14 -4
  49. package/lib/transform/db/flattening.js +13 -5
  50. package/lib/transform/db/transformExists.js +252 -58
  51. package/lib/transform/forHanaNew.js +7 -1
  52. package/lib/transform/forOdataNew.js +12 -8
  53. package/lib/transform/odata/attachPath.js +19 -4
  54. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  55. package/lib/transform/odata/referenceFlattener.js +44 -38
  56. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  57. package/lib/transform/odata/structuralPath.js +76 -0
  58. package/lib/transform/odata/structureFlattener.js +13 -10
  59. package/lib/transform/odata/typesExposure.js +22 -12
  60. package/lib/transform/transformUtilsNew.js +33 -1
  61. package/lib/transform/translateAssocsToJoins.js +6 -4
  62. package/lib/transform/universalCsnEnricher.js +67 -0
  63. package/package.json +1 -1
@@ -216,7 +216,8 @@ function getDefinerFunctions( model ) {
216
216
  applyExtensions();
217
217
 
218
218
  Object.keys( model.definitions ).forEach( preProcessArtifact );
219
- const commonLanguagesEntity = isBetaEnabled( options, 'addTextsLanguageAssoc' ) &&
219
+ const commonLanguagesEntity // TODO: remove beta after a grace period
220
+ = (options.addTextsLanguageAssoc || isBetaEnabled( options, 'addTextsLanguageAssoc' )) &&
220
221
  model.definitions['sap.common.Languages'];
221
222
  addTextsLanguageAssoc = !!(commonLanguagesEntity && commonLanguagesEntity.elements &&
222
223
  commonLanguagesEntity.elements.code);
@@ -492,7 +493,7 @@ function getDefinerFunctions( model ) {
492
493
  initDollarSelf( art ); // $self
493
494
  if (art.params)
494
495
  initParams( art ); // $parameters
495
- if (art.includes && !(art.name.absolute in extensionsDict))
496
+ if (art.includes && !(art.name.absolute in extensionsDict)) // TODO: in next phase?
496
497
  extensionsDict[art.name.absolute] = []; // structure with includes must be "extended"
497
498
 
498
499
  if (!art.query)
@@ -607,14 +608,7 @@ function getDefinerFunctions( model ) {
607
608
  if (query.on)
608
609
  initExprForQuery( query.on, query );
609
610
  // TODO: MIXIN with name = ...subquery (not yet supported anyway)
610
- for (const elem of query.columns || []) {
611
- if (elem && (elem.value || elem.expand)) {
612
- setProp( elem, '_block', query._block );
613
- defineAnnotations( elem, elem, query._block );
614
- initExprForQuery( elem.value, query );
615
- initExpandInline( elem );
616
- }
617
- }
611
+ initSelectItems( query, query.columns );
618
612
  if (query.where)
619
613
  initExprForQuery( query.where, query );
620
614
  if (query.having)
@@ -622,22 +616,41 @@ function getDefinerFunctions( model ) {
622
616
  initMembers( query, query, query._block );
623
617
  }
624
618
 
625
- function initExpandInline( elem ) {
626
- // TODO: forbid with :param, global:true, in ref-where, outside queries (CSN), ...
627
- for (const sub of elem.expand || elem.inline || []) {
628
- if (!sub) // parse error
619
+ function initSelectItems( parent, columns ) {
620
+ // TODO: forbid expand/inline with :param, global:true, in ref-where, outside queries (CSN), ...
621
+ let wildcard = null;
622
+ for (const col of columns || parent.expand || parent.inline || []) {
623
+ if (!col) // parse error
629
624
  continue;
630
- if (elem.value)
631
- setProp( sub, '_pathHead', elem ); // also set for '*' in expand/inline
632
- else if (elem._pathHead)
633
- setProp( sub, '_pathHead', elem._pathHead );
634
- if (sub.value || sub.expand) {
635
- setProp( sub, '_block', elem._block );
636
- defineAnnotations( sub, sub, elem._block ); // TODO: complain with inline
637
- initExpandInline( sub );
625
+ if (!columns) {
626
+ if (parent.value)
627
+ setProp( col, '_pathHead', parent ); // also set for '*' in expand/inline
628
+ else if (parent._pathHead)
629
+ setProp( col, '_pathHead', parent._pathHead );
630
+ }
631
+ if (col.val === '*') {
632
+ if (!wildcard) {
633
+ wildcard = col;
634
+ }
635
+ else {
636
+ // a late syntax error (this code also runs with parse-cdl), i.e.
637
+ // no semantic loc (wouldn't be available for expand/inline anyway)
638
+ error( 'syntax-duplicate-clause', [ col.location, null ],
639
+ { prop: '*', line: wildcard.location.line, col: wildcard.location.col },
640
+ 'You have provided a $(PROP) already at line $(LINE), column $(COL)' );
641
+ // TODO: extra text variants for expand/inline? - probably not
642
+ col.val = null; // do not consider it for expandWildcard()
643
+ }
644
+ }
645
+ else if (col.value || col.expand) {
646
+ setProp( col, '_block', parent._block );
647
+ defineAnnotations( col, col, parent._block ); // TODO: complain with inline
648
+ // TODO: allow sub queries? at least in top-level expand without parallel ref
649
+ if (columns)
650
+ initExprForQuery( col.value, parent );
651
+ initSelectItems( col );
638
652
  }
639
653
  }
640
- // TODO: allow sub queries in top-level expand without parallel ref
641
654
  }
642
655
 
643
656
  function initExprForQuery( expr, query ) {
@@ -656,9 +669,38 @@ function getDefinerFunctions( model ) {
656
669
  }
657
670
  else if (expr.path && expr.$expected === 'exists') {
658
671
  expr.$expected = 'approved-exists';
672
+ approveExistsInChildren(expr);
659
673
  }
660
674
  }
661
675
 
676
+ /**
677
+ * If we have a valid top-level exists, exists in filters of sub-expressions can be translated,
678
+ * since we will have a top-level subquery after exists-processing in the forHanaNew.
679
+ *
680
+ * Recursively drill down into:
681
+ * - the .path
682
+ * - the .args
683
+ * - the .where.args
684
+ *
685
+ * Any $expected === 'exists' encountered along the way are turned into 'approved-exists'
686
+ *
687
+ * working: exists toE[exists toE] -> select from E where exists toE
688
+ * not working: toE[exists toE] -> we don't support subqueries in filters
689
+ *
690
+ * @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
691
+ */
692
+ function approveExistsInChildren(exprOrPathElement) {
693
+ if (exprOrPathElement.$expected === 'exists')
694
+ exprOrPathElement.$expected = 'approved-exists';
695
+ // Drill down
696
+ if (exprOrPathElement.args)
697
+ exprOrPathElement.args.forEach(elem => approveExistsInChildren(elem));
698
+ else if (exprOrPathElement.where && exprOrPathElement.where.args)
699
+ exprOrPathElement.where.args.forEach(elem => approveExistsInChildren(elem));
700
+ else if (exprOrPathElement.path)
701
+ exprOrPathElement.path.forEach(elem => approveExistsInChildren(elem));
702
+ }
703
+
662
704
  // table is table expression in FROM, becomes an alias
663
705
  function initTableExpression( table, query, joinParents ) {
664
706
  if (!table) // parse error
@@ -706,6 +748,8 @@ function getDefinerFunctions( model ) {
706
748
  // ? ta._joinParent.args[ta.$joinArgsIndex] // in JOIN
707
749
  // : ta._parent.from ) // directly in FROM
708
750
  // Note for --raw-output: _joinParent pointing to CROSS JOIN node has not name
751
+ if (!tab) // parse error; time for #6241
752
+ return; // (parser method to only add non-null to array)
709
753
  setProp( tab, '_joinParent', table );
710
754
  tab.$joinArgsIndex = index;
711
755
  initTableExpression( tab, query, joinParents );
@@ -859,6 +903,7 @@ function getDefinerFunctions( model ) {
859
903
  * (which is basically the component name of the `parent` element plus a dot).
860
904
  */
861
905
  function initMembers( construct, parent, block, initExtensions = false ) {
906
+ // TODO: split extend from init
862
907
  const isQueryExtension = kindProperties[construct.kind].isExtension &&
863
908
  (parent._main || parent).query;
864
909
  let obj = construct;
@@ -914,7 +959,7 @@ function getDefinerFunctions( model ) {
914
959
  forEachInOrder( construct, 'params', init );
915
960
  const { returns } = construct;
916
961
  if (returns) {
917
- returns.kind = 'param';
962
+ returns.kind = (kindProperties[construct.kind].isExtension) ? construct.kind : 'param';
918
963
  init( returns, '' ); // '' is special name for returns parameter
919
964
  }
920
965
  return;
@@ -983,6 +1028,7 @@ function getDefinerFunctions( model ) {
983
1028
  }
984
1029
 
985
1030
  function checkDefinitions( construct, parent, prop, dict = construct[prop] ) {
1031
+ // TODO: do differently, see also annotateMembers() in resolver
986
1032
  // To have been checked by parsers:
987
1033
  // - artifacts (CDL-only anyway) only inside [extend] context|service
988
1034
  if (!dict)
@@ -1421,6 +1467,12 @@ function getDefinerFunctions( model ) {
1421
1467
 
1422
1468
  model.extensions.push(annotationArtifact);
1423
1469
  extendArtifact( exts, annotationArtifact ); // also sets _artifact link in extensions
1470
+ // if one of the annotate statement mentions 'returns', assume it
1471
+ // TODO: with warning/info?
1472
+ for (const ext of exts) {
1473
+ if (ext.$syntax === 'returns')
1474
+ annotationArtifact.$syntax = 'returns';
1475
+ }
1424
1476
  }
1425
1477
  }
1426
1478
  }
@@ -1704,6 +1756,7 @@ function getDefinerFunctions( model ) {
1704
1756
  }
1705
1757
 
1706
1758
  function extendMembers( extensions, art, noExtend ) {
1759
+ // TODO: do the whole extension stuff lazily if the elements are requested
1707
1760
  const elemExtensions = [];
1708
1761
  extensions.sort( compareLayer );
1709
1762
  for (const ext of extensions) {
@@ -1749,7 +1802,12 @@ function getDefinerFunctions( model ) {
1749
1802
  [ 'elements', 'actions' ].forEach( (prop) => {
1750
1803
  const dict = art._extend && art._extend[prop];
1751
1804
  for (const name in dict) {
1752
- const validDict = art[prop] || prop === 'elements' && art.enum;
1805
+ let obj = art;
1806
+ if (obj.targetAspect)
1807
+ obj = obj.targetAspect;
1808
+ while (obj.items)
1809
+ obj = obj.items;
1810
+ const validDict = obj[prop] || prop === 'elements' && obj.enum;
1753
1811
  const member = validDict[name];
1754
1812
  if (!member)
1755
1813
  extendNothing( dict[name], prop, name, art, validDict );
@@ -1988,8 +2046,8 @@ function getDefinerFunctions( model ) {
1988
2046
 
1989
2047
  if (isKey && isLocalized) { // key with localized is wrong - ignore localized
1990
2048
  const errpos = elem.localized || elem.type || elem.name;
1991
- warning( 'localized-key', [ errpos.location, elem ], {},
1992
- 'Keyword "localized" is ignored for primary keys' );
2049
+ warning( 'localized-key', [ errpos.location, elem ], { keyword: 'localized' },
2050
+ 'Keyword $(KEYWORD) is ignored for primary keys' );
1993
2051
  }
1994
2052
  }
1995
2053
  if (textElems.length <= keys)
@@ -2131,9 +2189,9 @@ function getDefinerFunctions( model ) {
2131
2189
  });
2132
2190
  }
2133
2191
  }
2134
- else { // use location of LOCALIZED keyword
2192
+ if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
2135
2193
  const localized = orig.localized || orig.type || orig.name;
2136
- elem.localized = { val: false, $inferred: 'localized', location: localized.location };
2194
+ elem.localized = { val: null, $inferred: 'localized', location: localized.location };
2137
2195
  }
2138
2196
  }
2139
2197
  if (fioriEnabled)
@@ -560,8 +560,12 @@ function resolve( model ) {
560
560
  // or should we use orig.location? - TODO: try to find test to see message
561
561
  .$inferred = 'expand-element';
562
562
  }
563
- art.$expand = 'origin'; // if value stays, elements won't appear in CSN
564
- // 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';
565
569
  return true;
566
570
  }
567
571
 
@@ -587,14 +591,45 @@ function resolve( model ) {
587
591
  return false;
588
592
  }
589
593
 
590
- 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
591
614
  while (elem._main) {
592
615
  elem = elem._parent;
593
- if (!elem.$expand)
616
+ if (elem.$expand !== 'origin')
594
617
  return;
595
- elem.$expand = 'target'; // meaning: expanded, containing assocs
618
+ elem.$expand = status; // meaning: expanded, containing assocs
596
619
  for (let line = elem.items; line; line = line.items)
597
- 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;
598
633
  }
599
634
  }
600
635
 
@@ -606,7 +641,7 @@ function resolve( model ) {
606
641
  // PRE: elem has no target, assoc has target prop
607
642
  if (elem.kind === '$tableAlias')
608
643
  return false;
609
- setExpandStatus( elem );
644
+ setExpandStatus( elem, 'target' );
610
645
  let target = resolvePath( assoc.target, 'target', assoc );
611
646
  // console.log( info( null, [ elem.location, elem ], {target,art:assoc,name:''+assoc.target},
612
647
  // 'RED').toString())
@@ -719,6 +754,7 @@ function resolve( model ) {
719
754
  const nullScope = {
720
755
  kind: 'namespace', name: { absolute: autoScopeName, location }, location,
721
756
  };
757
+ model.definitions[autoScopeName] = nullScope;
722
758
  initArtifact( nullScope );
723
759
  return nullScope;
724
760
  }
@@ -1550,14 +1586,23 @@ function resolve( model ) {
1550
1586
  }
1551
1587
  }
1552
1588
  if (art && art._annotate) {
1553
- if (art.$expand && art._annotate.elements)
1554
- art.$expand = 'annotate'; // do not omit expanded elements when they are annotated
1555
- // TODO: returns
1556
- let obj = art.returns || art; // why the extra `returns` for actions?
1557
- 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' );
1558
1596
  annotate( obj, 'element', 'elements', 'enum', art );
1559
1597
  annotate( art, 'action', 'actions' );
1560
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
+ // }
1561
1606
  }
1562
1607
  return;
1563
1608
 
@@ -1613,6 +1658,8 @@ function resolve( model ) {
1613
1658
  ext.kind = 'annotate'; // after setMemberParent()!
1614
1659
  setProp( art, '_extension', ext );
1615
1660
  setProp( ext.name, '_artifact', art );
1661
+ if (art.returns)
1662
+ ext.$syntax = 'returns';
1616
1663
  return ext;
1617
1664
  }
1618
1665
 
@@ -1816,7 +1863,7 @@ function resolve( model ) {
1816
1863
  return;
1817
1864
 
1818
1865
  function resolveJoinOn( join ) {
1819
- if (join.args) { // JOIN
1866
+ if (join && join.args) { // JOIN
1820
1867
  for (const j of join.args)
1821
1868
  resolveJoinOn( j );
1822
1869
  if (join.on)
@@ -1853,7 +1900,10 @@ function resolve( model ) {
1853
1900
  }
1854
1901
  const target = resolvePath( obj.target, 'target', art );
1855
1902
  if (obj.on) {
1856
- 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
1857
1907
  const isComposition = obj.type && obj.type.path && obj.type.path[0] &&
1858
1908
  obj.type.path[0].id === 'cds.Composition';
1859
1909
  message( 'assoc-as-type', [ obj.on.location, art ],
@@ -2294,6 +2344,9 @@ function resolve( model ) {
2294
2344
 
2295
2345
  // TODO: there is no need to rewrite the on condition of non-leading queries,
2296
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")
2297
2350
  function rewriteCondition( elem, assoc ) {
2298
2351
  if (enableExpandElements && elem._parent && elem._parent.kind === 'element') {
2299
2352
  // managed association as sub element not supported yet
@@ -2545,12 +2598,18 @@ function resolve( model ) {
2545
2598
  const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
2546
2599
  args.forEach( e => e && resolveExpr( e, e.$expected || expected, user, extDict ) );
2547
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 ) );
2548
2607
  }
2549
2608
 
2550
2609
  function resolveParamsAndWhere( step, expected, user, extDict, isLast ) {
2551
2610
  const alias = step._navigation && step._navigation.kind === '$tableAlias' && step._navigation;
2552
2611
  const type = alias || effectiveType( step._artifact );
2553
- const art = type && type.target && type.target._artifact || type;
2612
+ const art = (type && type.target) ? type.target._artifact : type;
2554
2613
  if (!art)
2555
2614
  return;
2556
2615
  const entity = (art.kind === 'entity') &&
@@ -2561,7 +2620,7 @@ function resolve( model ) {
2561
2620
  if (step.where)
2562
2621
  resolveExpr( step.where, 'filter', user, environment( type ) );
2563
2622
  }
2564
- else if (step.where || step.cardinality ) {
2623
+ else if (step.where && step.where.location || step.cardinality ) {
2565
2624
  const location = combinedLocation( step.where, step.cardinality );
2566
2625
  // XSN TODO: filter$location including […]
2567
2626
  message( 'expr-no-filter', [ location, user ], { '#': expected },
@@ -60,7 +60,7 @@ const kindProperties = {
60
60
 
61
61
  function propExists( prop, parent ) {
62
62
  const obj = parent.returns || parent;
63
- return (obj.items || obj)[prop];
63
+ return (obj.items || obj.targetAspect || obj)[prop];
64
64
  }
65
65
 
66
66
  function artifactsEnv( art ) {
@@ -657,13 +657,28 @@ function fns( model, environment = artifactsEnv ) {
657
657
  return false;
658
658
  continue;
659
659
  }
660
- if (art && art.$uncheckedElements) {
661
- // do not check any elements of the path, e.g. $session
662
- return art;
663
- }
660
+
664
661
  const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : environment;
665
662
  const env = fn( art, item.location, user, spec.assoc );
663
+
664
+ // do not check any elements of the path, e.g. $session - but still don't return path-head
665
+ if (art && art.$uncheckedElements) {
666
+ if (env && env[item.id]) // something like $user.id/$user.locale
667
+ return env[item.id];
668
+
669
+ // $user.foo - build our own valid path step obj
670
+ // Important: Don't directly modify item!
671
+ const obj = {
672
+ location: item.location,
673
+ kind: 'builtin',
674
+ name: { id: item.id, element: path.map(p => p.id).join('.') },
675
+ };
676
+ setLink(obj, art, '_parent');
677
+ return obj;
678
+ }
679
+
666
680
  const sub = setLink( item, env && env[item.id] );
681
+
667
682
  if (!sub)
668
683
  return (sub === 0) ? 0 : errorNotFound( item, env );
669
684
  else if (Array.isArray(sub)) // redefinitions
@@ -941,10 +956,11 @@ function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
941
956
 
942
957
  function setMemberParent( elem, name, parent, prop ) {
943
958
  if (prop) { // extension or structure include
944
- // TODO: consider ARRAY OF and RETURNS, COMPOSITION OF type
945
- if (!(prop in parent))
946
- parent[prop] = Object.create(null);
947
- dictAdd( parent[prop], name, elem );
959
+ // TODO: consider nested ARRAY OF and RETURNS, COMPOSITION OF type
960
+ const p = parent.items || parent.targetAspect || parent;
961
+ if (!(prop in p))
962
+ p[prop] = Object.create(null);
963
+ dictAdd( p[prop], name, elem );
948
964
  }
949
965
  if (parent._outer)
950
966
  parent = parent._outer;
@@ -1000,6 +1016,10 @@ function storeExtension( elem, name, prop, parent, block ) {
1000
1016
  const kind = `_${ elem.kind }`; // _extend or _annotate
1001
1017
  if (!parent[kind])
1002
1018
  setProp( parent, kind, {} );
1019
+ // if (name === '' && prop === 'params') {
1020
+ // pushToDict( parent[kind], 'returns', elem ); // not really a dict
1021
+ // return;
1022
+ // }
1003
1023
  if (!parent[kind][prop])
1004
1024
  parent[kind][prop] = Object.create(null);
1005
1025
  pushToDict( parent[kind][prop], name, elem );