@sap/cds-compiler 5.3.2 → 5.4.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 (49) hide show
  1. package/CHANGELOG.md +29 -2
  2. package/bin/cdsc.js +1 -1
  3. package/doc/CHANGELOG_BETA.md +2 -2
  4. package/lib/api/options.js +4 -2
  5. package/lib/base/builtins.js +0 -10
  6. package/lib/base/keywords.js +3 -31
  7. package/lib/base/message-registry.js +23 -5
  8. package/lib/base/messages.js +1 -1
  9. package/lib/checks/existsMustEndInAssoc.js +7 -2
  10. package/lib/checks/foreignKeys.js +12 -7
  11. package/lib/compiler/assert-consistency.js +11 -3
  12. package/lib/compiler/builtins.js +2 -0
  13. package/lib/compiler/checks.js +88 -38
  14. package/lib/compiler/define.js +2 -2
  15. package/lib/compiler/shared.js +9 -10
  16. package/lib/compiler/xpr-rewrite.js +11 -0
  17. package/lib/compiler/xsn-model.js +1 -1
  18. package/lib/edm/csn2edm.js +2 -0
  19. package/lib/edm/edm.js +2 -1
  20. package/lib/edm/edmPreprocessor.js +14 -1
  21. package/lib/edm/edmUtils.js +17 -2
  22. package/lib/gen/BaseParser.js +291 -197
  23. package/lib/gen/CdlParser.js +1631 -1605
  24. package/lib/gen/Dictionary.json +74 -6
  25. package/lib/gen/language.checksum +1 -1
  26. package/lib/gen/language.interp +1 -1
  27. package/lib/gen/languageParser.js +1808 -1804
  28. package/lib/language/antlrParser.js +8 -4
  29. package/lib/language/genericAntlrParser.js +3 -3
  30. package/lib/model/csnUtils.js +6 -1
  31. package/lib/optionProcessor.js +4 -0
  32. package/lib/parsers/AstBuildingParser.js +172 -108
  33. package/lib/parsers/CdlGrammar.g4 +154 -134
  34. package/lib/parsers/Lexer.js +3 -3
  35. package/lib/parsers/identifiers.js +59 -0
  36. package/lib/render/toCdl.js +5 -5
  37. package/lib/render/utils/common.js +5 -0
  38. package/lib/render/utils/delta.js +23 -5
  39. package/lib/transform/db/expansion.js +2 -1
  40. package/lib/transform/db/transformExists.js +10 -9
  41. package/lib/transform/effective/annotations.js +147 -0
  42. package/lib/transform/effective/main.js +16 -2
  43. package/lib/transform/forOdata.js +53 -10
  44. package/lib/transform/forRelationalDB.js +7 -0
  45. package/lib/transform/odata/createForeignKeys.js +180 -0
  46. package/lib/transform/odata/flattening.js +135 -19
  47. package/lib/transform/odata/typesExposure.js +4 -3
  48. package/lib/transform/transformUtils.js +6 -6
  49. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,33 @@
7
7
  Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
8
8
  The compiler behavior concerning `beta` features can change at any time without notice.
9
9
 
10
+ ## Version 5.4.2 - 2024-11-06
11
+
12
+ ### Fixed
13
+
14
+ - to.sql: For SQLite, map `cds.Map` to `JSON_TEXT` to ensure text affinity.
15
+
16
+ ## Version 5.4.0 - 2024-10-24
17
+
18
+ ### Added
19
+
20
+ - to.edm(x): `cds.Map` as empty open complex type with name `cds_Map` or if the definition
21
+ has been assigned `@open: false` as empty open complex type `cds_Map_closed` in OData V4.
22
+
23
+ ### Changed
24
+
25
+ - Update OData vocabularies: 'Capabilities', 'Common', 'Core', 'PersonalData', 'PDF', 'UI'.
26
+ - to.cdl: Identifiers using non-ASCII unicode characters, as introduced in v4.4.0, are no longer quoted.
27
+ - For propagated expressions as annotation values, the `=` is changed as well, if it is a simple identifier.
28
+
29
+ ### Fixed
30
+
31
+ - compiler: Some invalid CDL snippets could crash the parser and compiler.
32
+ - to.edm(x): OData V2: `Core.Links` watermark annotation has a `xmlns` attribute now.
33
+ - for.seal: Remove unapplied extensions from CSN.
34
+ - to.sql.migration: Handle `ALTER COLUMN` for columns with `NOT NULL` and a default value.
35
+
36
+
10
37
  ## Version 5.3.2 - 2024-10-08
11
38
 
12
39
  ### Fixed
@@ -19,8 +46,8 @@ The compiler behavior concerning `beta` features can change at any time without
19
46
  ### Added
20
47
 
21
48
  - compiler:
22
- - A warning is emitted if a string enum's values are longer than the specified length.
23
- - ON-condition rewriting has been improved and now supports secondary associations.
49
+ + A warning is emitted if a string enum's values are longer than the specified length.
50
+ + ON-condition rewriting has been improved and now supports secondary associations.
24
51
  - to.edm(x): Support optional action and function parameters in OData V4. The following rules apply:
25
52
  + A parameter declared `not null` without default value is mandatory.
26
53
  + A **function** parameter declared `null` without default value is mandatory.
package/bin/cdsc.js CHANGED
@@ -370,7 +370,7 @@ async function executeCommandLine( command, options, args ) {
370
370
  }
371
371
 
372
372
  function forSeal( model ) {
373
- const features = [ 'remapOdataAnnotations' ];
373
+ const features = [ 'remapOdataAnnotations', 'deriveAnalyticalAnnotations' ];
374
374
  for (const feature of features) {
375
375
  if (options[feature]) // map to boolean equivalent
376
376
  options[feature] = options[feature] === 'true';
@@ -305,9 +305,9 @@ This is now the default - see CHANGELOG entry for 2.6.0
305
305
  - `toSql`/`toHdbcds`: omit constraint generation if the option `skipDbConstraints` is set
306
306
  - If the database constraints are switched off by the global option,
307
307
  render constraints nevertheless if an association / composition
308
- is annotated with `@cds.persistency.assert.integrity: true`
308
+ is annotated with `@cds.persistence.assert.integrity: true`
309
309
  - omit constraint generation if an association / composition
310
- is annotated with `@cds.persistency.assert.integrity: false`
310
+ is annotated with `@cds.persistence.assert.integrity: false`
311
311
  -> for managed compositions, the `up_` link in the compositions target entity
312
312
  will not result in a constraint if the composition is annotated as described
313
313
 
@@ -54,6 +54,8 @@ const publicOptionsNewAPI = [
54
54
  'resolveProjections',
55
55
  'remapOdataAnnotations',
56
56
  'keepLocalized',
57
+ // for.seal
58
+ 'deriveAnalyticalAnnotations',
57
59
  // to.sql.migration
58
60
  'script',
59
61
  ];
@@ -210,7 +212,7 @@ module.exports = {
210
212
  return translateOptions(options, defaultOptions, hardOptions, undefined, undefined, 'for.hana');
211
213
  },
212
214
  effective: (options) => {
213
- const hardOptions = { addCdsPersistenceName: false };
215
+ const hardOptions = { addCdsPersistenceName: false, deriveAnalyticalAnnotations: false };
214
216
  const defaultOptions = {
215
217
  sqlMapping: 'plain', resolveSimpleTypes: true, resolveProjections: true, remapOdataAnnotations: false, keepLocalized: false,
216
218
  };
@@ -222,7 +224,7 @@ module.exports = {
222
224
  const hardOptions = {
223
225
  sqlMapping: 'plain', resolveSimpleTypes: true, resolveProjections: true, keepLocalized: false, addCdsPersistenceName: true,
224
226
  };
225
- const defaultOptions = { remapOdataAnnotations: true };
227
+ const defaultOptions = { remapOdataAnnotations: true, deriveAnalyticalAnnotations: false };
226
228
  const processed = translateOptions(options, defaultOptions, hardOptions, null, [ 'sql-dialect-and-naming' ], 'for.effective');
227
229
 
228
230
  return Object.assign({}, processed);
@@ -90,15 +90,6 @@ const xprInAnnoProperties = [
90
90
  'cast',
91
91
  ];
92
92
 
93
- /**
94
- * Functions without parentheses in CDL (common standard SQL-92 functions)
95
- * (do not add more - make it part of the SQL renderer to remove parentheses for
96
- * other funny SQL functions like CURRENT_UTCTIMESTAMP).
97
- */
98
- const functionsWithoutParens = [
99
- 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',
100
- 'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
101
- ];
102
93
 
103
94
  /**
104
95
  * Return whether JSON object `val` is a representation for an annotation expression
@@ -110,7 +101,6 @@ function isAnnotationExpression( val ) {
110
101
  module.exports = {
111
102
  propagationRules,
112
103
  xprInAnnoProperties,
113
- functionsWithoutParens,
114
104
  isInReservedNamespace,
115
105
  isBuiltinType,
116
106
  isMagicVariable,
@@ -1,40 +1,12 @@
1
1
  'use strict';
2
2
 
3
- const { functionsWithoutParens } = require('./builtins');
3
+ const { functionsWithoutParentheses, cdlKeywords } = require('../parsers/identifiers');
4
4
 
5
5
  module.exports = {
6
- // CDL reserved keywords, used for automatic quoting in 'toCdl' renderer
7
- // Keep in sync with reserved keywords in language.g4
8
- cdl: [
9
- 'ALL',
10
- 'ANY',
11
- 'AS',
12
- 'BY',
13
- 'CASE',
14
- 'CAST',
15
- 'DISTINCT',
16
- 'EXISTS',
17
- 'EXTRACT',
18
- 'FALSE', // boolean
19
- 'FROM',
20
- 'IN',
21
- 'KEY',
22
- 'NEW',
23
- 'NOT',
24
- 'NULL',
25
- 'OF',
26
- 'ON',
27
- 'SELECT',
28
- 'SOME',
29
- 'TRIM',
30
- 'TRUE', // boolean
31
- 'WHEN',
32
- 'WHERE',
33
- 'WITH',
34
- ],
6
+ cdl: cdlKeywords,
35
7
  // CDL functions, used for automatic quoting in 'toCdl' renderer,
36
8
  // only relevant for element references of path length 1.
37
- cdl_functions: functionsWithoutParens,
9
+ cdl_functions: functionsWithoutParentheses,
38
10
  // SQLite keywords, used to warn in 'toSql' renderer with dialect 'sqlite'
39
11
  // Taken from http://www.sqlite.org/draft/lang_keywords.html
40
12
  // Better use keywords in tool/mkkeywordhash.c of a sqlite distribution.
@@ -533,6 +533,13 @@ const centralMessageTexts = {
533
533
  annotation: 'Annotation definitions can\'t have calculated elements',
534
534
  param: 'Parameters can\'t have calculated elements',
535
535
  },
536
+ 'def-invalid-name': {
537
+ std: 'The character \'.\' is not allowed in identifiers',
538
+ element: 'The character \'.\' is not allowed in element names',
539
+ param: 'The character \'.\' is not allowed in parameter names',
540
+ action: 'The character \'.\' is not allowed in bound action names',
541
+ function: 'The character \'.\' is not allowed in bound function names',
542
+ },
536
543
  'ref-invalid-calc-elem': {
537
544
  std: 'Can\'t include artifact with calculated element',
538
545
  event: 'An event can\'t include an artifact with calculated elements',
@@ -579,6 +586,11 @@ const centralMessageTexts = {
579
586
  exists: 'With $(NAME), path steps must not start with $(ID)',
580
587
  'exists-filter': 'Unexpected $(ID) reference in filter of path $(ELEMREF) following “EXISTS” predicate',
581
588
  },
589
+ 'ref-unexpected-map': {
590
+ std: 'Unexpected reference to an element of type $(TYPE)', // unused
591
+ keys: 'Unexpected reference to an element of type $(TYPE) in foreign keys',
592
+ onCond: 'Unexpected reference to an element of type $(TYPE) in an ON-condition',
593
+ },
582
594
  'ref-undefined-def': {
583
595
  std: 'Artifact $(ART) has not been found',
584
596
  // TODO: proposal 'No definition of $(NAME) found',
@@ -695,9 +707,10 @@ const centralMessageTexts = {
695
707
  },
696
708
  'ref-unsupported-type': {
697
709
  std: 'Type $(TYPE) is not supported',
710
+ dialect: 'Type $(TYPE) is not supported for SQL dialect $(VALUE)',
698
711
  hana: 'Type $(TYPE) is only supported for SQL dialect $(VALUE), not $(OTHERVALUE)',
699
712
  hdbcds:'Type $(TYPE) is not supported in HDBCDS',
700
- odata: 'Type $(TYPE) is not supported for OData'
713
+ odata: 'Type $(TYPE) is not supported for OData $(VERSION)'
701
714
  },
702
715
  'ref-unexpected-var': {
703
716
  std: 'Variable $(NAME) can\'t be used here',
@@ -759,9 +772,10 @@ const centralMessageTexts = {
759
772
  'type-unexpected-default': {
760
773
  std: 'Unexpected $(KEYWORD) on an association/composition', // unused
761
774
  multi: 'Unexpected $(KEYWORD); expected exactly one foreign key in combination with default value, but found $(COUNT)',
762
- structured: 'Unexpected $(KEYWORD) in combination with structured foreign key $(NAME); $(KEYWORD) requires a non-structured foreign key',
775
+ structuredKey: 'Unexpected $(KEYWORD) in combination with structured foreign key $(NAME); $(KEYWORD) requires a non-structured foreign key',
763
776
  'onCond': 'Unexpected $(KEYWORD) on an association/composition with ON-condition; $(KEYWORD) requires exactly one foreign key',
764
- 'targetAspect': 'Unexpected $(KEYWORD) on composition of aspect'
777
+ 'targetAspect': 'Unexpected $(KEYWORD) on composition of aspect',
778
+ map: 'Unexpected $(KEYWORD) for type $(TYPE)',
765
779
  },
766
780
  'type-expecting-service-target': {
767
781
  std: 'Expecting service entity $(TARGET)',
@@ -823,6 +837,7 @@ const centralMessageTexts = {
823
837
  virtual: 'Unexpected $(PROP) for virtual element',
824
838
  // TODO: Better message?
825
839
  include: '$(ART) can\'t have additional keys (through include)',
840
+ invalidType: 'Unexpected $(PROP) for element of type $(TYPE)',
826
841
  },
827
842
  'def-unexpected-localized': {
828
843
  std: '$(ART) can\'t have localized elements',
@@ -890,7 +905,10 @@ const centralMessageTexts = {
890
905
  },
891
906
 
892
907
  'ref-expecting-$self': 'Use $(NEWCODE) instead of $(CODE) here or remove $(CODE) altogether if possible; the compiler has rewritten it to $(NEWCODE) in CSN',
893
- 'ref-expecting-assoc': 'Expecting path $(ELEMREF) following “EXISTS” predicate to end with association/composition, found $(TYPE)',
908
+ 'ref-expecting-assoc': {
909
+ std: 'Expecting path $(ELEMREF) following “EXISTS” predicate to end with association/composition',
910
+ 'with-type': 'Expecting path $(ELEMREF) following “EXISTS” predicate to end with association/composition, found $(TYPE)',
911
+ },
894
912
  'ref-expecting-const': 'A constant expression or variable is expected here',
895
913
  'ref-expecting-foreign-key': 'Expecting foreign key access after managed association $(NAME) in filter expression of $(ID), but found $(ALIAS)',
896
914
  'ref-invalid-target': {
@@ -1038,7 +1056,7 @@ const centralMessageTexts = {
1038
1056
  },
1039
1057
 
1040
1058
  'type-invalid-cast': {
1041
- std: 'Invalid cast to $(TYPE)', // unused
1059
+ std: 'Can\'t cast to $(TYPE)',
1042
1060
  'to-structure': 'Can\'t cast to a structured type',
1043
1061
  'from-structure': 'Structured elements can\'t be cast to a different type',
1044
1062
  'expr-to-structure': 'Can\'t cast an expression to a structured type',
@@ -1486,7 +1486,7 @@ function homeName( art, absoluteOnly ) {
1486
1486
  return homeName( art.name._artifact, absoluteOnly ); // use corresponding definition
1487
1487
  let main = art._main || art;
1488
1488
  while (main._outer) // anonymous aspect
1489
- main = main._outer._main;
1489
+ main = main._outer._main || main._outer; // w/o `_main` if wrongly in `type`
1490
1490
  return (absoluteOnly) ? main.name.id : `${ main.kind }:${ artName( art ) }`;
1491
1491
  }
1492
1492
 
@@ -14,8 +14,13 @@ function existsMustEndInAssoc( parent, prop, expression, path ) {
14
14
  const next = expression[i + 1];
15
15
  const { _art } = next;
16
16
  const errorPath = path.concat([ prop, i ]);
17
- if (!next.SELECT && !_art?.target)
18
- this.error('ref-expecting-assoc', errorPath, { elemref: next, type: _art.type });
17
+ if (!next.SELECT && !_art?.target) {
18
+ this.error('ref-expecting-assoc', errorPath, {
19
+ '#': _art.type ? 'with-type' : 'std',
20
+ elemref: next,
21
+ type: _art.type,
22
+ });
23
+ }
19
24
  }
20
25
  }
21
26
  }
@@ -51,13 +51,18 @@ function validateForeignKeys( member, memberName ) {
51
51
  handleStructured(mem);
52
52
  }
53
53
  else if (mem.type) {
54
- const type = mem.type.ref
55
- ? this.artifactRef(mem.type)
56
- : this.csn.definitions[mem.type];
57
- if (type && !type.$visited) {
58
- setProp(type, '$visited', true);
59
- checkForItemsOrMissingType(type, memName);
60
- delete type.$visited;
54
+ if (mem.type === 'cds.Map') {
55
+ this.error(null, member.$path, { type: mem.type }, 'Unexpected type $(TYPE) in foreign key');
56
+ }
57
+ else {
58
+ const type = mem.type.ref
59
+ ? this.artifactRef(mem.type)
60
+ : this.csn.definitions[mem.type];
61
+ if (type && !type.$visited) {
62
+ setProp(type, '$visited', true);
63
+ checkForItemsOrMissingType(type, memName);
64
+ delete type.$visited;
65
+ }
61
66
  }
62
67
  }
63
68
  else if (mem && !mem.type && this.options.transformation !== 'odata') {
@@ -530,6 +530,8 @@ function assertConsistency( model, stage ) {
530
530
  // CSN parser may let these properties slip through to XSN, even if input is invalid.
531
531
  'args', 'op', 'func', 'suffix',
532
532
  '$invalidPaths', '$parens',
533
+ // for invalid CDL (parser issues)
534
+ 'orderBy',
533
535
  ],
534
536
  // TODO: name requires if not in parser?
535
537
  },
@@ -857,9 +859,11 @@ function assertConsistency( model, stage ) {
857
859
  isObject( node, parent, prop, spec, idx );
858
860
 
859
861
  // eslint-disable-next-line no-nested-ternary
860
- const choice = (node.from !== undefined || node.columns)
861
- ? 'select'
862
- : (node.op) ? 'union' : 'none'; // from: null from parse error
862
+ const choice = (!noSyntaxErrors())
863
+ ? 'none'
864
+ : (node.from !== undefined)
865
+ ? 'select'
866
+ : 'union';
863
867
  if (spec[choice])
864
868
  assertProp( node, parent, prop, spec[choice], choice );
865
869
  else
@@ -921,6 +925,10 @@ function assertConsistency( model, stage ) {
921
925
  }
922
926
 
923
927
  function expressionSpec( node ) {
928
+ // When a condition failure is ignored (TODO: we might test specifically
929
+ // against this), an expression could have properties for query clauses:
930
+ if (!noSyntaxErrors())
931
+ return 'none';
924
932
  if (node.path)
925
933
  return 'ref';
926
934
  else if (node.literal || node.val)
@@ -33,6 +33,7 @@ const core = {
33
33
  Boolean: { category: 'boolean' },
34
34
  UUID: { category: 'string' },
35
35
  Vector: { parameters: [ 'length' /* , 'type' */ ], category: 'vector' },
36
+ Map: { category: 'map' },
36
37
  Association: { internal: true, category: 'relation' },
37
38
  Composition: { internal: true, category: 'relation' },
38
39
  };
@@ -336,6 +337,7 @@ const typeCategories = {
336
337
  relation: [],
337
338
  geo: [],
338
339
  vector: [],
340
+ map: [],
339
341
  };
340
342
  // Fill type categories with `cds.*` types
341
343
  Object.keys( core ).forEach( (type) => {
@@ -17,7 +17,6 @@ const {
17
17
  forEachMemberRecursively,
18
18
  isDeprecatedEnabled,
19
19
  } = require('../base/model');
20
- const { CompilerAssertion } = require('../base/error');
21
20
  const { typeParameters } = require('./builtins');
22
21
  const { propagationRules } = require('../base/builtins');
23
22
 
@@ -88,6 +87,7 @@ function check( model ) {
88
87
 
89
88
  checkTypeStructure( art );
90
89
  checkAssociation( art ); // type def could be assoc
90
+ checkDefaultValue( art );
91
91
  if (art.kind === 'enum')
92
92
  checkEnum( art );
93
93
  checkEnumType( art );
@@ -108,22 +108,29 @@ function check( model ) {
108
108
  forEachMember( member, m => checkMember( m, parentProps ) );
109
109
  }
110
110
 
111
- function checkVirtualKey( elem, parentProps ) {
112
- const isKey = parentProps.key?.val || elem.key?.val;
111
+ function checkKey( elem, parentProps ) {
112
+ const key = parentProps.key || elem.key;
113
+ if (!key?.val || key?.$inferred)
114
+ return;
115
+
113
116
  const isVirtual = parentProps.virtual?.val || elem.virtual?.val;
114
- if (isKey && isVirtual) {
117
+ if (isVirtual) {
115
118
  error( 'def-unexpected-key', [ (parentProps.key || elem.key).location, elem ],
116
119
  { '#': 'virtual', prop: 'key' } );
117
120
  }
121
+ else if (elem._effectiveType?.name?.id === 'cds.Map') {
122
+ error( 'def-unexpected-key', [ elem.type?.location || elem.location, elem ],
123
+ { '#': 'invalidType', prop: 'key', type: 'cds.Map' } );
124
+ }
118
125
  }
119
126
 
120
127
  function checkElement( elem, parentProps ) {
121
128
  checkLocalizedSubElement( elem );
122
- checkVirtualKey( elem, parentProps );
129
+ checkKey( elem, parentProps );
123
130
  checkLocalizedElement( elem );
124
131
 
125
132
  if (elem.value) {
126
- if (elem._main.query)
133
+ if (elem._main?.query)
127
134
  checkSelectItemValue( elem );
128
135
  else if (elem.$syntax === 'calc')
129
136
  checkCalculatedElementValue( elem );
@@ -141,8 +148,8 @@ function check( model ) {
141
148
  // Maybe remove the check? But consider runtimes that rely on '.' as element separator.
142
149
  if (construct.kind === 'element' || construct.kind === 'action' || construct.kind === 'param') {
143
150
  if (construct.name.id?.includes( '.' )) {
144
- error( null, [ construct.name.location, construct ], {},
145
- 'The character \'.\' is not allowed in identifiers' );
151
+ error( 'def-invalid-name', [ construct.name.location, construct ],
152
+ { '#': construct.kind || 'std' } );
146
153
  }
147
154
  }
148
155
  }
@@ -238,13 +245,15 @@ function check( model ) {
238
245
  function checkTypeCast( xpr, user ) {
239
246
  const isCast = (xpr.op?.val === 'cast');
240
247
  const elem = isCast
241
- ? xpr.args[0]?._artifact
248
+ ? xpr.args?.[0]?._artifact
242
249
  : xpr._artifact;
243
250
  const type = isCast ? xpr.type : user.type;
244
251
  if (!isCast && type.$inferred)
245
252
  return; // e.g. $inferred:'generated'
246
253
  if (elem && type) { // has explicit type
247
- if (type._artifact?.elements)
254
+ if (type._artifact?._effectiveType?.name.id === 'cds.Map')
255
+ error( 'type-invalid-cast', [ type.location, user ], { '#': 'std', type: 'cds.Map' } );
256
+ else if (type._artifact?.elements)
248
257
  error( 'type-invalid-cast', [ type.location, user ], { '#': 'to-structure' } );
249
258
  else if (elem.elements) // TODO: calc elements
250
259
  error( 'type-invalid-cast', [ type.location, user ], { '#': 'from-structure' } );
@@ -259,8 +268,14 @@ function check( model ) {
259
268
  function checkLocalizedElement( elem ) {
260
269
  if (elem.localized?.val) {
261
270
  const type = elem._effectiveType;
262
- // See discussion issue #6520: should we allow all scalar types?
263
- if (!type || !type.builtin || type.category !== 'string') {
271
+ // TODO(v6): Also for ` || type?.elements`; (#13154)
272
+ if (type?.category === 'map') {
273
+ error( 'ref-unexpected-localized-map', [ elem.type?.location, elem ],
274
+ { keyword: 'localized' },
275
+ 'Map types can\'t be used with $(KEYWORD)' );
276
+ }
277
+ else if (!type || !type.builtin || type.category !== 'string') {
278
+ // See discussion issue #6520: should we allow all scalar types?
264
279
  info( 'ref-expecting-localized-string', [ elem.type?.location, elem ],
265
280
  { keyword: 'localized' },
266
281
  'Expecting a string type in combination with keyword $(KEYWORD)' );
@@ -344,12 +359,22 @@ function check( model ) {
344
359
  if (!type || type.enum)
345
360
  return;
346
361
 
347
- // All builtin types are allowed except binary and relational types.
362
+ // All builtin types are allowed except binary, structured (Map), and relational types.
348
363
  // The latter are "internal" types.
349
- if (!type.builtin || type.internal || type.category === 'binary') {
364
+ // Structures/Arrays are not allowed.
365
+ // TODO(v6): Reverse coding: use allow-list approach; don't forget about geo, etc.
366
+ const invalidEnumBuiltins = {
367
+ __proto__: null,
368
+ structure: 'struct',
369
+ binary: 'binary',
370
+ relation: 'relation',
371
+ vector: 'vector',
372
+ map: 'map',
373
+ };
374
+ if (!type.builtin || type.internal || type.category in invalidEnumBuiltins) {
350
375
  let typeClass = 'std';
351
- if (type.category === 'binary' || type.category === 'relation')
352
- typeClass = type.category;
376
+ if (type.category in invalidEnumBuiltins)
377
+ typeClass = invalidEnumBuiltins[type.category];
353
378
  else if (type.elements)
354
379
  typeClass = 'struct';
355
380
  else if (type.items)
@@ -360,7 +385,9 @@ function check( model ) {
360
385
  binary: 'Binary types are not allowed as enums',
361
386
  relation: 'Relational types are not allowed as enums',
362
387
  struct: 'Structured types are not allowed as enums',
388
+ vector: 'Vector types are not allowed as enums',
363
389
  items: 'Arrayed types are not allowed as enums',
390
+ map: 'Map types are not allowed as enums',
364
391
  } );
365
392
  return;
366
393
  }
@@ -470,12 +497,14 @@ function check( model ) {
470
497
  const isLocalizedSubElement = element.localized?.val;
471
498
  if (isLocalizedSubElement || element.type?._artifact?.localized?.val) {
472
499
  const loc = isLocalizedSubElement ? element.localized.location : element.type.location;
473
- warning( 'localized-sub-element', [ loc, element ],
474
- { type: element.type, '#': isLocalizedSubElement ? 'std' : 'type' },
475
- {
476
- std: 'Keyword "localized" is ignored for sub elements',
477
- type: 'Keyword "localized" in type $(TYPE) is ignored for sub elements',
478
- } );
500
+ warning( 'localized-sub-element', [ loc, element ], {
501
+ type: element.type?._artifact || '',
502
+ '#': isLocalizedSubElement ? 'std' : 'type',
503
+ keyword: 'localized',
504
+ }, {
505
+ std: 'Keyword $(KEYWORD) is ignored for sub elements',
506
+ type: 'Keyword $(KEYWORD) in type $(TYPE) is ignored for sub elements',
507
+ } );
479
508
  }
480
509
  }
481
510
 
@@ -524,11 +553,14 @@ function check( model ) {
524
553
  if (elem.foreignKeys) {
525
554
  for (const k in elem.foreignKeys) {
526
555
  ++fkCount;
556
+ // Note: If the foreign key is structured, we don't check its elements!
527
557
  const key = elem.foreignKeys[k].targetElement;
528
558
  if (key && isVirtualElement( key._artifact ))
529
559
  error( 'ref-unexpected-virtual', [ key.location, elem ], { '#': 'fkey' } );
530
560
  else if (key._artifact?.$syntax === 'calc' && !key._artifact.value.stored?.val)
531
561
  error( 'ref-unexpected-calculated', [ key.location, elem ], { '#': 'fkey' } );
562
+ else if (key._artifact?._effectiveType?.name.id === 'cds.Map')
563
+ error( 'ref-unexpected-map', [ key.location, elem ], { '#': 'keys', type: 'cds.Map' } );
532
564
  }
533
565
  }
534
566
  if (elem.default?.val !== undefined) {
@@ -542,7 +574,7 @@ function check( model ) {
542
574
  const fkName = Object.keys( elem.foreignKeys )[0];
543
575
  if (elem.foreignKeys[fkName].targetElement._artifact?._effectiveType?.elements) {
544
576
  error( 'type-unexpected-default', [ elem.default.location, elem ], {
545
- '#': 'structured', keyword: 'default', name: fkName,
577
+ '#': 'structuredKey', keyword: 'default', name: fkName,
546
578
  } );
547
579
  }
548
580
  }
@@ -551,6 +583,20 @@ function check( model ) {
551
583
  checkOnCondition( elem );
552
584
  }
553
585
 
586
+ function checkDefaultValue( art ) {
587
+ if (!art.default || !art._effectiveType)
588
+ return;
589
+ // TODO(v6): Also reject default for structures (#13154)
590
+ const isStructured = art._effectiveType?.name.id === 'cds.Map';
591
+ if (!isStructured)
592
+ return;
593
+ if (art.default?.val !== undefined) {
594
+ error( 'type-unexpected-default', [ art.default.location, art ], {
595
+ '#': 'map', keyword: 'default', type: 'cds.Map',
596
+ } );
597
+ }
598
+ }
599
+
554
600
  function getBinaryOp( cond ) {
555
601
  const { op, args } = cond;
556
602
  return op?.val === 'ixpr' && args?.length === 3 && args[1].literal === 'token' &&
@@ -579,6 +625,7 @@ function check( model ) {
579
625
  'Used type $(TYPE) is not arrayed and conflicts with $(PROP) property' );
580
626
  }
581
627
  else if (artifact.elements && !finalType.elements) {
628
+ // TODO: Handle cds.Map!
582
629
  warning( 'type-elements-mismatch', [ artifact.type.location, artifact ],
583
630
  { type: artifact.type, prop: 'elements' },
584
631
  'Used type $(TYPE) is not structured and conflicts with $(PROP) property' );
@@ -686,18 +733,22 @@ function check( model ) {
686
733
  function checkOnCondition( elem ) {
687
734
  if (elem.$inferred === 'localized')
688
735
  return; // ignore
736
+ if (!elem.on || elem.on.$inferred)
737
+ return;
689
738
 
690
- // TODO: Move to checkAssociation
691
- if (elem.on && !elem.on.$inferred) {
692
- visitExpression( elem.on, elem, (xpr, user) => {
693
- checkExpressionNotVirtual( xpr, user );
694
- checkExpressionAssociationUsage( xpr, user, true );
739
+ visitExpression( elem.on, elem, (xpr, user) => {
740
+ checkExpressionNotVirtual( xpr, user );
741
+ checkExpressionAssociationUsage( xpr, user, true );
742
+
743
+ if (xpr._artifact?._effectiveType?.name.id === 'cds.Map') {
744
+ error( 'ref-unexpected-map', [ xpr.location, user ], { '#': 'onCond', type: 'cds.Map' } );
745
+ }
746
+ else if (xpr._artifact?.$syntax === 'calc' && !xpr._artifact.value.stored?.val) {
695
747
  // Essential check. Dependency handling for `on` conditions must change if
696
748
  // this is allowed. See test3/Associations/Dependencies/.
697
- if (xpr._artifact?.$syntax === 'calc' && !xpr._artifact.value.stored?.val)
698
- error( 'ref-unexpected-calculated', [ xpr.location, user ], { '#': 'on' } );
699
- } );
700
- }
749
+ error('ref-unexpected-calculated', [ xpr.location, user ], { '#': 'on' });
750
+ }
751
+ } );
701
752
  }
702
753
 
703
754
  function checkSelectItemValue( elem ) {
@@ -969,13 +1020,11 @@ function check( model ) {
969
1020
  return;
970
1021
  }
971
1022
 
972
- // Sanity checks
973
1023
  if (!elementDecl._effectiveType)
974
- throw new CompilerAssertion(`Expecting annotation declaration to have _finalType: ${ JSON.stringify( annoDecl ) }`);
975
-
1024
+ return; // type resolution error
976
1025
 
977
1026
  // Must have literal or path unless it is a boolean
978
- if (!anno.literal && !anno.path && elementDecl._effectiveType?.category !== 'boolean') {
1027
+ if (!anno.literal && !anno.path && elementDecl._effectiveType.category !== 'boolean') {
979
1028
  if (elementDecl.type?._artifact) {
980
1029
  warning( 'anno-expecting-value', [ anno.location || anno.name.location, art, anno ],
981
1030
  { '#': 'type', type: elementDecl.type._artifact } );
@@ -1089,7 +1138,8 @@ function check( model ) {
1089
1138
  // TODO: complain at definition instead
1090
1139
  }
1091
1140
  else if (!type.enum) {
1092
- throw new CompilerAssertion(`Unknown primitive type name: ${ type.name.id }`);
1141
+ // type error somewhere; ignore
1142
+ return;
1093
1143
  }
1094
1144
 
1095
1145
  // Check enums
@@ -1167,7 +1217,7 @@ function checkSapCommonTextsAspects( model ) {
1167
1217
  if (locale._effectiveType !== model.definitions['cds.String']) {
1168
1218
  const hasCommonLocale = !!model.definitions['sap.common.Locale'];
1169
1219
  const { error } = model.$messageFunctions;
1170
- error( 'def-invalid-element-type', [ locale.type.location, locale ], {
1220
+ error( 'def-invalid-element-type', [ (locale.type || locale.name).location, locale ], {
1171
1221
  '#': hasCommonLocale ? 'texts-aspect-locale' : 'std',
1172
1222
  art: name,
1173
1223
  elemref: 'locale',
@@ -343,8 +343,8 @@ function define( model ) {
343
343
  decl.usings.forEach( u => addUsing( u, src ) );
344
344
  return;
345
345
  }
346
- const { path } = decl.extern;
347
- if (path.broken || !path[0]) // syntax error
346
+ const path = decl.extern?.path;
347
+ if (!path || path.broken || !path[0]) // syntax error
348
348
  return;
349
349
  decl.extern.id = pathName( path );
350
350
  if (!decl.name)