@sap/cds-compiler 5.7.4 → 5.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +54 -2
  2. package/bin/cdsse.js +13 -1
  3. package/doc/CHANGELOG_BETA.md +7 -0
  4. package/lib/api/options.js +2 -1
  5. package/lib/api/validate.js +9 -0
  6. package/lib/base/message-registry.js +55 -20
  7. package/lib/base/messages.js +5 -2
  8. package/lib/base/model.js +4 -1
  9. package/lib/checks/assocOutsideService.js +40 -0
  10. package/lib/checks/featureFlags.js +4 -1
  11. package/lib/checks/types.js +7 -4
  12. package/lib/checks/validator.js +3 -0
  13. package/lib/compiler/assert-consistency.js +11 -5
  14. package/lib/compiler/checks.js +79 -17
  15. package/lib/compiler/define.js +57 -3
  16. package/lib/compiler/extend.js +1 -2
  17. package/lib/compiler/generate.js +1 -1
  18. package/lib/compiler/populate.js +17 -6
  19. package/lib/compiler/propagator.js +1 -1
  20. package/lib/compiler/resolve.js +181 -150
  21. package/lib/compiler/shared.js +276 -22
  22. package/lib/compiler/tweak-assocs.js +15 -4
  23. package/lib/compiler/xpr-rewrite.js +76 -50
  24. package/lib/edm/annotations/edmJson.js +1 -1
  25. package/lib/edm/annotations/genericTranslation.js +2 -2
  26. package/lib/edm/csn2edm.js +2 -2
  27. package/lib/edm/edmPreprocessor.js +15 -9
  28. package/lib/edm/edmUtils.js +12 -5
  29. package/lib/gen/CdlGrammar.checksum +1 -0
  30. package/lib/gen/CdlParser.js +2234 -2233
  31. package/lib/gen/Dictionary.json +55 -8
  32. package/lib/json/from-csn.js +37 -17
  33. package/lib/json/to-csn.js +4 -0
  34. package/lib/language/genericAntlrParser.js +7 -0
  35. package/lib/main.d.ts +5 -0
  36. package/lib/model/cloneCsn.js +1 -0
  37. package/lib/model/csnRefs.js +1 -0
  38. package/lib/model/csnUtils.js +0 -5
  39. package/lib/modelCompare/utils/filter.js +2 -2
  40. package/lib/optionProcessor.js +2 -0
  41. package/lib/parsers/AstBuildingParser.js +47 -17
  42. package/lib/parsers/CdlGrammar.g4 +10 -12
  43. package/lib/parsers/XprTree.js +206 -0
  44. package/lib/render/toCdl.js +61 -89
  45. package/lib/render/toSql.js +59 -29
  46. package/lib/render/utils/standardDatabaseFunctions.js +252 -15
  47. package/lib/transform/addTenantFields.js +9 -3
  48. package/lib/transform/db/assocsToQueries/transformExists.js +3 -0
  49. package/lib/transform/db/assocsToQueries/utils.js +10 -3
  50. package/lib/transform/db/expansion.js +3 -1
  51. package/lib/transform/db/flattening.js +7 -3
  52. package/lib/transform/db/killAnnotations.js +1 -0
  53. package/lib/transform/db/processSqlServices.js +70 -17
  54. package/lib/transform/draft/db.js +8 -3
  55. package/lib/transform/draft/odata.js +27 -4
  56. package/lib/transform/effective/main.js +37 -10
  57. package/lib/transform/effective/misc.js +4 -9
  58. package/lib/transform/effective/service.js +34 -0
  59. package/lib/transform/effective/types.js +28 -17
  60. package/lib/transform/forOdata.js +36 -10
  61. package/lib/transform/forRelationalDB.js +30 -18
  62. package/lib/transform/odata/adaptAnnotationRefs.js +37 -21
  63. package/lib/transform/odata/createForeignKeys.js +120 -116
  64. package/lib/transform/odata/flattening.js +10 -8
  65. package/lib/transform/transformUtils.js +58 -25
  66. package/lib/transform/translateAssocsToJoins.js +10 -6
  67. package/lib/transform/universalCsn/coreComputed.js +5 -1
  68. package/package.json +1 -1
  69. package/share/messages/message-explanations.json +1 -0
  70. package/share/messages/rewrite-not-supported.md +5 -0
  71. package/share/messages/rewrite-undefined-key.md +94 -0
@@ -46,7 +46,7 @@ const {
46
46
  isDeprecatedEnabled,
47
47
  } = require('../base/model');
48
48
  const { dictAdd } = require('../base/dictionaries');
49
- const { dictLocation, weakLocation } = require('../base/location');
49
+ const { weakLocation } = require('../base/location');
50
50
  const { combinedLocation } = require('../base/location');
51
51
  const { typeParameters } = require('./builtins');
52
52
 
@@ -92,15 +92,16 @@ function resolve( model ) {
92
92
  resolveDefinitionName,
93
93
  attachAndEmitValidNames,
94
94
  traverseExpr,
95
+ traverseTypedExpr,
95
96
  effectiveType,
96
97
  getOrigin,
97
98
  getInheritedProp,
98
99
  resolveTypeArgumentsUnchecked,
99
100
  } = model.$functions;
100
101
  Object.assign( model.$functions, {
101
- resolveExpr,
102
102
  addForeignKeyNavigations,
103
103
  redirectionChain,
104
+ resolveExprInAnnotations,
104
105
  } );
105
106
 
106
107
  const ignoreSpecifiedElements
@@ -260,6 +261,10 @@ function resolve( model ) {
260
261
  }
261
262
 
262
263
  function propagateKeyProps( view ) {
264
+ if (view.kind === 'type') {
265
+ // we don't propagate keys to type projections, see #13575
266
+ return;
267
+ }
263
268
  // Second argument true ensure that `key` is only propagated along simple
264
269
  // view, i.e. ref or subquery in FROM, not UNION or JOIN.
265
270
  traverseQueryPost( view.query, true, ( query ) => {
@@ -552,11 +557,11 @@ function resolve( model ) {
552
557
  addForeignKeyNavigations( art );
553
558
  }
554
559
 
555
- resolveExprWithEnum( art.default, 'default', art );
560
+ resolveExpr( art.default, 'default', art, art );
556
561
 
557
562
  // TODO: distinguish not by $syntax (it is semantics), but whether in query
558
563
  const valueCtx = (art.$syntax === 'calc') ? 'calc' : 'column';
559
- resolveExprWithEnum( art.value, valueCtx, art );
564
+ resolveExpr( art.value, valueCtx, art, art );
560
565
  if (art.type?.$inferred === 'cast')
561
566
  inferTypePropertiesFromCast( art );
562
567
  if (art.value) {
@@ -598,7 +603,7 @@ function resolve( model ) {
598
603
 
599
604
  // Check explicit types: If either side has one, so must the other.
600
605
  const sType = specifiedElement.type?._artifact;
601
- const iTypeArt = getInferredPropFromOrigin( 'type' )?._artifact;
606
+ const iTypeArt = getInheritedProp( inferredElement, 'type' )?._artifact;
602
607
  const iType = iTypeArt || inferredElement;
603
608
  // FIXME: The coding above returns incorrect iType for expand on associations
604
609
 
@@ -678,7 +683,7 @@ function resolve( model ) {
678
683
  }
679
684
 
680
685
  if (specifiedElement.virtual) {
681
- const iVirtual = getInferredPropFromOrigin( 'virtual' )?.val || false;
686
+ const iVirtual = getInheritedProp( inferredElement, 'virtual' )?.val || false;
682
687
  if (!specifiedElement.virtual.val !== !iVirtual) {
683
688
  error( 'query-mismatched-element', [
684
689
  specifiedElement.virtual.location || specifiedElement.location, user,
@@ -737,7 +742,7 @@ function resolve( model ) {
737
742
 
738
743
  if (specifiedElement.key) { // TODO: `|| inferredElement.key?.val`, once to.sql is fixed
739
744
  // TODO: Do not use _origin chain for key; has been propagated in propagateKeyProps().
740
- const iKey = getInferredPropFromOrigin( 'key' )?.val;
745
+ const iKey = getInheritedProp( inferredElement, 'key' )?.val;
741
746
  // If "key" is specified or truthy in the inferred element, the values must match.
742
747
  if (!iKey !== !specifiedElement.key?.val) {
743
748
  error( 'query-mismatched-element', [
@@ -793,16 +798,6 @@ function resolve( model ) {
793
798
  return false;
794
799
  }
795
800
 
796
- function getInferredPropFromOrigin( prop ) {
797
- // Inferred property via _origin chain (0 === circular).
798
- let element = inferredElement;
799
- if (element._effectiveType !== 0) {
800
- while (getOrigin( element ) && !element[prop])
801
- element = getOrigin( element );
802
- }
803
- return element[prop];
804
- }
805
-
806
801
  function getInferredCardinality() {
807
802
  let element = inferredElement;
808
803
  if (element._effectiveType !== 0) {
@@ -822,11 +817,19 @@ function resolve( model ) {
822
817
  // Set '@Core.Computed' in the Core Compiler to have it propagated...
823
818
  if (art.kind !== 'element' || art['@Core.Computed'])
824
819
  return;
820
+
821
+ // For events and types, elements can't be @Core.Computed, as values are only used
822
+ // to infer the element signature. For virtual, we keep @Core.Computed, as it's
823
+ // always been that way, even before type projections.
824
+ const elementsCanBeComputed = art._main?.kind !== 'type' && art._main?.kind !== 'event';
825
+
825
826
  if (art.virtual?.val ||
826
- art.value &&
827
+ elementsCanBeComputed && art.value &&
827
828
  (!art.value._artifact || !art.value.path || // in localization view: _artifact, but no path
828
829
  art.value.stored?.val || // calculated elements on-write are always computed
829
- art.value._artifact.kind === 'builtin' || art.value._artifact.kind === 'param' )) {
830
+ art.value._artifact.kind === 'builtin' ||
831
+ art.value._artifact.kind === 'param' ||
832
+ art.value.scope === 'param' )) {
830
833
  art['@Core.Computed'] = {
831
834
  name: {
832
835
  path: [ { id: 'Core.Computed', location: art.location } ],
@@ -1067,10 +1070,11 @@ function resolve( model ) {
1067
1070
  traverseExpr( art.on, 'on-check', art, (expr) => {
1068
1071
  const { path } = expr;
1069
1072
  if (!expr?._artifact || path?.length < 2 || issue['#'])
1070
- return; // no path or with error or already found issue
1073
+ return traverseExpr.SKIP; // no path or with error or already found issue
1071
1074
  const head = (path[0]._navigation?.kind === '$self') ? 1 : 0;
1072
1075
  if (path[head]._artifact === art)
1073
1076
  checkAutoRedirectedPathItem( path[head + 1], modelTarget, issue );
1077
+ return traverseExpr.SKIP;
1074
1078
  } );
1075
1079
  // Check explicit+implicit foreign keys: no renamed target element
1076
1080
  const implicit = art.foreignKeys?.[$inferred];
@@ -1390,6 +1394,7 @@ function resolve( model ) {
1390
1394
  const typeArt = resolvePath( art.type, 'type', user );
1391
1395
  if (typeArt)
1392
1396
  resolveTypeArgumentsUnchecked( art, typeArt, user );
1397
+ return typeArt;
1393
1398
  }
1394
1399
 
1395
1400
  function resolveExprInAnnotations( art ) {
@@ -1408,7 +1413,10 @@ function resolve( model ) {
1408
1413
  if (expr.$tokenTexts) {
1409
1414
  if (!anno.kind)
1410
1415
  initAnnotationForExpression( anno, art );
1411
- resolveExprWithEnum( expr, 'annotation', anno );
1416
+ const type = anno === expr && anno.name;
1417
+ // TODO: it might be best to set an _artifact link also for property values
1418
+ // like in `@Anno: [ { foo: #EnumSymbol }]
1419
+ resolveExpr( expr, 'annotation', anno, type );
1412
1420
  }
1413
1421
  else if (expr.literal === 'array') {
1414
1422
  expr.val.forEach( val => resolveAnnoExpr( val, art, anno ) );
@@ -1428,7 +1436,7 @@ function resolve( model ) {
1428
1436
  anno.kind = '$annotation';
1429
1437
  setLink( anno, '_outer', art );
1430
1438
  art.$contains ??= {};
1431
- art.$contains.$annotation = { // set in resolveExprItem
1439
+ art.$contains.$annotation = { // set in resolveExprNode
1432
1440
  $path: false,
1433
1441
  $self: false,
1434
1442
  };
@@ -1436,157 +1444,180 @@ function resolve( model ) {
1436
1444
  // Might be useful for future recursive types.
1437
1445
  }
1438
1446
 
1439
- function resolveExprWithEnum( expr, exprCtx, art ) {
1440
- traverseExpr( expr, exprCtx, art, resolveExprItem );
1441
- const sym = expr?.sym;
1442
- const assignment = art;
1443
- // Check enum as top-level annotation value:
1444
- if (art?.kind === '$annotation')
1445
- art = art === expr && art.name._artifact;
1446
- if (!art)
1447
- return;
1448
- const symbols = effectiveType( art )?.enum;
1449
- if (!sym || !symbols)
1450
- return;
1451
- // TODO: or warning if enum symbol but non-enum type?
1452
- if (symbols[sym.id]) {
1453
- setLink( sym, '_artifact', symbols[sym.id] );
1454
- }
1455
- else {
1456
- let type = art._effectiveType;
1457
- // inferred enums can't be extended (yet): show underlying enum
1458
- while (type.enum[$inferred])
1459
- type = getOrigin( type );
1460
- const err = message( 'ref-undefined-enum', [ sym.location, assignment ],
1461
- { id: sym.id, type } );
1462
- if (options.newParser || options.newparser)
1463
- attachAndEmitValidNames( err, symbols );
1464
- }
1447
+ function resolveExpr( expr, exprCtx, art, type = null ) {
1448
+ traverseTypedExpr( expr, exprCtx, art, type, resolveExprNode );
1465
1449
  }
1466
1450
 
1467
- function resolveExpr( expr, exprCtx, user ) {
1468
- traverseExpr( expr, exprCtx, user, resolveExprItem );
1469
- }
1470
-
1471
- function resolveExprItem( expr, expected, user ) {
1451
+ function resolveExprNode( expr, expected, user, type ) {
1472
1452
  if (expr.type) // e.g. cast( a as Integer )
1473
- resolveTypeExpr( expr, user._user || user );
1453
+ type = resolveTypeExpr( expr, user._user || user );
1474
1454
 
1475
1455
  if (expr.path) {
1476
- // TODO: re-think this $expected: 'exists' thing
1477
- if (expr.$expected === 'exists') {
1478
- if (expected !== 'annotation') { // `exists e[…]` allowed in annotation expressions
1479
- error( 'expr-unexpected-exists', [ expr.location, user ], {},
1480
- 'An EXISTS predicate is not expected here' );
1481
- }
1482
- // We complain about the EXISTS before, as EXISTS subquery is also not supported
1483
- // TODO: location of EXISTS, TODO: really do this in define.js
1484
- expr.$expected = 'approved-exists'; // only complain once
1485
- }
1486
- resolvePath( expr, expected, user );
1487
-
1488
- const last = expr.$expected !== 'approved-exists' && expr.path[expr.path.length - 1];
1489
- for (const step of expr.path) {
1490
- if (step && (step.args || step.where || step.cardinality) &&
1491
- step._artifact && !Array.isArray( step._artifact ) )
1492
- resolveParamsAndWhere( step, expected, user, step === last );
1493
- // TODO: delete 4th arg
1494
- }
1495
-
1496
- if (expected === 'annotation') {
1497
- user._outer.$contains.$annotation.$path = true;
1498
- user._outer.$contains.$annotation.$self ||= expr.path[0]?._navigation?.kind === '$self';
1499
- }
1456
+ type = resolveExprPath( expr, expected, user );
1457
+ }
1458
+ else if (expr.id) {
1459
+ type = resolvePathItem( expr, expected, user );
1460
+ }
1461
+ else if (expr.sym) {
1462
+ resolveEnumSymbol( expr, expected, user, type );
1500
1463
  }
1501
1464
  else if (expr.query) {
1465
+ // No traversal into query, art.$queries set in define.js
1466
+ // No subqueries for type projections, nor in annotation expressions.
1502
1467
  const { query } = expr;
1503
- if (query.kind || query._leadingQuery) { // UNION has _leadingQuery
1504
- // traverseQueryPost( query, false, resolveQuery );
1468
+ if (query._main?.kind === 'type' ||
1469
+ (!query.kind && !query._leadingQuery)) { // UNION has _leadingQuery
1470
+ error( 'expr-no-subquery', [ expr.location, user ], {
1471
+ '#': query._main?.kind === 'type' ? 'type' : 'std',
1472
+ }, {
1473
+ std: 'Subqueries are not supported here',
1474
+ type: 'Subqueries are not supported in type projections',
1475
+ } );
1505
1476
  }
1506
- else {
1507
- error( 'expr-no-subquery', [ expr.location, user ], {},
1508
- 'Subqueries are not supported here' );
1477
+ else if (query._main?.kind === 'event') {
1478
+ warning( 'expr-no-event-subquery', [ expr.location, user ],
1479
+ 'Subqueries are not supported in event projections' );
1509
1480
  }
1510
1481
  }
1482
+ return type;
1511
1483
  }
1512
1484
 
1513
- function resolveParamsAndWhere( step, expected, user, isLast ) {
1514
- const alias = (step._navigation?.kind === '$tableAlias') ? step._navigation : null;
1515
- const type = alias || effectiveType( step._artifact );
1516
- const art = type?.target ? type.target._artifact : type;
1517
- if (!art)
1518
- return;
1519
- const entity = art.kind === 'entity' &&
1520
- (!isLast || user.expand || user.inline || expWithFilter.includes( expected )) &&
1521
- art;
1522
- if (step.args)
1523
- resolveParams( step.args, art, entity, expected, user, step.location );
1524
-
1525
- // Publishing an association with filters is ok in columns and also ok in calculated elements.
1526
- const publishAssoc = art.kind === 'entity' && (expected === 'column' || expected === 'calc');
1527
-
1528
- if (entity || publishAssoc) {
1529
- if (step.where) {
1530
- setLink( step, '_user', user._user || user );
1531
- const inCalc = (expected === 'calc' || expected === 'calc-filter');
1532
- resolveExpr( step.where, (inCalc ? 'calc-filter' : 'filter'), step );
1485
+ function resolveExprPath( expr, expected, user ) {
1486
+ // TODO: re-think this $expected: 'exists' thing
1487
+ if (expr.$expected === 'exists') {
1488
+ if (expected !== 'annotation') { // `exists e[…]` allowed in annotation expressions
1489
+ error( 'expr-unexpected-exists', [ expr.location, user ], {},
1490
+ 'An EXISTS predicate is not expected here' );
1533
1491
  }
1492
+ // We complain about the EXISTS before, as EXISTS subquery is also not supported
1493
+ // TODO: location of EXISTS, TODO: really do this in define.js
1494
+ expr.$expected = 'approved-exists'; // only complain once
1495
+ }
1496
+ const ref = resolvePath( expr, expected, user );
1497
+
1498
+ if (expected === 'annotation') {
1499
+ user._outer.$contains.$annotation.$path = true;
1500
+ user._outer.$contains.$annotation.$self ||= expr.path[0]?._navigation?.kind === '$self';
1501
+ }
1502
+
1503
+ // check whether arguments and filters are allowed on last path item;
1504
+ // references in those are to be resolved below even if not allowed
1505
+ // (for code completion)
1506
+ const last = expr.path[expr.path.length - 1];
1507
+ if (!last || !(last.args || last.where || last.cardinality) ||
1508
+ expr.$expected === 'approved-exists' ||
1509
+ user.expand || user.inline ||
1510
+ expWithFilter.includes( expected ) || // `from`, …
1511
+ last._navigation?.kind === '$tableAlias') // error already reported
1512
+ return ref;
1513
+
1514
+ const type = effectiveType( last._artifact );
1515
+ const art = type && (type.kind === 'entity' ? type : type.target?._artifact);
1516
+ if (!art)
1517
+ return ref; // error already reported via resolvePathItem()
1518
+ const unexpectedFilter = expected !== 'column' && expected !== 'calc';
1519
+ if (last.args || last.where || last.cardinality)
1520
+ reportUnexpectedArgsAndFilter( last, expected, user, art, unexpectedFilter && 'std' );
1521
+ // TODO: we should have different message-ids for the "last" stuff: adding
1522
+ // `.item` likely corrects the ref, probably with location at end of ref
1523
+ return ref;
1524
+ }
1525
+
1526
+ function resolvePathItem( step, expected, user ) {
1527
+ // In FROM ref, first item artifact might be array (duplicate in same
1528
+ // `artifacts` dictionary)
1529
+ if (!step._artifact || Array.isArray( step._artifact ))
1530
+ return traverseExpr.SKIP; // cannot resolve filter refs for undef'd / duplicates
1531
+
1532
+ const isAlias = step._navigation?.kind === '$tableAlias';
1533
+ if (isAlias)
1534
+ return reportUnexpectedArgsAndFilter( step, expected, user, { params: {} }, 'tableAlias' );
1535
+ const type = effectiveType( step._artifact );
1536
+ const art = type && (type.kind === 'entity' ? type : type.target?._artifact);
1537
+ if (!art) {
1538
+ const exp = (expected === 'from') ? 'from' : 'std';
1539
+ return (type && type.target)
1540
+ ? traverseExpr.SKIP // something wrong with the association
1541
+ : reportUnexpectedArgsAndFilter( step, expected, user, { params: {} }, exp );
1542
+ }
1543
+ setLink( step, '_user', user._user || user );
1544
+ const { args } = step;
1545
+ if (!args)
1546
+ return null;
1547
+ if (Array.isArray( args )) {
1548
+ const loc = [ args[0]?.location || step.location, user ];
1549
+ error( 'expr-expected-named-argument', loc, {},
1550
+ 'Expected named parameters for the entity' );
1551
+ // only via CSN input - TODO: or do in CSN parser?
1552
+ return null;
1534
1553
  }
1535
- else if (step.where || step.cardinality) {
1536
- const location = combinedLocation( step.where, step.cardinality );
1537
- let variant = alias ? 'tableAlias' : 'std';
1538
- if (expected === 'from')
1539
- variant = 'from';
1540
- // XSN TODO: filter$location including […]
1541
- message( 'expr-unexpected-filter', [ location, user ], { '#': variant }, {
1542
- std: 'A filter can only be provided when navigating along associations',
1543
- // to help users for `… from E:toF { toF[…].x }`
1544
- // eslint-disable-next-line @stylistic/js/max-len
1545
- tableAlias: 'A filter can only be provided when navigating along associations, but found table alias',
1546
- from: 'A filter can only be provided for the source entity or associations',
1547
- } );
1554
+ if (!art.params) {
1555
+ // remark: from the location, this should be 'expr-unexpected-arguments',
1556
+ // or probably better: use a text variant of expr-undefined-param
1557
+ error( 'expr-unexpected-argument', [ args[$location] || step.location, user ],
1558
+ { art, '#': 'no-params' } ); // TODO: own message-id ?
1559
+ return null;
1560
+ }
1561
+
1562
+ for (const id in args) {
1563
+ const param = art.params[id];
1564
+ const { name } = args[id];
1565
+ setArtifactLink( name, param );
1566
+ if (!param) {
1567
+ error( 'expr-undefined-param', [ name.location, user ], { art, id },
1568
+ 'Entity $(ART) has no parameter $(ID)' );
1569
+ }
1570
+ // no need to do s/th special for duplicate arguments in args[id].$duplicates
1548
1571
  }
1572
+ return null;
1549
1573
  }
1550
1574
 
1551
- function resolveParams( dict, art, entity, expected, user, stepLocation ) {
1552
- if (!entity || !entity.params) {
1553
- let first = dict[Object.keys( dict )[0]];
1554
- if (Array.isArray( first ))
1555
- first = first[0];
1556
- error( 'expr-unexpected-argument',
1557
- [ dict[$location] || dictLocation( dict, first?.name?.location || stepLocation ),
1558
- user ],
1559
- { art, '#': (entity ? 'entity' : expected ) },
1560
- {
1561
- std: 'Parameters can only be provided when navigating along associations',
1562
- from: 'Parameters can only be provided for the source entity or associations',
1563
- // or extra message id for entity?
1564
- entity: 'Unexpected arguments for entity $(ART) without parameters',
1565
- } );
1575
+ function resolveEnumSymbol( expr, expected, user, type ) {
1576
+ const { sym } = expr;
1577
+ // CSN input with both '#'+val (recompilation) do not resolve
1578
+ if (!sym || expr.val !== undefined || !type)
1566
1579
  return;
1580
+ // The parameter def for an argument, and the annotation def for an assignment
1581
+ // is in `type._artifact`, it is not `type` itself:
1582
+ type = effectiveType( type.id ? type._artifact : type );
1583
+ // Remark: do not use `?.`, type could be 0 for cyclic parameter type
1584
+ if (type && type.foreignKeys) {
1585
+ const keys = Object.values( type.foreignKeys );
1586
+ if (keys.length === 1) {
1587
+ const elem = resolvePath( keys[0].targetElement, 'targetElement', keys[0] );
1588
+ type = effectiveType( elem );
1589
+ }
1567
1590
  }
1568
- const exp = (expected === 'from') ? 'from-args' : expected;
1569
- if (Array.isArray( dict )) {
1570
- const loc = [ dict[0] && dict[0].location || stepLocation, user ];
1571
- error( 'expr-expected-named-argument', loc, {},
1572
- 'Expected named parameters for the entity' );
1573
- for (const a of dict)
1574
- resolveExpr( a, exp, user );
1591
+ const symbols = type && type.enum;
1592
+ if (!symbols)
1575
1593
  return;
1594
+
1595
+ // TODO: or warning if enum symbol but non-enum type?
1596
+ if (symbols[sym.id]) {
1597
+ setLink( sym, '_artifact', symbols[sym.id] );
1576
1598
  }
1577
- // TODO: allow to specify expected for arguments in in specExpected
1578
- for (const name in dict) {
1579
- const param = art.params[name];
1580
- const arg = dict[name];
1581
- for (const a of Array.isArray( arg ) ? arg : [ arg ]) {
1582
- setArtifactLink( a.name, param );
1583
- if (!param) {
1584
- error( 'expr-undefined-param', [ a.name.location, user ], { art, id: name },
1585
- 'Entity $(ART) has no parameter $(ID)' );
1586
- }
1587
- resolveExpr( a, exp, user );
1588
- }
1599
+ else {
1600
+ // inferred enums can't be extended (yet): show underlying enum
1601
+ while (type.enum[$inferred])
1602
+ type = getOrigin( type );
1603
+ const err = message( 'ref-undefined-enum', [ sym.location, user ],
1604
+ { id: sym.id, type } );
1605
+ if (options.newParser || options.newparser)
1606
+ attachAndEmitValidNames( err, symbols );
1607
+ }
1608
+ }
1609
+
1610
+ function reportUnexpectedArgsAndFilter( step, expected, user, art, variant ) {
1611
+ if (step.args && art?.params) {
1612
+ const loc = [ step.args[$location] || step.location, user ];
1613
+ error( 'expr-unexpected-argument', loc, { '#': variant } );
1614
+ }
1615
+ if ((step.where || step.cardinality) && variant) {
1616
+ const location = combinedLocation( step.where, step.cardinality );
1617
+ // XSN TODO: filter$location including […]
1618
+ message( 'expr-unexpected-filter', [ location, user ], { '#': variant } );
1589
1619
  }
1620
+ return variant && traverseExpr.SKIP;
1590
1621
  }
1591
1622
  }
1592
1623