@sap/cds-compiler 4.0.0 → 4.1.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 (85) hide show
  1. package/CHANGELOG.md +115 -5
  2. package/bin/cdsc.js +12 -12
  3. package/doc/CHANGELOG_BETA.md +11 -0
  4. package/lib/api/main.js +60 -12
  5. package/lib/api/validate.js +1 -1
  6. package/lib/base/location.js +6 -7
  7. package/lib/base/message-registry.js +84 -38
  8. package/lib/base/messages.js +11 -10
  9. package/lib/base/model.js +6 -2
  10. package/lib/checks/defaultValues.js +6 -6
  11. package/lib/checks/foreignKeys.js +0 -5
  12. package/lib/checks/onConditions.js +17 -12
  13. package/lib/checks/queryNoDbArtifacts.js +132 -72
  14. package/lib/checks/sql-snippets.js +15 -4
  15. package/lib/checks/types.js +3 -3
  16. package/lib/checks/utils.js +1 -1
  17. package/lib/compiler/assert-consistency.js +44 -16
  18. package/lib/compiler/base.js +1 -0
  19. package/lib/compiler/builtins.js +7 -8
  20. package/lib/compiler/checks.js +274 -197
  21. package/lib/compiler/classes.js +62 -0
  22. package/lib/compiler/cycle-detector.js +3 -3
  23. package/lib/compiler/define.js +63 -50
  24. package/lib/compiler/extend.js +38 -20
  25. package/lib/compiler/finalize-parse-cdl.js +2 -1
  26. package/lib/compiler/generate.js +0 -8
  27. package/lib/compiler/index.js +9 -7
  28. package/lib/compiler/kick-start.js +2 -0
  29. package/lib/compiler/populate.js +139 -110
  30. package/lib/compiler/propagator.js +4 -3
  31. package/lib/compiler/resolve.js +157 -126
  32. package/lib/compiler/shared.js +706 -404
  33. package/lib/compiler/tweak-assocs.js +21 -10
  34. package/lib/compiler/utils.js +228 -36
  35. package/lib/edm/annotations/genericTranslation.js +30 -2
  36. package/lib/edm/edm.js +4 -1
  37. package/lib/edm/edmPreprocessor.js +12 -5
  38. package/lib/edm/edmUtils.js +2 -4
  39. package/lib/gen/Dictionary.json +34 -10
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +1 -1
  42. package/lib/gen/languageParser.js +3987 -3963
  43. package/lib/json/from-csn.js +43 -47
  44. package/lib/json/to-csn.js +11 -11
  45. package/lib/language/antlrParser.js +2 -1
  46. package/lib/language/genericAntlrParser.js +52 -43
  47. package/lib/language/language.g4 +59 -59
  48. package/lib/language/multiLineStringParser.js +2 -0
  49. package/lib/main.d.ts +5 -0
  50. package/lib/model/csnRefs.js +37 -19
  51. package/lib/model/csnUtils.js +20 -16
  52. package/lib/model/revealInternalProperties.js +29 -21
  53. package/lib/model/sortViews.js +4 -2
  54. package/lib/modelCompare/compare.js +112 -39
  55. package/lib/modelCompare/utils/filter.js +54 -24
  56. package/lib/optionProcessor.js +6 -6
  57. package/lib/render/manageConstraints.js +20 -17
  58. package/lib/render/toCdl.js +34 -20
  59. package/lib/render/toHdbcds.js +2 -2
  60. package/lib/render/toRename.js +4 -9
  61. package/lib/render/toSql.js +77 -26
  62. package/lib/render/utils/common.js +3 -3
  63. package/lib/render/utils/unique.js +52 -0
  64. package/lib/transform/db/applyTransformations.js +61 -20
  65. package/lib/transform/db/assertUnique.js +7 -8
  66. package/lib/transform/db/associations.js +2 -2
  67. package/lib/transform/db/cdsPersistence.js +8 -8
  68. package/lib/transform/db/expansion.js +17 -21
  69. package/lib/transform/db/flattening.js +23 -23
  70. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  71. package/lib/transform/db/temporal.js +1 -1
  72. package/lib/transform/db/transformExists.js +8 -7
  73. package/lib/transform/db/views.js +73 -33
  74. package/lib/transform/draft/db.js +11 -9
  75. package/lib/transform/draft/odata.js +1 -1
  76. package/lib/transform/{forOdataNew.js → forOdata.js} +56 -42
  77. package/lib/transform/forRelationalDB.js +69 -75
  78. package/lib/transform/localized.js +6 -5
  79. package/lib/transform/odata/toFinalBaseType.js +3 -3
  80. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +4 -101
  81. package/lib/transform/translateAssocsToJoins.js +14 -28
  82. package/package.json +1 -1
  83. package/share/messages/check-proper-type-of.md +1 -1
  84. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  85. package/share/messages/message-explanations.json +1 -1
@@ -13,16 +13,23 @@ const {
13
13
  pathName,
14
14
  userQuery,
15
15
  definedViaCdl,
16
+ isDirectComposition,
17
+ pathStartsWithSelf,
18
+ columnRefStartsWithSelf,
19
+ isAssocToPrimaryKeys,
20
+ artifactRefLocation,
16
21
  } = require('./utils');
17
22
 
23
+ const $inferred = Symbol.for('cds.$inferred');
24
+ const $location = Symbol.for('cds.$location');
25
+
18
26
  /**
19
27
  * Main export function of this file. Attach "resolve" functions shared for phase
20
28
  * "define" and "resolve" to `model.$functions`, where argument `model` is the XSN.
21
29
  *
22
30
  * Before calling `resolvePath`, make sure that the following function
23
31
  * in model.$function is set:
24
- * - `navigationEnv`: a function which returns the search environment defined by
25
- * its argument, e.g. a function which returns the dictionary of subartifacts.
32
+ * - `effectiveType`
26
33
  *
27
34
  * @param {XSN.Model} model
28
35
  */
@@ -30,7 +37,7 @@ const {
30
37
  function fns( model ) {
31
38
  const { options } = model;
32
39
  const {
33
- info, warning, error, message,
40
+ info, error, warning, message,
34
41
  } = model.$messageFunctions;
35
42
  const Functions = model.$functions;
36
43
 
@@ -59,53 +66,68 @@ function fns( model ) {
59
66
  isMainRef: 'all',
60
67
  lexical: userBlock,
61
68
  dynamic: modelDefinitions,
62
- notFound: false, // without message
69
+ notFound: () => null, // without message
63
70
  },
64
71
  include: {
65
72
  isMainRef: 'no-generated',
66
73
  lexical: userBlock,
67
74
  dynamic: modelBuiltinsOrDefinitions,
68
75
  notFound: undefinedDefinition,
76
+ accept: acceptStructOrBare,
77
+ },
78
+ _include: { // cyclic include: no accept
79
+ isMainRef: 'no-generated',
80
+ lexical: userBlock,
81
+ dynamic: modelBuiltinsOrDefinitions,
82
+ notFound: undefinedDefinition,
69
83
  },
70
- viewInclude: 'include', // TODO: do differently
71
84
  target: {
72
85
  isMainRef: 'no-autoexposed',
73
86
  lexical: userBlock,
74
87
  dynamic: modelBuiltinsOrDefinitions,
75
88
  notFound: undefinedDefinition,
76
- // special `scope`s for redirections:
89
+ accept: acceptEntity,
90
+ noDep: true,
91
+ // special `scope`s for auto-redirections:
77
92
  global: () => ({ isMainRef: 'all', dynamic: modelDefinitions }),
78
93
  },
79
- targetAspect: { // TODO: do differently
94
+ targetAspect: {
80
95
  isMainRef: 'no-autoexposed',
81
96
  lexical: userBlock,
82
97
  dynamic: modelBuiltinsOrDefinitions,
83
98
  notFound: undefinedDefinition,
99
+ accept: acceptAspect,
84
100
  },
85
101
  from: {
86
102
  isMainRef: 'no-autoexposed',
87
103
  lexical: userBlock,
88
104
  dynamic: modelBuiltinsOrDefinitions,
89
- notFound: undefinedDefinition,
90
105
  navigation: environment,
106
+ notFound: undefinedDefinition,
107
+ accept: acceptEntityOrAssoc,
108
+ noDep: '', // dependency special for from
91
109
  },
92
110
  type: {
93
111
  isMainRef: 'no-autoexposed',
94
112
  lexical: userBlock,
95
113
  dynamic: modelBuiltinsOrDefinitions,
114
+ navigation: staticTarget,
96
115
  notFound: undefinedDefinition,
97
- navigation: targetAspectOnly,
116
+ accept: acceptTypeOrElement,
98
117
  // special `scope`s for CDL parser - TYPE OF (TODO generated?), cds.Association:
99
118
  typeOf: typeOfSemantics,
100
- global: () => ({ isMainRef: 'no-autoexposed', dynamic: modelDefinitions }),
119
+ global: () => ({
120
+ isMainRef: 'no-autoexposed',
121
+ dynamic: modelDefinitions,
122
+ navigation: staticTarget, // TODO: Object.assign() with main
123
+ }),
101
124
  },
102
- actionParamType: 'type', // TODO: do differently
103
- eventType: 'type', // TODO: do differently
104
125
  // element references without lexical scope (except $self/$projection): -----
105
126
  targetElement: {
106
127
  lexical: null,
107
128
  dollar: false,
108
129
  dynamic: targetElements,
130
+ navigation: targetNavigation,
109
131
  notFound: undefinedTargetElement,
110
132
  param: paramSemantics,
111
133
  },
@@ -120,6 +142,7 @@ function fns( model ) {
120
142
  lexical: justDollarSelf,
121
143
  dollar: true,
122
144
  dynamic: targetElements,
145
+ navigation: calcElemNavigation,
123
146
  notFound: undefinedTargetElement,
124
147
  param: paramUnsupported,
125
148
  },
@@ -131,21 +154,33 @@ function fns( model ) {
131
154
  param: paramUnsupported,
132
155
  },
133
156
  // general element references -----------------------------------------------
134
- expr: { // TODO: this is too general -> column
157
+ where: {
135
158
  lexical: tableAliasesIfNotExtendAndSelf,
136
159
  dollar: true,
137
160
  dynamic: combinedSourcesOrParentElements,
138
161
  notFound: undefinedSourceElement,
162
+ check: checkRefInQuery,
163
+ param: paramSemantics,
164
+ },
165
+ having: 'where',
166
+ groupBy: 'where',
167
+ column: {
168
+ lexical: tableAliasesIfNotExtendAndSelf,
169
+ dollar: true,
170
+ dynamic: combinedSourcesOrParentElements,
171
+ notFound: undefinedSourceElement,
172
+ check: checkColumnRef,
139
173
  param: paramSemantics,
140
174
  nestedColumn: () => ({ // in expand and inline
141
175
  lexical: justDollarSelf,
142
176
  dollar: true,
143
177
  dynamic: nestedElements,
144
178
  notFound: undefinedNestedElement,
179
+ check: checkColumnRef,
145
180
  param: paramSemantics,
146
181
  }),
147
182
  },
148
- 'param-only': {
183
+ 'from-args': {
149
184
  lexical: null,
150
185
  dollar: true,
151
186
  dynamic: () => Object.create( null ),
@@ -156,10 +191,11 @@ function fns( model ) {
156
191
  lexical: justDollarSelf,
157
192
  dollar: true,
158
193
  dynamic: parentElements,
194
+ navigation: calcElemNavigation,
159
195
  notFound: undefinedParentElement,
160
196
  param: paramUnsupported,
161
197
  },
162
- joinOn: {
198
+ 'join-on': {
163
199
  lexical: tableAliasesAndSelf,
164
200
  dollar: true,
165
201
  dynamic: combinedSourcesOrParentElements, // TODO: source alone...
@@ -168,256 +204,99 @@ function fns( model ) {
168
204
  },
169
205
  on: { // unmanaged assoc: outside query, redirected or new assoc in column
170
206
  lexical: justDollarSelf,
171
- allowBareSelf: true,
172
207
  dollar: true,
173
208
  dynamic: parentElements,
209
+ navigation: assocOnNavigation,
174
210
  notFound: undefinedParentElement,
211
+ accept: acceptElemOrVarOrSelf,
212
+ check: checkAssocOn,
175
213
  param: paramUnsupported,
176
214
  nestedColumn: () => ({ // in expand and inline
177
215
  lexical: justDollarSelf,
178
216
  dollar: true,
179
217
  dynamic: parentElements,
218
+ navigation: assocOnNavigation,
180
219
  notFound: undefinedParentElement,
181
220
  }),
182
221
  },
183
222
  'mixin-on': {
184
223
  lexical: tableAliasesAndSelf,
185
- allowBareSelf: true,
186
224
  dollar: true,
187
225
  dynamic: combinedSourcesOrParentElements,
226
+ navigation: assocOnNavigation,
188
227
  notFound: undefinedSourceElement,
228
+ accept: acceptElemOrVarOrSelf,
229
+ check: checkAssocOn,
189
230
  param: paramSemantics, // TODO: check that assocs containing param in ON is not published
190
231
  },
191
- 'order-by-ref': {
232
+ 'orderBy-ref': {
192
233
  lexical: tableAliasesAndSelf,
193
234
  dollar: true,
194
235
  dynamic: parentElements,
195
236
  notFound: undefinedOrderByElement,
237
+ check: checkOrderByRef,
196
238
  param: paramSemantics,
197
239
  },
198
- 'order-by-expr': {
240
+ 'orderBy-expr': {
199
241
  lexical: tableAliasesAndSelf,
200
242
  dollar: true,
201
243
  dynamic: combinedSourcesOrParentElements,
202
244
  notFound: undefinedSourceElement,
245
+ check: checkRefInQuery,
203
246
  param: paramSemantics,
204
247
  },
205
- 'order-by-set-ref': {
248
+ 'orderBy-set-ref': {
206
249
  lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
207
250
  dollar: true,
208
251
  dynamic: queryElements,
209
252
  notFound: undefinedParentElement,
253
+ check: checkOrderByRef,
210
254
  param: paramSemantics,
211
255
  },
212
- 'order-by-set-expr': {
256
+ 'orderBy-set-expr': {
213
257
  lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
214
258
  dollar: true,
215
259
  dynamic: () => Object.create( null ),
216
260
  notFound: undefinedVariable,
261
+ check: checkRefInQuery,
217
262
  param: paramSemantics,
218
263
  },
219
264
  };
220
265
 
221
- // TODO: combine envFn and assoc ?
222
- const specExpected = {
223
- using: {}, // for using declaration
224
- // TODO: artifact references ---------------------------------------------
225
- extend: {},
226
- // ref in top-level EXTEND
227
- annotate: {},
228
- _extensions: {},
229
- type: { // TODO: more detailed later (e.g. for enum base type?)
230
- check: checkTypeRef,
231
- expectedMsgId: 'ref-expecting-type',
232
- sloppyMsgId: 'ref-sloppy-type',
233
- },
234
- actionParamType: {
235
- check: checkActionParamTypeRef,
236
- expectedMsgId: 'ref-expecting-action-param-type',
237
- sloppyMsgId: 'ref-sloppy-actionparam-type',
238
- },
239
- eventType: {
240
- check: checkEventTypeRef,
241
- expectedMsgId: 'ref-expecting-event-type',
242
- sloppyMsgId: 'ref-sloppy-event-type',
243
- },
244
- include: {
245
- check: checkIncludesRef,
246
- expectedMsgId: 'ref-expecting-struct',
247
- },
248
- viewInclude: {
249
- check: checkViewIncludesRef,
250
- expectedMsgId: 'ref-expecting-bare-aspect',
251
- },
252
- target: {
253
- check: checkEntityRef,
254
- expectedMsgId: 'ref-expecting-entity',
255
- noDep: true,
256
- },
257
- targetAspect: {
258
- check: checkTargetRef,
259
- expectedMsgId: 'ref-expecting-target',
260
- sloppyMsgId: 'ref-sloppy-target',
261
- noDep: 'only-entity',
262
- },
263
- from: {
264
- check: checkSourceRef,
265
- expectedMsgId: 'ref-expecting-source',
266
- assoc: 'from',
267
- argsSpec: 'expr',
268
- },
269
- // element references ----------------------------------------------------
270
- // TODO: dep for (explicit+implicit!) foreign keys
271
- // TODO: also check that we do not follow associations in foreign key? no args, no filter
272
- targetElement: { assoc: false },
273
- filter: {
274
- escape: 'param',
275
- },
276
- 'calc-filter': {
277
- escape: 'param',
278
- },
279
- default: {
280
- check: checkConstRef,
281
- expectedMsgId: 'ref-expecting-const',
282
- },
283
- expr: {
284
- escape: 'param', assoc: 'nav',
285
- },
286
- 'param-only': {
287
- escape: 'param',
288
- },
289
- calc: {
290
- assoc: 'nav',
291
- },
292
- joinOn: { // ON condition for JOIN: should be different to 'expr'!
293
- escape: 'param', assoc: 'nav',
294
- },
295
- on: { // TODO: there will also be a 'from-on' (see 'expr')
296
- noDep: true, // do not set dependency for circular-check
297
- allowSelf: true,
298
- }, // TODO: special assoc for only on user
299
- 'mixin-on': {
300
- escape: 'param', // TODO: extra check that assocs containing param in ON is not published
301
- noDep: true, // do not set dependency for circular-check
302
- allowSelf: true,
303
- }, // TODO: special assoc for only on user
304
- // ---------marker for getPathRoot replaced-----------
305
- 'order-by-ref': {
306
- next: '_$next',
307
- dollar: true,
308
- escape: 'param',
309
- assoc: 'nav',
310
- dynamic: 'query',
311
- deprecatedSourceRefs: true,
312
- },
313
- 'order-by-expr': {
314
- next: '_$next',
315
- dollar: true,
316
- escape: 'param',
317
- assoc: 'nav',
318
- },
319
- 'order-by-set-ref': {
320
- next: '_$next',
321
- dollar: true,
322
- escape: 'param',
323
- noDep: true,
324
- dynamic: 'query',
325
- lexical: 'next',
326
- },
327
- 'order-by-set-expr': {
328
- next: '_$next',
329
- dollar: true,
330
- escape: 'param',
331
- noDep: true,
332
- dynamic: false,
333
- lexical: 'next',
334
- },
335
- // expr TODO: better - on condition for assoc, other on
336
- // expr TODO: write dependency, but care for $self
337
- param: {
338
- check: checkConstRef,
339
- expectedMsgId: 'ref-expecting-const',
340
- },
341
- };
342
-
343
266
  Object.assign( model.$functions, {
267
+ traverseExpr,
344
268
  resolveUncheckedPath,
345
- resolveTypeArgumentsUnchecked,
269
+ resolveTypeArgumentsUnchecked, // TODO: move to some other file
346
270
  resolvePath,
271
+ checkExpr,
272
+ checkOnCondition,
273
+ nestedElements,
347
274
  attachAndEmitValidNames,
348
275
  } );
349
276
  return;
350
277
 
351
- function checkConstRef( art ) {
352
- return art.kind !== 'builtin' && art.kind !== 'param';
353
- }
354
-
355
- function checkIncludesRef( art ) {
356
- // We currently disallow using
357
- // - derived structure types: would have to follow type in extend/include;
358
- // - entities with params: clarify inheritance, use of param in ON/DEFAULT;
359
- // - query entities/events: difficult sequence of resolve steps
360
- // - aspect without elements (useful for actions/annotations)
361
- return !(art.elements && !art.query && !art.type && !art.params) && art.kind !== 'aspect';
362
- }
363
-
364
- /**
365
- * Returns true, if the given artifact can be included by a query entity / view.
366
- *
367
- * We currently allow:
368
- * - aspects without elements (the aspect may have actions):
369
- * either no `elements` property or empty dictionary
370
- *
371
- * @param {XSN.Artifact} art
372
- * @return {boolean}
373
- */
374
- function checkViewIncludesRef( art ) {
375
- return !(art.kind === 'aspect' && (!art.elements || Object.keys(art.elements).length === 0));
376
- }
377
-
378
- /**
379
- * @returns {boolean|string}
380
- */
381
- function checkTypeRef( art ) {
382
- if (art.kind === 'type' || art.kind === 'element')
383
- return false;
384
- return ![ 'entity', 'aspect', 'event' ].includes( art.kind ) || 'sloppy';
385
- }
386
-
387
- /**
388
- * @returns {boolean|string}
389
- */
390
- function checkActionParamTypeRef( art ) {
391
- return !(art.kind === 'entity' && art._service) && checkTypeRef( art );
392
- }
393
-
394
- /**
395
- * @returns {boolean|string}
396
- */
397
- function checkEventTypeRef( art ) {
398
- return art.kind !== 'event' && checkActionParamTypeRef( art );
399
- }
400
-
401
- function checkEntityRef( art ) {
402
- return art.kind !== 'entity';
403
- }
278
+ // Expression traversal function ----------------------------------------------
279
+ function traverseExpr( expr, exprCtx, user, callback ) {
280
+ if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
281
+ return;
404
282
 
405
- /**
406
- * @returns {boolean|string}
407
- */
408
- function checkTargetRef( art ) {
409
- if (art.kind === 'entity' || art.kind === 'aspect')
410
- return false;
411
- return art.kind !== 'type' || 'sloppy';
412
- }
283
+ if (expr.path) {
284
+ callback( expr, exprCtx, user );
285
+ // TODO: move arguments and filter traversal to here
286
+ return;
287
+ }
288
+ else if (expr.type || expr.query) {
289
+ callback( expr, exprCtx, user );
290
+ }
413
291
 
414
- function checkSourceRef( art, path ) { // for FROM
415
- if (!art._main)
416
- return (art.kind !== 'entity');
417
- const elem = path.find( item => item._artifact._main )._artifact;
418
- // at least the last main definition should be an entity
419
- // an additional check for target would need effectiveType()
420
- return (elem && elem._main.kind !== 'entity');
292
+ if (expr.args) {
293
+ const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
294
+ // TODO: re-think $expected
295
+ if (!callback.traverse?.( args, exprCtx, user, callback ))
296
+ args.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
297
+ }
298
+ if (expr.suffix) // fn( ) OVER …
299
+ expr.suffix.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
421
300
  }
422
301
 
423
302
  // Return absolute name for unchecked path `ref`. We first try searching for
@@ -441,7 +320,7 @@ function fns( model ) {
441
320
  if (Array.isArray( art ))
442
321
  art = art[0];
443
322
  if (!art)
444
- return (semantics.notFound) ? art : pathName( path );
323
+ return (semantics.dynamic !== modelDefinitions) ? art : pathName( path );
445
324
  if (path.length === 1)
446
325
  return art.name.absolute; // TODO: name.id
447
326
  return `${ art.name.absolute }.${ pathName( ref.path.slice(1) ) }`;
@@ -465,7 +344,7 @@ function fns( model ) {
465
344
  return setArtifactLink( ref, undefined );
466
345
  }
467
346
 
468
- const s = referenceSemantics[expected]; // TODO: temp indirection
347
+ const s = referenceSemantics[expected];
469
348
  const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
470
349
 
471
350
  const r = getPathRoot( ref, semantics, origUser );
@@ -473,59 +352,38 @@ function fns( model ) {
473
352
  if (!root)
474
353
  return setArtifactLink( ref, root );
475
354
 
476
- let spec = specExpected[expected];
477
- if (ref.scope === 'param') {
478
- if (!spec.escape)
479
- throw new CompilerAssertion( 'getPathRoot() should have returned falsy val' );
480
- spec = specExpected[spec.escape];
481
- }
482
-
483
355
  // how many path items are for artifacts (rest: elements)
484
356
  // console.log(expected, ref.path.map(a=>a.id),artItemsCount)
485
- let art = getPathItem( ref, semantics, spec, user );
357
+ let art = getPathItem( ref, semantics, user );
486
358
  if (!art)
487
359
  return setArtifactLink( ref, art );
488
360
 
489
- if (art.$autoElement) {
490
- const { location } = path[path.length - 1];
491
- const step = { id: art.$autoElement, $inferred: '$autoElement', location };
492
- art = art.elements[step.id];
493
- setArtifactLink( step, art );
494
- path.push( step );
495
- }
496
- if (spec.check && !ref.$expected) { // do not check with exists path (is already error)
497
- const fail = spec.check( art, path );
498
- if (fail === true) {
499
- signalNotFound( spec.expectedMsgId, [ ref.location, user ], null );
500
- return setArtifactLink( ref, false );
501
- }
502
- else if (fail) {
503
- signalNotFound( spec.sloppyMsgId, [ ref.location, user ], null );
504
- // no return!
505
- }
506
- }
507
- if (spec.warn) {
508
- const msgId = spec.warn( art, user );
509
- if (msgId)
510
- warning( msgId, [ ref.location, user ] );
511
- }
512
- if (user && (!spec.noDep ||
513
- spec.noDep === 'only-entity' && art.kind !== 'entity')) {
514
- const { location } = ref; // || combinedLocation( head, path[tail.length] );
515
- // TODO: location of last path item if not main artifact
516
- if (spec.assoc === 'from' && art._main) {
517
- dependsOn( user, art._main, location );
361
+ // TODO: use isMainRef string value here?
362
+ const acceptFn = semantics.accept || (semantics.isMainRef ? a => a : acceptElemOrVar);
363
+ art = setArtifactLink( ref, acceptFn( art, user, ref, semantics ) );
364
+
365
+ // TODO TMP: remove noDep: an association does not depend on the target, only
366
+ // -- on its keys/on, which depend on certain target elements
367
+ if (art && user && !semantics.noDep) {
368
+ const location = artifactRefLocation( ref );
369
+ if (semantics.noDep === '' && art._main) { // assoc in FROM
518
370
  environment( art, location, user );
371
+ const target = art._effectiveType?.target?._artifact;
372
+ if (target)
373
+ dependsOn( user._main, target, location, user );
519
374
  }
520
- else if (art.kind !== 'select') { // no real dependency to bare $self
375
+ else if (art._main && art.kind !== 'select' || path[0]._navigation?.kind !== '$self') {
376
+ // no real dependency to bare $self (or actually: the underlaying query)
521
377
  dependsOn( user, art, location );
522
378
  // Without on-demand resolve, we can simply signal 'undefined "x"'
523
379
  // instead of 'illegal cycle' in the following case:
524
380
  // element elem: type of elem.x;
525
381
  }
382
+ // TODO: really write dependency with expand/inline? write test
383
+ // (removing it is not incompatible => not urgent)
526
384
  }
527
385
  // TODO: follow FROM here, see csnRef - fromRef
528
- return setArtifactLink( ref, art );
386
+ return art;
529
387
  }
530
388
 
531
389
  /**
@@ -533,7 +391,7 @@ function fns( model ) {
533
391
  * User is used for semantic message location.
534
392
  *
535
393
  * For builtins, for each property name `<prop>` in `typeArtifact.parameters`, we move a value
536
- * in art.$typeArgs (a vector of numbers with locations) to `artifact.<prop>`.
394
+ * from `art.$typeArgs` (a vector of numbers with locations) to `artifact.<prop>`.
537
395
  *
538
396
  * For non-builtins, we take either one or two arguments and interpret them
539
397
  * as `length` or `precision`/`scale`.
@@ -550,14 +408,12 @@ function fns( model ) {
550
408
  let args = artifact.$typeArgs || [];
551
409
  const parameters = typeArtifact?.parameters || [];
552
410
 
553
- if (parameters.length > 0) {
411
+ if (args.length > 0 && parameters.length > 0) {
554
412
  // For Builtins
555
413
  for (let i = 0; i < parameters.length; ++i) {
556
- let par = parameters[i];
557
- if (!(par instanceof Object))
558
- par = { name: par };
559
- if (!artifact[par.name] && i < args.length)
560
- artifact[par.name] = args[i];
414
+ const par = parameters[i].name || parameters[i];
415
+ if (!artifact[par] && i < args.length)
416
+ artifact[par] = args[i];
561
417
  }
562
418
  args = args.slice(parameters.length);
563
419
  }
@@ -622,7 +478,7 @@ function fns( model ) {
622
478
  const [ nextProp, dictProp ] = (isMainRef)
623
479
  ? [ '_block', 'artifacts' ]
624
480
  : [ '_$next', '$tableAliases' ];
625
- // let notApplicable = ...; // for table aliases in JOIN-ON and UNION order-by
481
+ // let notApplicable = ...; // for table aliases in JOIN-ON and UNION orderBy
626
482
  for (let env = lexical; env; env = env[nextProp]) {
627
483
  const dict = env[dictProp] || Object.create(null);
628
484
  const r = dict[head.id];
@@ -646,8 +502,6 @@ function fns( model ) {
646
502
  valid.push( dynamicDict );
647
503
  else
648
504
  valid.push( model.$magicVariables.artifacts, removeDollarNames( dynamicDict ) );
649
- if (semantics.notFound === false)
650
- return setArtifactLink( head, null );
651
505
  // TODO: streamline function arguments (probably: user, path, semantics )
652
506
  const undef = semantics.notFound( ruser, head, valid, dynamicDict,
653
507
  !isMainRef && user._user && user._artifact, path );
@@ -661,7 +515,7 @@ function fns( model ) {
661
515
  // TODO - think about setting _navigation for all $navElement – the
662
516
  // "ref: ['tabAlias']: inline: […]" handling might be easier
663
517
  // (no _pathHead consultation for key prop and renaming support)
664
- function getPathItem( ref, semantics, spec, user ) {
518
+ function getPathItem( ref, semantics, user ) {
665
519
  // let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
666
520
  const { path } = ref;
667
521
  let artItemsCount = 0;
@@ -671,11 +525,10 @@ function fns( model ) {
671
525
  (ref.scope ? 1 : path.length);
672
526
  }
673
527
  let art = null;
674
- let nav = spec.assoc !== '$keys' && null; // false for '$keys'
675
- const last = path[path.length - 1];
676
- // TODO: change elementsEnv via semantics for static versus dynamic assoc navigation
677
528
  const elementsEnv = semantics.navigation || environment;
529
+ let index = -1;
678
530
  for (const item of path) {
531
+ ++index;
679
532
  --artItemsCount;
680
533
  if (!item?.id) // incomplete AST due to parse error
681
534
  return undefined;
@@ -686,33 +539,24 @@ function fns( model ) {
686
539
 
687
540
  const prev = art;
688
541
  const envFn = (artItemsCount >= 0) ? artifactsEnv : elementsEnv;
689
- const env = envFn( art, item.location, user, spec.assoc );
690
- art = setArtifactLink( item, env?.[item.id] );
542
+ // TOOD: call envFn with location of last item (for dependency error)
543
+ const env = envFn( art, path[index - 1].location, user );
544
+ const found = env && env[item.id]; // not env?.[item.id] ! …we want to keep the 0
545
+ // Reject `$self.$_column_1`:
546
+ art = setArtifactLink( item, (found?.name?.$inferred === '$internal') ? undefined : found );
691
547
 
692
548
  if (!art) {
693
- // element was not found in environment
694
-
695
549
  // TODO (done?): if `env` was 0, we might set a dependency to induce an
696
550
  // illegal-cycle error instead of reporting via `errorNotFound`.
697
- if (prev.$uncheckedElements) { // magic variable / replacement variable
698
- signalNotFound( 'ref-unknown-var', [ item.location, user ], [ env ],
699
- { id: pathName( path ) } );
700
- }
701
- else if (isMainRef && artItemsCount >= 0) { // artifact ref
702
- if (semantics.notFound === false)
703
- return null;
704
- // TODO: streamline funtion arguments (probably: user, path, semantics, prev )
705
- semantics.notFound( user, item, [ env ], null, prev, path );
706
- }
707
- else {
708
- errorNotFound( item, env, prev, spec, user );
709
- }
551
+ const notFound = (artItemsCount >= 0) ? semantics.notFound : undefinedItemElement;
552
+ // TODO: streamline function arguments (probably: user, path, semantics, prev )
553
+ // false returned by semantics.navigation: no further error:
554
+ if (env !== false)
555
+ notFound( user, item, [ env ], null, prev, path );
710
556
  return null;
711
557
  }
712
- // TODO: what what about extra dependencies if we navigate along
713
- // associations? See also extra args for environment()
714
- nav = setSomeNavigationLinkForAssoc( nav, item, art, prev?.target, last, user );
715
558
  // need to do that here, because we also need to disallow Service.AutoExposed:elem
559
+ // TODO: but Service.AutoExposed.NotAuto should be fine
716
560
  if (isMainRef !== 'all' && artItemsCount === 0 &&
717
561
  art.$inferred === 'autoexposed' && !user.$inferred) {
718
562
  // Depending on the processing sequence, the following could be a
@@ -721,92 +565,12 @@ function fns( model ) {
721
565
  error( 'ref-unexpected-autoexposed', [ item.location, user ], { art },
722
566
  // eslint-disable-next-line max-len
723
567
  'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
724
- // return null;
568
+ return null; // continuation semantics: like “not found”
725
569
  }
726
570
  }
727
- // Final checks on artifact (could be done in an extra function)
728
- if (art.$requireElementAccess) { // on some CDS variables
729
- // Path with only one item, but we expect an element, e.g. `$at.from`.
730
- signalMissingElementAccess(art, [ last.location, user ]);
731
- }
732
571
  return art;
733
572
  }
734
573
 
735
- // To be analysed what it does exactly, see changes #3660, #3666:
736
- function setSomeNavigationLinkForAssoc( nav, item, sub, target, last, user ) {
737
- if (nav) { // we have already "pseudo-followed" a managed association
738
- // We currently rely on the check that targetElement references do
739
- // not (pseudo-) follow associations, otherwise potential redirection
740
- // there had to be considered, too. Also, fk refs to sub elements in
741
- // combinations with redirections of the target which directly access
742
- // the potentially renamed sub elements would be really complex.
743
- // With our restriction, no renaming must be considered for item.id.
744
- setTargetReferenceKey( item.id );
745
- }
746
- // Now set an _navigation link for managed assocs in ON condition etc
747
- else if (target && nav != null) {
748
- // Find the original ref for sub and the original foreign key
749
- // definition. This way, we do not need the foreign keys with
750
- // rewritten target element path, which might not be available at
751
- // this point (rewriteKeys in Resolver Phase 5). If we want to
752
- // follow associations in foreign key definitions, rewriteKeys must
753
- // be moved to the on-demand Resolver Phase 2.
754
- let orig; // for the original target element
755
- for (let o = sub; o; o = o.value && o.value._artifact) // TODO: or use _origin?
756
- orig = o;
757
- nav = (orig._effectiveType || orig).$keysNavigation;
758
- setTargetReferenceKey( orig.name.id );
759
- }
760
- return nav;
761
-
762
- function setTargetReferenceKey( id ) {
763
- const node = nav && nav[id];
764
- nav = null;
765
- if (node) {
766
- if (node._artifact) {
767
- // set the original(!) foreign key for the assoc - the "right" ones
768
- // after rewriteKeys() is the one with the same name.id
769
- setLink( item, '_navigation', node._artifact );
770
- if (item === last)
771
- return;
772
- }
773
- else if (item !== last) {
774
- nav = node.$keysNavigation;
775
- return;
776
- }
777
- }
778
- error( null, [ item.location, user ], {},
779
- // eslint-disable-next-line max-len
780
- 'You can\'t follow associations other than to elements referred to in a managed association\'s key' );
781
- }
782
- }
783
-
784
- function errorNotFound( item, env, art, spec, user ) {
785
- if (art.name && art.name.select && art.name.select > 1) {
786
- // TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
787
- // and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
788
- // TODO: probably not extra messageId, but text variant
789
- // TODO: views elements are proxies to query-0 elements, not the same
790
- // TODO: better message text
791
- signalNotFound( 'query-undefined-element', [ item.location, user ],
792
- [ env ], { id: item.id } );
793
- }
794
- else if (art.kind === '$parameters') {
795
- signalNotFound( 'ref-undefined-param', [ item.location, user ],
796
- [ env ], { art: art._main, id: item.id } );
797
- }
798
- else {
799
- const variant = art.kind === 'aspect' && !art.name && 'aspect';
800
- signalNotFound( spec.undefinedDef || 'ref-undefined-element', [ item.location, user ],
801
- [ env ], {
802
- '#': variant,
803
- art: (variant ? '' : searchName( art, item.id, 'element' )),
804
- id: item.id,
805
- } );
806
- }
807
- return null;
808
- }
809
-
810
574
  // Helper functions for resolve[Unchecked]Path, getPath{Root,Item}: -----------
811
575
 
812
576
  function acceptLexical( art, path, semantics, user ) {
@@ -822,7 +586,7 @@ function fns( model ) {
822
586
  return true;
823
587
  }
824
588
  // return !art.$internal && art;
825
- return art.$inferred !== '$internal';
589
+ return art.name?.$inferred !== '$internal'; // not a compiler-generated internal alias
826
590
  }
827
591
 
828
592
  function acceptPathRoot( art, ref, semantics, user ) {
@@ -859,12 +623,6 @@ function fns( model ) {
859
623
  // TODO: remove again, should be easy enough in to-csn without.
860
624
  if (path.length === 1 && art.kind === '$tableAlias')
861
625
  user.$noOrigin = true;
862
- if (path.length === 1 && !semantics.allowBareSelf && !user.expand && !user.inline) {
863
- // TODO: better ref-invalid-self
864
- error( 'ref-unexpected-self', [ head.location, user ], { id: head.id } );
865
- // TODO: reject bare $projection here (new message id, configurable)
866
- // not really helpful to attach valid names here (would include $self)
867
- }
868
626
  return art;
869
627
  }
870
628
  case '$parameters': { // TODO: remove from CC
@@ -902,11 +660,13 @@ function fns( model ) {
902
660
 
903
661
  function typeOfSemantics( user, [ head ] ) {
904
662
  // `type of` is only allowed for (sub) elements of main artifacts
663
+ while (!user.kind && user._outer)
664
+ user = user._outer;
905
665
  let struct = user;
906
666
  while (struct.kind === 'element')
907
667
  struct = struct._parent;
908
668
  if (struct === user._main && struct.kind !== 'annotation')
909
- return { dynamic: typeOfParentDict };
669
+ return { dynamic: typeOfParentDict, navigation: staticTarget };
910
670
  error( 'type-unexpected-typeof', [ head.location, user ],
911
671
  { keyword: 'type of', '#': struct.kind } );
912
672
  return false;
@@ -916,7 +676,7 @@ function fns( model ) {
916
676
  return { dynamic: artifactParams, notFound: undefinedParam };
917
677
  }
918
678
  function paramUnsupported( user, _path, location ) {
919
- error( 'ref-unexpected-scope', [ location, user ],
679
+ error( 'ref-unexpected-scope', [ location, user ], // TODO: ref-unexpected-param
920
680
  // why an extra text for calculated elements? or separate for all?
921
681
  { '#': (user.$syntax === 'calc' ? 'calc' : 'std') } );
922
682
  return false;
@@ -979,9 +739,10 @@ function fns( model ) {
979
739
  }
980
740
 
981
741
  function targetElements( user, pathItemArtifact ) {
982
- // const assoc = user._parent;
983
- // const target = resolvePath( assoc.target, 'target', assoc );
984
- return environment( pathItemArtifact || user._parent, null, null, null, true );
742
+ // has already been computed - no further `navigationEnv` args needed
743
+ const env = navigationEnv( pathItemArtifact || user._parent );
744
+ // do not use env?.elements: a `0` should stay a `0`:
745
+ return env && env.elements;
985
746
  }
986
747
 
987
748
  function combinedSourcesOrParentElements( user ) {
@@ -999,16 +760,27 @@ function fns( model ) {
999
760
  }
1000
761
 
1001
762
  function nestedElements( user ) {
1002
- Functions.navigationEnv( user._pathHead ); // set _origin
1003
- return environment( user._pathHead._origin );
763
+ const colParent = user._pathHead;
764
+ Functions.effectiveType( colParent ); // set _origin
765
+ const path = colParent?.value?.path;
766
+ if (!path?.length)
767
+ return undefined;
768
+ // also set dependency when navigating along assoc → provide location
769
+ return environment( colParent._origin, path[path.length - 1].location, colParent );
770
+ }
771
+
772
+ // Function called via semantics.navigation: ----------------------------------
773
+ // default is function `environment`
774
+
775
+ function artifactsEnv( art ) {
776
+ return art._subArtifacts || Object.create(null);
1004
777
  }
1005
778
 
1006
- function targetAspectOnly( prev ) {
1007
- let env = Functions.navigationEnv( prev, null, null, 'targetAspectOnly' );
1008
- while (env?.target && !env.targetAspect)
1009
- env = env._origin || env.type?._artifact;
779
+ function staticTarget( prev ) {
780
+ let env = navigationEnv( prev ); // we do not write dependencies for assoc navigation
1010
781
  if (env === 0)
1011
782
  return 0;
783
+ // Last try - Composition with targetAspect only (in aspect def):
1012
784
  const target = env?.targetAspect;
1013
785
  if (target) {
1014
786
  if (target.elements)
@@ -1018,8 +790,28 @@ function fns( model ) {
1018
790
  return env?.elements || Object.create(null);
1019
791
  }
1020
792
 
1021
- function artifactsEnv( art ) {
1022
- return art._subArtifacts || Object.create(null);
793
+ function targetNavigation( art, location, user ) {
794
+ const env = navigationEnv( art, location, user, false );
795
+ // do not use env?.elements: a `0`/false should stay a `0`/false:
796
+ return env && env.elements;
797
+ }
798
+
799
+ function assocOnNavigation( art, location, user ) {
800
+ const env = navigationEnv( art, location, user, null );
801
+ // `null` means: do not write a dependency from target of any association
802
+ // otherwise “following” own assoc would lead to cycle.
803
+ // TODO: disallow navigation other than of own assoc, and to foreign keys
804
+ // This way (not here though, but later in resolve.js)
805
+ if (env === 0)
806
+ return 0;
807
+ return env?.elements || Object.create(null);
808
+ }
809
+
810
+ function calcElemNavigation( art, location, user ) {
811
+ const env = navigationEnv( art, location, user, 'calc' );
812
+ if (env === 0)
813
+ return 0;
814
+ return env?.elements || Object.create(null);
1023
815
  }
1024
816
 
1025
817
  // Return effective search environment provided by artifact `art`, i.e. the
@@ -1027,13 +819,47 @@ function fns( model ) {
1027
819
  // chain and resolve the association `target`. View elements are calculated
1028
820
  // on demand.
1029
821
  // TODO: what about location/user when called from getPath ?
1030
- // TODO: think of always acting as if falsyIfNone would be true
822
+ // TODO: think of removing `|| Object.create(null)`.
1031
823
  // (if not possible, move to second param position)
1032
- function environment( art, location, user, assocSpec, falsyIfNone ) {
1033
- const env = Functions.navigationEnv( art, location, user, assocSpec );
824
+ function environment( art, location, user ) {
825
+ const env = navigationEnv( art, location, user, 'nav' );
1034
826
  if (env === 0)
1035
827
  return 0;
1036
- return env?.elements || !falsyIfNone && Object.create(null);
828
+ return env?.elements || Object.create(null);
829
+ }
830
+
831
+ function navigationEnv( art, location, user, assocSpec ) {
832
+ // = effectiveType() on from-path, TODO: should actually already part of
833
+ // resolvePath() on FROM
834
+ if (!art)
835
+ return undefined;
836
+ let type = Functions.effectiveType( art );
837
+ while (type?.items) // TODO: disallow navigation to many sometimes
838
+ type = Functions.effectiveType( type.items );
839
+ if (!type?.target)
840
+ return type;
841
+
842
+ if (assocSpec === false) { // TODO: move to getPathItem
843
+ error( null, [ location, user ], {},
844
+ 'Following an association is not allowed in an association key definition' );
845
+ return false;
846
+ } // TODO: else warning for assoc usage with falsy assocSpec
847
+ const target = type?.target._artifact;
848
+ if (!target)
849
+ return target;
850
+ // TODO: really write final dependency with expand/inline?
851
+ if (target && assocSpec && user) {
852
+ if (assocSpec !== 'calc')
853
+ dependsOn( user._main || user, target, location || user.location, user );
854
+ else // (TODO: users of) calc elements must depend on navigation target
855
+ dependsOn( user, target, location || user.location );
856
+ // TODO: have some _delayedDeps for calc elements
857
+ }
858
+ const effectiveTarget = Functions.effectiveType( target );
859
+ // if (effectiveTarget === 0 && location)
860
+ // dependsOn( user, user, (user.target || user.type || user.value || user).location );
861
+ // console.log('NT:',assocSpec,!!user,target)
862
+ return effectiveTarget;
1037
863
  }
1038
864
 
1039
865
  // Functions called via semantics.notFound: -----------------------------------
@@ -1072,10 +898,12 @@ function fns( model ) {
1072
898
  // TODO: avoid message if we have already complained about `(exists …)`?
1073
899
  const { id } = head;
1074
900
  const isVar = id.charAt( 0 ) === '$' && id !== '$self';
901
+ // TODO: for wrong $self, also use ref-undefined-var, but with extra msg id
902
+ // otherwise, use s/th like ref-unexpected-element
1075
903
  signalNotFound( (isVar ? 'ref-undefined-var' : 'ref-expecting-const'),
1076
904
  [ head.location, user ],
1077
905
  valid, { '#': 'std', id } );
1078
- // TODO: use s/th better than 'ref-expecting-const'?
906
+ // TODO: use s/th better than 'ref-expecting-const' !!
1079
907
  }
1080
908
 
1081
909
  function undefinedSourceElement( user, head, valid, dynamicDict ) {
@@ -1110,7 +938,7 @@ function fns( model ) {
1110
938
  }
1111
939
  else {
1112
940
  // TODO: extra msg like ref-rejected-on if elem found in source elements?
1113
- // also whether users wronly tried to refer to aliases/mixins?
941
+ // also whether users wrongly tried to refer to aliases/mixins?
1114
942
  const msgVar = userQuery( user ) ? 'query' : null;
1115
943
  // TODO: better with ON in expand if that is supported
1116
944
  signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
@@ -1132,16 +960,489 @@ function fns( model ) {
1132
960
  return null;
1133
961
  }
1134
962
 
1135
- function undefinedNestedElement( user, head, valid ) {
1136
- // environment( user._pathHead ); // set _origin
963
+ function undefinedNestedElement( user, head, valid, _dict, _art, path ) {
1137
964
  const art = user._pathHead._origin;
1138
- // if (!art) console.log('UNE:',user,user._pathHead)
1139
965
  if (!art)
1140
- return; // no consequential error
1141
- // TODO: better message with $ref
1142
- signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
1143
- { art: searchName( art, head.id, 'element' ) } );
1144
- // TODO: remove use of searchName() ?
966
+ return null; // no consequential error
967
+ return undefinedItemElement( user, head, valid, null, art, path );
968
+ }
969
+
970
+ function undefinedItemElement( user, item, valid, _dict, art, path ) {
971
+ if (art.name && art.name.select && art.name.select > 1) {
972
+ // TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
973
+ // and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
974
+ // both as text variants to ref-undefined-element
975
+ signalNotFound( 'query-undefined-element', [ item.location, user ],
976
+ valid, { id: item.id } );
977
+ }
978
+ else if (art.kind === '$parameters') {
979
+ signalNotFound( 'ref-undefined-param', [ item.location, user ],
980
+ valid, { art: art._main, id: item.id } );
981
+ }
982
+ else if (art.kind === 'builtin') { // magic variable / replacement variable
983
+ const id = (item === path[path.length - 1])
984
+ ? item.id
985
+ : pathName( path.slice( path.indexOf( item ) ) );
986
+ signalNotFound( (art.$uncheckedElements ? 'ref-unknown-var' : 'ref-undefined-var'),
987
+ [ item.location, user ], valid,
988
+ { id: `${ art.name.element }.${ id }` } );
989
+ }
990
+ else {
991
+ const variant = art.kind === 'aspect' && !art.name && 'aspect';
992
+ signalNotFound( 'ref-undefined-element', [ item.location, user ],
993
+ valid, {
994
+ '#': variant,
995
+ art: (variant ? '' : searchName( art, item.id, 'element' )),
996
+ id: item.id,
997
+ } );
998
+ }
999
+ return null;
1000
+ }
1001
+
1002
+ // Functions called via semantics.accept: -------------------------------------
1003
+ // function arguments ( art, user, ref, semantics ),
1004
+ // default (for elements only): acceptElemOrVar
1005
+
1006
+ function acceptElemOrVarOrSelf( art, user, ref ) {
1007
+ // TODO: make $self._artifact point to the $self alias, not the entity
1008
+ return (!(art._main && art.kind !== 'select') && ref.path[0]._navigation?.kind === '$self')
1009
+ ? art
1010
+ : acceptElemOrVar( art, user, ref );
1011
+ }
1012
+
1013
+ function acceptElemOrVar( art, user, ref ) {
1014
+ const { path } = ref;
1015
+ if (art.kind === 'builtin') {
1016
+ if (user.expand || user.inline) {
1017
+ const location = (user.expand || user.inline)[$location];
1018
+ const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
1019
+ message( 'def-unexpected-nested-proj', [ location, user ], { '#': 'var', code } );
1020
+ }
1021
+ else if (art.$requireElementAccess) { // on some CDS variables
1022
+ // Path with only one item, but we expect an element, e.g. `$at.from`.
1023
+ signalMissingElementAccess(art, [ path[0].location, user ]);
1024
+ return null;
1025
+ }
1026
+ else if (art.$autoElement) {
1027
+ const { location } = path[0];
1028
+ const step = { id: art.$autoElement, $inferred: '$autoElement', location };
1029
+ path.push( step );
1030
+ art = art.elements[step.id];
1031
+ return setArtifactLink( step, art );
1032
+ }
1033
+ }
1034
+ // TODO: combine $requireElementAccess/$autoElement to $bareRoot ?
1035
+ else if (!user.expand && !user.inline && // $self._artifact to main artifact
1036
+ !(art._main && art.kind !== 'select') && ref.path[0]._navigation?.kind === '$self') {
1037
+ // TODO: better ref-invalid-self
1038
+ const { location, id } = path[0];
1039
+ error( 'ref-unexpected-self', [ location, user ], { id } );
1040
+ // TODO: reject bare $projection here (new message id, configurable)
1041
+ // TODO: should we also attach valid names? Probably not...
1042
+ // TODO: return false; ??
1043
+ // return false;
1044
+ }
1045
+ return art;
1046
+ }
1047
+
1048
+ function acceptStructOrBare( art, user, ref ) { // for includes[]
1049
+ // It had been checked before that `includes` is already forbidden for
1050
+ // non-entity/aspect/type/event.
1051
+ //
1052
+ // We currently disallow as include:
1053
+ // - non-structured types or derived type of structured:
1054
+ // would have to follow type in extend/include;
1055
+ // - entities with params: clarify inheritance, use of param in ON/DEFAULT;
1056
+ // - query entities/events: difficult sequence of resolve steps
1057
+ // - aspect with one ore more elements on query entities / events
1058
+ // - aspect with `elements` property on non-structured types
1059
+
1060
+ // TODO: adapt `user` if it is an `extend`? NOTE: we cannot call
1061
+ // effectiveType() on user - it might be in the process of being computed!
1062
+ // Also, it is not clear whether `art.elements` has been completed → testing
1063
+ // its length might be processing-sequence dependent, see #11346. We must
1064
+ // ensure that an include does not add the `elements` property!
1065
+ const base = (user.kind === 'extend' ? user.name._artifact : user);
1066
+ if (!base)
1067
+ return art;
1068
+ if (base.query || base.type || !base.elements) {
1069
+ // Remark: it is not necessary to test for user.elements[$inferred], because
1070
+ // the type could only have inferred elements if it has a type expression.
1071
+ // Including aspects with elements is forbidden for aspects without the
1072
+ // `elements` property (TODO: ensure that, see #11346). Testing for the length of
1073
+ // `art.elements` requires that we have applied potential `includes` of
1074
+ // `art` before!
1075
+ if (art.kind === 'aspect' &&
1076
+ (!art.elements || base.query && !Object.keys( art.elements ).length))
1077
+ return art;
1078
+ signalNotFound( 'ref-invalid-include', [ ref.location, user ], null,
1079
+ { '#': 'bare' } );
1080
+ }
1081
+ else {
1082
+ if (!art.query && !art.type && !art.params && (art.elements || art.kind === 'aspect'))
1083
+ return art;
1084
+ signalNotFound( 'ref-invalid-include', [ ref.location, user ], null );
1085
+ }
1086
+ return false;
1087
+ }
1088
+
1089
+ // Remember: an aspect should have already been moved to XSN targetAspect, but
1090
+ // the error messages should still talk about potential aspects
1091
+ function acceptEntity( art, user, ref ) { // for target
1092
+ if (art.kind === 'entity')
1093
+ return art;
1094
+ // Extra msg text with Composition of NeitherEntityNorAspect:
1095
+ const bare = !art.elements || art.elements[$inferred];
1096
+ const std = (art.targetAspect || !isDirectComposition( user ) || user.kind === 'mixin');
1097
+ const msg = std && 'std' || (bare && art.kind === 'aspect' ? 'bare' : 'composition');
1098
+ signalNotFound( 'ref-invalid-target', [ ref.location, user ], null,
1099
+ { '#': msg } );
1100
+ return false;
1101
+ }
1102
+
1103
+ function acceptAspect( art, user, ref ) { // for targetAspect
1104
+ const bare = !art.elements || art.elements[$inferred];
1105
+ if (!bare) {
1106
+ if (art.kind === 'aspect')
1107
+ return art;
1108
+ if (art.kind === 'type') { // v4: Warning → config Error
1109
+ signalNotFound( 'ref-sloppy-target', [ ref.location, user ], null );
1110
+ return art;
1111
+ }
1112
+ }
1113
+ signalNotFound( 'ref-invalid-target', [ ref.location, user ], null,
1114
+ { '#': (bare ? 'bare' : 'aspect'), prop: 'targetAspect' } );
1115
+ return false;
1116
+ }
1117
+
1118
+ function acceptEntityOrAssoc( art, user, ref ) { // for FROM
1119
+ const { path, scope } = ref;
1120
+ // see getPathItem(): how many path items are for the main artifact ref?
1121
+ const artItemsCount = (typeof scope === 'number' && scope) || (scope ? 1 : path.length);
1122
+ // at least the last main definition should be an entity
1123
+ // an additional check for target would need effectiveType()
1124
+ const source = path[artItemsCount - 1]._artifact;
1125
+ if (source.kind !== 'entity') {
1126
+ signalNotFound( 'ref-invalid-source', [ ref.location, user ], null );
1127
+ return false;
1128
+ }
1129
+ if (source === art)
1130
+ return art;
1131
+ const assoc = Functions.effectiveType( art );
1132
+ if (assoc.target)
1133
+ return art; // TODO: use target here
1134
+ signalNotFound( 'ref-invalid-source', [ ref.location, user ], null );
1135
+ return false;
1136
+ }
1137
+
1138
+ function acceptTypeOrElement( art, user, ref ) { // for type
1139
+ // was ['action', 'function'].includes( user._parent?.kind ))
1140
+ while (user._outer)
1141
+ user = user._outer;
1142
+ const kind = (user.kind !== 'param' || user._parent?.kind !== 'entity')
1143
+ ? user.kind
1144
+ : 'entity-param';
1145
+ switch (art.kind) {
1146
+ case 'type':
1147
+ case 'element':
1148
+ return art;
1149
+ case 'entity':
1150
+ if (kind === 'param' && art._service)
1151
+ return art;
1152
+ // FALLTHROUGH
1153
+ case 'event':
1154
+ if (kind === 'event')
1155
+ return art;
1156
+ break;
1157
+ default:
1158
+ break;
1159
+ }
1160
+ signalNotFound( 'ref-invalid-type', [ ref.location, user ], null, { '#': kind } );
1161
+ return false;
1162
+ }
1163
+
1164
+ // Functions called via semantics.check by checkExpr(): -----------------------
1165
+ //
1166
+ // function arguments: ( expr, exprCtx, user )
1167
+ // default: tbd (nothing for main artifac ref)
1168
+
1169
+ // Performs checks which would be too early to do via semantics.accept. It is
1170
+ // actually assumed that the foreign-keys / ON-condition rewrite has already
1171
+ // been done.
1172
+
1173
+ // Main check area "navigation" (see also semantics.navigation):
1174
+ // - navigation along any assoc
1175
+ // - navigation only along foreign keys
1176
+ // - (no navigation already via semantics.navigation for target refs of foreign keys)
1177
+ // - special (ON-condition of unmanaged associations)
1178
+
1179
+ // Main check area: checks on the referred artifact
1180
+ // - all artifacts are allowed
1181
+ // - all except unmanaged associations
1182
+ // - ...
1183
+
1184
+ function checkExpr( expr, exprCtx, user ) {
1185
+ if (!expr)
1186
+ return;
1187
+ const s = referenceSemantics[exprCtx];
1188
+ const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
1189
+ const checkFn = semantics.check; // || !semantics.isMainRef && checkElementStd;
1190
+
1191
+ if (checkFn)
1192
+ traverseExpr( expr, exprCtx, user, checkFn );
1193
+ }
1194
+
1195
+ // TODO: Don't allow path args and filter!
1196
+ function checkOnCondition( expr, exprCtx, user ) {
1197
+ if (!expr || expr.$inferred)
1198
+ return;
1199
+ const { op } = expr;
1200
+ let { args } = expr;
1201
+ if (!op || !args) {
1202
+ checkExpr( expr, exprCtx, user );
1203
+ return;
1204
+ }
1205
+ if (op?.val === '=') // TMP
1206
+ args = [ args[0], { val: '=', literal: 'token' }, args[1] ];
1207
+
1208
+ for (let index = 0; index < args.length; ++index) {
1209
+ const item = args[index];
1210
+ const eq = args[index + 1];
1211
+ if (eq?.val === '=' && eq.literal === 'token' && item.path && !item.scope) {
1212
+ const right = args[index + 2];
1213
+ if (right?.path && !right.scope &&
1214
+ (isDollarSelfPair( item, right, user ) || isDollarSelfPair( right, item, user ))) {
1215
+ checkAssocOnSelf( item, exprCtx, user );
1216
+ checkAssocOnSelf( right, exprCtx, user );
1217
+ index += 2;
1218
+ continue;
1219
+ }
1220
+ }
1221
+ checkOnCondition( item, exprCtx, user );
1222
+ }
1223
+ }
1224
+
1225
+ // // standard element reference check;
1226
+ // function checkElementStd( art, _user, ref, semantics ) {
1227
+ // // No further checks on navigation: nothing to do
1228
+ // // Must not end with any association (TODO: allow managed?):
1229
+ // if (art.target) { // && art.on
1230
+ // // error
1231
+ // }
1232
+ // }
1233
+
1234
+ function checkColumnRef( expr, exprCtx, user ) {
1235
+ if (!expr.path)
1236
+ return;
1237
+ if (expr === user.value && // is already a syntax error with non-ref expression
1238
+ (user.expand || user.inline))
1239
+ checkExpandInlineRef( expr._artifact, user, expr );
1240
+
1241
+ const self = pathStartsWithSelf( expr );
1242
+ // console.log('NAV:',expr.path.map(r=>r.id),self)
1243
+ if (self || self == null && columnRefStartsWithSelf( user )) {
1244
+ checkOnlyForeignKeyNavigation( user, expr.path, 0, 'self-' );
1245
+ checkNoUnmanaged( expr, user, 'self-unmanaged' );
1246
+ }
1247
+ // TODO: set navigation dependencies later to avoid both ref-cyclic and
1248
+ // ref-invalid-navigation/ref-unexpected-assoc
1249
+ }
1250
+
1251
+ function checkOrderByRef( expr, exprCtx, user ) {
1252
+ const { path } = expr;
1253
+ if (!path)
1254
+ return;
1255
+ if (path?.[0]?._navigation?.kind !== '$tableAlias')
1256
+ checkOnlyForeignKeyNavigation( user, expr.path );
1257
+ checkNoUnmanaged( expr, user );
1258
+ }
1259
+
1260
+ function checkRefInQuery( expr, exprCtx, user ) {
1261
+ const { path } = expr;
1262
+ if (!path)
1263
+ return;
1264
+ if (pathStartsWithSelf( expr ))
1265
+ checkOnlyForeignKeyNavigation( user, expr.path, 0, 'self-' );
1266
+ checkNoUnmanaged( expr, user );
1267
+ }
1268
+
1269
+ function checkExpandInlineRef( art, user, ref ) {
1270
+ if (!art || !art._main || // $self has entity as _artifact
1271
+ art.kind === 'builtin') // no repeated error for CDS variables
1272
+ return;
1273
+ const effective = art._effectiveType;
1274
+ if (!effective || effective.target || effective.elements)
1275
+ return;
1276
+ const { path } = ref;
1277
+ const location = (user.expand || user.inline)[$location];
1278
+ // mention `table alias` in text only with initial single path item ref,
1279
+ // but do not mention that $self { … } is allowed, shouldn't be advertised:
1280
+ const txt = (path.length > 1 || user._pathHead) ? 'struct' : 'init';
1281
+ const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
1282
+ message( 'def-unexpected-nested-proj', [ location, user ], { '#': txt, code } );
1283
+ }
1284
+
1285
+ function isDollarSelfPair( left, right, user ) {
1286
+ if (!left.path || !right.path || left.scope || right.scope)
1287
+ return false; // param ref `:$self` is not $self
1288
+ // $self in entity (TODO: mixin? new assoc in col? in aspect?)
1289
+ const kind = right._artifact?.kind;
1290
+ if (kind !== 'entity' && kind !== 'aspect' && kind !== 'select')
1291
+ return false; // (ok, this would also return `false` for `:$self`)
1292
+ const { path } = left;
1293
+ // TODO: we might return true and issue an extra error here
1294
+ return userTargetElementPathIndex( user, path ) > 0;
1295
+ }
1296
+
1297
+ function checkAssocOn( ref, exprCtx, user ) {
1298
+ const { path } = ref;
1299
+ if (!path)
1300
+ return;
1301
+ if (path.length === 1 && path[0]._navigation?.kind === '$self') {
1302
+ // resolvePath() for `on` allowed bare $self: disallow except in checkAssocOnSelf
1303
+ const { location, id } = path[0];
1304
+ error( 'ref-unexpected-self', [ location, user ], { '#': 'on', id } );
1305
+ return;
1306
+ }
1307
+ const index = userTargetElementPathIndex( user, path );
1308
+ checkOnlyForeignKeyNavigation( user, path, index );
1309
+ if (ref._artifact?.on) {
1310
+ const target = index > 0 && index < path.length && ref._artifact?.target;
1311
+ const msg = (target?._artifact === user._main) ? 'self' : 'unmanaged';
1312
+ const last = path[path.length - 1];
1313
+ error( 'ref-unexpected-assoc', [ last.location, user ],
1314
+ { '#': msg, code: '= $self' } );
1315
+ }
1316
+ }
1317
+
1318
+ function checkAssocOnSelf( ref, exprCtx, user ) {
1319
+ // TODO: fully specify what `‹current_assoc›.‹backlink› = $self` means if the
1320
+ // target of ‹backlink› is not the current entity.
1321
+ // - what does it mean in an aspect
1322
+ // - how about auto-redirections/rewrite
1323
+ const { path } = ref;
1324
+ if (path.length === 1 && path[0]._navigation?.kind === '$self') {
1325
+ const query = userQuery( user );
1326
+ const main = query?._main;
1327
+ if (query && query !== main._leadingQuery) {
1328
+ const { txt, op } = getQueryOperatorName( query );
1329
+ const { location, id } = path[0];
1330
+ error( 'ref-unexpected-self', [ location, user ], { '#': txt, id, op } );
1331
+ }
1332
+ return;
1333
+ }
1334
+ const index = userTargetElementPathIndex( user, path );
1335
+ checkOnlyForeignKeyNavigation( user, path, index );
1336
+ const target = index > 0 && index < path.length && ref._artifact?.target;
1337
+ if (!target) {
1338
+ const last = path[path.length - 1];
1339
+ error( 'ref-expecting-target-assoc', [ last.location, user ], { id: '$self' },
1340
+ 'Only an association of the target side can be compared to $(ID)' );
1341
+ }
1342
+ // in entity: target must match
1343
+ // in aspect: must end with assoc, TODO: target must include aspect (+ add/ check)
1344
+ else if (target._artifact && target._artifact !== user._main && user._main.kind === 'entity') {
1345
+ const last = path[path.length - 1];
1346
+ warning( 'ref-invalid-backlink', [ last.location, user ], { art: target, id: '$self' },
1347
+ // eslint-disable-next-line max-len
1348
+ 'The target $(ART) of the association is not the current entity represented by $(ID)' );
1349
+ }
1350
+ }
1351
+
1352
+
1353
+ // right of union: parent = main → get correct operator
1354
+ // FROM subquery: parent = tab alias of outer query
1355
+ // other sub query: parent = outer query
1356
+ function getQueryOperatorName( query ) {
1357
+ if (query._parent !== query._main)
1358
+ return { txt: 'subQuery', op: '' };
1359
+
1360
+ for (let set = query._main.query; set.op; set = set.args[0]) {
1361
+ const right = set.args[1];
1362
+ if (query.name.select >= (right._leadingQuery || right).name.select)
1363
+ return { txt: 'setQuery', op: set.op.val };
1364
+ }
1365
+ throw new CompilerAssertion( 'Did we pass the leading query as argument?' );
1366
+ }
1367
+
1368
+ function userTargetElementPathIndex( user, path ) {
1369
+ const head = path[0];
1370
+ if (head._artifact === user) // standard case
1371
+ return 1;
1372
+ if (head._navigation?.kind !== '$self')
1373
+ return 0;
1374
+ for (let index = 1; index < path.length; ++index) {
1375
+ const assoc = path[index]?._artifact;
1376
+ if (assoc?.target)
1377
+ return (assoc === user) ? index + 1 : 0;
1378
+ }
1379
+ return 0;
1380
+ }
1381
+
1382
+ function checkOnlyForeignKeyNavigation( user, path, startIndex = 0, msgPrefix = '' ) {
1383
+ // has to be run after foreign-key rewrite
1384
+ const outer = user._pathHead?._origin;
1385
+ let assoc = outer?.foreignKeys &&
1386
+ pathStartsWithSelf( { path } ) == null && // not $self or CDS var like $now
1387
+ outer;
1388
+ for (let index = startIndex; index < path.length; ++index) {
1389
+ if (assoc?.target) {
1390
+ if (!assoc.foreignKeys) {
1391
+ error( 'ref-unexpected-assoc', [ path[index - 1].location, user ],
1392
+ { '#': `${ msgPrefix }unmanaged`, alias: '$self' } );
1393
+ return;
1394
+ }
1395
+ if (!assoc.$keysNavigation)
1396
+ Functions.addForeignKeyNavigations( assoc, true );
1397
+ index = checkCoveredByForeignKey( assoc, path, index, user, msgPrefix );
1398
+ }
1399
+ assoc = path[index]?._artifact;
1400
+ }
1401
+ }
1402
+
1403
+ function checkCoveredByForeignKey( nav, path, index, user, msgPrefix = '' ) {
1404
+ const assoc = nav;
1405
+ while (index < path.length) {
1406
+ const item = path[index];
1407
+ nav = nav.$keysNavigation?.[item.id];
1408
+ if (!nav)
1409
+ break;
1410
+ if (nav._artifact)
1411
+ return index;
1412
+ ++index;
1413
+ }
1414
+ const last = path[index] || path[index - 1];
1415
+ // TODO: or just location of `last`?
1416
+ // TODO: extra text variant if the foreign keys are the key elements !
1417
+ // eslint-disable-next-line no-nested-ternary
1418
+ const txt = index >= path.length
1419
+ ? 'complete'
1420
+ : (isAssocToPrimaryKeys( assoc ) ? 'keys' : 'std');
1421
+ // eslint-disable-next-line max-len
1422
+ error( 'ref-invalid-navigation', [ last.location, user ], {
1423
+ '#': msgPrefix + txt, art: assoc, name: last.id, alias: '$self',
1424
+ }, {
1425
+ std: 'Can follow association $(ART) only to its foreign key references, not to $(NAME)',
1426
+ keys: 'Can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
1427
+ complete: 'The reference must cover a full foreign key reference of association $(ART)',
1428
+ // eslint-disable-next-line max-len
1429
+ 'self-std': 'In column ref starting with $(ALIAS), we can follow association $(ART) only to its foreign key references, not to $(NAME)',
1430
+ // eslint-disable-next-line max-len
1431
+ 'self-keys': 'In column ref starting with $(ALIAS), we can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
1432
+ // eslint-disable-next-line max-len
1433
+ 'self-complete': 'The column reference starting with $(ALIAS) must cover a full foreign key reference of association $(ART)',
1434
+ } );
1435
+ // TODO later: mention allowed ones
1436
+ return path.length;
1437
+ }
1438
+
1439
+ function checkNoUnmanaged( ref, user, messageVariant = 'unmanaged' ) {
1440
+ if (ref._artifact?.on && !ref.$expected) {
1441
+ const { path } = ref;
1442
+ const last = path[path.length - 1];
1443
+ error( 'ref-unexpected-assoc', [ last.location, user ],
1444
+ { '#': messageVariant, alias: '$self' } );
1445
+ }
1145
1446
  }
1146
1447
 
1147
1448
  // Low-level functions --------------------------------------------------------
@@ -1155,7 +1456,7 @@ function fns( model ) {
1155
1456
  * @param {object} [textParams]
1156
1457
  */
1157
1458
  function signalNotFound( msgId, location, valid, textParams ) {
1158
- if (location.$notFound)
1459
+ if (location.$notFound) // TODO: still necessary?
1159
1460
  return;
1160
1461
  location.$notFound = true;
1161
1462
  /** @type {object} */
@@ -1177,6 +1478,7 @@ function fns( model ) {
1177
1478
  * @param {any} location
1178
1479
  */
1179
1480
  function signalMissingElementAccess( art, location ) {
1481
+ // TODO: ref-undefined-var ?
1180
1482
  const err = message( 'ref-expected-element', location,
1181
1483
  { '#': 'magicVar', id: art.name.id } );
1182
1484
  // Mapping for better valid names: from -> $at.from
@@ -1205,7 +1507,7 @@ function fns( model ) {
1205
1507
  const art = valid[name];
1206
1508
  // ignore internal types such as cds.Association, ignore names with dot for
1207
1509
  // CDL references to main artifacts:
1208
- if (!art.internal && !art.deprecated && art.$inferred !== '$internal' &&
1510
+ if (!art.internal && !art.deprecated && art.name?.$inferred !== '$internal' &&
1209
1511
  (viaCdl ? art._main || !name.includes('.') : art.kind !== 'namespace'))
1210
1512
  msg.validNames[name] = art;
1211
1513
  }