@sap/cds-compiler 6.1.0 → 6.2.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 (53) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/bin/cdsc.js +6 -2
  3. package/bin/cdsse.js +1 -1
  4. package/bin/cdsv2m.js +1 -1
  5. package/lib/api/main.js +29 -7
  6. package/lib/base/builtins.js +9 -0
  7. package/lib/base/keywords.js +1 -1
  8. package/lib/base/message-registry.js +5 -3
  9. package/lib/base/messages.js +2 -2
  10. package/lib/base/model.js +1 -0
  11. package/lib/base/optionProcessorHelper.js +7 -2
  12. package/lib/checks/featureFlags.js +4 -1
  13. package/lib/compiler/assert-consistency.js +3 -1
  14. package/lib/compiler/base.js +1 -1
  15. package/lib/compiler/builtins.js +1 -1
  16. package/lib/compiler/checks.js +38 -21
  17. package/lib/compiler/define.js +24 -5
  18. package/lib/compiler/extend.js +1 -1
  19. package/lib/compiler/finalize-parse-cdl.js +9 -1
  20. package/lib/compiler/generate.js +4 -4
  21. package/lib/compiler/lsp-api.js +2 -0
  22. package/lib/compiler/populate.js +8 -8
  23. package/lib/compiler/propagator.js +1 -1
  24. package/lib/compiler/resolve.js +15 -14
  25. package/lib/compiler/shared.js +6 -6
  26. package/lib/compiler/tweak-assocs.js +6 -6
  27. package/lib/compiler/utils.js +9 -16
  28. package/lib/compiler/xpr-rewrite.js +2 -2
  29. package/lib/gen/BaseParser.js +35 -29
  30. package/lib/gen/CdlGrammar.checksum +1 -1
  31. package/lib/gen/CdlParser.js +1423 -1432
  32. package/lib/gen/Dictionary.json +1 -0
  33. package/lib/gen/cdlKeywords.json +26 -0
  34. package/lib/inspect/inspectPropagation.js +1 -1
  35. package/lib/json/from-csn.js +2 -2
  36. package/lib/json/to-csn.js +1 -1
  37. package/lib/language/multiLineStringParser.js +1 -1
  38. package/lib/model/cloneCsn.js +1 -0
  39. package/lib/optionProcessor.js +8 -7
  40. package/lib/parsers/AstBuildingParser.js +24 -21
  41. package/lib/parsers/identifiers.js +2 -30
  42. package/lib/render/toCdl.js +63 -9
  43. package/lib/render/toSql.js +127 -108
  44. package/lib/render/utils/sql.js +67 -0
  45. package/lib/transform/addTenantFields.js +4 -4
  46. package/lib/transform/db/killAnnotations.js +1 -0
  47. package/lib/transform/db/processSqlServices.js +20 -2
  48. package/lib/transform/forOdata.js +91 -2
  49. package/lib/transform/forRelationalDB.js +1 -1
  50. package/lib/transform/odata/flattening.js +1 -1
  51. package/lib/transform/translateAssocsToJoins.js +2 -26
  52. package/lib/utils/moduleResolve.js +1 -1
  53. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -8,6 +8,34 @@ Note: `beta` fixes, changes and features are usually not listed in this ChangeLo
8
8
  but in [doc/CHANGELOG_BETA.md](doc/CHANGELOG_BETA.md).
9
9
  The compiler behavior concerning `beta` features can change at any time without notice.
10
10
 
11
+ ## Version 6.2.2 - 2025-07-28
12
+
13
+ ### Fixed
14
+
15
+ - compiler: `@extension.code` was accidentally restricted to non-expression values.
16
+
17
+ ## Version 6.2.0 - 2025-07-25
18
+
19
+ ### Added
20
+
21
+ - parser: CDL-casts in queries now support all type expressions, e.g. `field : many String not null`.
22
+ - compiler: Association paths in annotation expressions can now end with a filter, e.g. `@anno: (assoc[1=1])`.
23
+
24
+ ### Changed
25
+
26
+ - compiler: Annotation `@extension.code` is no longer propagated.
27
+ - Update OData vocabularies: Common
28
+ - The list of CDL keywords was updated for the latest CDL grammar.
29
+ - to.cdl: Foreign keys of managed associations are only rendered explicitly if
30
+ the compiler can't infer them when recompiled.
31
+ - cdsc: The command `parseCdl` was renamed to `parse`, since it also supports CSN input.
32
+
33
+ ### Fixed
34
+
35
+ - compiler:
36
+ + Calculated elements can now have a localized type.
37
+ + Associations in sub-queries of an `order by` of a `UNION` are now redirected.
38
+
11
39
  ## Version 6.1.0 - 2025-06-27
12
40
 
13
41
  ### Added
@@ -135,6 +163,21 @@ The compiler behavior concerning `beta` features can change at any time without
135
163
 
136
164
  - to.edm(x): Fixed crash for rare case if annotation expressions were used.
137
165
 
166
+ ## Version 5.9.8 - 2025-07-14
167
+
168
+ ### Fixed
169
+
170
+ - compiler: Calculated elements can now have a localized type
171
+
172
+ ## Version 5.9.6 - 2025-06-18
173
+
174
+ ### Fixed
175
+
176
+ - to.sql: Fix error when calculated element refers to a localized element.
177
+ - to.edm(x):
178
+ + Fix errors for service entities containing multiple path steps (e.g. `Service.Prefix.MyEntity`).
179
+ + Support enum references in annotation expressions that were resolved by the compiler.
180
+
138
181
  ## Version 5.9.4 - 2025-05-22
139
182
 
140
183
  ### Fixed
package/bin/cdsc.js CHANGED
@@ -151,7 +151,7 @@ function cdscMain() {
151
151
  cmdLine.options.attachValidNames = true;
152
152
 
153
153
  // Internally, parseCdl/parseOnly are options, so we map the command to it.
154
- if (cmdLine.command === 'parseCdl') {
154
+ if (cmdLine.command === 'parse') {
155
155
  cmdLine.command = 'toCsn';
156
156
  cmdLine.options.toCsn = cmdLine.options.parseCdl;
157
157
  cmdLine.options.parseCdl = true;
@@ -704,7 +704,11 @@ async function executeCommandLine( command, options, args ) {
704
704
  return;
705
705
  if (!omitHeadline) {
706
706
  const sqlTypes = {
707
- sql: true, hdbconstraint: true, hdbtable: true, hdbview: true,
707
+ sql: true,
708
+ hdbconstraint: true,
709
+ hdbtable: true,
710
+ hdbview: true,
711
+ hdbprojectionview: true,
708
712
  };
709
713
  const commentStarter = fileName.split('.').pop() in sqlTypes ? '--$' : '//';
710
714
  process.stdout.write(`${ commentStarter } ------------------- ${ fileName } -------------------\n`);
package/bin/cdsse.js CHANGED
@@ -10,7 +10,7 @@
10
10
  // capabilities supported at the moments is: complete, find, lint.
11
11
  // Syntax highlighting is supported by ./cdshi.js.
12
12
 
13
- /* eslint @stylistic/js/max-len: 0, no-console: 0 */
13
+ /* eslint @stylistic/max-len: 0, no-console: 0 */
14
14
 
15
15
  // @ts-nocheck
16
16
 
package/bin/cdsv2m.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- /* eslint @stylistic/js/max-len: 0, no-console: 0 */
3
+ /* eslint @stylistic/max-len: 0, no-console: 0 */
4
4
 
5
5
  'use strict';
6
6
 
package/lib/api/main.js CHANGED
@@ -15,6 +15,7 @@ const generateDrafts = lazyload('../transform/draft/odata');
15
15
  const tenant = lazyload('../transform/addTenantFields');
16
16
  const toSql = lazyload('../render/toSql');
17
17
  const toCdl = require('../render/toCdl');
18
+ const sqlRenderUtils = lazyload('../render/utils/sql');
18
19
  const modelCompare = lazyload('../modelCompare/compare');
19
20
  const diffFilter = lazyload('../modelCompare/utils/filter');
20
21
  const sortViews = lazyload('../model/sortViews');
@@ -386,11 +387,13 @@ function hdi( csn, options, messageFunctions ) {
386
387
 
387
388
  objectUtils.forEach(flat, (key) => {
388
389
  const artifactNameLikeInCsn = key.replace(/\.[^/.]+$/, '');
389
- nameMapping[artifactNameLikeInCsn] = key;
390
- if (key.endsWith('.hdbtable') || key.endsWith('.hdbview'))
390
+ if (key.endsWith('.hdbtable') || key.endsWith('.hdbview') || key.endsWith('.hdbprojectionview')) {
391
+ nameMapping[artifactNameLikeInCsn] = key;
391
392
  sqlArtifactsWithCSNNamesToSort[artifactNameLikeInCsn] = flat[key];
392
- else
393
+ }
394
+ else {
393
395
  sqlArtifactsNotToSort[key] = flat[key];
396
+ }
394
397
  });
395
398
 
396
399
  const sorted = sortViews({ sql: sqlArtifactsWithCSNNamesToSort, csn: sqlCSN })
@@ -687,7 +690,7 @@ function hdiMigration( csn, options, messageFunctions, beforeImage ) {
687
690
  return {
688
691
  afterImage,
689
692
  definitions: createSqlDefinitions(hdbkinds, afterImage),
690
- deletions: createSqlDeletions(deletions, beforeImage),
693
+ deletions: createSqlDeletions(deletions, beforeImage, options),
691
694
  migrations: createSqlMigrations(migrations, afterImage),
692
695
  };
693
696
  }
@@ -719,11 +722,30 @@ function createSqlDefinitions( hdbkinds, afterImage ) {
719
722
  * @param {CSN.Model} beforeImage CSN used to create correct file names in result structure.
720
723
  * @returns {object[]} Array of objects, each having: name and suffix - only .hdbtable as suffix for now
721
724
  */
722
- function createSqlDeletions( deletions, beforeImage ) {
725
+ function createSqlDeletions( deletions, beforeImage, options ) {
723
726
  const result = [];
724
- objectUtils.forEach(deletions, name => result.push({ name: getFileName(name, beforeImage), suffix: beforeImage.definitions[name].query ? '.hdbview' : '.hdbtable' }));
727
+ objectUtils.forEach(deletions, name => result.push({
728
+ name: getFileName(name, beforeImage), suffix: getSuffix(beforeImage, beforeImage.definitions[name], options),
729
+ }));
725
730
  return result;
726
731
  }
732
+
733
+ /**
734
+ * Determines the appropriate file suffix based on the type of the provided artifact.
735
+ *
736
+ * @param {CSN.Artifact} artifact - The artifact object to evaluate.
737
+ * @returns {string} The file suffix corresponding to the artifact type:
738
+ * - '.hdbview' for query artifacts
739
+ * - '.hdbprojectionview' for projection artifacts
740
+ * - '.hdbtable' for other artifacts
741
+ */
742
+ function getSuffix( csn, artifact, options ) {
743
+ if (artifact.query || artifact.projection)
744
+ return sqlRenderUtils.isProjectionView(csn, artifact, options) ? '.hdbprojectionview' : '.hdbview';
745
+
746
+ return '.hdbtable';
747
+ }
748
+
727
749
  /**
728
750
  * From the given migrations, create the correct result structure.
729
751
  *
@@ -1382,7 +1404,7 @@ function handleTenantDiscriminator( options, internalOptions, messageFunctions )
1382
1404
  */
1383
1405
 
1384
1406
  /**
1385
- * A map of { <file.hdbtable|hdbview|hdbconstraint...>:<content> }.
1407
+ * A map of { <file.hdbtable|hdbview|hdbprojectionview|hdbconstraint...>:<content> }.
1386
1408
  *
1387
1409
  * @typedef {object} HDIArtifacts
1388
1410
  */
@@ -29,6 +29,14 @@ const propagationRules = {
29
29
  '@sql.append': 'never',
30
30
  '@sql.prepend': 'never',
31
31
  '@sql.replace': 'never',
32
+ '@extension.code': 'never', // for cds-oyster, not security relevant, but convenience
33
+ };
34
+ /**
35
+ * Of the annotations above, only these accept expressions as annotation values.
36
+ */
37
+ const acceptsExprValues = {
38
+ __proto__: null,
39
+ '@extension.code': true,
32
40
  };
33
41
 
34
42
  /**
@@ -101,6 +109,7 @@ function isAnnotationExpression( val ) {
101
109
 
102
110
  module.exports = {
103
111
  propagationRules,
112
+ acceptsExprValues,
104
113
  xprInAnnoProperties,
105
114
  isInReservedNamespace,
106
115
  isBuiltinType,
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- /* eslint @stylistic/js/no-multi-spaces: 0 */
3
+ /* eslint @stylistic/no-multi-spaces: 0 */
4
4
 
5
5
  const { functionsWithoutParentheses, cdlKeywords } = require('../parsers/identifiers');
6
6
 
@@ -29,9 +29,9 @@
29
29
  // "sloppy": with an upcoming _minor_ version of the compiler, the compilation
30
30
  // might lead to an error anyway or the compiled CSN might look different.
31
31
 
32
- /* eslint @stylistic/js/no-multi-spaces: 0 */
33
- /* eslint @stylistic/js/max-len: 0 */
34
- /* eslint @stylistic/js/key-spacing: 0 */
32
+ /* eslint @stylistic/no-multi-spaces: 0 */
33
+ /* eslint @stylistic/max-len: 0 */
34
+ /* eslint @stylistic/key-spacing: 0 */
35
35
 
36
36
  'use strict';
37
37
 
@@ -1219,11 +1219,13 @@ const centralMessageTexts = {
1219
1219
  'type-invalid-cast': {
1220
1220
  std: 'Can\'t cast to $(TYPE)',
1221
1221
  'to-structure': 'Can\'t cast to a structured type',
1222
+ 'to-inline-structure': 'Can\'t cast to a structure',
1222
1223
  'from-structure': 'Structured elements can\'t be cast to a different type',
1223
1224
  'expr-to-structure': 'Can\'t cast an expression to a structured type',
1224
1225
  'val-to-structure': 'Can\'t cast $(VALUE) to a structured type',
1225
1226
  'from-assoc': 'Invalid type cast on an association',
1226
1227
  assoc: 'Can\'t cast to an association',
1228
+ expand: 'Expands can\'t have a type cast', // TODO: Improve
1227
1229
  },
1228
1230
 
1229
1231
  // -----------------------------------------------------------------------------------
@@ -1329,8 +1329,8 @@ function compareMessage( a, b ) {
1329
1329
  const aFile = a.$location && a.$location.file;
1330
1330
  const bFile = b.$location && b.$location.file;
1331
1331
  if (aFile && bFile) {
1332
- const aEnd = a.$location.endLine && a.$location.endCol && a.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/js/max-len
1333
- const bEnd = b.$location.endLine && b.$location.endCol && b.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/js/max-len
1332
+ const aEnd = a.$location.endLine && a.$location.endCol && a.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/max-len
1333
+ const bEnd = b.$location.endLine && b.$location.endCol && b.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/max-len
1334
1334
  return ( c( aFile, bFile ) ||
1335
1335
  c( a.$location.line, b.$location.line ) ||
1336
1336
  c( a.$location.col, b.$location.col ) ||
package/lib/base/model.js CHANGED
@@ -29,6 +29,7 @@ const availableBetaFlags = {
29
29
  draftMessages: true,
30
30
  rewriteAnnotationExpressionsViaType: true,
31
31
  sqlServiceDummies: true,
32
+ projectionViews: true,
32
33
  // disabled by --beta-mode
33
34
  nestedServices: false,
34
35
  };
@@ -71,8 +71,9 @@ function createOptionProcessor() {
71
71
  /**
72
72
  * API: Define a command
73
73
  * @param {string} cmdString Command name, short and long form, e.g. 'S, toSql'
74
+ * @param {object} [cmdOptions] Optional options, e.g. `aliases`.
74
75
  */
75
- function command( cmdString ) {
76
+ function command( cmdString, cmdOptions = null ) {
76
77
  /** @type {object} */
77
78
  const cmd = {
78
79
  options: {},
@@ -83,6 +84,7 @@ function createOptionProcessor() {
83
84
  return cmd;
84
85
  },
85
86
  help: commandHelp,
87
+ aliases: cmdOptions?.aliases,
86
88
  ..._parseCommandString(cmdString),
87
89
  };
88
90
  if (optionProcessor.commands[cmd.longName])
@@ -96,6 +98,10 @@ function createOptionProcessor() {
96
98
 
97
99
  optionProcessor.commands[cmd.shortName] = cmd;
98
100
  }
101
+
102
+ for (const alias of cmdOptions?.aliases ?? [])
103
+ optionProcessor.commands[alias] = cmd;
104
+
99
105
  return cmd;
100
106
 
101
107
  // Command API: Define a command option
@@ -400,7 +406,6 @@ function createOptionProcessor() {
400
406
  else
401
407
  result.errors.push(errorMsg);
402
408
  }
403
-
404
409
  return result;
405
410
 
406
411
  /**
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { setProp } = require('../base/model');
4
4
  const { featureFlags } = require('../transform/featureFlags');
5
- const { isSqlService, isDummyService } = require('../transform/db/processSqlServices');
5
+ const { isSqlService, isDummyService, isDataProductService } = require('../transform/db/processSqlServices');
6
6
 
7
7
  /**
8
8
  *
@@ -32,5 +32,8 @@ module.exports = {
32
32
 
33
33
  if (isDummyService(artifact, this.options))
34
34
  setFeatureFlag( '$dummyService' ).call(this);
35
+
36
+ if (isDataProductService(artifact, this.options))
37
+ setFeatureFlag( '$dataProductService' ).call(this);
35
38
  },
36
39
  };
@@ -647,6 +647,8 @@ function assertConsistency( model, stage ) {
647
647
  'where', 'columns', 'mixin', 'quantifier', 'offset',
648
648
  'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
649
649
  '$limit', 'limit', '_status', '_origin',
650
+ // via casts
651
+ 'enum',
650
652
  ],
651
653
  },
652
654
  _leadingQuery: { kind: true, test: TODO },
@@ -1059,7 +1061,7 @@ function assertConsistency( model, stage ) {
1059
1061
  // TODO
1060
1062
  // else if (spec.instanceOf && spec.instanceOf !== 'ignore' &&
1061
1063
  // Object.getPrototypeOf( node ) !== spec.instanceOf.prototype)
1062
- // eslint-disable-next-line @stylistic/js/max-len
1064
+ // eslint-disable-next-line @stylistic/max-len
1063
1065
  // throw new InternalConsistencyError( `Expected object of class ${ spec.instanceOf.name } but found ${ found }${ at( [ null, parent ], prop, name ) }` );
1064
1066
  }
1065
1067
 
@@ -21,7 +21,7 @@ const kindProperties = {
21
21
  entity: {
22
22
  elements: true, actions: true, params: () => false, include: true,
23
23
  },
24
- select: { normalized: 'select', elements: true },
24
+ select: { normalized: 'select', elements: propExists, enum: propExists },
25
25
  $join: { normalized: 'select' },
26
26
  $tableAlias: { normalized: 'alias' }, // table alias in select
27
27
  $self: { normalized: 'alias' }, // table alias in select
@@ -222,7 +222,7 @@ const dateRegEx = /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/;
222
222
  // YYYY - MM - dd
223
223
  const timeRegEx = /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
224
224
  // T HH : mm : ss TZD
225
- // eslint-disable-next-line @stylistic/js/max-len, sonarjs/regex-complexity
225
+ // eslint-disable-next-line @stylistic/max-len, sonarjs/regex-complexity
226
226
  const timestampRegEx = /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
227
227
  // YYYY - MM - dd T HH : mm : ss . fraction TZD
228
228
  // eslint-disable-next-line sonarjs/regex-complexity
@@ -5,7 +5,6 @@
5
5
  // * Different ad-hoc value/type checks (associations, enum, ...) -
6
6
  // specify a proper one and use consistently
7
7
  // * Using name comparisons instead proper object comparisons.
8
- // * effectiveType issues.
9
8
  // * Often forgot to consider CSN input
10
9
 
11
10
  'use strict';
@@ -18,7 +17,7 @@ const {
18
17
  isDeprecatedEnabled,
19
18
  } = require('../base/model');
20
19
  const { typeParameters } = require('./builtins');
21
- const { propagationRules } = require('../base/builtins');
20
+ const { propagationRules, acceptsExprValues } = require('../base/builtins');
22
21
  const { annotationVal } = require('./utils');
23
22
 
24
23
  const $location = Symbol.for( 'cds.$location' );
@@ -84,7 +83,7 @@ function check( model ) {
84
83
  checkName( art );
85
84
  checkTypeArguments( art );
86
85
 
87
- if (art.value && !art.$calcDepElement && art.type)
86
+ if (art.value && !art.$calcDepElement && (art.type || art.elements || art.items))
88
87
  checkTypeCast( art.value, art );
89
88
 
90
89
  for (const anno of iterateAnnotations( art ))
@@ -245,25 +244,43 @@ function check( model ) {
245
244
  }
246
245
 
247
246
  function checkTypeCast( xpr, user ) {
248
- const isCast = (xpr.op?.val === 'cast');
249
- const elem = isCast
247
+ const isSqlCast = (xpr.op?.val === 'cast');
248
+ const elem = isSqlCast
250
249
  ? xpr.args?.[0]?._artifact
251
250
  : xpr._artifact;
252
- const type = isCast ? xpr.type : user.type;
253
- if (!isCast && type.$inferred)
251
+
252
+ const typeArt = isSqlCast ? xpr : user;
253
+ if (!elem || !isSqlCast && typeArt.type?.$inferred)
254
254
  return; // e.g. $inferred:'generated'
255
- if (elem && type) { // has explicit type
256
- if (type._artifact?._effectiveType?.name.id === 'cds.Map')
255
+
256
+ const { type } = typeArt;
257
+ if (type) { // has explicit type
258
+ if (type._artifact?._effectiveType?.name.id === 'cds.Map') {
257
259
  error( 'type-invalid-cast', [ type.location, user ], { '#': 'std', type: 'cds.Map' } );
258
- else if (type._artifact?.elements)
260
+ }
261
+ else if (type._artifact?.elements) {
259
262
  error( 'type-invalid-cast', [ type.location, user ], { '#': 'to-structure' } );
260
- else if (elem.elements) // TODO: calc elements
263
+ }
264
+ else if (elem.elements) { // TODO: calc elements
261
265
  error( 'type-invalid-cast', [ type.location, user ], { '#': 'from-structure' } );
262
- else if (elem.target && !type._artifact?.target)
266
+ }
267
+ else if (elem.target && !type._artifact?.target) {
263
268
  error( 'type-invalid-cast', [ type.location, user ], { '#': 'from-assoc' } );
264
- else if (!elem.target && type._artifact?.target && !user.type?.$inferred)
265
- // $inferred already reported in resolve.js
266
- error( 'type-invalid-cast', [ type.location, user ], { '#': 'assoc' } );
269
+ }
270
+ else if (!elem.target && // referenced element is not association
271
+ !user.type?.$inferred && // $inferred types already reported in resolve.js.
272
+ (
273
+ // assoc used in SQL cast
274
+ type._artifact?.target && isSqlCast ||
275
+ // there is a target and the type is a direct `cds.Association`;
276
+ // other types handled by resolver already.
277
+ typeArt.target && type._artifact?.category === 'relation'
278
+ )
279
+ ) {
280
+ // - redirection-check in resolve.js already checks this for CDL-casts
281
+ // - `"cast": { "target": "…", "type": "cds.Association", … }` via CSN input.
282
+ error('type-invalid-cast', [ type.location, user ], { '#': 'assoc' });
283
+ }
267
284
  }
268
285
  }
269
286
 
@@ -935,16 +952,16 @@ function check( model ) {
935
952
  // One argument must be "$self" and the other an assoc
936
953
  if (xpr.op.val === '=' && xpr.args.length === 2) {
937
954
  // Tree-ish expression from the compiler (not augmented)
938
- // eslint-disable-next-line @stylistic/js/max-len
955
+ // eslint-disable-next-line @stylistic/max-len
939
956
  return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[1] ) ||
940
- // eslint-disable-next-line @stylistic/js/max-len
957
+ // eslint-disable-next-line @stylistic/max-len
941
958
  isAssociationOperand( xpr.args[1] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
942
959
  }
943
960
  else if (xpr.args.length === 3 && xpr.args[1].val === '=') {
944
961
  // Tree-ish expression from the compiler (not augmented)
945
- // eslint-disable-next-line @stylistic/js/max-len
962
+ // eslint-disable-next-line @stylistic/max-len
946
963
  return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[2] ) ||
947
- // eslint-disable-next-line @stylistic/js/max-len
964
+ // eslint-disable-next-line @stylistic/max-len
948
965
  isAssociationOperand( xpr.args[2] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
949
966
  }
950
967
 
@@ -963,7 +980,7 @@ function check( model ) {
963
980
  const name = anno.name?.id;
964
981
  if (!name)
965
982
  return true;
966
- if (!propagationRules[`@${ name }`])
983
+ if (!propagationRules[`@${ name }`] || acceptsExprValues[`@${ name }`])
967
984
  return true;
968
985
  error( 'anno-unexpected-expr', [ anno.location, art ], { anno: name },
969
986
  'Unexpected expression as value for $(ANNO)' );
@@ -1165,7 +1182,7 @@ function check( model ) {
1165
1182
  value.literal !== 'timestamp' && value.literal !== 'string') {
1166
1183
  // Hm, actually date and time cannot be mixed
1167
1184
  warning( null, loc, { type, anno },
1168
- // eslint-disable-next-line @stylistic/js/max-len
1185
+ // eslint-disable-next-line @stylistic/max-len
1169
1186
  'A date/time value or a string is required for type $(TYPE) for annotation $(ANNO)' );
1170
1187
  }
1171
1188
  }
@@ -1010,6 +1010,7 @@ function define( model ) {
1010
1010
  }
1011
1011
  // Either expression (value), expand, new virtual or new association
1012
1012
  else if (col.value || col.name) {
1013
+ col.kind = 'element';
1013
1014
  if (!col._block)
1014
1015
  setLink( col, '_block', parent._block );
1015
1016
  if (col.inline) { // `@anno elem.{ * }` does not work
@@ -1031,8 +1032,7 @@ function define( model ) {
1031
1032
  initSelectItems( col, null, user ); // TODO: use col as user (i.e. remove param)
1032
1033
  }
1033
1034
 
1034
- initItemsLinks( col, parent._block );
1035
- initExprAnnoBlock( col, parent._block );
1035
+ initCdlTypeCast( col, parent );
1036
1036
  }
1037
1037
 
1038
1038
  if (hasItems && !wildcard && parent.excludingDict && !options.$recompile) {
@@ -1044,6 +1044,26 @@ function define( model ) {
1044
1044
  }
1045
1045
  }
1046
1046
 
1047
+ function initCdlTypeCast( col, parent ) {
1048
+ if (col.val)
1049
+ return; // e.g. '*' column
1050
+
1051
+ setMemberParent( col, col.name, parent );
1052
+ initMembers( col, col, col._block );
1053
+
1054
+ // We don't allow CDL-style casts to anonymous structures. We reject it already here
1055
+ // and not in checks.js to ensure that it's rejected in parseCdl.
1056
+ if (col.elements) {
1057
+ error('type-invalid-cast', [ col.elements[$location] ?? col.location, col ],
1058
+ { '#': 'to-inline-structure' });
1059
+ }
1060
+ else if (col.expand && (col.type || col.elements || col.items)) {
1061
+ const loc = (col.type?.location || col.elements?.[$location] ||
1062
+ col.items?.location || col.location);
1063
+ error('type-invalid-cast', [ loc, col ], { '#': 'expand' });
1064
+ }
1065
+ }
1066
+
1047
1067
  /**
1048
1068
  * If we have a valid top-level exists, exists in filters of sub-expressions can be translated,
1049
1069
  * since we will have a top-level subquery after exists-processing in the forRelationalDB.
@@ -1150,7 +1170,7 @@ function define( model ) {
1150
1170
  // We do not want to complain separately about all element properties:
1151
1171
  error( 'ext-unexpected-element', [ e.location, construct ],
1152
1172
  { name: e.name.id, code: 'extend … with enum' },
1153
- // eslint-disable-next-line @stylistic/js/max-len
1173
+ // eslint-disable-next-line @stylistic/max-len
1154
1174
  'Unexpected elements like $(NAME) in an extension for an enum. Additionally, use $(CODE) when extending enums' );
1155
1175
  // Don't emit 'ext-expecting-enum' if this error is emitted.
1156
1176
  return;
@@ -1238,7 +1258,6 @@ function define( model ) {
1238
1258
  if (elem.$duplicates === true && add)
1239
1259
  elem.$duplicates = null;
1240
1260
  setMemberParent( elem, name, parent, add && prop );
1241
- // console.log(message( null, elem.location, elem, {}, 'Info', 'INIT').toString())
1242
1261
  checkRedefinition( elem );
1243
1262
  initMembers( elem, elem, bl, initExtensions );
1244
1263
  if (boundSelfParamType && (elem.kind === 'action' || elem.kind === 'function'))
@@ -1320,7 +1339,7 @@ function define( model ) {
1320
1339
  // - artifacts (CDL-only anyway) only inside [extend] context|service
1321
1340
  if (!dict)
1322
1341
  return false;
1323
- const feature = kindProperties[parent.kind][prop];
1342
+ const feature = kindProperties[parent.kind ?? 'element'][prop];
1324
1343
  if (feature &&
1325
1344
  (feature === true || construct.kind !== 'extend' || feature( prop, parent )))
1326
1345
  return true;
@@ -561,7 +561,7 @@ function extend( model ) {
561
561
  {
562
562
  std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
563
563
  array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
564
- // eslint-disable-next-line @stylistic/js/max-len
564
+ // eslint-disable-next-line @stylistic/max-len
565
565
  struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
566
566
  boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
567
567
  null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
@@ -117,7 +117,15 @@ function finalizeParseCdl( model ) {
117
117
  // containing it. Otherwise some type's aren't properly resolved.
118
118
  // TODO: If resolveTypeUnchecked is reworked, we may be able to simplify this coding.
119
119
  (artifact.$queries || []).forEach( art => resolveTypesForParseCdl( art, artifact ) );
120
- (artifact.columns || []).forEach( art => resolveTypesForParseCdl( art, artifact ) );
120
+ (artifact.columns || []).forEach( (col) => {
121
+ // TODO: Can we use "ensureColumnName" of populate.js? It depends on column indices
122
+ // _after_ wildcards were expanded, though.
123
+ if (!col.name && col.value?.path) {
124
+ const last = col.value.path.at(-1);
125
+ col.name = { id: last?.id || '', location: last?.location, $inferred: 'as' };
126
+ }
127
+ resolveTypesForParseCdl( col, artifact );
128
+ } );
121
129
  forEachGeneric( artifact, 'mixin', art => resolveTypesForParseCdl( art, artifact ) );
122
130
 
123
131
  // For better error messages for `type of`s in `returns`, we pass the object as the new main.
@@ -104,7 +104,7 @@ function generate( model ) {
104
104
  const lang = textsAspect.elements.language;
105
105
  error( 'def-unexpected-element', [ lang.name.location, lang ],
106
106
  { option: 'addTextsLanguageAssoc', art: textsAspect, name: 'language' },
107
- // eslint-disable-next-line @stylistic/js/max-len
107
+ // eslint-disable-next-line @stylistic/max-len
108
108
  '$(ART) is not used because option $(OPTION) conflicts with existing element $(NAME); remove either option or element' );
109
109
  hasError = true;
110
110
  }
@@ -209,7 +209,7 @@ function generate( model ) {
209
209
  conflictingElements.push( elem );
210
210
 
211
211
  const isKey = elem.key && elem.key.val;
212
- const isLocalized = hasTruthyProp( elem, 'localized' );
212
+ const isLocalized = elem.$syntax !== 'calc' && hasTruthyProp( elem, 'localized' );
213
213
 
214
214
  if (isKey) {
215
215
  keys += 1;
@@ -245,7 +245,7 @@ function generate( model ) {
245
245
  (fioriEnabled && art.elements.ID_texts)) {
246
246
  // TODO if we have too much time: check all elements of texts entity for safety
247
247
  warning( null, [ art.name.location, art ], { art: textsEntity },
248
- // eslint-disable-next-line @stylistic/js/max-len
248
+ // eslint-disable-next-line @stylistic/max-len
249
249
  'Texts entity $(ART) can\'t be created as there is another definition with that name' );
250
250
  info( null, [ textsEntity.name.location, textsEntity ], { art },
251
251
  'Texts entity for $(ART) can\'t be created with this definition' );
@@ -640,7 +640,7 @@ function generate( model ) {
640
640
  }
641
641
  if (model.definitions[entityName]) {
642
642
  error( null, [ location, elem ], { art: entityName },
643
- // eslint-disable-next-line @stylistic/js/max-len
643
+ // eslint-disable-next-line @stylistic/max-len
644
644
  'Target entity $(ART) can\'t be created as there is another definition with this name' );
645
645
  return false;
646
646
  }
@@ -397,6 +397,8 @@ function* nameAsReference( ref, hint = null ) {
397
397
  function* definitionNameTokens( name, art ) {
398
398
  if (!art.kind)
399
399
  return null; // e.g. parameter references
400
+ if (!name)
401
+ return null; // e.g. column that couldn't be populated
400
402
  if (art.kind === '$annotation')
401
403
  return null; // annotation name, e.g. in `@anno: (elem)`
402
404