@sap/cds-compiler 3.9.4 → 4.0.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 (94) hide show
  1. package/CHANGELOG.md +92 -4
  2. package/README.md +0 -1
  3. package/bin/cdsc.js +11 -23
  4. package/bin/cdsse.js +3 -3
  5. package/doc/API.md +5 -0
  6. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  7. package/doc/CHANGELOG_BETA.md +17 -1
  8. package/doc/CHANGELOG_DEPRECATED.md +28 -0
  9. package/lib/api/.eslintrc.json +1 -1
  10. package/lib/api/main.js +26 -8
  11. package/lib/api/options.js +2 -0
  12. package/lib/base/error.js +2 -0
  13. package/lib/base/message-registry.js +143 -64
  14. package/lib/base/messages.js +213 -107
  15. package/lib/base/model.js +11 -11
  16. package/lib/checks/.eslintrc.json +1 -1
  17. package/lib/checks/annotationsOData.js +2 -2
  18. package/lib/checks/elements.js +1 -1
  19. package/lib/checks/enricher.js +26 -3
  20. package/lib/checks/onConditions.js +67 -12
  21. package/lib/checks/queryNoDbArtifacts.js +106 -105
  22. package/lib/checks/sql-snippets.js +2 -0
  23. package/lib/checks/types.js +12 -6
  24. package/lib/checks/validator.js +2 -2
  25. package/lib/compiler/assert-consistency.js +10 -8
  26. package/lib/compiler/builtins.js +8 -2
  27. package/lib/compiler/checks.js +52 -35
  28. package/lib/compiler/define.js +31 -26
  29. package/lib/compiler/extend.js +120 -65
  30. package/lib/compiler/finalize-parse-cdl.js +12 -43
  31. package/lib/compiler/generate.js +16 -5
  32. package/lib/compiler/index.js +8 -5
  33. package/lib/compiler/kick-start.js +4 -3
  34. package/lib/compiler/populate.js +96 -95
  35. package/lib/compiler/propagator.js +7 -8
  36. package/lib/compiler/resolve.js +377 -103
  37. package/lib/compiler/shared.js +794 -517
  38. package/lib/compiler/tweak-assocs.js +8 -6
  39. package/lib/compiler/utils.js +44 -0
  40. package/lib/edm/annotations/genericTranslation.js +12 -4
  41. package/lib/edm/csn2edm.js +34 -32
  42. package/lib/edm/edm.js +34 -31
  43. package/lib/edm/edmAnnoPreprocessor.js +0 -23
  44. package/lib/edm/edmInboundChecks.js +7 -2
  45. package/lib/edm/edmPreprocessor.js +18 -17
  46. package/lib/edm/edmUtils.js +8 -4
  47. package/lib/gen/Dictionary.json +18 -0
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +4 -2
  50. package/lib/gen/languageParser.js +5006 -4582
  51. package/lib/json/from-csn.js +157 -112
  52. package/lib/json/to-csn.js +60 -89
  53. package/lib/language/antlrParser.js +17 -13
  54. package/lib/language/docCommentParser.js +11 -1
  55. package/lib/language/genericAntlrParser.js +13 -10
  56. package/lib/language/language.g4 +168 -97
  57. package/lib/main.d.ts +128 -36
  58. package/lib/main.js +1 -1
  59. package/lib/model/csnRefs.js +24 -5
  60. package/lib/model/csnUtils.js +9 -8
  61. package/lib/model/revealInternalProperties.js +7 -12
  62. package/lib/modelCompare/compare.js +1 -1
  63. package/lib/modelCompare/utils/filter.js +40 -2
  64. package/lib/optionProcessor.js +0 -3
  65. package/lib/render/toCdl.js +247 -214
  66. package/lib/render/toHdbcds.js +197 -181
  67. package/lib/render/toSql.js +325 -289
  68. package/lib/render/utils/common.js +42 -4
  69. package/lib/render/utils/delta.js +1 -1
  70. package/lib/render/utils/sql.js +3 -3
  71. package/lib/transform/braceExpression.js +2 -2
  72. package/lib/transform/db/.eslintrc.json +1 -1
  73. package/lib/transform/db/applyTransformations.js +3 -3
  74. package/lib/transform/db/associations.js +24 -12
  75. package/lib/transform/db/expansion.js +17 -18
  76. package/lib/transform/db/flattening.js +17 -21
  77. package/lib/transform/db/rewriteCalculatedElements.js +171 -64
  78. package/lib/transform/db/views.js +3 -4
  79. package/lib/transform/draft/db.js +21 -12
  80. package/lib/transform/draft/odata.js +4 -0
  81. package/lib/transform/forOdataNew.js +11 -10
  82. package/lib/transform/forRelationalDB.js +12 -7
  83. package/lib/transform/localized.js +4 -2
  84. package/lib/transform/odata/toFinalBaseType.js +5 -5
  85. package/lib/transform/odata/typesExposure.js +3 -3
  86. package/lib/transform/parseExpr.js +3 -0
  87. package/lib/transform/transformUtilsNew.js +43 -23
  88. package/lib/transform/translateAssocsToJoins.js +7 -6
  89. package/lib/transform/universalCsn/.eslintrc.json +1 -1
  90. package/lib/transform/universalCsn/coreComputed.js +7 -5
  91. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
  92. package/package.json +2 -2
  93. package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
  94. package/share/messages/message-explanations.json +1 -1
@@ -45,10 +45,24 @@ const {
45
45
  setExpandStatus,
46
46
  setExpandStatusAnnotate,
47
47
  } = require('./utils');
48
+ const { typeParameters } = require('./builtins');
48
49
 
49
50
  const $inferred = Symbol.for('cds.$inferred');
50
51
  const $location = Symbol.for('cds.$location');
51
52
 
53
+ /**
54
+ * These properties are copied from specified elements.
55
+ */
56
+ const typePropertiesFromSpecifiedElements = {
57
+ // 'key' is special case, see setSpecifiedElementTypeProperties()
58
+ // TODO: Decide on behavior if an actual key does not have "key" property in specified elements,
59
+ // and another non-key is marked key in them.
60
+ // key: 'if-undefined',
61
+ default: 1,
62
+ notNull: 1,
63
+ localized: 1,
64
+ ...typeParameters.expectedLiteralsFor,
65
+ };
52
66
 
53
67
  // Export function of this file.
54
68
  function populate( model ) {
@@ -64,11 +78,11 @@ function populate( model ) {
64
78
  extendArtifactBefore,
65
79
  extendArtifactAfter,
66
80
  } = model.$functions;
67
- model.$volatileFunctions.environment = environment;
68
81
  Object.assign( model.$functions, {
69
82
  effectiveType,
70
83
  getOrigin,
71
84
  resolveType,
85
+ navigationEnv,
72
86
  } );
73
87
  // let depth = 100;
74
88
 
@@ -113,17 +127,8 @@ function populate( model ) {
113
127
  //--------------------------------------------------------------------------
114
128
  // Phase 2: call effectiveType() on-demand, which also calculates view elems
115
129
 
116
- // Return effective search environment provided by artifact `art`, i.e. the
117
- // `artifacts` or `elements` dictionary. For the latter, follow the `type`
118
- // chain and resolve the association `target`. View elements are calculated
119
- // on demand.
120
- function environment( art, location, user, assocSpec ) {
121
- const env = navigationEnv( art, location, user, assocSpec );
122
- if (env === 0)
123
- return 0;
124
- return env && env.elements || Object.create(null);
125
- }
126
-
130
+ // TODO: move setting dependencies and complaining about assocs in keys
131
+ // to resolvePath - then remove params: location, user
127
132
  function navigationEnv( art, location, user, assocSpec ) {
128
133
  // = effectiveType() on from-path, TODO: should actually already part of
129
134
  // resolvePath() on FROM
@@ -132,7 +137,7 @@ function populate( model ) {
132
137
  let type = effectiveType( art );
133
138
  while (type?.items) // TODO: disallow navigation to many sometimes
134
139
  type = effectiveType( type.items );
135
- if (!type?.target)
140
+ if (!type?.target || assocSpec === 'targetAspectOnly')
136
141
  return type;
137
142
 
138
143
  if (assocSpec === false) {
@@ -215,12 +220,14 @@ function populate( model ) {
215
220
  // Without type, value.path or _origin at beginning, link to itself:
216
221
  extendArtifactBefore( a );
217
222
  art = populateArtifact( a, art ) || a;
218
- if (a.elements$ || a.enum$)
219
- mergeSpecifiedElementsOrEnum( a );
220
223
  setLink( a, '_effectiveType', art );
221
224
  a.$effectiveSeqNo = ++effectiveSeqNo;
225
+ if (a.elements$ || a.enum$)
226
+ mergeSpecifiedElementsOrEnum( a );
222
227
  // console.log( 'ET-DO:', effectiveSeqNo, a?.kind, a?.name, a._extensions?.elements?.length )
223
228
  extendArtifactAfter( a ); // after setting _effectiveType (for messages)
229
+ if (a.typeProps$)
230
+ setSpecifiedElementTypeProperties(a);
224
231
  }
225
232
  // console.log( 'ET-END:', art?.kind, art?.name )
226
233
  return art;
@@ -241,6 +248,9 @@ function populate( model ) {
241
248
  if (!art.expand)
242
249
  return art;
243
250
  }
251
+ else if (art.targetAspect) { // target aspect in aspect
252
+ return art;
253
+ }
244
254
 
245
255
  // With properties to be calculated: ----------------------------------------
246
256
  if (art.query && art.kind !== '$tableAlias') { // query entity
@@ -313,19 +323,21 @@ function populate( model ) {
313
323
  return getOrigin( art.$queries?.[0] );
314
324
  }
315
325
  else {
326
+ // TODO: write checks for path in enum?
316
327
  if (art.value?.path)
317
- return resolvePath( art.value, 'expr', art, null );
328
+ return resolvePath( art.value, (art.$syntax === 'calc' ? 'calc' : 'expr'), art );
318
329
  if (art.kind === 'select')
319
330
  return getOrigin( dictFirst( art.$tableAliases ) );
320
331
  // init sets _origin for alias to sub query, only need to handle ref here:
321
332
  if (art.kind === '$tableAlias') {
322
333
  // do not use navigationEnv(): it would always call effectiveType() on the
323
- // source → we would would have a deeper callstack
334
+ // source → we would have a deeper callstack
324
335
  const source = resolvePath( art, 'from', art._parent );
325
336
  if (!source?._main)
326
337
  return source; // direct entity (or undefined)
327
338
  // Before having done the resolvePath cleanup, do not rely on resolvePath
328
339
  // to call effectiveType() on the last assoc of a from ref:
340
+ // TODO: check this with test3/Queries/DollarSelf/CorruptedSource.err.cds
329
341
  const assoc = effectiveType( source );
330
342
  return resolvePath( assoc?.target, 'target', assoc );
331
343
  }
@@ -338,25 +350,6 @@ function populate( model ) {
338
350
  return ref._artifact;
339
351
  while (user._outer) // in items
340
352
  user = user._outer;
341
- if (ref.scope === 'typeOf') {
342
- let struct = user;
343
- while (struct.kind === 'element')
344
- struct = struct._parent;
345
- if (struct.kind === 'select' || struct.kind === 'annotation') {
346
- // `type of` in annotation definitions can't work, because csn type refs
347
- // always refer to definitions.
348
- message( 'type-unexpected-typeof', [ ref.location, user ],
349
- { keyword: 'type of', '#': struct.kind } );
350
- // we actually refer to an element in _combined; TODO: return null if
351
- // not configurable; would produce illegal CSN with sub queries in FROM
352
- }
353
- else if (struct !== user._main) {
354
- message( 'type-unexpected-typeof', [ ref.location, user ],
355
- { keyword: 'type of', '#': struct.kind } );
356
- return setArtifactLink( ref, null );
357
- }
358
- return resolvePath( ref, 'typeOf', user );
359
- }
360
353
  if (user.kind === 'event')
361
354
  return resolvePath( ref, 'eventType', user );
362
355
  if (user.kind === 'param' && user._parent &&
@@ -525,8 +518,7 @@ function populate( model ) {
525
518
  * Merge _specified_ elements with _inferred_ elements in the given view/element,
526
519
  * where specified elements can appear through CSN.
527
520
  *
528
- * We only copy annotations, since they are not part of `columns`,
529
- * but only appear in `elements` in CSN.
521
+ * We only copy annotations.
530
522
  *
531
523
  * This is important to ensure re-compilability.
532
524
  *
@@ -535,6 +527,8 @@ function populate( model ) {
535
527
  * @param art
536
528
  */
537
529
  function mergeSpecifiedElementsOrEnum( art ) {
530
+ let wasAnnotated = false;
531
+
538
532
  for (const id in (art.elements || art.enum)) {
539
533
  const ielem = art.elements ? art.elements[id] : art.enum[id]; // inferred element
540
534
  const selem = art.elements$ ? art.elements$[id] : art.enum$[id]; // specified element
@@ -543,44 +537,69 @@ function populate( model ) {
543
537
  'Element $(ID) is missing in specified elements' );
544
538
  }
545
539
  else {
546
- let wasAnnotated = false;
547
540
  for (const prop in selem) {
548
541
  // just annotation assignments and doc comments for the moment
549
542
  if (prop.charAt(0) === '@' || prop === 'doc') {
550
543
  ielem[prop] = selem[prop];
551
544
  // required for gensrc mode of to-csn.js, otherwise the annotation
552
545
  // may be lost during recompilation.
546
+ // TODO: Clarify: Should gensrc add this annotation to the column or as an
547
+ // annotate statement? Currently it's at the column.
553
548
  ielem[prop].$priority = 'annotate';
554
549
  wasAnnotated = true;
555
550
  }
551
+ else if (typePropertiesFromSpecifiedElements[prop]) {
552
+ if (!ielem.typeProps$)
553
+ setLink(ielem, 'typeProps$', Object.create(null));
554
+ // Note: At this point in time, effectiveType() was likely not called on the
555
+ // element, yet. Setting it here, we can't compare it to it's value from _origin.
556
+ ielem.typeProps$[prop] = selem[prop];
557
+ }
556
558
  }
557
559
 
558
- if (wasAnnotated)
559
- setExpandStatusAnnotate(art, 'annotate');
560
-
561
560
  selem.$replacement = true;
562
- if (selem.elements) {
561
+ if (selem.elements)
563
562
  setLink(ielem, 'elements$', selem.elements);
564
- delete selem.elements;
565
- }
566
- if (selem.enum) {
563
+ if (selem.enum)
567
564
  setLink(ielem, 'enum$', selem.enum);
568
- delete selem.enum;
569
- }
570
565
  }
571
566
  }
567
+
568
+ if (wasAnnotated)
569
+ setExpandStatusAnnotate(art, 'annotate');
570
+
572
571
  // TODO: We don't check enum$, yet! We first need to fix expansion for
573
572
  // `cast(elem as EnumType)` (see #9421)
574
573
  for (const id in art.elements$) {
575
- const selem = art.elements$[id]; // specified element
576
- if (!selem.$replacement) {
577
- // console.log( 'QED:', art.name, art.kind, art.elements )
578
- error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
574
+ const specifiedElement = art.elements$[id];
575
+ // TODO: Custom kind?
576
+ specifiedElement.$isSpecifiedElement = true;
577
+ if (!specifiedElement.$replacement) {
578
+ const loc = [ specifiedElement.name.location, specifiedElement ];
579
+ error( 'query-unspecified-element', loc, { id },
579
580
  'Element $(ID) does not result from the query' );
580
581
  }
581
582
  }
582
583
  }
583
584
 
585
+ function setSpecifiedElementTypeProperties( art ) {
586
+ for (const prop in art.typeProps$) {
587
+ let o = art;
588
+ if (o._effectiveType !== 0) { // cyclic
589
+ while (!o[prop] && getOrigin(o))
590
+ o = getOrigin(o);
591
+ }
592
+
593
+ if (typePropertiesFromSpecifiedElements[prop] === 'if-undefined') {
594
+ if (!o[prop])
595
+ art[prop] = art.typeProps$[prop];
596
+ }
597
+ else if (!o[prop] || art.typeProps$[prop].val !== o[prop]?.val) {
598
+ art[prop] = art.typeProps$[prop];
599
+ }
600
+ }
601
+ }
602
+
584
603
  function populateQuery( query ) {
585
604
  if (query._combined || !query.from || !query.$tableAliases)
586
605
  // already done or $join query or parse error
@@ -633,7 +652,7 @@ function populate( model ) {
633
652
  return null;
634
653
  // If we allow CDL-style casts of `expand`s to associations in the future, we
635
654
  // need to ignore an explicit type, i.e. not getOrigin():
636
- const assoc = resolvePath( elem.value, 'expr', elem, null );
655
+ const assoc = resolvePath( elem.value, 'expr', elem ); // TODO: extra 'column'?
637
656
  if (!effectiveType( assoc )?.target)
638
657
  return initFromColumns( elem, elem.expand );
639
658
  const { targetMax } = path[path.length - 1].cardinality || getCardinality( assoc );
@@ -727,8 +746,10 @@ function populate( model ) {
727
746
  return;
728
747
  }
729
748
  if (!elem.type && elem.value?.type) { // top-level CAST( expr AS type )
730
- if (!elem.target) // TODO: we might issue an error if there is a target
749
+ if (!elem.target) { // TODO: we might issue an error if there is a target
731
750
  elem.type = { ...elem.value.type, $inferred: 'cast' };
751
+ // TODO: What about other direct properties in cast such as items/enum/...?
752
+ }
732
753
  }
733
754
  if (elem.foreignKeys) // REDIRECTED with explicit foreign keys
734
755
  forEachGeneric( elem, 'foreignKeys', (key, name) => initKey( key, name, elem ) );
@@ -832,9 +853,12 @@ function populate( model ) {
832
853
 
833
854
  function columnEnv( envParent, query ) { // etc. wildcard._pathHead;
834
855
  // if (envParent) console.log( 'CE:', envParent._origin, query );
835
- return (envParent)
836
- ? environment( getOrigin( envParent ) ) // not the col with expand, but the referred
837
- : userQuery( query )._combined;
856
+ if (!envParent)
857
+ return userQuery( query )._combined;
858
+ const env = navigationEnv( getOrigin( envParent ) );
859
+ if (env === 0)
860
+ return 0;
861
+ return env?.elements || Object.create(null);
838
862
  }
839
863
 
840
864
  function reportReplacement( sibling, navElem, query ) {
@@ -903,13 +927,17 @@ function populate( model ) {
903
927
  // PRE: elem has no target, assoc has target prop
904
928
  if (elem.kind === '$tableAlias')
905
929
  return false;
930
+ // Specified elements could lead to warnings that seem unfixable by the user.
931
+ // TODO: Custom kind?
932
+ if (elem.$isSpecifiedElement)
933
+ return false;
906
934
  const assocTarget = resolvePath( assoc.target, 'target', assoc );
907
935
  let target = assocTarget;
908
936
  // console.log( info( null, [ elem.location, elem ], {target,art:assoc,name:''+assoc.target},
909
937
  // 'RED').toString())
910
938
  if (!target)
911
939
  return false; // error in target ref
912
- const { location } = elem.value || elem.type || elem.name;
940
+ const { location } = elem.value || elem.type || elem.name || elem;
913
941
  const service = (elem._main || elem)._service;
914
942
  if (service && service !== target._service && assocIsToBeRedirected( elem )) {
915
943
  if (service !== (assoc._main || assoc)._service ||
@@ -920,36 +948,9 @@ function populate( model ) {
920
948
  if (elem === assoc) { // redirection of user-provided target
921
949
  if (assocTarget === target) // no change (due to no implicit redirection)
922
950
  return true;
923
- const type = resolvePath( elem.type, 'type', elem ); // cds.Association or cds.Composition
924
- const origin = {
925
- kind: elem.kind, // necessary for rewrite, '$user-provided' would be best
926
- name: elem.name,
927
- type: { // TODO: necessary?
928
- path: [ { id: type.name.absolute, location: elem.type.location } ],
929
- scope: 'global',
930
- location: elem.type.location,
931
- $inferred: 'REDIRECTED',
932
- },
933
- target: elem.target,
934
- $inferred: 'REDIRECTED',
935
- location: elem.target.location,
936
- };
937
- setLink( elem, '_origin', origin );
938
- setArtifactLink( elem.type, type );
939
- setLink( origin, '_outer', elem );
940
- setLink( origin, '_parent', elem._parent );
941
- if (elem._main) // remark: the param `elem` can also be a type
942
- setLink( origin, '_main', elem._main );
943
- setLink( origin, '_effectiveType', origin );
944
- setLink( origin, '_block', elem._block );
945
- if (elem.foreignKeys) {
946
- origin.foreignKeys = elem.foreignKeys;
947
- delete elem.foreignKeys; // will be rewritten
948
- }
949
- if (elem.on) {
950
- origin.on = elem.on;
951
- delete elem.on; // will be rewritten
952
- }
951
+ elem.target.$inferred = '';
952
+ setArtifactLink( elem.target, target );
953
+ return true;
953
954
  }
954
955
  if (target !== assocTarget)
955
956
  setExpandStatus( elem, 'target' ); // (might) also set in rewriteCondition
@@ -1235,22 +1236,22 @@ function populate( model ) {
1235
1236
  }
1236
1237
  return autoexposed;
1237
1238
  }
1238
- error( 'duplicate-autoexposed', [ service.name.location, service ],
1239
- { target, art: absolute },
1240
- 'Name $(ART) of auto-exposed entity for $(TARGET) collides with other definition' );
1239
+ message( 'def-duplicate-autoexposed', [ service.name.location, service ],
1240
+ { target, art: absolute },
1241
+ 'Name $(ART) of auto-exposed entity for $(TARGET) collides with other definition' );
1241
1242
  info( null, [ target.name.location, target ],
1242
1243
  { art: service },
1243
1244
  'Expose this (or the competing) entity explicitly in service $(ART)' );
1244
1245
  if (autoexposed.$inferred !== 'autoexposed')
1245
1246
  return target;
1246
1247
  const firstTarget = autoexposed.query.from._artifact;
1247
- error( 'duplicate-autoexposed', [ service.name.location, service ],
1248
- { target: firstTarget, art: absolute },
1249
- 'Name $(ART) of auto-exposed entity for $(TARGET) collides with other definition' );
1248
+ message( 'def-duplicate-autoexposed', [ service.name.location, service ],
1249
+ { target: firstTarget, art: absolute },
1250
+ 'Name $(ART) of auto-exposed entity for $(TARGET) collides with other definition' );
1250
1251
  info( null, [ firstTarget.name.location, firstTarget ],
1251
1252
  { art: service },
1252
1253
  'Expose this (or the competing) entity explicitly in service $(ART)' );
1253
- autoexposed.$inferred = 'duplicate-autoexposed';
1254
+ autoexposed.$inferred = 'def-duplicate-autoexposed';
1254
1255
  return target;
1255
1256
  }
1256
1257
  // console.log(absolute)
@@ -13,7 +13,6 @@ const {
13
13
  forEachMember,
14
14
  forEachGeneric,
15
15
  isDeprecatedEnabled,
16
- isBetaEnabled,
17
16
  } = require( '../base/model');
18
17
  const {
19
18
  setLink,
@@ -43,8 +42,8 @@ function propagate( model ) {
43
42
  '@cds.external': never,
44
43
  '@cds.redirection.target': never,
45
44
  '@fiori.draft.enabled': onlyViaArtifact,
46
- '@': annotation, // always except in 'items'
47
- doc: annotation, // always except in 'items'
45
+ '@': annotation, // always except in 'items' (and parameters for entity return types)
46
+ doc: annotation, // always except in 'items' (and parameters for entity return types)
48
47
  default: withKind, // always except in 'items'
49
48
  virtual,
50
49
  notNull,
@@ -74,7 +73,6 @@ function propagate( model ) {
74
73
  // eslint-disable-next-line max-len
75
74
  const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '_oldVirtualNotNullPropagation' );
76
75
  const { warning, throwWithError } = model.$messageFunctions;
77
- const propagateToReturns = isBetaEnabled( options, 'v4preview' );
78
76
 
79
77
  forEachDefinition( model, run );
80
78
 
@@ -157,7 +155,7 @@ function propagate( model ) {
157
155
 
158
156
  function step({ target, source }) {
159
157
  // console.log('PROPS:',source&&source.name,'->',target.name)
160
- const viaType = target.type && // TODO: falsy $inferred value instead 'cast'?
158
+ const viaType = target.type && // TODO: falsy $inferred value instead of 'cast'?
161
159
  (!target.type.$inferred || target.type.$inferred === 'cast');
162
160
  const keys = Object.keys( source );
163
161
  for (const prop of keys) {
@@ -279,13 +277,14 @@ function propagate( model ) {
279
277
  const from = viewFromPrimary( target )?.path;
280
278
  // do not propagate from member / if follow assoc in from or into `returns` of actions (v4)
281
279
  if (!(from ? from[from.length - 1]._artifact : source)._main &&
282
- !(propagateToReturns && target._parent && target._parent.returns === target))
280
+ !(target._parent && target._parent.returns === target))
283
281
  annotation( prop, target, source );
284
282
  }
285
283
 
286
284
  function withKind( prop, target, source ) {
287
- if (target.kind &&
288
- (propagateToReturns || !target._parent || target._parent.returns !== target))
285
+ if (target.kind === 'param' && source.kind === 'entity')
286
+ return; // Don't propagate from entity types to parameters (+ return type).
287
+ if (target.kind)
289
288
  always(prop, target, source); // not in 'items'
290
289
  }
291
290