@sap/cds-compiler 6.6.0 → 6.7.1

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 (45) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/bin/cdsc.js +2 -0
  3. package/bin/cdsse.js +1 -1
  4. package/lib/base/message-registry.js +6 -7
  5. package/lib/base/model.js +0 -72
  6. package/lib/checks/elements.js +1 -1
  7. package/lib/checks/featureFlags.js +2 -2
  8. package/lib/compiler/assert-consistency.js +3 -4
  9. package/lib/compiler/base.js +8 -0
  10. package/lib/compiler/builtins.js +8 -9
  11. package/lib/compiler/checks.js +27 -6
  12. package/lib/compiler/cycle-detector.js +4 -4
  13. package/lib/compiler/define.js +65 -83
  14. package/lib/compiler/extend.js +357 -325
  15. package/lib/compiler/finalize-parse-cdl.js +3 -4
  16. package/lib/compiler/generate.js +205 -203
  17. package/lib/compiler/kick-start.js +34 -49
  18. package/lib/compiler/populate.js +95 -28
  19. package/lib/compiler/propagator.js +3 -5
  20. package/lib/compiler/resolve.js +17 -13
  21. package/lib/compiler/shared.js +47 -19
  22. package/lib/compiler/tweak-assocs.js +2 -4
  23. package/lib/compiler/utils.js +84 -31
  24. package/lib/gen/BaseParser.js +924 -1055
  25. package/lib/gen/CdlGrammar.checksum +1 -1
  26. package/lib/gen/CdlParser.js +5 -2
  27. package/lib/json/from-csn.js +25 -16
  28. package/lib/main.d.ts +13 -0
  29. package/lib/model/revealInternalProperties.js +18 -0
  30. package/lib/parsers/AstBuildingParser.js +22 -5
  31. package/lib/render/toHdbcds.js +2 -2
  32. package/lib/render/utils/sql.js +2 -2
  33. package/lib/render/utils/standardDatabaseFunctions.js +2 -2
  34. package/lib/transform/db/constraints.js +3 -4
  35. package/lib/transform/db/killAnnotations.js +1 -1
  36. package/lib/transform/db/processSqlServices.js +10 -11
  37. package/lib/transform/effective/associations.js +1 -1
  38. package/lib/transform/forOdata.js +7 -124
  39. package/lib/transform/odata/fioriTreeViews.js +173 -0
  40. package/lib/transform/odata/flattening.js +2 -2
  41. package/lib/transform/translateAssocsToJoins.js +7 -4
  42. package/package.json +1 -1
  43. package/share/messages/message-explanations.json +0 -2
  44. package/share/messages/type-unexpected-foreign-keys.md +0 -52
  45. package/share/messages/type-unexpected-on-condition.md +0 -52
@@ -1,24 +1,25 @@
1
1
  // Kick-start: prepare to resolve all references
2
2
 
3
+ // Remark: it would probably be better to move this to extend.js
4
+
3
5
  'use strict';
4
6
 
5
- const { builtinLocation } = require('../base/location');
6
- const { isBetaEnabled, forEachGeneric } = require('../base/model');
7
+ const { isBetaEnabled } = require('../base/model');
7
8
  const {
8
9
  setLink,
9
10
  annotationVal,
10
11
  annotationIsFalse,
11
- isDirectComposition,
12
+ forEachGeneric,
12
13
  } = require('./utils');
13
14
 
14
15
  function kickStart( model ) {
15
16
  const { options } = model;
16
17
  const { message } = model.$messageFunctions;
17
18
 
18
- const { resolveUncheckedPath, initMainArtifact } = model.$functions;
19
+ const { resolveUncheckedPath, createGapArtifact } = model.$functions;
19
20
 
20
21
  // Set _service link (sorted to set it on parent first). Could be set
21
- // directly, but beware a namespace becoming a service later.
22
+ // directly, but beware a gap artifact becoming a service later.
22
23
  Object.keys( model.definitions ).sort().forEach( setAncestorsAndService );
23
24
  forEachGeneric( model, 'definitions', postProcessArtifact );
24
25
  return;
@@ -40,8 +41,9 @@ function kickStart( model ) {
40
41
  const art = model.definitions[name];
41
42
  if (art._parent === undefined)
42
43
  return; // nothing to do for builtins and redefinitions
43
- if (art.query && art._ancestors === undefined && art.kind === 'entity')
44
- setProjectionAncestors( art );
44
+ if ((art.query || art._entityIncludes) &&
45
+ art._ancestors === undefined && art.kind === 'entity')
46
+ setProjectionAncestors( art ); // TODO: set to [] for other entities
45
47
 
46
48
  let parent = art._parent;
47
49
  if (parent === model.definitions.localized)
@@ -62,7 +64,7 @@ function kickStart( model ) {
62
64
  }
63
65
  }
64
66
 
65
- function setProjectionAncestors( art ) {
67
+ function setProjectionAncestors( art ) { // TODO: rename
66
68
  // Must be run after processLocalizedData() as we could have a projection
67
69
  // on a generated entity.
68
70
 
@@ -70,19 +72,36 @@ function kickStart( model ) {
70
72
  // no redirection target for E if Service2.E = projection on Service1.E and
71
73
  // Service1.E = projection on E
72
74
 
73
- // Remark: _ancestors are also set with includes, and there also for aspects,
74
- // types and events (TODO: entity only)
75
- //
75
+ // Remark: _ancestors are also set with entity includes in extend.js,
76
76
  // Remark: _ancestors are also tested in populate.js for minmal exposure
77
+ //
78
+ // Remark: @cds.autoexpose is considered on-demand in populate.js
77
79
  const chain = [];
78
80
  const autoexposed = annotationVal( art['@cds.autoexposed'] );
79
81
  // no need to set preferredRedirectionTarget in the while loop as we would
80
82
  // use the projection having @cds.redirection.target anyhow instead of
81
83
  // `art` anyway (if we do the no-x-service-implicit-redirection TODO above)
82
- while (art?.query?.from?.path && // direct select with one source
83
- art._ancestors !== 0) { // prevent inf-loop
84
- chain.push( art );
84
+ while (art?.kind === 'entity' &&
85
+ (art.query?.from?.path || // direct select with one source
86
+ art._entityIncludes?.length) &&
87
+ !art._ancestors && art._ancestors !== 0) { // prevent inf-loop
85
88
  setLink( art, '_ancestors', 0 ); // avoid infloop with cyclic from
89
+ if (art._entityIncludes?.length) {
90
+ if (art._entityIncludes.length === 1) {
91
+ chain.push( art );
92
+ art = art._entityIncludes[0];
93
+ continue;
94
+ }
95
+ setLink( art, '_ancestors', [] );
96
+ for (const incl of art._entityIncludes) {
97
+ if ((incl.query || incl._entityIncludes?.length) &&
98
+ incl._ancestors === undefined)
99
+ setProjectionAncestors( incl );
100
+ art._ancestors.push( ...(incl._ancestors || []), incl );
101
+ }
102
+ break;
103
+ }
104
+ chain.push( art );
86
105
  const name = resolveUncheckedPath( art.query.from, 'from', art );
87
106
  art = name && (model.definitions[name] || createGapArtifact( name ));
88
107
  if (autoexposed)
@@ -97,26 +116,7 @@ function kickStart( model ) {
97
116
  }
98
117
  }
99
118
 
100
- function createGapArtifact( name, location = builtinLocation() ) {
101
- // TODO: make it probably part of define.js
102
- // TODO: make it work without location (or value undefined/null)
103
- // TODO: change the location later if overwritten
104
- const art = {
105
- kind: 'namespace', name: { id: name, location }, location,
106
- };
107
- model.definitions[name] = art;
108
- initMainArtifact( art );
109
- return art;
110
- }
111
-
112
119
  function postProcessArtifact( art ) {
113
- tagCompositionTargets( art );
114
- if (art.$queries) {
115
- for (const query of art.$queries) {
116
- if (query.mixin)
117
- forEachGeneric( query, 'mixin', tagCompositionTargets );
118
- }
119
- }
120
120
  if (!art._ancestors || art.kind !== 'entity')
121
121
  return; // redirections only to entities
122
122
  const service = art._service;
@@ -130,28 +130,13 @@ function kickStart( model ) {
130
130
  if (ancestor._service === service || annotationIsFalse( art['@cds.redirection.target'] ))
131
131
  return;
132
132
  const desc = ancestor._descendants ||
133
- setLink( ancestor, '_descendants', Object.create( null ) );
133
+ setLink( ancestor, '_descendants', Object.create( null ) );
134
134
  if (!desc[sname])
135
135
  desc[sname] = [ art ];
136
136
  else
137
137
  desc[sname].push( art );
138
138
  }
139
139
  }
140
-
141
- function tagCompositionTargets( elem ) {
142
- // TODO: together with test for targetIsTargetAspect()
143
- if (elem.target && isDirectComposition( elem )) {
144
- // A target aspect would have already moved to property `targetAspect` in
145
- // define.js (hm... more something for kick-start.js...)
146
- // TODO: for safety, just use resolveUncheckedPath()
147
- const target = resolveUncheckedPath( elem.target, 'target', elem );
148
- if (target)
149
- model.$compositionTargets[target] = true;
150
- }
151
- if (elem.targetAspect?.elements)
152
- elem = elem.targetAspect;
153
- forEachGeneric( elem, 'elements', tagCompositionTargets );
154
- }
155
140
  }
156
141
 
157
142
  module.exports = kickStart;
@@ -17,12 +17,7 @@
17
17
 
18
18
  'use strict';
19
19
 
20
- const {
21
- isDeprecatedEnabled,
22
- forEachDefinition,
23
- forEachMember,
24
- forEachGeneric,
25
- } = require('../base/model');
20
+ const { isDeprecatedEnabled } = require('../base/model');
26
21
  const {
27
22
  dictAdd, dictAddArray, dictFirst, dictForEach,
28
23
  } = require('../base/dictionaries');
@@ -44,6 +39,9 @@ const {
44
39
  setExpandStatusAnnotate,
45
40
  dependsOnSilent,
46
41
  columnRefStartsWithSelf,
42
+ forEachDefinition,
43
+ forEachMember,
44
+ forEachGeneric,
47
45
  } = require('./utils');
48
46
  const { typeParameters } = require('./builtins');
49
47
 
@@ -73,39 +71,48 @@ function populate( model ) {
73
71
  } = model.$messageFunctions;
74
72
  const {
75
73
  resolvePath,
74
+ resolveUncheckedPath,
75
+ createGapArtifact,
76
76
  nestedElements,
77
77
  attachAndEmitValidNames,
78
78
  initMainArtifact,
79
+ initArtifactParentLink,
79
80
  extendArtifactBefore,
80
81
  extendArtifactAfter,
82
+ extendArtifactAdd,
83
+ generateForEntity,
84
+ populateGeneratedEntity,
81
85
  } = model.$functions;
82
86
  Object.assign( model.$functions, {
83
87
  effectiveType,
88
+ generateOnDemand,
84
89
  getOrigin,
85
90
  getInheritedProp,
86
91
  mergeSpecifiedForeignKeys,
92
+ registerGeneratedEntity,
87
93
  } );
88
94
  // let depth = 100;
89
95
 
90
96
  let effectiveSeqNo = 0; // artifact number set after having set _effectiveType
91
97
  /** @type {any} may also be a boolean */
92
- let newAutoExposed = [];
98
+ let newGeneratedEntities = [];
93
99
 
94
100
  const ignoreSpecifiedElements
95
101
  = isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' );
96
102
 
97
103
  forEachDefinition( model, traverseElementEnvironments );
98
- while (newAutoExposed.length) {
99
- // console.log( newAutoExposed.map( a => a.name.id ) )
100
- const all = newAutoExposed;
101
- newAutoExposed = [];
104
+ while (newGeneratedEntities.length) {
105
+ // console.log( newGeneratedEntities.map( a => a.name.id ) )
106
+ const all = newGeneratedEntities;
107
+ newGeneratedEntities = [];
102
108
  all.forEach( traverseElementEnvironments );
103
109
  }
104
- newAutoExposed = true; // internal error if auto-expose after here
110
+ newGeneratedEntities = true; // internal error if entity generation after here
105
111
  return;
106
112
 
107
113
  /** Make sure that effectiveType() is called on all members and items */
108
114
  function traverseElementEnvironments( art ) {
115
+ // console.log('TEE:',require('../model/revealInternalProperties').ref(art))
109
116
  // We leave out foreign keys (as they are traversed via forEachMember).
110
117
  // Keys are handled in tweak-assocs.js
111
118
  if (art.kind === 'key')
@@ -117,10 +124,15 @@ function populate( model ) {
117
124
  art.$queries.forEach( traverseElementEnvironments );
118
125
  if (art.mixin)
119
126
  dictForEach( art.mixin, effectiveType );
120
- if (art.targetAspect?.elements)
127
+ if (art.targetAspect?.elements) {
121
128
  effectiveType( art.targetAspect );
122
- if (art !== art._main?._leadingQuery) // already done
129
+ forEachGeneric( art.targetAspect, 'elements', traverseElementEnvironments );
130
+ }
131
+ else if (art !== art._main?._leadingQuery) { // already done for leading query elems
132
+ while (art.items)
133
+ art = art.items;
123
134
  forEachMember( art, traverseElementEnvironments );
135
+ }
124
136
  }
125
137
 
126
138
 
@@ -191,6 +203,7 @@ function populate( model ) {
191
203
  }
192
204
  chain.reverse();
193
205
  for (const a of chain) {
206
+ // console.log('PEA:',require('../model/revealInternalProperties').ref(a))
194
207
  // Ensure that the _effectiveType of the parent has been calculated. This
195
208
  // is usually the case, but might not be for elements of anonymous target
196
209
  // aspects. Without it, extensions/annotations might get lost.
@@ -202,6 +215,10 @@ function populate( model ) {
202
215
  // TODO: forbid $self+$self.elem inline, see expandWildcard()
203
216
  // Without type, value.path or _origin at beginning, link to itself:
204
217
  extendArtifactBefore( a );
218
+ populateGeneratedEntity( a );
219
+ if (a.includes)
220
+ a.includes.forEach( i => resolveInclude( i, a ) );
221
+ extendArtifactAdd( a );
205
222
  art = populateArtifact( a, art ) || a;
206
223
  setLink( a, '_effectiveType', art );
207
224
  a.$effectiveSeqNo = ++effectiveSeqNo;
@@ -209,6 +226,8 @@ function populate( model ) {
209
226
  if (a.elements$ || a.enum$)
210
227
  mergeSpecifiedElementsOrEnum( a );
211
228
  // console.log( 'ET-DO:', effectiveSeqNo, a?.kind, a?.name, a._extensions?.elements?.length )
229
+ if (a.kind === 'entity' && !a.query)
230
+ generateForEntity( a );
212
231
  extendArtifactAfter( a ); // after setting _effectiveType (for messages)
213
232
  if (a.typeProps$)
214
233
  setSpecifiedElementTypeProperties( a );
@@ -217,13 +236,22 @@ function populate( model ) {
217
236
  return art;
218
237
  }
219
238
 
239
+ function resolveInclude( include, art ) {
240
+ // includes have been resolved with resolveUncheckedPath() with art/ext
241
+ // before, i.e. it is ok to just use `a` as "user"
242
+ const name = resolveUncheckedPath( include, 'include', art );
243
+ // use effectiveType(), which applies extensions, before resolvePath()
244
+ // where we check for the number of elements in the accept functions
245
+ if (name)
246
+ effectiveType( model.definitions[name] );
247
+ resolvePath( include, 'include', art );
248
+ }
249
+
220
250
  function populateArtifact( art, origEffective ) {
221
251
  // Name-resolution relevant properties directly at artifact:
222
252
  // ‹view›.elements of input must have been moved (to elements$) before!
223
253
  // console.log('Q:',art.elements,art.enum,art.items,!!art.query)
224
254
  // console.log('PA:',require('../model/revealInternalProperties').ref(art))
225
- if (art.includes) // first version of includes via effectiveTpe()
226
- art.includes.forEach( i => effectiveType( i._artifact ) );
227
255
  if (art.elements != null || art.enum != null || art.items != null)
228
256
  return art;
229
257
  if (art.target) {
@@ -292,6 +320,36 @@ function populate( model ) {
292
320
  return origEffective;
293
321
  }
294
322
 
323
+ /**
324
+ * Potentially create texts or target entity on demand.
325
+ *
326
+ * `nameOrArt` is the name of the requested entity or a gap artifact having that
327
+ * name. Returns the generated entity, or null if no such entity is created.
328
+ */
329
+ function generateOnDemand( nameOrArt ) {
330
+ const gap = (typeof nameOrArt === 'string')
331
+ ? model.definitions[nameOrArt] || createGapArtifact( nameOrArt )
332
+ : nameOrArt;
333
+ let base = gap._parent;
334
+ if (!base)
335
+ return null;
336
+ const chain = [];
337
+ // We could have requested Base.component.texts before Base.component:
338
+ // from Base, first create Base.component, then Base.component.texts
339
+ for (; base?.kind === 'namespace'; base = base._parent)
340
+ chain.push( base );
341
+ if (base?.kind !== 'entity' || base.query || base.$duplicates || !base.elements)
342
+ return null;
343
+ effectiveType( base ); // induces localized data and target entity creation
344
+ chain.reverse();
345
+ for (const art of chain) {
346
+ if (art.kind !== 'entity')
347
+ return null;
348
+ effectiveType( art ); // induces localized data and target entity creation
349
+ }
350
+ return gap.kind === 'entity' ? gap : null;
351
+ }
352
+
295
353
  // TODO: test it in combination with top-level CAST function
296
354
  // TODO: we could probably "extend" this function to all other cases where we
297
355
  // set an _origin in Universal CSN
@@ -497,7 +555,6 @@ function populate( model ) {
497
555
 
498
556
  // TODO: delete XSN._entities
499
557
  // TODO: delete ENTITY._from - use _origin? instead _from[0]
500
- // TODO (after on-demand ext): delete XSN.$entity
501
558
 
502
559
  /**
503
560
  * Merge _specified_ elements with _inferred_ elements in the given view/element,
@@ -1048,7 +1105,8 @@ function populate( model ) {
1048
1105
 
1049
1106
  if (!exposed.length) {
1050
1107
  const origTarget = target;
1051
- if (isAutoExposed( target ))
1108
+ // TODO: probably calculate effectiveType of target earlier
1109
+ if (effectiveType( target ) && isAutoExposed( target ))
1052
1110
  target = createAutoExposed( origTarget, service, elemScope );
1053
1111
  const desc = origTarget._descendants ||
1054
1112
  setLink( origTarget, '_descendants', Object.create( null ) );
@@ -1217,6 +1275,8 @@ function populate( model ) {
1217
1275
  function isAutoExposed( target ) {
1218
1276
  if (target.$autoexpose !== undefined)
1219
1277
  return target.$autoexpose;
1278
+ // Remark: this can probably be simplified now that we have called
1279
+ // effectiveType() on the original target
1220
1280
  const origTarget = target;
1221
1281
  const chain = [];
1222
1282
  const alias1 = target._from?.[0]; // TODO: delete ENTITY._from ?
@@ -1314,7 +1374,7 @@ function populate( model ) {
1314
1374
  // console.log(absolute)
1315
1375
  const location = weakRefLocation( target.name );
1316
1376
  const from = { path: [ { id: target.name.id, location } ], location };
1317
- let art = {
1377
+ const art = registerGeneratedEntity( {
1318
1378
  kind: 'entity',
1319
1379
  name: { location, id: absolute },
1320
1380
  location,
@@ -1325,7 +1385,7 @@ function populate( model ) {
1325
1385
  name: { path: [ { id: 'cds.autoexposed', location } ], location },
1326
1386
  $inferred: '$generated',
1327
1387
  },
1328
- };
1388
+ } );
1329
1389
  // forward target parameters to projection
1330
1390
  if (target.params) {
1331
1391
  art.params = Object.create( null );
@@ -1342,19 +1402,26 @@ function populate( model ) {
1342
1402
  } );
1343
1403
  }
1344
1404
  // TODO: do we need to tag the generated entity with elemScope = 'auto'?
1345
- if (autoexposed) {
1346
- Object.assign( autoexposed, art );
1347
- art = autoexposed;
1348
- }
1349
- else {
1350
- model.definitions[absolute] = art;
1351
- }
1352
1405
  setLink( art, '_service', service );
1353
1406
  setLink( art, '_block', model.$internal );
1354
1407
  initMainArtifact( art, !!autoexposed );
1355
1408
  effectiveType( art );
1356
1409
  // TODO: try to set locations of elements locations of orig target elements
1357
- newAutoExposed.push( art );
1410
+ newGeneratedEntities.push( art );
1411
+ return art;
1412
+ }
1413
+
1414
+ function registerGeneratedEntity( art ) {
1415
+ const { id } = art.name;
1416
+ const gap = model.definitions[id];
1417
+ if (gap)
1418
+ art = Object.assign( gap, art );
1419
+ else
1420
+ model.definitions[id] = art;
1421
+ newGeneratedEntities.push( art );
1422
+ // add gen entity to _subArtifacts of parent (not done in createGapArtifact()):
1423
+ if (art.$inferred !== 'autoexposed')
1424
+ initArtifactParentLink( art, model.definitions );
1358
1425
  return art;
1359
1426
  }
1360
1427
  }
@@ -8,17 +8,15 @@
8
8
 
9
9
  'use strict';
10
10
 
11
- const {
12
- forEachDefinition,
13
- forEachMember,
14
- forEachGeneric,
15
- } = require( '../base/model');
16
11
  const {
17
12
  setLink,
18
13
  linkToOrigin,
19
14
  withAssociation,
20
15
  viewFromPrimary,
21
16
  copyExpr,
17
+ forEachDefinition,
18
+ forEachMember,
19
+ forEachGeneric,
22
20
  } = require('./utils');
23
21
  const { propagationRules } = require('../base/builtins');
24
22
  const $inferred = Symbol.for( 'cds.$inferred' );
@@ -38,13 +38,7 @@
38
38
 
39
39
  'use strict';
40
40
 
41
- const {
42
- forEachDefinition,
43
- forEachMember,
44
- forEachGeneric,
45
- forEachInOrder,
46
- isDeprecatedEnabled,
47
- } = require('../base/model');
41
+ const { isDeprecatedEnabled } = require('../base/model');
48
42
  const { dictAdd } = require('../base/dictionaries');
49
43
  const { weakLocation } = require('../base/location');
50
44
  const { combinedLocation } = require('../base/location');
@@ -65,6 +59,10 @@ const {
65
59
  compositionTextVariant,
66
60
  targetCantBeAspect,
67
61
  userParam,
62
+ forEachDefinition,
63
+ forEachMember,
64
+ forEachGeneric,
65
+ forEachInOrder,
68
66
  } = require('./utils');
69
67
 
70
68
  const detectCycles = require('./cycle-detector');
@@ -109,7 +107,9 @@ function resolve( model ) {
109
107
  = isDeprecatedEnabled( options, 'ignoreSpecifiedQueryElements' );
110
108
 
111
109
  forEachGeneric( model, 'sources', resolveUsings );
112
- return doResolve();
110
+ doResolve();
111
+ model.$functions.checkGenerateConditions();
112
+ return model;
113
113
 
114
114
  /**
115
115
  * Resolve the using declarations in `using`.
@@ -173,7 +173,6 @@ function resolve( model ) {
173
173
  error( '$internal-expecting-cyclic', null, {},
174
174
  'INTERNAL: the compiler should have issued an Error[ref-cyclic]' );
175
175
  }
176
- return model;
177
176
  }
178
177
 
179
178
  //--------------------------------------------------------------------------
@@ -447,6 +446,8 @@ function resolve( model ) {
447
446
  if (getInheritedProp( art, 'targetAspect' )) {
448
447
  error( 'def-invalid-key', [ key.location, art ], { '#': 'composition' } );
449
448
  // TODO: test with managed composition exposed with explicit KEY
449
+ // TODO: this should be checked in generate.js (with setting), see
450
+ // test3/ManagedCompositions/CompositionAsKey/
450
451
  }
451
452
  else if (art.target && getInheritedProp( art, 'on' )) {
452
453
  error( 'def-invalid-key', [ key.location, art ], { '#': 'unmanaged' } );
@@ -587,7 +588,7 @@ function resolve( model ) {
587
588
  }
588
589
 
589
590
  resolveExprInAnnotations( art );
590
- forEachMember( art, resolveRefs, art.targetAspect );
591
+ forEachMember( obj.targetAspect || obj, resolveRefs );
591
592
  // After the resolving of foreign keys (and adding implicit ones):
592
593
  if (obj.target?.$inferred === '')
593
594
  checkRedirectedUserTarget( art );
@@ -916,7 +917,8 @@ function resolve( model ) {
916
917
  error( 'ref-invalid-calc-elem', [ include.location || art.value.location, art ],
917
918
  { '#': art._main.kind } );
918
919
  }
919
- else {
920
+ else if (art._main.kind !== 'extend') {
921
+ // if there was an error while extending, the main kind can be still 'extend'
920
922
  error( 'def-invalid-calc-elem', loc, { '#': art._main.kind } );
921
923
  }
922
924
  }
@@ -1072,8 +1074,9 @@ function resolve( model ) {
1072
1074
  const target = resolvePath( obj.target, 'target', art );
1073
1075
 
1074
1076
  if (obj._columnParent && obj.type && !obj.type.$inferred && art._main && art._main.query) {
1075
- // New association inside expand/inline: The on-condition can't be properly checked,
1076
- // so abort early. See #8797
1077
+ // New association inside expand/inline: The on-condition can't be properly
1078
+ // checked, so abort early. See #8797. If we'd allow this in the future, we
1079
+ // must consider composition targets in expand/inline for auto-redirections
1077
1080
  error( 'query-unexpected-assoc', [ obj.name.location, art ], {},
1078
1081
  'Unexpected new association in expand/inline' );
1079
1082
  return; // avoid subsequent errors
@@ -1199,6 +1202,7 @@ function resolve( model ) {
1199
1202
  return max && (typeof max.val !== 'number' || max.val > 1);
1200
1203
  }
1201
1204
 
1205
+ // TODO: move to populate.js and call there
1202
1206
  function addImplicitForeignKeys( art, obj, target ) {
1203
1207
  if (!art.$inferred && !art.virtual?.val && isQuasiVirtualAssociation( obj )) {
1204
1208
  if (!isDeprecatedEnabled( options, 'noQuasiVirtualAssocs' )) {