@sap/cds-compiler 6.4.2 → 6.5.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 +87 -1159
  2. package/README.md +1 -10
  3. package/doc/IncompatibleChanges_v5.md +436 -0
  4. package/doc/IncompatibleChanges_v6.md +659 -0
  5. package/doc/Versioning.md +3 -7
  6. package/lib/api/main.js +1 -0
  7. package/lib/api/options.js +5 -0
  8. package/lib/api/validate.js +3 -0
  9. package/lib/base/message-registry.js +25 -2
  10. package/lib/base/messages.js +1 -1
  11. package/lib/base/model.js +3 -2
  12. package/lib/checks/actionsFunctions.js +6 -3
  13. package/lib/checks/existsExpressionsOnlyForeignKeys.js +16 -10
  14. package/lib/checks/existsInForbiddenPlaces.js +32 -0
  15. package/lib/checks/existsMustEndInAssoc.js +1 -1
  16. package/lib/checks/existsMustNotStartWithDollarSelf.js +31 -0
  17. package/lib/checks/validator.js +6 -2
  18. package/lib/compiler/assert-consistency.js +5 -7
  19. package/lib/compiler/builtins.js +5 -6
  20. package/lib/compiler/checks.js +4 -8
  21. package/lib/compiler/define.js +244 -459
  22. package/lib/compiler/extend.js +297 -11
  23. package/lib/compiler/finalize-parse-cdl.js +2 -10
  24. package/lib/compiler/generate.js +29 -1
  25. package/lib/compiler/populate.js +21 -63
  26. package/lib/compiler/propagator.js +1 -2
  27. package/lib/compiler/resolve.js +2 -12
  28. package/lib/compiler/shared.js +145 -114
  29. package/lib/compiler/tweak-assocs.js +14 -10
  30. package/lib/compiler/utils.js +97 -0
  31. package/lib/compiler/xpr-rewrite.js +113 -140
  32. package/lib/edm/annotations/edmJson.js +9 -6
  33. package/lib/edm/annotations/genericTranslation.js +8 -4
  34. package/lib/edm/csn2edm.js +3 -4
  35. package/lib/edm/edmInboundChecks.js +1 -2
  36. package/lib/edm/edmPreprocessor.js +3 -3
  37. package/lib/gen/CdlGrammar.checksum +1 -1
  38. package/lib/gen/CdlParser.js +4 -3
  39. package/lib/gen/Dictionary.json +16 -1
  40. package/lib/json/from-csn.js +4 -6
  41. package/lib/json/to-csn.js +3 -3
  42. package/lib/model/csnRefs.js +13 -4
  43. package/lib/model/enrichCsn.js +4 -2
  44. package/lib/optionProcessor.js +8 -4
  45. package/lib/parsers/AstBuildingParser.js +1 -1
  46. package/lib/render/utils/sql.js +3 -2
  47. package/lib/transform/db/applyTransformations.js +1 -1
  48. package/lib/transform/db/assertUnique.js +3 -3
  49. package/lib/transform/db/assocsToQueries/normalizeFrom.js +33 -0
  50. package/lib/transform/db/assocsToQueries/transformExists.js +17 -13
  51. package/lib/transform/db/assocsToQueries/utils.js +1 -6
  52. package/lib/transform/db/backlinks.js +4 -4
  53. package/lib/transform/db/cdsPersistence.js +4 -4
  54. package/lib/transform/db/constraints.js +4 -4
  55. package/lib/transform/db/expansion.js +5 -5
  56. package/lib/transform/db/flattening.js +4 -5
  57. package/lib/transform/db/rewriteCalculatedElements.js +3 -3
  58. package/lib/transform/db/temporal.js +11 -11
  59. package/lib/transform/draft/db.js +2 -0
  60. package/lib/transform/draft/odata.js +5 -7
  61. package/lib/transform/effective/flattening.js +1 -2
  62. package/lib/transform/forOdata.js +3 -3
  63. package/lib/transform/forRelationalDB.js +1 -1
  64. package/lib/transform/localized.js +13 -20
  65. package/lib/transform/odata/createForeignKeys.js +1 -2
  66. package/lib/transform/odata/flattening.js +1 -2
  67. package/lib/transform/odata/toFinalBaseType.js +52 -55
  68. package/lib/transform/transformUtils.js +3 -4
  69. package/package.json +3 -3
  70. package/doc/CHANGELOG_BETA.md +0 -464
  71. package/doc/CHANGELOG_DEPRECATED.md +0 -235
@@ -75,7 +75,6 @@ const $location = Symbol.for( 'cds.$location' );
75
75
  const $inferred = Symbol.for( 'cds.$inferred' );
76
76
 
77
77
  // TODO: make this part of specExpected in shared.js
78
- // (standard: not possible on last if !ref.$expected → ref.scope: '$exists')
79
78
  const expWithFilter = [ 'from', 'expand', 'inline' ];
80
79
 
81
80
  // Export function of this file. Resolve type references in augmented CSN
@@ -1557,16 +1556,6 @@ function resolve( model ) {
1557
1556
  }
1558
1557
 
1559
1558
  function resolveExprPath( expr, expected, user ) {
1560
- // TODO: re-think this $expected: 'exists' thing
1561
- if (expr.$expected === 'exists') {
1562
- if (expected !== 'annotation') { // `exists e[…]` allowed in annotation expressions
1563
- error( 'expr-unexpected-exists', [ expr.location, user ], {},
1564
- 'An EXISTS predicate is not expected here' );
1565
- }
1566
- // We complain about the EXISTS before, as EXISTS subquery is also not supported
1567
- // TODO: location of EXISTS, TODO: really do this in define.js
1568
- expr.$expected = 'approved-exists'; // only complain once
1569
- }
1570
1559
  const ref = resolvePath( expr, expected, user );
1571
1560
 
1572
1561
  if (expected === 'annotation') {
@@ -1588,9 +1577,10 @@ function resolve( model ) {
1588
1577
  last._navigation?.kind === '$tableAlias') // error already reported
1589
1578
  return ref;
1590
1579
 
1591
- if (expr.$expected === 'approved-exists') {
1580
+ if (expr.$syntax === 'after-exists') {
1592
1581
  if (last.where?.args?.length === 0) {
1593
1582
  // at the moment, empty filter is not allowed on last path step
1583
+ // TODO: allow it at some places
1594
1584
  reportUnexpectedArgsAndFilter( last, expected, user, null, 'last-empty-filter' );
1595
1585
  }
1596
1586
  return ref;
@@ -49,6 +49,13 @@ function fns( model ) {
49
49
  dynamic: modelDefinitions,
50
50
  notFound: undefinedDefinition,
51
51
  },
52
+ // scope:'global': for cds.Association and auto-redirected targets
53
+ $global: {
54
+ isMainRef: 'all',
55
+ lexical: null,
56
+ dynamic: modelDefinitions,
57
+ notFound: undefinedDefinition,
58
+ },
52
59
  // only used for the main annotate/extend statements, not inner ones:
53
60
  annotate: {
54
61
  isMainRef: 'all',
@@ -57,6 +64,17 @@ function fns( model ) {
57
64
  notFound: undefinedForAnnotate,
58
65
  accept: extendableArtifact,
59
66
  },
67
+ 'annotate-sec': {
68
+ isMainRef: 'all',
69
+ lexical: userBlock,
70
+ dynamic: modelDefinitions,
71
+ notFound: undefinedDefinition,
72
+ messageMap: {
73
+ 'ref-undefined-art': 'ext-undefined-art-sec',
74
+ 'ref-undefined-def': 'ext-undefined-def-sec',
75
+ },
76
+ accept: extendableArtifact,
77
+ },
60
78
  extend: {
61
79
  isMainRef: 'no-generated',
62
80
  lexical: userBlock,
@@ -85,7 +103,7 @@ function fns( model ) {
85
103
  accept: acceptEntity,
86
104
  noDep: true,
87
105
  // special `scope`s for auto-redirections:
88
- global: () => ({ isMainRef: 'all', dynamic: modelDefinitions }),
106
+ global: () => '$global',
89
107
  },
90
108
  targetAspect: {
91
109
  isMainRef: 'no-autoexposed',
@@ -102,6 +120,7 @@ function fns( model ) {
102
120
  notFound: undefinedDefinition,
103
121
  accept: acceptQuerySource,
104
122
  noDep: '', // dependency special for from
123
+ args: () => 'from-args',
105
124
  },
106
125
  type: {
107
126
  isMainRef: 'no-autoexposed',
@@ -112,11 +131,11 @@ function fns( model ) {
112
131
  accept: acceptTypeOrElement,
113
132
  // special `scope`s for CDL parser - TYPE OF (TODO generated?), cds.Association:
114
133
  typeOf: typeOfSemantics,
115
- global: () => ({
116
- isMainRef: 'no-autoexposed',
117
- dynamic: modelDefinitions,
118
- navigation: staticTarget, // TODO: Object.assign() with main
119
- }),
134
+ global: () => '$global', // TODO: do we need `navigation: staticTarget`?
135
+ },
136
+ $typeOf: {
137
+ dynamic: typeOfParentDict,
138
+ navigation: staticTarget,
120
139
  },
121
140
  // element references without lexical scope (except $self/$projection): -----
122
141
  targetElement: {
@@ -125,22 +144,24 @@ function fns( model ) {
125
144
  dynamic: targetElements,
126
145
  navigation: targetNavigation,
127
146
  notFound: undefinedTargetElement,
128
- param: paramSemantics,
147
+ param: () => '$scopePar',
129
148
  },
130
149
  filter: {
131
150
  lexical: justDollarAliases,
132
151
  dollar: true,
133
152
  dynamic: targetElements,
134
153
  notFound: undefinedTargetElement,
135
- param: paramSemantics,
154
+ param: () => '$scopePar',
155
+ nestedColumn: () => 'filter',
136
156
  },
137
- 'calc-filter': {
157
+ 'calc-filter': { // TODO: what is so special about this?
138
158
  lexical: justDollarAliases,
139
159
  dollar: true,
140
160
  dynamic: targetElements,
141
161
  navigation: calcElemNavigation,
142
162
  notFound: undefinedTargetElement,
143
163
  param: paramUnsupported,
164
+ filter: () => 'calc-filter',
144
165
  },
145
166
  default: {
146
167
  lexical: null,
@@ -154,7 +175,7 @@ function fns( model ) {
154
175
  dollar: true,
155
176
  dynamic: () => Object.create( null ),
156
177
  notFound: undefinedVariable,
157
- param: paramSemantics,
178
+ param: () => '$scopePar',
158
179
  },
159
180
  'limit-offset': 'limit-rows',
160
181
  // general element / variable references --------------------------------------
@@ -164,7 +185,7 @@ function fns( model ) {
164
185
  dynamic: combinedSourcesOrParentElements,
165
186
  notFound: undefinedSourceElement,
166
187
  check: checkRefInQuery,
167
- param: paramSemantics,
188
+ param: () => '$scopePar',
168
189
  },
169
190
  having: 'where',
170
191
  groupBy: 'where',
@@ -174,22 +195,15 @@ function fns( model ) {
174
195
  dynamic: combinedSourcesOrParentElements,
175
196
  notFound: undefinedSourceElement,
176
197
  check: checkColumnRef,
177
- param: paramSemantics,
178
- nestedColumn: () => ({ // in expand and inline
179
- lexical: justDollarAliases,
180
- dollar: true,
181
- dynamic: nestedElements,
182
- notFound: undefinedNestedElement,
183
- check: checkColumnRef,
184
- param: paramSemantics,
185
- }),
198
+ param: () => '$scopePar',
199
+ nestedColumn: () => '$srcRefInNestedColumn',
186
200
  },
187
201
  'from-args': {
188
202
  lexical: null,
189
203
  dollar: true,
190
204
  dynamic: () => Object.create( null ),
191
205
  notFound: undefinedVariable,
192
- param: paramSemantics,
206
+ param: () => '$scopePar',
193
207
  },
194
208
  calc: {
195
209
  lexical: justDollarAliases,
@@ -198,6 +212,7 @@ function fns( model ) {
198
212
  navigation: calcElemNavigation,
199
213
  notFound: undefinedParentElement,
200
214
  param: paramUnsupported,
215
+ filter: () => 'calc-filter',
201
216
  },
202
217
  'join-on': {
203
218
  lexical: tableAliasesAndSelf,
@@ -205,7 +220,7 @@ function fns( model ) {
205
220
  dynamic: combinedSourcesOrParentElements,
206
221
  rejectRoot: rejectOwnExceptVisibleAliases,
207
222
  notFound: undefinedSourceElement,
208
- param: paramSemantics,
223
+ param: () => '$scopePar',
209
224
  },
210
225
  on: { // unmanaged assoc: outside query, redirected or new assoc in column
211
226
  lexical: justDollarAliases,
@@ -216,15 +231,8 @@ function fns( model ) {
216
231
  accept: acceptElemOrVarOrSelf,
217
232
  check: checkAssocOn,
218
233
  param: paramUnsupported,
219
- nestedColumn: () => ({ // in expand and inline
220
- lexical: justDollarAliases,
221
- dollar: true,
222
- dynamic: parentElements,
223
- navigation: assocOnNavigation,
224
- notFound: undefinedParentElement,
225
- rewriteProjectionToSelf: true,
226
- }),
227
234
  rewriteProjectionToSelf: true,
235
+ nestedColumn: () => '$projRefInNestedColumn',
228
236
  },
229
237
  'mixin-on': {
230
238
  lexical: tableAliasesAndSelf,
@@ -234,15 +242,16 @@ function fns( model ) {
234
242
  notFound: undefinedSourceElement,
235
243
  accept: acceptElemOrVarOrSelf,
236
244
  check: checkAssocOn,
237
- param: paramSemantics, // TODO: check that assocs containing param in ON is not published
245
+ param: () => '$scopePar', // TODO: check that assocs containing param in ON is not published
238
246
  },
247
+ 'rewrite-on': {}, // only for traversal when rewriting on condition
239
248
  'orderBy-ref': {
240
249
  lexical: tableAliasesAndSelf,
241
250
  dollar: true,
242
251
  dynamic: parentElements,
243
252
  notFound: undefinedOrderByElement,
244
253
  check: checkOrderByRef,
245
- param: paramSemantics,
254
+ param: () => '$scopePar',
246
255
  },
247
256
  'orderBy-expr': {
248
257
  lexical: tableAliasesAndSelf,
@@ -250,7 +259,7 @@ function fns( model ) {
250
259
  dynamic: combinedSourcesOrParentElements,
251
260
  notFound: undefinedSourceElement,
252
261
  check: checkRefInQuery,
253
- param: paramSemantics,
262
+ param: () => '$scopePar',
254
263
  },
255
264
  'orderBy-set-ref': {
256
265
  lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
@@ -259,7 +268,7 @@ function fns( model ) {
259
268
  rejectRoot: rejectOwnAliasesAndMixins,
260
269
  notFound: undefinedParentElement,
261
270
  check: checkOrderByRef,
262
- param: paramSemantics,
271
+ param: () => '$scopePar',
263
272
  },
264
273
  'orderBy-set-expr': {
265
274
  lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
@@ -268,7 +277,7 @@ function fns( model ) {
268
277
  rejectRoot: rejectAllOwn,
269
278
  notFound: undefinedVariable,
270
279
  check: checkRefInQuery,
271
- param: paramSemantics,
280
+ param: () => '$scopePar',
272
281
  },
273
282
  annotation: { // annotation assignments
274
283
  lexical: justDollarAliases,
@@ -277,19 +286,14 @@ function fns( model ) {
277
286
  navigation: assocOnNavigation,
278
287
  noDep: true,
279
288
  notFound: undefinedParentElement,
289
+ accept: acceptElemOrAnyVar,
290
+ variableFilter: (dict => dict),
280
291
  messageMap: {
281
292
  'ref-undefined-element': 'anno-undefined-element',
282
293
  'ref-undefined-param': 'anno-undefined-param',
283
294
  },
284
- param: paramSemantics,
285
- nestedColumn: () => ({
286
- lexical: justDollarAliases,
287
- dollar: true,
288
- dynamic: parentElements,
289
- navigation: assocOnNavigation,
290
- notFound: undefinedParentElement,
291
- rewriteProjectionToSelf: true,
292
- }),
295
+ param: () => '$annotationScopePar',
296
+ nestedColumn: () => '$projRefInNestedColumn',
293
297
  },
294
298
  // TODO: introduce some kind of inheritance
295
299
  // used by xpr-rewrite.js to resolve rewritten path roots.
@@ -300,17 +304,36 @@ function fns( model ) {
300
304
  navigation: assocOnNavigation,
301
305
  noDep: true,
302
306
  notFound: null, // no error, just falsy links
303
- param: paramSemantics,
304
- nestedColumn: () => ({
305
- lexical: justDollarAliases,
306
- dollar: true,
307
- dynamic: parentElements,
308
- navigation: assocOnNavigation,
309
- notFound: undefinedParentElement,
310
- rewriteProjectionToSelf: true,
311
- }),
307
+ accept: acceptElemOrAnyVar,
308
+ param: () => '$scopePar',
309
+ nestedColumn: () => '$projRefInNestedColumn',
310
+ },
311
+ $scopePar: {
312
+ dynamic: artifactParams,
313
+ notFound: undefinedParam,
314
+ },
315
+ $annotationScopePar: {
316
+ messageMap: {
317
+ 'ref-undefined-element': 'anno-undefined-element',
318
+ 'ref-undefined-param': 'anno-undefined-param',
319
+ },
320
+ dynamic: artifactParams,
321
+ notFound: undefinedParam,
322
+ },
323
+ // for `nestedColumn`, these two will be merged with base semantics:
324
+ $projRefInNestedColumn: { // for assoc-`on` and annotations
325
+ lexical: justDollarAliases,
326
+ dynamic: parentElements,
327
+ navigation: assocOnNavigation, // like std `environment`, but no dependency
328
+ rewriteProjectionToSelf: true,
329
+ },
330
+ $srcRefInNestedColumn: { // for column refs
331
+ lexical: justDollarAliases,
332
+ dollar: true,
333
+ dynamic: nestedElements,
334
+ navigation: environment,
335
+ notFound: undefinedNestedElement,
312
336
  },
313
- //
314
337
  };
315
338
 
316
339
  Object.assign( model.$functions, {
@@ -371,7 +394,7 @@ function fns( model ) {
371
394
  }
372
395
  if (expr.args) {
373
396
  const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
374
- for (const arg of args ) {
397
+ for (const arg of args) {
375
398
  if (traverseExpr( arg, exprCtx, user, callback ) === traverseExpr.STOP)
376
399
  return traverseExpr.STOP;
377
400
  }
@@ -389,16 +412,13 @@ function fns( model ) {
389
412
  const exit = callback( step, exprCtx, user );
390
413
  if (exit === traverseExpr.STOP)
391
414
  return true;
392
- if (step.where && exit !== traverseExpr.SKIP &&
393
- traverseExpr( step.where,
394
- // TODO: use property in fn dictionary above
395
- ( exprCtx === 'calc' || exprCtx === 'calc-filter'
396
- ? 'calc-filter'
397
- : 'filter' ),
398
- step, callback ) === traverseExpr.STOP)
399
- return true;
415
+ if (step.where && exit !== traverseExpr.SKIP) {
416
+ const ctx = referenceSemantics[exprCtx].filter?.() || 'filter';
417
+ if (traverseExpr( step.where, ctx, step, callback ) === traverseExpr.STOP)
418
+ return true;
419
+ }
400
420
  if (step.args) {
401
- const ctx = (exprCtx === 'from') ? 'from-args' : exprCtx;
421
+ const ctx = referenceSemantics[exprCtx].args?.() || exprCtx;
402
422
  const args = Array.isArray( step.args ) ? step.args : Object.values( step.args );
403
423
  // TODO: there should be no array `args` on path item
404
424
  for (const arg of args) {
@@ -412,7 +432,7 @@ function fns( model ) {
412
432
  // Special expression traversal function for `resolveExpr`. Let's see
413
433
  // later whether we can use this version as the general one.
414
434
  // If we continue to have separate ones, remove the STOP stuff – it is not
415
- // needed for `resolveExpr`.
435
+ // needed for `resolveExpr`; SKIP is used, though.
416
436
 
417
437
  function traverseTypedExpr( expr, exprCtx, user, type, callback ) {
418
438
  if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
@@ -565,16 +585,13 @@ function fns( model ) {
565
585
  const exit = callback( step, exprCtx, user, null );
566
586
  if (exit === traverseExpr.STOP)
567
587
  return true;
568
- if (step.where && exit !== traverseExpr.SKIP &&
569
- traverseTypedExpr( step.where,
570
- // TODO: use property in fn dictionary above
571
- ( exprCtx === 'calc' || exprCtx === 'calc-filter'
572
- ? 'calc-filter'
573
- : 'filter' ),
574
- step, null, callback ) === traverseExpr.STOP)
575
- return true;
588
+ if (step.where && exit !== traverseExpr.SKIP) {
589
+ const ctx = referenceSemantics[exprCtx].filter?.() || 'filter';
590
+ if (traverseTypedExpr( step.where, ctx, step, null, callback ) === traverseExpr.STOP)
591
+ return true;
592
+ }
576
593
  if (step.args) {
577
- const ctx = (exprCtx === 'from') ? 'from-args' : exprCtx;
594
+ const ctx = referenceSemantics[exprCtx].args?.() || exprCtx;
578
595
  const args = Array.isArray( step.args ) ? step.args : Object.values( step.args );
579
596
  // TODO: there should be no array `args` on path item
580
597
  for (const arg of args) {
@@ -637,7 +654,6 @@ function fns( model ) {
637
654
 
638
655
  const s = referenceSemantics[expected];
639
656
  const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
640
- semantics.name = expected;
641
657
 
642
658
  const r = getPathRoot( ref, semantics, origUser );
643
659
  const root = r && acceptPathRoot( r, ref, semantics, origUser );
@@ -778,19 +794,26 @@ function fns( model ) {
778
794
  ruser = ruser._outer._outer;
779
795
  }
780
796
 
781
- // Handle expand/inline, `type of`, :param, global (internally for CDL):
797
+ // Handle expand/inline before `type of`, :param, global (internally for CDL):
782
798
  if (user._columnParent && !semantics.isMainRef) { // in expand/inline
783
- const { name } = semantics;
784
- semantics = semantics.nestedColumn();
785
- semantics.name = name;
799
+ const func = semantics.nestedColumn;
800
+ if (!func)
801
+ throw new CompilerAssertion( 'Unexpected ref context in nested column' );
802
+ const ctx = func();
803
+ semantics = (typeof ctx === 'string')
804
+ ? ({ ...semantics, ...referenceSemantics[ctx] })
805
+ : ctx;
786
806
  }
787
807
  if (typeof scope === 'string') { // typeOf, param, global
788
- semantics = semantics?.[scope] && semantics[scope]( ruser, path, location, semantics );
789
- if (!semantics) {
790
- if (semantics == null)
791
- throw new CompilerAssertion( `Scope ${ scope } is not expected here` );
808
+ const func = semantics[scope] || scope === 'param' && paramUnsupported;
809
+ // 'param' is a user scope → useful default (error msg ref-unexpected-param)
810
+ // 'global' and 'typeOf' are internal scopes of the compiler → dump if not provided
811
+ if (!func)
812
+ throw new CompilerAssertion( `Unexpected scope ${ scope }, no handler defined in context` );
813
+ const ctx = func( ruser, path, location, semantics );
814
+ semantics = (typeof ctx === 'string') ? referenceSemantics[ctx] : ctx;
815
+ if (!semantics)
792
816
  return setArtifactLink( head, null );
793
- }
794
817
  }
795
818
  const valid = [];
796
819
 
@@ -825,7 +848,8 @@ function fns( model ) {
825
848
  valid.push( dynamicDict );
826
849
  }
827
850
  else {
828
- valid.push( removeInvalidMagicVariables( model.$magicVariables.elements, semantics ),
851
+ const filterFn = semantics.variableFilter || removeRestrictedVariables;
852
+ valid.push( filterFn( model.$magicVariables.elements ),
829
853
  removeDollarNames( dynamicDict ) );
830
854
  }
831
855
  // TODO: streamline function arguments (probably: user, path, semantics )
@@ -1020,11 +1044,17 @@ function fns( model ) {
1020
1044
  return art;
1021
1045
  }
1022
1046
  case 'builtin': {
1047
+ // TODO: use properties in builtins
1023
1048
  if (art.name.id === '$at') {
1024
1049
  message( 'ref-deprecated-variable', [ head.location, user ],
1025
1050
  { code: '$at', newcode: '$valid' },
1026
1051
  '$(CODE) is deprecated; use $(NEWCODE) instead' );
1027
1052
  }
1053
+ else if (art.$restricted && semantics.accept !== acceptElemOrAnyVar) {
1054
+ error( 'ref-unexpected-var', [ head.location, user ],
1055
+ { '#': 'annotation', name: head.id } );
1056
+ return null; // no further error on `unknown` for $draft.unknown
1057
+ }
1028
1058
  return art;
1029
1059
  }
1030
1060
  default:
@@ -1059,20 +1089,12 @@ function fns( model ) {
1059
1089
  while (struct.kind === 'element')
1060
1090
  struct = struct._parent;
1061
1091
  if (struct === user._main && struct.kind !== 'annotation')
1062
- return { dynamic: typeOfParentDict, navigation: staticTarget };
1092
+ return '$typeOf';
1063
1093
  error( 'type-unexpected-typeof', [ head.location, user ],
1064
1094
  { keyword: 'type of', '#': struct.kind } );
1065
1095
  return false;
1066
1096
  }
1067
1097
 
1068
- function paramSemantics( _user, _path, _loction, semantics ) {
1069
- return {
1070
- messageMap: semantics.messageMap,
1071
- dynamic: artifactParams,
1072
- notFound: undefinedParam,
1073
- };
1074
- }
1075
-
1076
1098
  function paramUnsupported( user, _path, location ) {
1077
1099
  error( 'ref-unexpected-scope', [ location, user ], // TODO: ref-unexpected-param
1078
1100
  // why an extra text for calculated elements? or separate for all?
@@ -1273,11 +1295,11 @@ function fns( model ) {
1273
1295
 
1274
1296
  // Functions called via semantics.notFound: -----------------------------------
1275
1297
 
1276
- function undefinedDefinition( user, item, valid, _dict, prev ) {
1298
+ function undefinedDefinition( user, item, valid, _dict, prev, _path, semantics ) {
1277
1299
  // in a CSN source or for `using`, only one env was tested (valid.length 1) :
1278
1300
  const art = (!prev) ? item.id : searchName( prev, item.id, 'absolute' );
1279
1301
  signalNotFound( (valid.length > 1 ? 'ref-undefined-art' : 'ref-undefined-def'),
1280
- [ item.location, user ], valid, { art } );
1302
+ [ item.location, user ], valid, { art }, semantics );
1281
1303
  // TODO: improve text, use text variant for: "or builtin" or "definitions" or none
1282
1304
  }
1283
1305
 
@@ -1430,8 +1452,7 @@ function fns( model ) {
1430
1452
  while ((head = head?._parent) && head.kind === 'builtin')
1431
1453
  id = `${ head.name.id }.${ id }`;
1432
1454
  const msgId = (art.$uncheckedElements) ? 'ref-unknown-var' : 'ref-undefined-var';
1433
- signalNotFound( msgId, [ item.location, user ],
1434
- removeInvalidMagicVariables( valid, semantics ), { id }, semantics );
1455
+ signalNotFound( msgId, [ item.location, user ], valid, { id }, semantics );
1435
1456
  }
1436
1457
  else if (art.kind === 'aspect' && !art.name) { // anonymous target aspect - TODO: still?
1437
1458
  signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
@@ -1513,16 +1534,17 @@ function fns( model ) {
1513
1534
  : acceptElemOrVar( art, user, ref );
1514
1535
  }
1515
1536
 
1516
- function acceptElemOrVar( art, user, ref, semantics ) {
1537
+ function acceptElemOrVar( art, user, ref ) {
1538
+ if (art.kind !== 'builtin' || !art.$restricted)
1539
+ return acceptElemOrAnyVar( art, user, ref );
1540
+ error( 'ref-unexpected-var', [ ref.location, user ],
1541
+ { '#': 'annotation', name: pathName( ref.path ) } );
1542
+ return null;
1543
+ }
1544
+
1545
+ function acceptElemOrAnyVar( art, user, ref ) {
1517
1546
  const { path } = ref;
1518
1547
  if (art.kind === 'builtin') {
1519
- if (art.$onlyInExprCtx && !art.$onlyInExprCtx.includes(semantics.name)) {
1520
- error( 'ref-unexpected-var', [ ref.location, user ], {
1521
- '#': art.$onlyInExprCtx[0], name: pathName( path ),
1522
- });
1523
- return null;
1524
- }
1525
-
1526
1548
  if (user.expand || user.inline) {
1527
1549
  const location = (user.expand || user.inline)[$location];
1528
1550
  const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
@@ -1866,8 +1888,9 @@ function fns( model ) {
1866
1888
  }
1867
1889
  const index = userTargetElementPathIndex( user, path );
1868
1890
  checkOnlyForeignKeyNavigation( user, path, index );
1891
+ // TODO: did we check for no filter/args, no `exists` etc?
1869
1892
  const last = path[path.length - 1];
1870
- if (!last.where && ref._artifact?.on) { // filter already complained about
1893
+ if (!last.where && ref._artifact?.on) { // filter already complained about - TODO: where?
1871
1894
  const target = index > 0 && index < path.length && ref._artifact?.target;
1872
1895
  const msg = (target?._artifact === user._main) ? 'self' : 'unmanaged';
1873
1896
  error( 'ref-unexpected-assoc', [ last.location, user ],
@@ -2006,7 +2029,7 @@ function fns( model ) {
2006
2029
  }
2007
2030
 
2008
2031
  function checkNoUnmanaged( ref, user, self, messageVariant = 'unmanaged' ) {
2009
- if (ref._artifact?.on && !ref.$expected) {
2032
+ if (ref._artifact?.on && ref.$syntax !== 'after-exists') {
2010
2033
  const { path } = ref;
2011
2034
  const last = path[path.length - 1];
2012
2035
  if (self && last.where) // already complained about filter
@@ -2035,7 +2058,8 @@ function fns( model ) {
2035
2058
  const err = message( semantics?.messageMap?.[msgId] || msgId, location, textParams );
2036
2059
  if (valid) {
2037
2060
  const user = Array.isArray( location ) && location[1];
2038
- err.validNames = (user && definedViaCdl( user )); // viaCdl -> '.'?
2061
+ // TODO: see TODO missing param `viaCdl` in attachAndEmitValidNames:
2062
+ err.validNames = (user && definedViaCdl( user ));
2039
2063
  valid.reverse();
2040
2064
  attachAndEmitValidNames( err, ...valid );
2041
2065
  }
@@ -2076,6 +2100,8 @@ function fns( model ) {
2076
2100
  const valid = Object.assign( Object.create( null ), ...validDicts );
2077
2101
  msg.validNames = Object.create( null );
2078
2102
  for (const name of Object.keys( valid )) {
2103
+ if (!name)
2104
+ continue;
2079
2105
  const art = valid[name];
2080
2106
  // ignore internal types such as cds.Association, ignore names with dot for
2081
2107
  // CDL references to main artifacts:
@@ -2100,14 +2126,19 @@ function fns( model ) {
2100
2126
  }
2101
2127
  }
2102
2128
 
2103
- function removeInvalidMagicVariables( variables, semantics ) {
2104
- if (Array.isArray(variables))
2105
- return variables.map(variable => removeInvalidMagicVariables( variable, semantics ));
2106
-
2129
+ /**
2130
+ * Filter out restricted variables from a dictionary of variables.
2131
+ * Variables with the `$restricted` property set to true are excluded.
2132
+ *
2133
+ * @param {object} variables - Dictionary of variable objects
2134
+ * @returns {object} New dictionary with restricted variables removed
2135
+ */
2136
+ function removeRestrictedVariables( variables ) {
2137
+ // TODO: we could also do something with `deprecated`, $requiresBetaFlag, …
2107
2138
  const valid = Object.create(null);
2108
2139
  for (const name in variables) {
2109
2140
  const variable = variables[name];
2110
- if (!variable.$onlyInExprCtx || variable.$onlyInExprCtx.includes( semantics.name ))
2141
+ if (!variable.$restricted)
2111
2142
  valid[name] = variable;
2112
2143
  }
2113
2144
  return valid;
@@ -5,7 +5,6 @@
5
5
  const {
6
6
  forEachGeneric,
7
7
  forEachInOrder,
8
- isBetaEnabled,
9
8
  } = require('../base/model');
10
9
  const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
11
10
 
@@ -138,10 +137,10 @@ function tweakAssocs( model ) {
138
137
 
139
138
  function handleQueryElements( column ) {
140
139
  rewriteAssociationCheck( column );
141
- if (!isBetaEnabled( model.options, '$calcForDraft' ))
142
- return;
140
+ if (model.options.noDollarCalc || column._columnParent)
141
+ return; // no $calc supported with inline
143
142
  const { value } = column; // `value` = column expression
144
- if (!value || !value.args && !value.suffix)
143
+ if (!value || value.path) // not with references
145
144
  return;
146
145
  // TODO: what about non-simple refs (assocs, even with filter/args)?
147
146
 
@@ -457,7 +456,7 @@ function tweakAssocs( model ) {
457
456
  return;
458
457
 
459
458
  if (elem._parent?.kind === 'element') {
460
- // managed association as sub element not supported yet
459
+ // unmanaged association as sub element not supported yet
461
460
  // TODO: Only report once for multi-include chains, see
462
461
  // Associations/SubElements/UnmanagedInSubElement.err.cds
463
462
  error( 'type-unsupported-rewrite', [ elem.location, elem ], { '#': 'sub-element' } );
@@ -946,6 +945,8 @@ function tweakAssocs( model ) {
946
945
  * for item.
947
946
  */
948
947
  function rewriteColumnPath( ref, column ) {
948
+ if (ref.query)
949
+ return traverseExpr.STOP; // sub queries rewrite not supported
949
950
  if (!ref._artifact)
950
951
  return null;
951
952
  const root = ref.path?.[0];
@@ -1010,9 +1011,12 @@ function tweakAssocs( model ) {
1010
1011
  state = setArtifactLink( i, elem );
1011
1012
  }
1012
1013
  else {
1013
- state = rewriteItem( state, i, assoc );
1014
- if (!state || state === true)
1015
- break;
1014
+ state = rewriteItem( state, i, assoc, !location );
1015
+ if (state && state !== true)
1016
+ continue;
1017
+ if (!state && !location)
1018
+ return true;
1019
+ break;
1016
1020
  }
1017
1021
  }
1018
1022
  if (state !== true)
@@ -1032,7 +1036,7 @@ function tweakAssocs( model ) {
1032
1036
  * @param item Path segment to rewrite.
1033
1037
  * @param assoc Published association of query.
1034
1038
  */
1035
- function rewriteItem( elem, item, assoc ) {
1039
+ function rewriteItem( elem, item, assoc, noError ) {
1036
1040
  if (!elem._redirected)
1037
1041
  return true;
1038
1042
  let name = item.id;
@@ -1055,7 +1059,7 @@ function tweakAssocs( model ) {
1055
1059
  if (env?.target)
1056
1060
  env = env.target._artifact?._effectiveType;
1057
1061
  const found = setArtifactLink( item, env?.elements?.[name] );
1058
- if (found)
1062
+ if (found || noError)
1059
1063
  return found;
1060
1064
 
1061
1065
  const isExplicit = elem.target && !elem.target.$inferred;