@sap/cds-compiler 3.6.2 → 3.7.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.
- package/CHANGELOG.md +49 -0
- package/README.md +3 -0
- package/bin/cdsc.js +9 -5
- package/doc/CHANGELOG_BETA.md +20 -2
- package/doc/CHANGELOG_DEPRECATED.md +2 -2
- package/lib/api/main.js +2 -1
- package/lib/base/dictionaries.js +10 -0
- package/lib/base/message-registry.js +56 -12
- package/lib/base/messages.js +39 -20
- package/lib/base/model.js +1 -0
- package/lib/base/shuffle.js +2 -1
- package/lib/checks/elements.js +29 -1
- package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +9 -5
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/onConditions.js +8 -5
- package/lib/checks/types.js +6 -1
- package/lib/checks/validator.js +7 -3
- package/lib/compiler/assert-consistency.js +20 -23
- package/lib/compiler/base.js +1 -2
- package/lib/compiler/builtins.js +2 -2
- package/lib/compiler/checks.js +237 -242
- package/lib/compiler/define.js +63 -75
- package/lib/compiler/extend.js +325 -22
- package/lib/compiler/finalize-parse-cdl.js +1 -55
- package/lib/compiler/kick-start.js +6 -7
- package/lib/compiler/populate.js +284 -288
- package/lib/compiler/propagator.js +15 -13
- package/lib/compiler/resolve.js +136 -306
- package/lib/compiler/shared.js +42 -44
- package/lib/compiler/tweak-assocs.js +29 -27
- package/lib/compiler/utils.js +29 -3
- package/lib/edm/annotations/genericTranslation.js +7 -13
- package/lib/edm/annotations/preprocessAnnotations.js +3 -0
- package/lib/edm/csn2edm.js +0 -4
- package/lib/edm/edm.js +6 -4
- package/lib/edm/edmAnnoPreprocessor.js +1 -0
- package/lib/edm/edmPreprocessor.js +1 -5
- package/lib/gen/Dictionary.json +34 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +2429 -2401
- package/lib/inspect/inspectPropagation.js +2 -0
- package/lib/json/from-csn.js +87 -41
- package/lib/json/to-csn.js +47 -16
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +109 -28
- package/lib/language/language.g4 +20 -4
- package/lib/model/csnRefs.js +1 -1
- package/lib/model/csnUtils.js +1 -0
- package/lib/model/revealInternalProperties.js +1 -2
- package/lib/modelCompare/compare.js +2 -1
- package/lib/render/manageConstraints.js +5 -2
- package/lib/render/toCdl.js +20 -7
- package/lib/render/toHdbcds.js +2 -8
- package/lib/render/toSql.js +4 -3
- package/lib/render/utils/common.js +9 -5
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/expansion.js +2 -0
- package/lib/transform/db/flattening.js +37 -36
- package/lib/transform/db/rewriteCalculatedElements.js +559 -0
- package/lib/transform/db/transformExists.js +4 -0
- package/lib/transform/db/views.js +40 -37
- package/lib/transform/forRelationalDB.js +38 -28
- package/lib/transform/odata/typesExposure.js +50 -15
- package/lib/transform/parseExpr.js +14 -8
- package/lib/transform/transformUtilsNew.js +6 -5
- package/lib/transform/translateAssocsToJoins.js +49 -33
- package/package.json +1 -1
|
@@ -14,7 +14,12 @@ const {
|
|
|
14
14
|
forEachGeneric,
|
|
15
15
|
isDeprecatedEnabled,
|
|
16
16
|
} = require( '../base/model');
|
|
17
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
setLink,
|
|
19
|
+
linkToOrigin,
|
|
20
|
+
withAssociation,
|
|
21
|
+
viewFromPrimary,
|
|
22
|
+
} = require('./utils');
|
|
18
23
|
const $inferred = Symbol.for('cds.$inferred');
|
|
19
24
|
// const { refString } = require( '../base/messages')
|
|
20
25
|
|
|
@@ -81,7 +86,7 @@ function propagate( model ) {
|
|
|
81
86
|
function run( art ) {
|
|
82
87
|
if (!art)
|
|
83
88
|
return;
|
|
84
|
-
if (!checkAndSetStatus( art )) {
|
|
89
|
+
if (!checkAndSetStatus( art ) || art.kind === 'select') {
|
|
85
90
|
runMembers( art );
|
|
86
91
|
return;
|
|
87
92
|
}
|
|
@@ -254,7 +259,8 @@ function propagate( model ) {
|
|
|
254
259
|
}
|
|
255
260
|
|
|
256
261
|
function onlyViaArtifact( prop, target, source ) {
|
|
257
|
-
const from = target
|
|
262
|
+
const from = viewFromPrimary( target )?.path;
|
|
263
|
+
// do not propagate from member / if follow assoc in from:
|
|
258
264
|
if (!(from ? from[from.length - 1]._artifact : source)._main)
|
|
259
265
|
annotation( prop, target, source );
|
|
260
266
|
}
|
|
@@ -280,7 +286,7 @@ function propagate( model ) {
|
|
|
280
286
|
if (!viaType)
|
|
281
287
|
always( prop, target, source );
|
|
282
288
|
else if (!oldVirtualNotNullPropagation) // NULL would block strange propagation to sub element
|
|
283
|
-
target[prop] = { $inferred: 'NULL', val: undefined }; // set null value in
|
|
289
|
+
target[prop] = { $inferred: 'NULL', val: undefined }; // set null value in Universal CSN
|
|
284
290
|
}
|
|
285
291
|
|
|
286
292
|
function checkVirtual( view ) {
|
|
@@ -331,18 +337,14 @@ function targetMinZero( art ) {
|
|
|
331
337
|
}
|
|
332
338
|
|
|
333
339
|
function getOrigin( art ) {
|
|
334
|
-
|
|
340
|
+
let origin = art._origin;
|
|
341
|
+
while (origin?.kind === 'select')
|
|
342
|
+
origin = origin._origin;
|
|
343
|
+
if (origin)
|
|
335
344
|
// Do not consider _origin if due to expand of table alias ref
|
|
336
|
-
return (!art.expand ||
|
|
345
|
+
return (!art.expand || origin.kind === 'element') && origin;
|
|
337
346
|
// Remark: a column with an 'inline' is never an element -> no need to check
|
|
338
347
|
// art.inline
|
|
339
|
-
if (art._from && art._from.length) { // query
|
|
340
|
-
const tabref = art._from[0]._artifact;
|
|
341
|
-
return (tabref && tabref.kind === 'element')
|
|
342
|
-
? tabref._effectiveType && tabref._effectiveType.target &&
|
|
343
|
-
tabref._effectiveType.target._artifact
|
|
344
|
-
: tabref;
|
|
345
|
-
}
|
|
346
348
|
|
|
347
349
|
return (art.type && (!art.type.$inferred || art.type.$inferred === 'cast'))
|
|
348
350
|
? art.type._artifact
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -52,11 +52,9 @@ const { typeParameters } = require('./builtins');
|
|
|
52
52
|
|
|
53
53
|
const { kindProperties } = require('./base');
|
|
54
54
|
const {
|
|
55
|
+
pushLink,
|
|
55
56
|
setLink,
|
|
56
57
|
setArtifactLink,
|
|
57
|
-
annotationHasEllipsis,
|
|
58
|
-
pathName,
|
|
59
|
-
linkToOrigin,
|
|
60
58
|
setMemberParent,
|
|
61
59
|
withAssociation,
|
|
62
60
|
storeExtension,
|
|
@@ -69,7 +67,6 @@ const {
|
|
|
69
67
|
} = require('./utils');
|
|
70
68
|
|
|
71
69
|
const detectCycles = require('./cycle-detector');
|
|
72
|
-
const { CompilerAssertion } = require('../base/error');
|
|
73
70
|
|
|
74
71
|
const $location = Symbol.for('cds.$location');
|
|
75
72
|
|
|
@@ -94,12 +91,11 @@ function resolve( model ) {
|
|
|
94
91
|
lateExtensions,
|
|
95
92
|
applyTypeExtensions,
|
|
96
93
|
effectiveType,
|
|
97
|
-
|
|
94
|
+
getOrigin,
|
|
95
|
+
chooseAnnotationsInArtifact,
|
|
96
|
+
extensionFor,
|
|
98
97
|
resolveType,
|
|
99
98
|
resolveTypeArgumentsUnchecked,
|
|
100
|
-
populateQuery,
|
|
101
|
-
layeredAssignments,
|
|
102
|
-
assignmentsOfHighestLayers,
|
|
103
99
|
} = model.$functions;
|
|
104
100
|
const { environment } = model.$volatileFunctions;
|
|
105
101
|
Object.assign( model.$functions, {
|
|
@@ -117,7 +113,11 @@ function resolve( model ) {
|
|
|
117
113
|
// addImplicitForeignKeys() in populate.js, as we might need to know the
|
|
118
114
|
// foreign keys in populate.js (foreign key access w/o JOINs).
|
|
119
115
|
|
|
120
|
-
// Phase 3: calculate keys along simple queries in collected views:
|
|
116
|
+
// Phase 2+3: calculate keys along simple queries in collected views:
|
|
117
|
+
model._entities = Object.values( model.definitions )
|
|
118
|
+
.filter( art => art.$effectiveSeqNo )
|
|
119
|
+
.sort( (x, y) => x.$effectiveSeqNo - y.$effectiveSeqNo );
|
|
120
|
+
model._entities.forEach( setNavigationProjections );
|
|
121
121
|
model._entities.forEach( propagateKeyProps );
|
|
122
122
|
// While most dependencies leading have been added at this point, new
|
|
123
123
|
// cycles could be added later (e.g. via assocs in where conditions),
|
|
@@ -146,9 +146,38 @@ function resolve( model ) {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
//--------------------------------------------------------------------------
|
|
149
|
-
// Phase 3: calculate propagated KEYs
|
|
149
|
+
// Phase 2+3: calculate propagated KEYs
|
|
150
150
|
//--------------------------------------------------------------------------
|
|
151
151
|
|
|
152
|
+
function setNavigationProjections( view ) {
|
|
153
|
+
if (!view.$queries)
|
|
154
|
+
return;
|
|
155
|
+
for (const query of view.$queries) {
|
|
156
|
+
forEachGeneric( query, 'elements', ( elem ) => {
|
|
157
|
+
if (!elem._origin || elem.expand || !elem.value)
|
|
158
|
+
return;
|
|
159
|
+
// TODO: what about elements where _origin is set without value?
|
|
160
|
+
// TODO: or should we push elems with `expand` sibling to extra list for
|
|
161
|
+
// better messages? (Whatever that means exaclty.)
|
|
162
|
+
const nav = pathNavigation( elem.value );
|
|
163
|
+
const { path } = elem.value;
|
|
164
|
+
const item = path[path.length - 1];
|
|
165
|
+
if (nav.navigation && nav.item === item) {
|
|
166
|
+
// sourceElem, alias.sourceElem, mixin:
|
|
167
|
+
// redirectImplicitly( elem, origin );
|
|
168
|
+
pushLink( nav.navigation, '_projections', elem );
|
|
169
|
+
}
|
|
170
|
+
else if (elem._pathHead?.kind === '$inline' && path.length === 1) {
|
|
171
|
+
const hpath = elem._pathHead.value?.path;
|
|
172
|
+
const head = hpath?.length === 1 && hpath[0]._navigation;
|
|
173
|
+
// Alias .{ elem } - but not with Alias.{ $magic }: consider for ON-rewrite
|
|
174
|
+
if (head?.kind === '$tableAlias' && item.id.charAt(0) !== '$')
|
|
175
|
+
pushLink( head.elements[item.id], '_projections', elem );
|
|
176
|
+
}
|
|
177
|
+
} );
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
152
181
|
function propagateKeyProps( view ) {
|
|
153
182
|
// Second argument true ensure that `key` is only propagated along simple
|
|
154
183
|
// view, i.e. ref or subquery in FROM, not UNION or JOIN.
|
|
@@ -293,16 +322,17 @@ function resolve( model ) {
|
|
|
293
322
|
function resolveRefs( art ) {
|
|
294
323
|
if (art.builtin)
|
|
295
324
|
return;
|
|
296
|
-
// console.log(
|
|
297
|
-
// console.log(
|
|
325
|
+
// console.log(info( null, [ art.location, art ], {}, 'REFS').toString());
|
|
326
|
+
// console.log(info( null, [ art.location, art ], { art: art.target || 'none' },
|
|
327
|
+
// 'RR: $(ART)').toString());
|
|
298
328
|
const parent = art._parent;
|
|
299
329
|
const allowedInMain = [ 'entity', 'aspect', 'event' ].includes( adHocOrMainKind( art ) );
|
|
300
330
|
const isTopLevelElement = parent && (parent.kind !== 'element' || parent.targetAspect);
|
|
301
|
-
if (art.key
|
|
331
|
+
if (art.key?.val && !art.key.$inferred && !(allowedInMain && isTopLevelElement)) {
|
|
302
332
|
warning( 'unexpected-key', [ art.key.location, art ],
|
|
303
|
-
{ '#': allowedInMain ? 'sub' : 'std' }, {
|
|
304
|
-
std: '
|
|
305
|
-
sub: '
|
|
333
|
+
{ '#': allowedInMain ? 'sub' : 'std', keyword: 'key' }, {
|
|
334
|
+
std: '$(KEYWORD) is only supported for elements in an entity or an aspect',
|
|
335
|
+
sub: '$(KEYWORD) is only supported for top-level elements',
|
|
306
336
|
});
|
|
307
337
|
}
|
|
308
338
|
if (art.targetAspect && !(allowedInMain && isTopLevelElement)) {
|
|
@@ -348,7 +378,7 @@ function resolve( model ) {
|
|
|
348
378
|
if (elemtype && effectiveType( elemtype )) {
|
|
349
379
|
const assocType = getAssocSpec( elemtype ) || {};
|
|
350
380
|
if (assocType.on && !obj.on)
|
|
351
|
-
obj.on = { $inferred: 'rewrite' };
|
|
381
|
+
obj.on = { $inferred: 'rewrite' }; // TODO: no extra rewrite here
|
|
352
382
|
if (assocType.targetAspect) {
|
|
353
383
|
error( 'composition-as-type-of', [ obj.type.location, art ], {},
|
|
354
384
|
'A managed aspect composition element can\'t be used as type' );
|
|
@@ -406,9 +436,7 @@ function resolve( model ) {
|
|
|
406
436
|
if (art.$queries)
|
|
407
437
|
art.$queries.forEach( resolveQuery );
|
|
408
438
|
|
|
409
|
-
|
|
410
|
-
effectiveType(obj); // set _effectiveType if appropriate, (future?): copy elems if extended
|
|
411
|
-
|
|
439
|
+
// TODO: or should we set silent dependencies in init()?
|
|
412
440
|
if (obj.elements) { // silent dependencies
|
|
413
441
|
forEachGeneric( obj, 'elements', elem => dependsOnSilent( art, elem ) );
|
|
414
442
|
}
|
|
@@ -424,11 +452,14 @@ function resolve( model ) {
|
|
|
424
452
|
|
|
425
453
|
resolveExpr( art.default, 'default', art );
|
|
426
454
|
resolveExpr( art.value, 'expr', art, undefined, art.expand || art.inline );
|
|
427
|
-
if (art.
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
455
|
+
if (art.type?.$inferred === 'cast')
|
|
456
|
+
inferTypePropertiesFromCast( art );
|
|
457
|
+
if (art.value) {
|
|
458
|
+
if (art.$syntax === 'calc')
|
|
459
|
+
checkCalculatedElement(art);
|
|
460
|
+
else if (art.type && !art.$inferred )
|
|
461
|
+
checkStructureCast(art);
|
|
462
|
+
}
|
|
432
463
|
|
|
433
464
|
annotateMembers( art ); // TODO recheck - recursively, but also forEachMember below
|
|
434
465
|
chooseAnnotationsInArtifact( art );
|
|
@@ -452,36 +483,86 @@ function resolve( model ) {
|
|
|
452
483
|
}
|
|
453
484
|
}
|
|
454
485
|
|
|
486
|
+
function checkCalculatedElement( art ) {
|
|
487
|
+
const loc = [ art.value.location, art ];
|
|
488
|
+
if (art._parent.kind === 'element') {
|
|
489
|
+
// TODO: Support calculated elements in structures.
|
|
490
|
+
// The checks below are already aware of those.
|
|
491
|
+
message( 'def-unsupported-calc-elem', loc, { '#': 'nested' } );
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const allowedInKind = [ 'entity', 'aspect', 'element' ];
|
|
495
|
+
let parent = art._parent;
|
|
496
|
+
while (parent.kind === 'element')
|
|
497
|
+
parent = parent._parent;
|
|
498
|
+
|
|
499
|
+
if (!allowedInKind.includes(art._main.kind)) {
|
|
500
|
+
error( 'def-invalid-calc-elem', loc, { '#': art._main.kind } );
|
|
501
|
+
}
|
|
502
|
+
else if (!allowedInKind.includes(parent.kind)) {
|
|
503
|
+
error( 'def-invalid-calc-elem', loc, { '#': parent.kind } );
|
|
504
|
+
}
|
|
505
|
+
else if (effectiveType(art)?.elements) {
|
|
506
|
+
if (art.type)
|
|
507
|
+
error( 'type-unexpected-structure', [ art.type.location, art ], { '#': 'calc' } );
|
|
508
|
+
else
|
|
509
|
+
error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
const noTruthyAllowed = [ 'localized', 'key', 'virtual' ];
|
|
513
|
+
for (const prop of noTruthyAllowed) {
|
|
514
|
+
if (art[prop]?.val) {
|
|
515
|
+
// probably better than a parse error (which is good for DEFAULT vs calc),
|
|
516
|
+
// also appears with parse-cdl:
|
|
517
|
+
error('def-invalid-calc-elem', loc, { '#': prop });
|
|
518
|
+
return; // one error is enough
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function checkStructureCast( art ) {
|
|
525
|
+
const elem = (art.value.op?.val === 'cast')
|
|
526
|
+
? art.value.args[0]?._artifact
|
|
527
|
+
: art.value._artifact;
|
|
528
|
+
if (elem && art.type) { // has explicit type
|
|
529
|
+
if (art.type._artifact?.elements) {
|
|
530
|
+
error('type-cast-to-structured', [ art.type.location, art ], {},
|
|
531
|
+
'Can\'t cast to structured element');
|
|
532
|
+
}
|
|
533
|
+
else if (elem.elements) { // TODO: calc elements
|
|
534
|
+
error('type-cast-structured', [ art.type.location, art ], {},
|
|
535
|
+
'Structured elements can\'t be cast to a different type');
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
455
540
|
// Return type containing the assoc spec (keys, on); note that no
|
|
456
541
|
// propagation/rewrite has been done yet, cyclic dependency must have been
|
|
457
542
|
// checked before!
|
|
458
543
|
function getAssocSpec( type ) {
|
|
459
|
-
const cyclic = new Set(); // TODO(#8942): May not be necessary if effectiveType() is adapted.
|
|
460
544
|
// only to be called without cycles
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
if (type.on
|
|
545
|
+
let unmanaged = null;
|
|
546
|
+
while (type) {
|
|
547
|
+
if (type.on) // if unmanaged, continue trying to find targetAspect
|
|
548
|
+
unmanaged = type;
|
|
549
|
+
else if (type.foreignKeys || type.targetAspect)
|
|
464
550
|
return type;
|
|
465
|
-
type =
|
|
551
|
+
type = getOrigin( type );
|
|
466
552
|
}
|
|
467
|
-
return
|
|
553
|
+
return unmanaged;
|
|
468
554
|
}
|
|
469
555
|
|
|
470
|
-
function
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
// op.val is also correctly set with CSN input
|
|
475
|
-
elem.type = { ...type, $inferred: 'cast' };
|
|
476
|
-
setArtifactLink( elem.type, type._artifact );
|
|
477
|
-
for (const prop of typeParameters.list) {
|
|
478
|
-
if (elem.value[prop])
|
|
479
|
-
elem[prop] = { ...elem.value[prop], $inferred: 'cast' };
|
|
480
|
-
}
|
|
556
|
+
function inferTypePropertiesFromCast( elem ) {
|
|
557
|
+
for (const prop of typeParameters.list) {
|
|
558
|
+
if (elem.value[prop])
|
|
559
|
+
elem[prop] = { ...elem.value[prop], $inferred: 'cast' };
|
|
481
560
|
}
|
|
482
561
|
}
|
|
483
562
|
|
|
484
563
|
// Phase 4 - annotations ---------------------------------------------------
|
|
564
|
+
// Some functions remain here for the moment, as resolve functionality is called
|
|
565
|
+
// from annotate functions, which should not be the case (TODO!)
|
|
485
566
|
|
|
486
567
|
function annotateUnknown( ext ) {
|
|
487
568
|
// extensions may have annotations for elements/actions/... which may
|
|
@@ -577,16 +658,10 @@ function resolve( model ) {
|
|
|
577
658
|
}
|
|
578
659
|
}
|
|
579
660
|
if (art?._annotate) {
|
|
580
|
-
if (art.kind === 'action' || art.kind === 'function') {
|
|
581
|
-
expandParameters( art );
|
|
582
|
-
if (art.returns)
|
|
583
|
-
effectiveType( art.returns );
|
|
584
|
-
}
|
|
585
661
|
const aor = art.returns || art;
|
|
586
662
|
const obj = aor.items || aor.targetAspect || aor;
|
|
587
663
|
// Currently(?), effectiveType() does not calculate the effective type of
|
|
588
664
|
// its line item:
|
|
589
|
-
effectiveType( obj );
|
|
590
665
|
if (art._annotate.elements) // explicit $expand on aor needed
|
|
591
666
|
setExpandStatusAnnotate( aor, 'annotate' );
|
|
592
667
|
annotate( obj, 'element', 'elements', 'enum', art );
|
|
@@ -628,259 +703,13 @@ function resolve( model ) {
|
|
|
628
703
|
}
|
|
629
704
|
}
|
|
630
705
|
|
|
631
|
-
function expandParameters( action ) {
|
|
632
|
-
// see also expandElements()
|
|
633
|
-
if (!effectiveType( action ))
|
|
634
|
-
return;
|
|
635
|
-
const chain = [];
|
|
636
|
-
// Should we be able to consider params and returns separately?
|
|
637
|
-
// Probably not, let to-csn omit unchanged params/returns.
|
|
638
|
-
while (action._origin && !action.params) {
|
|
639
|
-
chain.push( action );
|
|
640
|
-
action = action._origin;
|
|
641
|
-
}
|
|
642
|
-
chain.reverse();
|
|
643
|
-
for (const art of chain) {
|
|
644
|
-
const origin = art._origin;
|
|
645
|
-
if (!art.params && origin.params) {
|
|
646
|
-
for (const name in origin.params) {
|
|
647
|
-
// TODO: we could check _annotate here to decide whether we really
|
|
648
|
-
// not to create proxies
|
|
649
|
-
const orig = origin.params[name];
|
|
650
|
-
linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
|
|
651
|
-
.$inferred = 'expanded';
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
if (!art.returns && origin.returns) {
|
|
655
|
-
// TODO: make linkToOrigin() work for returns, kind/name?
|
|
656
|
-
const location = weakLocation( origin.returns.location );
|
|
657
|
-
art.returns = {
|
|
658
|
-
name: Object.assign( {}, art.name, { id: '', param: '', location } ),
|
|
659
|
-
kind: 'param',
|
|
660
|
-
location,
|
|
661
|
-
$inferred: 'expanded',
|
|
662
|
-
};
|
|
663
|
-
setLink( art.returns, '_parent', art );
|
|
664
|
-
setLink( art.returns, '_main', art._main || art );
|
|
665
|
-
setLink( art.returns, '_origin', origin.returns );
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
function extensionFor( art ) {
|
|
671
|
-
if (art.kind === 'annotate')
|
|
672
|
-
return art;
|
|
673
|
-
if (art._extension)
|
|
674
|
-
return art._extension;
|
|
675
|
-
|
|
676
|
-
// $extension means: already applied
|
|
677
|
-
const ext = {
|
|
678
|
-
kind: art.kind, // set kind for setMemberParent()
|
|
679
|
-
$extension: 'exists',
|
|
680
|
-
location: art.location, // location( extension to existing art ) = location(art)
|
|
681
|
-
};
|
|
682
|
-
const { location } = art.name;
|
|
683
|
-
if (!art._main) {
|
|
684
|
-
ext.name = {
|
|
685
|
-
path: [ { id: art.name.absolute, location } ],
|
|
686
|
-
location,
|
|
687
|
-
absolute: art.name.absolute,
|
|
688
|
-
};
|
|
689
|
-
if (model.extensions)
|
|
690
|
-
model.extensions.push(ext);
|
|
691
|
-
else
|
|
692
|
-
model.extensions = [ ext ];
|
|
693
|
-
}
|
|
694
|
-
else {
|
|
695
|
-
ext.name = { id: art.name.id, location };
|
|
696
|
-
const parent = extensionFor( art._parent );
|
|
697
|
-
const kind = kindProperties[art.kind].normalized || art.kind;
|
|
698
|
-
// enums would be first in elements
|
|
699
|
-
if ( parent[kindProperties[kind].dict] &&
|
|
700
|
-
parent[kindProperties[kind].dict][art.name.id] )
|
|
701
|
-
throw new CompilerAssertion(art.name.id);
|
|
702
|
-
setMemberParent( ext, art.name.id, parent, kindProperties[kind].dict );
|
|
703
|
-
}
|
|
704
|
-
ext.kind = 'annotate'; // after setMemberParent()!
|
|
705
|
-
setLink( art, '_extension', ext );
|
|
706
|
-
setArtifactLink( ext.name, art );
|
|
707
|
-
if (art.returns)
|
|
708
|
-
ext.$syntax = 'returns';
|
|
709
|
-
return ext;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
/**
|
|
713
|
-
* Goes through all (applied) annotations in the given artifact and chooses one
|
|
714
|
-
* if multiple exist according to the module layer.
|
|
715
|
-
*
|
|
716
|
-
* @param {XSN.Artifact} art
|
|
717
|
-
*/
|
|
718
|
-
function chooseAnnotationsInArtifact( art ) {
|
|
719
|
-
for (const prop in art) {
|
|
720
|
-
if (prop.charAt(0) === '@')
|
|
721
|
-
chooseAssignment( prop, art );
|
|
722
|
-
}
|
|
723
|
-
if (art.doc)
|
|
724
|
-
chooseAssignment( 'doc', art );
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
function chooseAssignment( annoName, art ) {
|
|
728
|
-
let anno = art[annoName];
|
|
729
|
-
if (!Array.isArray( anno )) { // just one assignment -> use it
|
|
730
|
-
if (!annotationHasEllipsis( anno ))
|
|
731
|
-
return;
|
|
732
|
-
anno = [ anno ];
|
|
733
|
-
}
|
|
734
|
-
// console.log('ASSIGN:',art.name.absolute,annoName)
|
|
735
|
-
const scheduledAssignments = [];
|
|
736
|
-
// sort assignment according to layer (define is bottom layer):
|
|
737
|
-
const layeredAnnos = layeredAssignments( anno );
|
|
738
|
-
let cont = true;
|
|
739
|
-
while (cont) {
|
|
740
|
-
const { assignments, issue } = assignmentsOfHighestLayers( layeredAnnos );
|
|
741
|
-
let index = assignments.length;
|
|
742
|
-
cont = !!index; // safety
|
|
743
|
-
while (--index >= 0) {
|
|
744
|
-
const a = assignments[index];
|
|
745
|
-
scheduledAssignments.push( a );
|
|
746
|
-
if (!annotationHasEllipsis( a )) {
|
|
747
|
-
cont = false;
|
|
748
|
-
break;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
if (issue) {
|
|
752
|
-
// eslint-disable-next-line no-nested-ternary
|
|
753
|
-
const msg = (issue === true)
|
|
754
|
-
? 'anno-duplicate'
|
|
755
|
-
: (index >= 0) ? 'anno-duplicate-unrelated-layer' : 'anno-unstable-array';
|
|
756
|
-
const variant = annoName === 'doc' ? 'doc' : 'std';
|
|
757
|
-
for (const a of assignments) {
|
|
758
|
-
if (!a.$errorReported) {
|
|
759
|
-
message( msg, [ a.name?.location || a.location, art ],
|
|
760
|
-
{ '#': variant, anno: annoName } );
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
// else if (index > 0) -- if we allow multiple assignments in one file - the last wins
|
|
765
|
-
}
|
|
766
|
-
// Now apply the assignments - all but the first have a '...'
|
|
767
|
-
let result = null;
|
|
768
|
-
scheduledAssignments.reverse();
|
|
769
|
-
for (const a of scheduledAssignments)
|
|
770
|
-
result = applyAssignment( result, a, art, annoName );
|
|
771
|
-
art[annoName] = result.name ? result
|
|
772
|
-
: Object.assign( {}, scheduledAssignments[scheduledAssignments.length - 1], result );
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
function applyAssignment( previousAnno, anno, art, annoName ) {
|
|
776
|
-
const hasBase = previousAnno?.literal === 'array';
|
|
777
|
-
if (!previousAnno) {
|
|
778
|
-
const firstEllipsis = annotationHasEllipsis( anno );
|
|
779
|
-
if (!firstEllipsis)
|
|
780
|
-
return anno;
|
|
781
|
-
if (anno.$priority) { // already complained about with Define
|
|
782
|
-
const loc = firstEllipsis.location || anno.name.location;
|
|
783
|
-
message( 'anno-unexpected-ellipsis-layers', [ loc, art ], { code: '...' } );
|
|
784
|
-
}
|
|
785
|
-
previousAnno = { val: [] };
|
|
786
|
-
}
|
|
787
|
-
else if (previousAnno.literal !== 'array') {
|
|
788
|
-
// TODO: If we introduce sub-messages, point to the non-array base value.
|
|
789
|
-
error( 'anno-mismatched-ellipsis', [ anno.name.location, art ], { code: '...' } );
|
|
790
|
-
previousAnno = { val: [] };
|
|
791
|
-
}
|
|
792
|
-
const previousValue = previousAnno.val;
|
|
793
|
-
let prevPos = 0;
|
|
794
|
-
const result = [];
|
|
795
|
-
for (const item of anno.val) {
|
|
796
|
-
const ell = item && item.literal === 'token' && item.val === '...';
|
|
797
|
-
if (!ell) {
|
|
798
|
-
result.push( item );
|
|
799
|
-
}
|
|
800
|
-
else {
|
|
801
|
-
let upToSpec = item.upTo && checkUpToSpec( item.upTo, art, annoName, true );
|
|
802
|
-
while (prevPos < previousValue.length) {
|
|
803
|
-
const prevItem = previousValue[prevPos++];
|
|
804
|
-
result.push( prevItem );
|
|
805
|
-
if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
|
|
806
|
-
upToSpec = false;
|
|
807
|
-
break;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
if (upToSpec && hasBase) {
|
|
811
|
-
// non-matched UP TO; if there is no base to apply to, there is already an error.
|
|
812
|
-
warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
|
|
813
|
-
'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
// console.log('TP:',previousValue.map(se),anno.val.map(se),'->',result.map(se))
|
|
818
|
-
return { val: result, literal: 'array' };
|
|
819
|
-
}
|
|
820
|
-
// function se(a) { return a.upTo ? [a.val,a.upTo.val] : a.val ; }
|
|
821
|
-
|
|
822
|
-
function checkUpToSpec( upToSpec, art, annoName, isFullUpTo ) {
|
|
823
|
-
const { literal } = upToSpec;
|
|
824
|
-
if (!isFullUpTo) { // inside struct of UP TO
|
|
825
|
-
if (literal !== 'struct' && literal !== 'array' )
|
|
826
|
-
return true;
|
|
827
|
-
}
|
|
828
|
-
else if (literal === 'struct') {
|
|
829
|
-
return Object.values( upToSpec.struct ).every( v => checkUpToSpec( v, art, annoName ) );
|
|
830
|
-
}
|
|
831
|
-
else if (literal !== 'array' && literal !== 'boolean' && literal !== 'null') {
|
|
832
|
-
return true;
|
|
833
|
-
}
|
|
834
|
-
error( null, [ upToSpec.location, art ],
|
|
835
|
-
{ anno: annoName, code: '... up to', '#': literal },
|
|
836
|
-
{
|
|
837
|
-
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
|
|
838
|
-
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
|
|
839
|
-
// eslint-disable-next-line max-len
|
|
840
|
-
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
|
|
841
|
-
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
|
|
842
|
-
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
|
|
843
|
-
} );
|
|
844
|
-
return false;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
function equalUpTo( previousItem, upToSpec ) {
|
|
848
|
-
if (!previousItem)
|
|
849
|
-
return false;
|
|
850
|
-
if ('val' in upToSpec) {
|
|
851
|
-
if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
|
|
852
|
-
return true;
|
|
853
|
-
const typeUpTo = typeof upToSpec.val;
|
|
854
|
-
const typePrev = typeof previousItem.val;
|
|
855
|
-
if (typeUpTo === 'number')
|
|
856
|
-
return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
|
|
857
|
-
if (typePrev === 'number')
|
|
858
|
-
return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
|
|
859
|
-
}
|
|
860
|
-
else if (upToSpec.path) {
|
|
861
|
-
return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
|
|
862
|
-
}
|
|
863
|
-
else if (upToSpec.sym) {
|
|
864
|
-
return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
|
|
865
|
-
}
|
|
866
|
-
else if (upToSpec.struct && previousItem.struct) {
|
|
867
|
-
return Object.entries( upToSpec.struct )
|
|
868
|
-
.every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
|
|
869
|
-
}
|
|
870
|
-
return false;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
function normalizeRef( node ) { // see to-csn.js
|
|
874
|
-
const ref = pathName( node.path );
|
|
875
|
-
return node.variant ? `${ ref }#${ node.variant.id }` : ref;
|
|
876
|
-
}
|
|
877
|
-
|
|
878
706
|
// Phase 4 - queries and associations --------------------------------------
|
|
879
707
|
|
|
880
708
|
function resolveQuery( query ) {
|
|
881
|
-
if (!query._main)
|
|
709
|
+
if (!query._main || !query._effectiveType) // parse error
|
|
882
710
|
return;
|
|
883
|
-
|
|
711
|
+
// TODO: or set silent dependencies in init?
|
|
712
|
+
forEachGeneric( query, 'elements', elem => dependsOnSilent( query, elem ) );
|
|
884
713
|
forEachGeneric( query, '$tableAliases', ( alias ) => {
|
|
885
714
|
// console.log( info( null, [alias.location,alias], 'SQA:' ).toString() );
|
|
886
715
|
if (alias.kind === 'mixin')
|
|
@@ -889,6 +718,7 @@ function resolve( model ) {
|
|
|
889
718
|
// pure path has been resolved, resolve args and filter now:
|
|
890
719
|
resolveExpr( alias, 'from', query._parent );
|
|
891
720
|
} );
|
|
721
|
+
// if (!query.$inlines) console.log('RQ:',query)
|
|
892
722
|
for (const col of query.$inlines)
|
|
893
723
|
resolveExpr( col.value, 'expr', col, undefined, true );
|
|
894
724
|
// for (const col of query.$inlines)
|
|
@@ -1002,13 +832,9 @@ function resolve( model ) {
|
|
|
1002
832
|
return; // avoid subsequent errors
|
|
1003
833
|
}
|
|
1004
834
|
else if (target && !obj.foreignKeys && target.kind === 'entity') {
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
}
|
|
1008
|
-
else if (obj.type && obj.type._artifact && obj.type._artifact.internal) {
|
|
1009
|
-
// cds.Association, ...
|
|
835
|
+
// redirected or explicit type cds.Association, ...
|
|
836
|
+
if (obj.$inferred === 'REDIRECTED' || obj.type?._artifact?.internal)
|
|
1010
837
|
addImplicitForeignKeys( art, obj, target );
|
|
1011
|
-
}
|
|
1012
838
|
}
|
|
1013
839
|
|
|
1014
840
|
if (target && !target.$inferred) {
|
|
@@ -1072,8 +898,10 @@ function resolve( model ) {
|
|
|
1072
898
|
// TODO: add this somehow to tweak-assocs.js ?
|
|
1073
899
|
function resolveRedirected( elem, target ) {
|
|
1074
900
|
setLink( elem, '_redirected', null ); // null = do not touch path steps after assoc
|
|
1075
|
-
const assoc =
|
|
901
|
+
const assoc = getOrigin( elem );
|
|
1076
902
|
const origType = assoc && effectiveType( assoc );
|
|
903
|
+
if (origType === 0)
|
|
904
|
+
return;
|
|
1077
905
|
if (!origType || !origType.target) {
|
|
1078
906
|
const path = (elem.value && elem.value.path);
|
|
1079
907
|
const loc = (path && path[path.length - 1] || elem.value || elem).location;
|
|
@@ -1240,11 +1068,12 @@ function resolve( model ) {
|
|
|
1240
1068
|
// Also: For enums, it points to the enum type, which is why this trick is needed.
|
|
1241
1069
|
// TODO(#8942): May not be necessary if effectiveType() is adapted. Furthermore, the enum
|
|
1242
1070
|
// trick may be removed if effectiveType() does not stop at enums.
|
|
1071
|
+
// TODO: this is wrong - we must check typeArt.enum, not its effectiveType
|
|
1243
1072
|
const cyclic = new Set();
|
|
1244
1073
|
let effectiveTypeArt = effectiveType( typeArt );
|
|
1245
1074
|
while (effectiveTypeArt && effectiveTypeArt.enum && !cyclic.has(effectiveTypeArt)) {
|
|
1246
1075
|
cyclic.add(effectiveTypeArt);
|
|
1247
|
-
const underlyingEnumType =
|
|
1076
|
+
const underlyingEnumType = getOrigin(effectiveTypeArt);
|
|
1248
1077
|
if (underlyingEnumType)
|
|
1249
1078
|
effectiveTypeArt = effectiveType(underlyingEnumType);
|
|
1250
1079
|
else
|
|
@@ -1261,7 +1090,7 @@ function resolve( model ) {
|
|
|
1261
1090
|
if (artWithType[param] !== undefined) {
|
|
1262
1091
|
if (!params.includes(param)) {
|
|
1263
1092
|
// Whether the type ref itself is a builtin or a custom type with a builtin as base.
|
|
1264
|
-
const type =
|
|
1093
|
+
const type = getOrigin(artWithType);
|
|
1265
1094
|
|
|
1266
1095
|
let variant;
|
|
1267
1096
|
if (type.builtin)
|
|
@@ -1274,6 +1103,7 @@ function resolve( model ) {
|
|
|
1274
1103
|
// effectiveType is not a builtin -> array or structured
|
|
1275
1104
|
variant = 'non-scalar';
|
|
1276
1105
|
|
|
1106
|
+
// console.log(typeArt.name,artWithType.name,effectiveTypeArt.name)
|
|
1277
1107
|
error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
|
|
1278
1108
|
'#': variant, prop: param, art: artWithType.type, type: effectiveTypeArt,
|
|
1279
1109
|
});
|