@sap/cds-compiler 3.5.2 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/bin/cdsc.js +14 -6
  3. package/doc/CHANGELOG_ARCHIVE.md +10 -10
  4. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  5. package/lib/api/main.js +32 -55
  6. package/lib/api/options.js +1 -0
  7. package/lib/api/validate.js +5 -0
  8. package/lib/base/message-registry.js +104 -32
  9. package/lib/base/messages.js +277 -212
  10. package/lib/base/model.js +33 -22
  11. package/lib/base/optionProcessorHelper.js +9 -2
  12. package/lib/base/shuffle.js +50 -0
  13. package/lib/checks/actionsFunctions.js +37 -20
  14. package/lib/checks/foreignKeys.js +13 -6
  15. package/lib/checks/nonexpandableStructured.js +1 -2
  16. package/lib/checks/onConditions.js +21 -19
  17. package/lib/checks/parameters.js +1 -1
  18. package/lib/checks/queryNoDbArtifacts.js +2 -0
  19. package/lib/checks/types.js +16 -22
  20. package/lib/compiler/assert-consistency.js +31 -28
  21. package/lib/compiler/builtins.js +20 -4
  22. package/lib/compiler/checks.js +72 -63
  23. package/lib/compiler/define.js +396 -314
  24. package/lib/compiler/extend.js +55 -49
  25. package/lib/compiler/index.js +5 -0
  26. package/lib/compiler/populate.js +28 -11
  27. package/lib/compiler/propagator.js +2 -1
  28. package/lib/compiler/resolve.js +29 -20
  29. package/lib/compiler/shared.js +15 -10
  30. package/lib/compiler/utils.js +7 -7
  31. package/lib/edm/annotations/genericTranslation.js +51 -46
  32. package/lib/edm/annotations/preprocessAnnotations.js +39 -42
  33. package/lib/edm/csn2edm.js +69 -21
  34. package/lib/edm/edm.js +2 -2
  35. package/lib/edm/edmInboundChecks.js +6 -8
  36. package/lib/edm/edmPreprocessor.js +88 -80
  37. package/lib/edm/edmUtils.js +6 -15
  38. package/lib/gen/Dictionary.json +81 -13
  39. package/lib/gen/language.checksum +1 -1
  40. package/lib/gen/language.interp +2 -1
  41. package/lib/gen/languageParser.js +4680 -4484
  42. package/lib/inspect/inspectModelStatistics.js +2 -1
  43. package/lib/inspect/inspectPropagation.js +2 -1
  44. package/lib/json/from-csn.js +131 -78
  45. package/lib/json/to-csn.js +39 -23
  46. package/lib/language/antlrParser.js +0 -3
  47. package/lib/language/docCommentParser.js +7 -3
  48. package/lib/language/errorStrategy.js +3 -2
  49. package/lib/language/genericAntlrParser.js +96 -41
  50. package/lib/language/language.g4 +112 -128
  51. package/lib/language/multiLineStringParser.js +2 -1
  52. package/lib/main.d.ts +115 -2
  53. package/lib/main.js +16 -3
  54. package/lib/model/csnRefs.js +3 -3
  55. package/lib/model/csnUtils.js +109 -179
  56. package/lib/model/enrichCsn.js +13 -8
  57. package/lib/model/revealInternalProperties.js +4 -3
  58. package/lib/optionProcessor.js +23 -3
  59. package/lib/render/manageConstraints.js +11 -15
  60. package/lib/render/toCdl.js +144 -47
  61. package/lib/render/toHdbcds.js +22 -22
  62. package/lib/render/toRename.js +3 -4
  63. package/lib/render/toSql.js +29 -20
  64. package/lib/render/utils/delta.js +3 -1
  65. package/lib/render/utils/sql.js +3 -16
  66. package/lib/transform/db/associations.js +6 -6
  67. package/lib/transform/db/cdsPersistence.js +3 -3
  68. package/lib/transform/db/constraints.js +8 -8
  69. package/lib/transform/db/expansion.js +4 -4
  70. package/lib/transform/db/flattening.js +12 -15
  71. package/lib/transform/db/temporal.js +4 -3
  72. package/lib/transform/db/transformExists.js +2 -1
  73. package/lib/transform/draft/db.js +7 -7
  74. package/lib/transform/forOdataNew.js +15 -4
  75. package/lib/transform/forRelationalDB.js +53 -39
  76. package/lib/transform/odata/toFinalBaseType.js +106 -82
  77. package/lib/transform/odata/typesExposure.js +26 -17
  78. package/lib/transform/odata/utils.js +1 -1
  79. package/lib/transform/parseExpr.js +1 -1
  80. package/lib/transform/transformUtilsNew.js +33 -10
  81. package/lib/transform/translateAssocsToJoins.js +8 -7
  82. package/lib/transform/universalCsn/coreComputed.js +7 -5
  83. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -4
  84. package/lib/utils/timetrace.js +2 -2
  85. package/package.json +1 -2
@@ -113,6 +113,10 @@
113
113
  //
114
114
  // * If you want to set a property starting with '$' like $syntax, use
115
115
  // obj['$'+'syntax'] as the ANTLR tool would replace $syntax by $ctx.syntax
116
+ //
117
+ // * If you want to use Unicode characters, move the corresponding code to
118
+ // ./genericAntlrParser.js; ANTLR or the TypeScript wrapper might destroy
119
+ // Unicode characters on certain operating systems.
116
120
 
117
121
  grammar language;
118
122
  options {
@@ -219,8 +223,8 @@ artifactDefOrExtend[ outer, defOnly = false ] locals[ art = {} ] // cannot use `
219
223
  annotationAssignment_ll1[ $art ]*
220
224
  (
221
225
  DEFINE?
222
- ( serviceDef[ $art, $outer, defOnly ]
223
- | contextDef[ $art, $outer, defOnly ]
226
+ ( serviceDef[ $art, $outer, $defOnly ]
227
+ | contextDef[ $art, $outer, $defOnly ]
224
228
  | entityDef[ $art, $outer ]
225
229
  | typeDef[ $art, $outer ]
226
230
  | aspectDef[ $art, $outer ]
@@ -231,10 +235,7 @@ artifactDefOrExtend[ outer, defOnly = false ] locals[ art = {} ] // cannot use `
231
235
  )
232
236
  |
233
237
  extend=EXTEND
234
- { if (defOnly) // TODO: nicer message text
235
- this.error( 'syntax-extend-context', $extend,
236
- { code: 'EXTEND artifact', meta: defOnly },
237
- 'No $(CODE) within $(META) extensions' );
238
+ { this.reportUnexpectedExtension( $defOnly, $extend );
238
239
  if (!$outer.extensions) $outer.extensions = [];
239
240
  }
240
241
  // #ATN: EXTEND art, while CONTEXT, ENTITY etc are not reserved
@@ -248,10 +249,7 @@ artifactDefOrExtend[ outer, defOnly = false ] locals[ art = {} ] // cannot use `
248
249
  )
249
250
  |
250
251
  annotate=ANNOTATE
251
- { if (defOnly) // TODO: improve message text
252
- this.error( 'syntax-extend-context', $annotate,
253
- { code: 'ANNOTATE artifact', meta: defOnly },
254
- 'No $(CODE) within $(META) extensions' );
252
+ { this.reportUnexpectedExtension( $defOnly, $annotate );
255
253
  if (!$outer.extensions) $outer.extensions = [];
256
254
  this.meltKeywordToIdentifier();
257
255
  }
@@ -515,8 +513,10 @@ actionFunctionDef[ outer ] locals[ art = {} ]
515
513
  parameterDef[ outer ] locals[ art = {} ]
516
514
  @after { this.attachLocation( $art ); }
517
515
  :
518
- { this.docComment( $art ); }
519
- annotationAssignment_ll1[ $art ]*
516
+ { this.meltKeywordToIdentifier();; this.docComment( $art ); }
517
+ ( annotationAssignment_ll1[ $art ]
518
+ { this.meltKeywordToIdentifier(); }
519
+ )*
520
520
  name=ident['Param']
521
521
  { this.addDef( $art, $outer, 'params', 'param', $name.id );
522
522
  this.docComment( $art ); }
@@ -532,12 +532,17 @@ parameterListDef[ art ]
532
532
  '(' { $art.params = this.createDict(); }
533
533
  // also empty param list (we might do some hacking later to allow reserved words)
534
534
  // see annotationAssignment_paren
535
- (
535
+ {
536
+ if (this.isStraightBefore(')')) {
537
+ this.matchWildcard(); // we know it is the ')' - we do not reach the final match
538
+ this.finalizeDictOrArray( $art.params );
539
+ return $ctx;
540
+ }
541
+ }
542
+ parameterDef[ $art ]
543
+ ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
536
544
  parameterDef[ $art ]
537
- ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
538
- parameterDef[ $art ]
539
- )*
540
- )?
545
+ )*
541
546
  ')' { this.finalizeDictOrArray( $art.params ); }
542
547
  ;
543
548
 
@@ -572,7 +577,7 @@ elementDef[ outer ] locals[ $art = {} ]
572
577
  elementDefInner[ $art, $outer, false ]
573
578
  ;
574
579
 
575
- elementDefInner[ art, outer, allowEq ]
580
+ elementDefInner[ art, outer, mightBeEnum ]
576
581
  @after{ this.attachLocation( $art ); }
577
582
  :
578
583
  // VIRTUAL is keyword, except if before the following tokens texts:
@@ -587,13 +592,11 @@ elementDefInner[ art, outer, allowEq ]
587
592
  }
588
593
  )?
589
594
  { this.setLocalToken( 'ELEMENT', 'ELEMENT', /^[:{@=}]$/ ); }
590
- ELEMENT?
595
+ ( ELEMENT { $mightBeEnum = false; } )? // auto-recognizable at other places
591
596
  name=ident['Element']
592
597
  { this.addDef( $art, $outer, 'elements', 'element', $name.id );
593
598
  this.docComment( $art ); }
594
599
  annotationAssignment_fix[ $art ]*
595
- // TODO: we can think of making the typeSpec optional and do checks instead:
596
- // type optional with '=', type required otherwise
597
600
  (
598
601
  typeStruct[ $art ]
599
602
  ( nullability[ $art ]
@@ -602,15 +605,14 @@ elementDefInner[ art, outer, allowEq ]
602
605
  )
603
606
  |
604
607
  ':'
605
- elementType[ $art ]
606
- |
607
- // this is also called for enum symbols (in EXTEND)
608
- eq='=' e=expression // never introduce AS as syntax variant of '='
609
- {
610
- if (!$allowEq || $e.expr && !$e.expr.literal )
611
- this.error( 'syntax-unsupported-calc-elem', $eq );
612
- else if ($e.expr)
613
- $art.value = $e.expr;
608
+ elementType[ $art, $mightBeEnum ]
609
+ |
610
+ '=' e=expression // SQL has syntax variant using AS - we DO NOT
611
+ { $art.value = $e.expr;
612
+ // this.setIntroLocation( eq ); -- future
613
+ if ($mightBeEnum && ($e.expr.val !== undefined || $e.expr.sym !== undefined) &&
614
+ !$virtual && !$key && !$masked && !$art.elements && !$art.type)
615
+ $art['$'+'syntax'] = 'enum';
614
616
  }
615
617
  { this.docComment( $art ); }
616
618
  annotationAssignment_ll1[ $art ]* // for enum symbol def via EXTEND
@@ -632,11 +634,11 @@ elementType[ art ] // TODO: split this monster rule
632
634
  typeStruct[ $art.target, true ] optionalSemi
633
635
  |
634
636
  one=ONE
635
- { this.setMaxCardinality( $art, $one, this.numberLiteral( $one, null, '1' ) ); }
637
+ { this.setMaxCardinality( $art, this.numberLiteral( $one, null, '1' ) ); }
636
638
  typeCompoStruct[ $art.target ] optionalSemi
637
639
  |
638
640
  many=MANY
639
- { this.setMaxCardinality( $art, $many, { literal: 'string', val: '*' } ); }
641
+ { this.setMaxCardinality( $art, { literal: 'string', val: '*' }, $many ); }
640
642
  typeCompoStruct[ $art.target ] optionalSemi
641
643
  |
642
644
  // we do not support `Composition of many { e }` - ambiguity ad-hoc target versus foreign keys!
@@ -657,15 +659,10 @@ elementType[ art ] // TODO: split this monster rule
657
659
  nullability[ $art.items ]?
658
660
  | // #ATN: typeRefOptArgs/typeTypeOf can start with TYPE
659
661
  ( typeTypeOf[ $art.items ] | typeRefOptArgs[ $art.items ] )
660
- nullability[ $art.items ]? // only if not followed by `enum`
662
+ nullability[ $art.items ]?
661
663
  { this.docComment( $art ); }
662
664
  annotationAssignment_ll1[ $art ]*
663
665
  (
664
- { if ($art.items.notNull) {
665
- this.message( 'syntax-unexpected-null', $art.items.notNull.location,
666
- { keyword: $art.items.notNull.val ? 'not null' : 'null' } );
667
- }
668
- }
669
666
  ENUM '{' { $art.items.enum = this.createDict(); }
670
667
  enumSymbolDef[ $art.items ]*
671
668
  '}' { this.finalizeDictOrArray( $art.items.enum ); }
@@ -676,6 +673,7 @@ elementType[ art ] // TODO: split this monster rule
676
673
  |
677
674
  l=LOCALIZED { $art.localized = this.valueWithTokenLocation( true, $l ); }
678
675
  typeRefOptArgs[ $art ]
676
+ optInvisibleNullability[ $art ]
679
677
  { this.docComment( $art ); }
680
678
  annotationAssignment_ll1[ $art ]*
681
679
  ( elementProperties[ $art ]
@@ -685,6 +683,7 @@ elementType[ art ] // TODO: split this monster rule
685
683
  requiredSemi
686
684
  |
687
685
  typeTypeOf[ $art ] // Note: Same as the typeRefOptArgs rule below
686
+ optInvisibleNullability[ $art ]
688
687
  { this.docComment( $art ); }
689
688
  annotationAssignment_ll1[ $art ]*
690
689
  (
@@ -700,6 +699,7 @@ elementType[ art ] // TODO: split this monster rule
700
699
  requiredSemi // also req after foreign key spec
701
700
  |
702
701
  typeRefOptArgs[ $art ] // Note: Same as the typeTypeOf rule above
702
+ optInvisibleNullability[ $art ]
703
703
  { this.docComment( $art ); }
704
704
  annotationAssignment_ll1[ $art ]*
705
705
  (
@@ -717,15 +717,13 @@ elementType[ art ] // TODO: split this monster rule
717
717
 
718
718
  elementProperties[ elem ]
719
719
  :
720
+ defaultValue[ $elem ]
721
+ nullability[ $elem ]? // placement accoring to SQL spec
722
+ |
720
723
  nullability[ $elem ]
721
724
  defaultValue[ $elem ]?
722
725
  |
723
- defaultValue[ $elem ]
724
- nullability[ $elem ]?
725
- |
726
- eq='='
727
- { this.error( 'syntax-unsupported-calc-elem', $eq ); }
728
- e=expression { $elem.value = $e.expr; }
726
+ '=' e=expression { $elem.value = $e.expr; }
729
727
  ;
730
728
 
731
729
  defaultValue[ art ] locals[ elem, elements = {} ]
@@ -756,7 +754,7 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
756
754
  (
757
755
  // all the alternatives from `extendWithOptElementsOrType` --------------
758
756
  '{' { $art.elements = this.createDict(); }
759
- elementDefOrExtend[ $art ]*
757
+ elementDefOrExtend[ $art, true ]*
760
758
  '}' { this.finalizeDictOrArray( $art.elements ); }
761
759
  { this.checkExtensionDict( $art.elements ); }
762
760
  optionalSemi
@@ -786,7 +784,7 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
786
784
  |
787
785
  DEFINITIONS
788
786
  '{' { $art.artifacts = this.createDict(); }
789
- artifactDefOrExtend[ $art, true ]*
787
+ artifactDefOrExtend[ $art, 'definitions' ]*
790
788
  '}' { this.finalizeDictOrArray( $art.artifacts ); }
791
789
  optionalSemi
792
790
  |
@@ -817,7 +815,7 @@ extendService[ art, outer ] locals[ name = {} ]
817
815
  simplePath[ $name, 'Service' ]
818
816
  { $art.name = $name; this.addItem( $art, $outer, 'extensions', 'extend' ); }
819
817
  ( WITH { this.noSemicolonHere(); } )?
820
- optArtifactsBlock[ art, 'SERVICE' ]
818
+ optArtifactsBlock[ art, 'service' ]
821
819
  ;
822
820
 
823
821
  extendContext[ art, outer ] locals[ name = {} ]
@@ -827,7 +825,7 @@ extendContext[ art, outer ] locals[ name = {} ]
827
825
  simplePath[ $name, 'Context' ]
828
826
  { $art.name = $name; this.addItem( $art, $outer, 'extensions', 'extend' ); }
829
827
  ( WITH { this.noSemicolonHere(); } )?
830
- optArtifactsBlock[ art, 'CONTEXT' ]
828
+ optArtifactsBlock[ art, 'context' ]
831
829
  ;
832
830
 
833
831
  extendEntityOrAspect[ art, outer ] locals[ name = {} ]
@@ -924,12 +922,12 @@ extendType[ art, outer ] locals[ name = {} ]
924
922
  annotationAssignment_ll1[ $art ]*
925
923
  (
926
924
  '{' { $art.elements = this.createDict(); }
927
- elementDefOrExtend[ $art ]*
925
+ elementDefOrExtend[ $art, true ]*
928
926
  '}' { this.finalizeDictOrArray( $art.elements ); }
929
927
  { this.checkExtensionDict( $art.elements ); }
930
928
  optionalSemi
931
929
  |
932
- // extend type|element Art with (length: 10);
930
+ // extend type Art with (length: 10);
933
931
  typeNamedArgList[ $art ]
934
932
  requiredSemi
935
933
  |
@@ -949,7 +947,7 @@ extendWithOptElementsOrType[ art ]
949
947
  annotationAssignment_ll1[ $art ]*
950
948
  (
951
949
  '{' { $art.elements = this.createDict(); }
952
- elementDefOrExtend[ $art ]*
950
+ elementDefOrExtend[ $art, true ]*
953
951
  '}' { this.finalizeDictOrArray( $art.elements ); }
954
952
  { this.checkExtensionDict( $art.elements ); }
955
953
  optionalSemi
@@ -979,7 +977,7 @@ extendWithOptElementsNoWith[ art ]
979
977
  annotationAssignment_ll1[ $art ]*
980
978
  (
981
979
  '{' { $art.elements = this.createDict(); }
982
- elementDefOrExtend[ $art ]*
980
+ elementDefOrExtend[ $art, true ]*
983
981
  '}' { this.finalizeDictOrArray( $art.elements ); }
984
982
  { this.checkExtensionDict( $art.elements ); }
985
983
  optionalSemi
@@ -990,7 +988,7 @@ extendWithOptElementsNoWith[ art ]
990
988
 
991
989
  // For `extend … with elements` or `extend entity … with`, `extend aspect … with`,
992
990
  // i.e. definitions in { … } are never enums
993
- elementDefOrExtend[ outer ] locals[ art = {} ]
991
+ elementDefOrExtend[ outer, mightBeEnum = false ] locals[ art = {} ]
994
992
  @after { /* #ATN 1 */ } // if ($art) this.attachLocation( $art ); }
995
993
  :
996
994
  { $art.location = this.startLocation();; this.docComment( $art ); }
@@ -1000,7 +998,7 @@ elementDefOrExtend[ outer ] locals[ art = {} ]
1000
998
  EXTEND
1001
999
  extendElement[ $art, $outer ]
1002
1000
  |
1003
- elementDefInner[ $art, $outer, true ]
1001
+ elementDefInner[ $art, $outer, $mightBeEnum ]
1004
1002
  )
1005
1003
  ;
1006
1004
 
@@ -1158,6 +1156,7 @@ typeSpec[ art ] // for parameterDef
1158
1156
  ENUM '{' { $art.enum = this.createDict(); }
1159
1157
  enumSymbolDef[ $art ]*
1160
1158
  '}' { this.finalizeDictOrArray( $art.enum ); }
1159
+ nullability[ $art ]?
1161
1160
  )?
1162
1161
  // TODO: no LOCALIZED ?
1163
1162
  | typeRefOptArgs[ $art ]
@@ -1166,6 +1165,7 @@ typeSpec[ art ] // for parameterDef
1166
1165
  ENUM '{' { $art.enum = this.createDict(); }
1167
1166
  enumSymbolDef[ $art ]*
1168
1167
  '}' { this.finalizeDictOrArray( $art.enum ); }
1168
+ nullability[ $art ]?
1169
1169
  )?
1170
1170
  )
1171
1171
  ;
@@ -1184,6 +1184,7 @@ returnTypeSpec[ art ]
1184
1184
  ENUM '{' { $art.returns.enum = this.createDict(); }
1185
1185
  enumSymbolDef[ $art.returns ]*
1186
1186
  '}' { this.finalizeDictOrArray( $art.returns.enum ); }
1187
+ nullability[ $art.returns ]?
1187
1188
  )?
1188
1189
  // TODO: no LOCALIZED ?
1189
1190
  | typeRefOptArgs[ $art.returns ]
@@ -1192,6 +1193,7 @@ returnTypeSpec[ art ]
1192
1193
  ENUM '{' { $art.returns.enum = this.createDict(); }
1193
1194
  enumSymbolDef[ $art.returns ]*
1194
1195
  '}' { this.finalizeDictOrArray( $art.returns.enum ); }
1196
+ nullability[ $art.returns ]?
1195
1197
  )?
1196
1198
  )
1197
1199
 
@@ -1235,21 +1237,11 @@ typeSpecSemi[ art ] // with 'includes', for type and annotation defs
1235
1237
  ( typeStruct[ $art.items ]
1236
1238
  nullability[ $art.items ]?
1237
1239
  optionalSemi
1238
- | typeTypeOf[ $art.items ]
1240
+ | ( typeTypeOf[ $art.items ] | typeRefOptArgs[ $art.items ] )
1239
1241
  nullability[ $art.items ]?
1240
1242
  { this.docComment( $art ); }
1241
1243
  annotationAssignment_ll1[ $art ]*
1242
- requiredSemi
1243
- | typeRefOptArgs[ $art.items ]
1244
- nullability[ $art.items ]? // only if not followed by `enum`
1245
- { this.docComment( $art ); }
1246
- annotationAssignment_ll1[ $art ]*
1247
1244
  (
1248
- { if ($art.items.notNull) {
1249
- this.message( 'syntax-unexpected-null', $art.items.notNull.location,
1250
- { keyword: $art.items.notNull.val ? 'not null' : 'null' } );
1251
- }
1252
- }
1253
1245
  ENUM '{' { $art.items.enum = this.createDict(); }
1254
1246
  enumSymbolDef[ $art.items ]*
1255
1247
  '}' { this.finalizeDictOrArray( $art.items.enum ); }
@@ -1348,6 +1340,7 @@ typeArray[ art ]
1348
1340
  ENUM '{' { $art.items.enum = this.createDict(); }
1349
1341
  enumSymbolDef[ $art.items ]*
1350
1342
  '}' { this.finalizeDictOrArray( $art.items.enum ); }
1343
+ nullability[ $art.items ]?
1351
1344
  )?
1352
1345
  | typeRefOptArgs[ $art.items ]
1353
1346
  nullability[ $art.items ]?
@@ -1355,6 +1348,7 @@ typeArray[ art ]
1355
1348
  ENUM '{' { $art.items.enum = this.createDict(); }
1356
1349
  enumSymbolDef[ $art.items ]*
1357
1350
  '}' { this.finalizeDictOrArray( $art.items.enum ); }
1351
+ nullability[ $art.items ]?
1358
1352
  )?
1359
1353
  )
1360
1354
  ;
@@ -1423,14 +1417,14 @@ typeAssociationElementCont[ art ] // including Composition
1423
1417
  typeToOne[ art ]
1424
1418
  :
1425
1419
  one=ONE
1426
- { this.setMaxCardinality( $art, $one, this.numberLiteral( $one, null, '1' ) ); }
1420
+ { this.setMaxCardinality( $art, this.numberLiteral( $one, null, '1' ) ); }
1427
1421
  simplePath[ $art.target, 'artref' ]
1428
1422
  ;
1429
1423
 
1430
1424
  typeToMany[ art ]
1431
1425
  :
1432
1426
  many=MANY
1433
- { this.setMaxCardinality( $art, $many, { literal: 'string', val: '*' } ); }
1427
+ { this.setMaxCardinality( $art, { literal: 'string', val: '*' }, $many ); }
1434
1428
  simplePath[ $art.target, 'artref' ]
1435
1429
  ;
1436
1430
 
@@ -1467,13 +1461,18 @@ cardinality[ art ] locals[ card = {} ]
1467
1461
  ']'
1468
1462
  ;
1469
1463
 
1464
+ // TO be used when NOT and NULL are already in the lookahead set:
1465
+ optInvisibleNullability[ art ]
1466
+ : { this.setLocalTokenForId( 1, { 'NOT': 'HelperToken1', 'NULL': 'HelperToken2' } ); }
1467
+ ( n1=HelperToken1 n2=NULL { this.setNullability( $art, $n1, $n2 ); }
1468
+ | n1=HelperToken2 { this.setNullability( $art, $n1, null ); }
1469
+ )?
1470
+ ;
1471
+
1470
1472
  nullability[ art ]
1471
- :
1472
- not=NOT n1=NULL
1473
- { $art.notNull = this.valueWithTokenLocation( true, $not, $n1 ); }
1474
- |
1475
- n2=NULL
1476
- { $art.notNull = this.valueWithTokenLocation( false, $n2 ); }
1473
+ @after{ this.setNullability( $art, $n1, $n2 ); }
1474
+ : n1=NOT n2=NULL
1475
+ | n1=NULL
1477
1476
  ;
1478
1477
 
1479
1478
  foreignKey[ outer ] locals[ art = {}, elem = {} ]
@@ -1806,29 +1805,16 @@ projectionExclusion[ outer ] locals[ art = {} ]
1806
1805
  // Actually, this is a subset if elementDefInner...
1807
1806
  // TODO: the corresponding restrictions must also be checked in the core
1808
1807
  // compiler, as the mixin element could come via CSN
1809
- mixinElementDef[ outer ] locals[ art = {} ]
1810
- @after { /* #ATN 2 */ this.attachLocation($art); }
1808
+ mixinElementDef[ outer ] locals[ art = { target: {} } ]
1809
+ @after { /* #ATN 1 */ this.attachLocation($art); }
1811
1810
  :
1812
- name=ident['Mixin']
1813
- { this.addDef( $art, $outer, 'mixin', 'mixin', $name.id ); }
1814
- (
1815
- ':'
1816
- // #ATN: referenced type name can be ASSOCIATION or COMPOSITION
1817
- (
1818
- typeAssociationBase[ $art, false ]
1819
- // #ATN: path could start with MANY or ONE - make sure a token follows in same rule!
1820
- ( typeToMany[ $art ] | typeToOne[ $art ] | simplePath[ $art.target, 'artref' ] )
1821
- typeAssociationCont[ $art ]?
1822
- |
1823
- typeRefOptArgs[ $art ]
1824
- ( as='=' expression
1825
- { this.error( 'syntax-unsupported-calc-elem', $as ); }
1826
- )?
1827
- )
1828
- |
1829
- as='=' expression
1830
- { this.error( 'syntax-unsupported-calc-elem', $as ); }
1831
- )
1811
+ name=ident['Mixin'] ':'
1812
+ typeAssociationBase[ $art, false ]
1813
+ { if ($art.type) this.addDef( $art, $outer, 'mixin', 'mixin', $name.id ); }
1814
+ // #ATN: path could start with MANY or ONE - make sure a token follows in same rule!
1815
+ ( typeToMany[ $art ] | typeToOne[ $art ] | simplePath[ $art.target, 'artref' ] )
1816
+ // TODO CC: exclude every token other than ON
1817
+ typeAssociationCont[ $art ]? // better error reporting than simply `ON condition`
1832
1818
  requiredSemi
1833
1819
  ;
1834
1820
 
@@ -1866,7 +1852,6 @@ selectItemDefBody[ art, outer ] locals[ assoc ]
1866
1852
  { this.reportExpandInline( $art, false ); }
1867
1853
  selectItemInlineList[ $art, 'expand' ]
1868
1854
  excludingClause[ $art ]?
1869
- // TODO: we might alternatively allow AS here
1870
1855
  |
1871
1856
  { this.reportExpandInline( $art, $as || this._input.LT(-1) ); }
1872
1857
  DOTbeforeBRACE // ...orASTERISK
@@ -2056,7 +2041,7 @@ conditionEOF returns [ cond ]
2056
2041
  ;
2057
2042
 
2058
2043
  condition returns [ cond ] locals[ args = [] ]
2059
- @after{ $cond = this.argsExpression( $args ); }
2044
+ @after{ $cond = this.argsExpression( $args, true ); }
2060
2045
  :
2061
2046
  c1=conditionAnd { $args.push($c1.cond); }
2062
2047
  (
@@ -2066,7 +2051,7 @@ condition returns [ cond ] locals[ args = [] ]
2066
2051
  ;
2067
2052
 
2068
2053
  conditionAnd returns [ cond ] locals[ args = [] ]
2069
- @after{ $cond = this.argsExpression( $args, null, 'and' ); } // 'and' used by A2J and checks
2054
+ @after{ $cond = this.argsExpression( $args, 'and' ); } // 'and' used by A2J and checks
2070
2055
  :
2071
2056
  c1=conditionTerm { $args.push($c1.cond); }
2072
2057
  (
@@ -2077,7 +2062,7 @@ conditionAnd returns [ cond ] locals[ args = [] ]
2077
2062
 
2078
2063
  // Note: New operators need to be added to functionExpressionOperatorsRequireParentheses[] in toCdl.js.
2079
2064
  conditionTerm returns [ cond ] locals[ args = [] ]
2080
- @after{ $cond = this.argsExpression( $args, null, '=' ); }// op: '=' used by A2J and checks
2065
+ @after{ $cond = this.argsExpression( $args, '=' ); }// op: '=' (not nary) used by A2J and checks
2081
2066
  :
2082
2067
  nt=NOT { this.pushXprToken( $args ); }
2083
2068
  ct=conditionTerm { $args.push( $ct.cond ); }
@@ -2133,7 +2118,7 @@ predicate[ args ]
2133
2118
  ;
2134
2119
 
2135
2120
  expression returns [ expr ] locals [ args = [] ]
2136
- @after{ $expr = this.argsExpression( $args ); }
2121
+ @after{ $expr = this.argsExpression( $args, true ); }
2137
2122
  :
2138
2123
  e1=expressionSum { $args.push( $e1.expr ); }
2139
2124
  (
@@ -2143,7 +2128,7 @@ expression returns [ expr ] locals [ args = [] ]
2143
2128
  ;
2144
2129
 
2145
2130
  expressionSum returns [ expr ] locals [ args = [] ]
2146
- @after{ $expr = this.argsExpression( $args ); }
2131
+ @after{ $expr = this.argsExpression( $args, true ); }
2147
2132
  :
2148
2133
  e1=expressionFactor { $args.push( $e1.expr ); }
2149
2134
  (
@@ -2153,7 +2138,7 @@ expressionSum returns [ expr ] locals [ args = [] ]
2153
2138
  ;
2154
2139
 
2155
2140
  expressionFactor returns [ expr ] locals [ args = [] ]
2156
- @after{ $expr = this.argsExpression( $args ); }
2141
+ @after{ $expr = this.argsExpression( $args, true ); }
2157
2142
  :
2158
2143
  e1=expressionTerm { $args.push( $e1.expr ); }
2159
2144
  (
@@ -2163,7 +2148,7 @@ expressionFactor returns [ expr ] locals [ args = [] ]
2163
2148
  ;
2164
2149
 
2165
2150
  expressionTerm returns [ expr ] locals [ args = [] ]
2166
- @after{ /* #ATN 1 */ $expr = this.argsExpression( $args ); }
2151
+ @after{ /* #ATN 1 */ $expr = this.argsExpression( $args, false ); }
2167
2152
  :
2168
2153
  ( '+' | '-' ) { this.pushXprToken( $args ); }
2169
2154
  e1=expressionTerm // prefix op or part of the number
@@ -2210,12 +2195,13 @@ expressionTerm returns [ expr ] locals [ args = [] ]
2210
2195
  { $args[0].suffix.push( this.surroundByParens( $over.over, $open, $close ) ); }
2211
2196
  )?
2212
2197
  |
2213
- ':'
2198
+ colon=':' { this.reportUnexpectedSpace( $colon ); }
2214
2199
  (
2215
2200
  vp=valuePath[ 'paramref', this.startLocation() ]
2216
2201
  {{ const par = $vp.qp;; par.scope = 'param';; $args.push( par ); }}
2217
2202
  |
2218
2203
  pp=Number
2204
+ // TODO: no extra XSN property `param` for this, re-use `val`
2219
2205
  { $args.push( { param: this.numberLiteral( $pp ), scope: 'param' } );
2220
2206
  this.csnParseOnly( 'syntax-unsupported-param', [ $pp ], { '#': 'positional', code: ':' + $pp.text } );
2221
2207
  }
@@ -2224,6 +2210,7 @@ expressionTerm returns [ expr ] locals [ args = [] ]
2224
2210
  qm= '?' // is automatically not mentioned as CC candidate
2225
2211
  // if we have an HideAlternatives here, we would block it to use it in
2226
2212
  // parallel to an expression (would produce adaptivePredict() otherwise)
2213
+ // TODO: no extra XSN property `param` for this, re-use `val`
2227
2214
  { $args.push( { param: this.valueWithTokenLocation( '?', $qm ), scope: 'param' } );
2228
2215
  this.csnParseOnly( 'syntax-unsupported-param', [ $qm ], { '#': 'dynamic', code: '?' } );
2229
2216
  }
@@ -2315,7 +2302,7 @@ pathArguments[ pathStep, considerSpecial ]
2315
2302
  // For code completion, we need to handle generic tokens directly after the
2316
2303
  // '('. To avoid invalidating an assoc `trim` to an entity with parameter
2317
2304
  // `leading` (ok, a bit constructed), we do not do it with named parameters.
2318
- { if (!this.setLocalTokenForId( { ':': 'HelperToken1', '=>': 'HelperToken2' } ))
2305
+ { if (!this.setLocalTokenForId( 2, { ':': 'HelperToken1', '=>': 'HelperToken2' } ))
2319
2306
  this.prepareGenericKeywords( $considerSpecial ); }
2320
2307
  (
2321
2308
  { $pathStep.args = this.createDict(); $pathStep['$'+'syntax'] = ':'; }
@@ -2366,7 +2353,7 @@ namedExpression[ pathStep, id ]
2366
2353
 
2367
2354
  funcExpression[ pathStep, considerSpecial ] locals[ args = [] ]
2368
2355
  @init { this.prepareGenericKeywords( $considerSpecial ); }
2369
- @after{ $pathStep.args.push( this.argsExpression( $args, true ) ); }
2356
+ @after{ $pathStep.args.push( this.argsExpression( $args, false ) ); }
2370
2357
  :
2371
2358
  (
2372
2359
  expr=expression
@@ -2417,9 +2404,8 @@ funcExpression[ pathStep, considerSpecial ] locals[ args = [] ]
2417
2404
  ;
2418
2405
 
2419
2406
  overClause returns[ over ] locals[ args = [] ]
2420
- @after{ $over = this.argsExpression( $args ); }
2407
+ @after{ $over = this.argsExpression( $args, false ); }
2421
2408
  :
2422
- // TODO: empty really allowed?
2423
2409
  ( PARTITION { this.pushXprToken( $args ); } BY { this.pushXprToken( $args ); }
2424
2410
  pb=partitionByClause { $args.push( $pb.expr ); }
2425
2411
  )?
@@ -2432,7 +2418,7 @@ overClause returns[ over ] locals[ args = [] ]
2432
2418
  ;
2433
2419
 
2434
2420
  partitionByClause returns [ expr ] locals[ args = [] ]
2435
- @after{ $expr = this.argsExpression( $args ); }
2421
+ @after{ $expr = this.argsExpression( $args, false ); }
2436
2422
  :
2437
2423
  e1=expression { $args.push( $e1.expr ); }
2438
2424
  ( ',' { this.pushXprToken( $args ); }
@@ -2442,7 +2428,7 @@ partitionByClause returns [ expr ] locals[ args = [] ]
2442
2428
 
2443
2429
  // ORDER BY clause in generic functions, e.g. `first_value(id order by name)`
2444
2430
  funcOrderByClause[ args ] returns[ expr ]
2445
- @after{ $expr = this.argsExpression( args, true ); }
2431
+ @after{ $expr = this.argsExpression( $args, false, $args[0]?.location ); }
2446
2432
  :
2447
2433
  ORDER { this.pushXprToken( $args ); } BY { this.pushXprToken( $args ); }
2448
2434
  ob=exprOrderByClause { $args.push( $ob.expr ); }
@@ -2450,7 +2436,7 @@ funcOrderByClause[ args ] returns[ expr ]
2450
2436
 
2451
2437
  // ORDER BY clause in generic functions or OVER clause
2452
2438
  exprOrderByClause returns[ expr ] locals[ args = [] ]
2453
- @after{ $expr = this.argsExpression( $args ); }
2439
+ @after{ $expr = this.argsExpression( $args, false ); }
2454
2440
  :
2455
2441
  orderBySpecInExpr[ $args ]
2456
2442
  ( ',' { this.pushXprToken( $args ); } orderBySpecInExpr[ $args ] )*
@@ -2468,7 +2454,7 @@ orderBySpecInExpr[ args ]
2468
2454
  ;
2469
2455
 
2470
2456
  windowFrameClause returns[ wf ] locals[ args = [] ]
2471
- @after{ $wf = this.argsExpression( $args ); }
2457
+ @after{ $wf = this.argsExpression( $args, false ); }
2472
2458
  :
2473
2459
  (
2474
2460
  windowFrameStartSpec[ $args ]
@@ -2517,7 +2503,7 @@ optionalCardinality[ pathStep ]
2517
2503
  :
2518
2504
  // Make sure to test second token to allow expressions starting with Number
2519
2505
  // without introducing WHERE - that would be @options{k=2}. The code
2520
- // completion just produces `:`after having inserted a Number - TODO.
2506
+ // completion just produces `:` after having inserted a Number - TODO.
2521
2507
  { if (this._input.LT(2).text !== ':') return $ctx; }
2522
2508
  ( trgMax=Number ':'
2523
2509
  { if ($pathStep) $pathStep.cardinality = { targetMax: this.numberLiteral( $trgMax ), location: this.startLocation() }; }
@@ -2547,7 +2533,7 @@ optionalWhereForFilter
2547
2533
  // - "atn": at the beginning of a column definition
2548
2534
  //
2549
2535
  // want to let the ambiguity in select items (solution: "either" possibility)
2550
- //
2536
+ //
2551
2537
  // entity E @Anno: Base { … }; // Base is include (chosen w/ warning) or @Anno value?
2552
2538
  // entity V(p) as select from E { // either: anno value "ref p", select item -x
2553
2539
  // @anno :p - x as x; // or: anno value true, select item :p-x
@@ -2561,7 +2547,7 @@ annotationAssignment_fix[ art ] locals[ assignment ]
2561
2547
  this.docComment( $art );
2562
2548
  }
2563
2549
  } :
2564
- '@'
2550
+ at='@' { this.reportUnexpectedSpace( $at ); }
2565
2551
  (
2566
2552
  annotationAssignment_paren[ $art ]
2567
2553
  |
@@ -2579,7 +2565,7 @@ annotationAssignment_ll1[ art ] locals[ assignment ]
2579
2565
  this.docComment( $art );
2580
2566
  }
2581
2567
  } :
2582
- '@'
2568
+ at='@' { this.reportUnexpectedSpace( $at ); }
2583
2569
  (
2584
2570
  annotationAssignment_paren[ $art ]
2585
2571
  |
@@ -2601,7 +2587,7 @@ annotationAssignment_atn[ art ] locals[ assignment ]
2601
2587
  this.docComment( $art );
2602
2588
  }
2603
2589
  } :
2604
- '@'
2590
+ at='@' { this.reportUnexpectedSpace( $at ); }
2605
2591
  (
2606
2592
  annotationAssignment_paren[ $art ]
2607
2593
  |
@@ -2612,7 +2598,8 @@ annotationAssignment_atn[ art ] locals[ assignment ]
2612
2598
  // -> used to introduce variant name if and only if in same line as previous token
2613
2599
  { this.setLocalToken( '#', 'HelperToken1', null, true ); }
2614
2600
  (
2615
- HelperToken1 { this.meltKeywordToIdentifier(); }
2601
+ hash=HelperToken1
2602
+ { this.meltKeywordToIdentifier();; this.reportUnexpectedSpace( $hash ); }
2616
2603
  variant=ident['variant'] { $assignment.name.variant = $variant.id; }
2617
2604
  )?
2618
2605
  // ':' is in the follow set of this rule, as it is used in rule "selectItemDef"
@@ -2624,10 +2611,11 @@ annotationAssignment_atn[ art ] locals[ assignment ]
2624
2611
  (
2625
2612
  val=annoValueBase[ $assignment ]
2626
2613
  |
2627
- at='@'? annotationPath[ $assignment, 'ref', $at ]
2614
+ atv='@'? annotationPath[ $assignment, 'ref', $atv ]
2628
2615
  { this.setLocalToken( '#', 'HelperToken1', null, true ); } // see above
2629
2616
  (
2630
- HelperToken1 { this.meltKeywordToIdentifier(); }
2617
+ hash=HelperToken1
2618
+ { this.meltKeywordToIdentifier();; this.reportUnexpectedSpace( $hash ); }
2631
2619
  variant=ident['variant'] { $assignment.variant = $variant.id; }
2632
2620
  )?
2633
2621
  )
@@ -2638,15 +2626,10 @@ annotationAssignment_atn[ art ] locals[ assignment ]
2638
2626
  annotationAssignment_paren[ art ]
2639
2627
  :
2640
2628
  '('
2641
- // allow completely useless `@()` with a warning, do not offer it for completion
2629
+ // allow completely useless `@()`; no warning anymore - who cares?
2642
2630
  {
2643
2631
  this.meltKeywordToIdentifier();
2644
2632
  if (this.isStraightBefore(')')) {
2645
- // TODO: or should we simple accept this without warning? (but still no CC)
2646
- this.warning( 'syntax-unexpected-right-paren',
2647
- this.tokenLocation( this.getCurrentToken() ),
2648
- { offending: "')'", expecting: ['Identifier'], code: '@()' },
2649
- 'Unexpected $(OFFENDING), expecting $(EXPECTING); ignoring $(CODE)' );
2650
2633
  this.matchWildcard(); // we know it is the ')' - we do not reach the final match
2651
2634
  return $ctx;
2652
2635
  }
@@ -2695,7 +2678,7 @@ annotationPathVariant[ art ] locals[ variant = {} ]
2695
2678
  @after { this.attachLocation($art); }
2696
2679
  :
2697
2680
  // TODO: warning for space after '#'
2698
- '#' { this.meltKeywordToIdentifier(); }
2681
+ hash='#' { this.meltKeywordToIdentifier();; this.reportUnexpectedSpace( $hash ); }
2699
2682
  simplePath[ $variant, 'variant' ] { $art.variant = $variant; }
2700
2683
  ;
2701
2684
 
@@ -2832,7 +2815,8 @@ literalValue returns[ val ] locals[ tok ]
2832
2815
  @init{ $tok = this.getCurrentToken(); }
2833
2816
  @after { this.attachLocation($val); }
2834
2817
  :
2835
- '#' name=ident['enumref']
2818
+ hash='#' { this.meltKeywordToIdentifier();; this.reportUnexpectedSpace( $hash ); }
2819
+ name=ident['enumref'] // TODO v4: remove from this rule (not in enum!)
2836
2820
  { $val = { literal: 'enum', sym: $name.id } }
2837
2821
  |
2838
2822
  NULL