@sap/cds-compiler 4.2.4 → 4.3.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.
- package/CHANGELOG.md +26 -0
- package/bin/cdsc.js +8 -0
- package/bin/cdshi.js +3 -3
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/main.js +19 -0
- package/lib/base/location.js +16 -0
- package/lib/base/message-registry.js +47 -16
- package/lib/base/messages.js +49 -38
- package/lib/base/model.js +1 -1
- package/lib/checks/checkPathsInStoredCalcElement.js +83 -0
- package/lib/checks/existsExpressionsOnlyForeignKeys.js +71 -0
- package/lib/checks/existsMustEndInAssoc.js +27 -0
- package/lib/checks/onConditions.js +47 -1
- package/lib/checks/validator.js +10 -1
- package/lib/compiler/assert-consistency.js +23 -15
- package/lib/compiler/base.js +31 -14
- package/lib/compiler/builtins.js +21 -20
- package/lib/compiler/checks.js +36 -49
- package/lib/compiler/define.js +71 -91
- package/lib/compiler/extend.js +27 -25
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +67 -87
- package/lib/compiler/kick-start.js +7 -5
- package/lib/compiler/populate.js +32 -30
- package/lib/compiler/propagator.js +2 -0
- package/lib/compiler/resolve.js +29 -25
- package/lib/compiler/shared.js +57 -31
- package/lib/compiler/tweak-assocs.js +203 -22
- package/lib/compiler/utils.js +0 -18
- package/lib/gen/Dictionary.json +10 -4
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/languageParser.js +3 -3
- package/lib/inspect/inspectPropagation.js +2 -1
- package/lib/json/from-csn.js +63 -28
- package/lib/json/to-csn.js +23 -13
- package/lib/language/antlrParser.js +1 -1
- package/lib/language/errorStrategy.js +5 -1
- package/lib/language/genericAntlrParser.js +67 -61
- package/lib/main.d.ts +26 -1
- package/lib/main.js +2 -1
- package/lib/model/csnRefs.js +1 -0
- package/lib/model/csnUtils.js +28 -0
- package/lib/model/revealInternalProperties.js +3 -9
- package/lib/optionProcessor.js +17 -1
- package/lib/render/toCdl.js +1 -1
- package/lib/transform/db/associations.js +3 -4
- package/lib/transform/db/backlinks.js +293 -0
- package/lib/transform/db/expansion.js +9 -7
- package/lib/transform/db/flattening.js +3 -2
- package/lib/transform/db/rewriteCalculatedElements.js +1 -67
- package/lib/transform/db/transformExists.js +3 -58
- package/lib/transform/db/views.js +8 -14
- package/lib/transform/effective/.eslintrc.json +4 -0
- package/lib/transform/effective/associations.js +101 -0
- package/lib/transform/effective/main.js +88 -0
- package/lib/transform/effective/misc.js +61 -0
- package/lib/transform/effective/queries.js +42 -0
- package/lib/transform/effective/types.js +121 -0
- package/lib/transform/forRelationalDB.js +12 -235
- package/lib/transform/localized.js +22 -3
- package/lib/transform/parseExpr.js +7 -3
- package/lib/transform/transformUtils.js +5 -22
- package/lib/transform/translateAssocsToJoins.js +42 -38
- package/lib/transform/universalCsn/universalCsnEnricher.js +17 -1
- package/package.json +1 -2
- package/lib/language/language.g4 +0 -3260
package/lib/compiler/populate.js
CHANGED
|
@@ -26,7 +26,7 @@ const {
|
|
|
26
26
|
const {
|
|
27
27
|
dictAdd, dictAddArray, dictFirst, dictForEach,
|
|
28
28
|
} = require('../base/dictionaries');
|
|
29
|
-
const { weakLocation } = require('../base/
|
|
29
|
+
const { weakLocation } = require('../base/location');
|
|
30
30
|
const { CompilerAssertion } = require('../base/error');
|
|
31
31
|
|
|
32
32
|
const { kindProperties } = require('./base');
|
|
@@ -37,7 +37,6 @@ const {
|
|
|
37
37
|
annotationIsFalse,
|
|
38
38
|
annotationLocation,
|
|
39
39
|
augmentPath,
|
|
40
|
-
splitIntoPath,
|
|
41
40
|
linkToOrigin,
|
|
42
41
|
setMemberParent,
|
|
43
42
|
proxyCopyMembers,
|
|
@@ -104,7 +103,7 @@ function populate( model ) {
|
|
|
104
103
|
|
|
105
104
|
forEachDefinition( model, traverseElementEnvironments );
|
|
106
105
|
while (newAutoExposed.length) {
|
|
107
|
-
// console.log( newAutoExposed.map( a => a.name.
|
|
106
|
+
// console.log( newAutoExposed.map( a => a.name.id ) )
|
|
108
107
|
const all = newAutoExposed;
|
|
109
108
|
newAutoExposed = [];
|
|
110
109
|
all.forEach( traverseElementEnvironments );
|
|
@@ -251,7 +250,7 @@ function populate( model ) {
|
|
|
251
250
|
if (leading._effectiveType !== undefined) {
|
|
252
251
|
// You cannot refer to a query of another artifact:
|
|
253
252
|
throw new CompilerAssertion(
|
|
254
|
-
`Unexpected _effectiveType on leading query of ${ art.name.
|
|
253
|
+
`Unexpected _effectiveType on leading query of ${ art.name.id }`
|
|
255
254
|
);
|
|
256
255
|
}
|
|
257
256
|
// TODO: try just return (effectiveType( leading )) === 0 ? 0 : art;
|
|
@@ -443,7 +442,7 @@ function populate( model ) {
|
|
|
443
442
|
// TODO: make linkToOrigin() work for returns, kind/name?
|
|
444
443
|
const location = weakLocation( origin.returns.location );
|
|
445
444
|
art.returns = {
|
|
446
|
-
name: Object.assign( {}, art.name, { id: '',
|
|
445
|
+
name: Object.assign( {}, art.name, { id: '', location } ),
|
|
447
446
|
kind: 'param',
|
|
448
447
|
location,
|
|
449
448
|
$inferred: 'expanded',
|
|
@@ -685,15 +684,18 @@ function populate( model ) {
|
|
|
685
684
|
if (!col.value && !col.expand && !(col.target && col.type))
|
|
686
685
|
continue; // error should have been reported by parser
|
|
687
686
|
if (col.inline) {
|
|
687
|
+
const q = userQuery( query );
|
|
688
|
+
q.$inlines.push( col );
|
|
688
689
|
col.kind = '$inline';
|
|
689
|
-
col.name = {};
|
|
690
|
+
col.name = { id: `.${ q.$inlines.length }`, $inferred: '$internal' };
|
|
691
|
+
// TODO: really use $inferred: '$internal', not '$inline' ? Re-check.
|
|
690
692
|
// a name for this internal symtab entry (e.g. '.2' to avoid clashes
|
|
691
693
|
// with real elements) is only relevant for `cdsc -R`/debugging
|
|
692
|
-
|
|
693
|
-
|
|
694
|
+
// TODO: use number = column position if "top-level", negative numbers otherwise
|
|
695
|
+
// (is also relevant for the semantic location - only use positive)
|
|
694
696
|
dependsOnSilent( q, col );
|
|
695
697
|
// or use userQuery( query ) in the following, too?
|
|
696
|
-
setMemberParent( col,
|
|
698
|
+
setMemberParent( col, null, query );
|
|
697
699
|
initFromColumns( query, col.inline, col );
|
|
698
700
|
}
|
|
699
701
|
else if (!col.$replacement) {
|
|
@@ -835,7 +837,7 @@ function populate( model ) {
|
|
|
835
837
|
}
|
|
836
838
|
else if (Array.isArray( navElem )) {
|
|
837
839
|
const names = navElem.filter( e => !e.$duplicates )
|
|
838
|
-
.map( e => `${ e.name.
|
|
840
|
+
.map( e => `${ e._parent.name.id }.${ e.name.id }` );
|
|
839
841
|
if (names.length) {
|
|
840
842
|
error( 'wildcard-ambiguous', [ location, query ], { id: name, names },
|
|
841
843
|
'Ambiguous wildcard, select $(ID) explicitly with $(NAMES)' );
|
|
@@ -982,7 +984,7 @@ function populate( model ) {
|
|
|
982
984
|
if (target !== assocTarget)
|
|
983
985
|
setExpandStatus( elem, 'target' ); // (might) also set in rewriteCondition
|
|
984
986
|
elem.target = {
|
|
985
|
-
path: [ { id: target.name.
|
|
987
|
+
path: [ { id: target.name.id, location } ],
|
|
986
988
|
scope: 'global',
|
|
987
989
|
location,
|
|
988
990
|
$inferred: (target !== assocTarget ? 'IMPLICIT' : 'rewrite' ),
|
|
@@ -1000,7 +1002,7 @@ function populate( model ) {
|
|
|
1000
1002
|
}
|
|
1001
1003
|
|
|
1002
1004
|
function redirectImplicitlyDo( elem, assoc, target, service ) {
|
|
1003
|
-
// console.log('ES:',elem.name.
|
|
1005
|
+
// console.log('ES:',elem.name.id,elem.name.element);
|
|
1004
1006
|
if (assoc._main === target && elem._main?.kind === 'entity' &&
|
|
1005
1007
|
elem._main?._ancestors?.includes( target )) {
|
|
1006
1008
|
// source and target of the model association are the same entity, and
|
|
@@ -1017,10 +1019,10 @@ function populate( model ) {
|
|
|
1017
1019
|
target = createAutoExposed( origTarget, service, elemScope );
|
|
1018
1020
|
const desc = origTarget._descendants ||
|
|
1019
1021
|
setLink( origTarget, '_descendants', Object.create( null ) );
|
|
1020
|
-
if (!desc[service.name.
|
|
1021
|
-
desc[service.name.
|
|
1022
|
+
if (!desc[service.name.id]) // could be the target itself (no repeated msgs)!
|
|
1023
|
+
desc[service.name.id] = [ target ];
|
|
1022
1024
|
else
|
|
1023
|
-
desc[service.name.
|
|
1025
|
+
desc[service.name.id].push( target );
|
|
1024
1026
|
}
|
|
1025
1027
|
else if (exposed.length === 1) {
|
|
1026
1028
|
return exposed[0];
|
|
@@ -1079,7 +1081,7 @@ function populate( model ) {
|
|
|
1079
1081
|
// auto-exposed entity when successful, or `target` otherwise (no/failed autoexposure)
|
|
1080
1082
|
function minimalExposure( target, service, elemScope ) {
|
|
1081
1083
|
const descendants = scopedExposure( target._descendants &&
|
|
1082
|
-
target._descendants[service.name.
|
|
1084
|
+
target._descendants[service.name.id] ||
|
|
1083
1085
|
[],
|
|
1084
1086
|
elemScope, target );
|
|
1085
1087
|
const preferred = descendants.filter( d => annotationVal( d['@cds.redirection.target'] ) );
|
|
@@ -1121,7 +1123,7 @@ function populate( model ) {
|
|
|
1121
1123
|
return false; // all (there could be no scoped autoexposed)
|
|
1122
1124
|
// scoped target in model:
|
|
1123
1125
|
const exposed = minimalExposure( targetScope, service, false );
|
|
1124
|
-
// console.log('PES:',elem.name.
|
|
1126
|
+
// console.log('PES:',elem.name.id,elem.name.element,exposed.map(e=>e.name.id))
|
|
1125
1127
|
if (exposed.length === 1) // unique redirection for target scope: use that
|
|
1126
1128
|
return exposed[0];
|
|
1127
1129
|
// TODO: warning if exposed.length >= 2? Probably not
|
|
@@ -1134,7 +1136,7 @@ function populate( model ) {
|
|
|
1134
1136
|
return autoScope;
|
|
1135
1137
|
const { location } = service.name;
|
|
1136
1138
|
const nullScope = {
|
|
1137
|
-
kind: 'namespace', name: {
|
|
1139
|
+
kind: 'namespace', name: { id: autoScopeName, location }, location,
|
|
1138
1140
|
};
|
|
1139
1141
|
model.definitions[autoScopeName] = nullScope;
|
|
1140
1142
|
initArtifact( nullScope );
|
|
@@ -1216,33 +1218,33 @@ function populate( model ) {
|
|
|
1216
1218
|
}
|
|
1217
1219
|
// no @cds.autoexpose or @cds.autoexpose:null
|
|
1218
1220
|
// TODO: introduce deprecated._noInheritedAutoexposeViaComposition
|
|
1219
|
-
art.$autoexpose = model.$compositionTargets[art.name.
|
|
1221
|
+
art.$autoexpose = model.$compositionTargets[art.name.id]
|
|
1220
1222
|
? autoexposeViaComposition
|
|
1221
1223
|
: null;
|
|
1222
1224
|
return true; // still check for inherited @cds.autoexpose
|
|
1223
1225
|
}
|
|
1224
1226
|
|
|
1225
1227
|
function autoExposedName( target, service, elemScope ) {
|
|
1226
|
-
const
|
|
1228
|
+
const absolute = target.name.id;
|
|
1227
1229
|
if (isDeprecatedEnabled( options, '_shortAutoexposed' )) {
|
|
1228
1230
|
const parent = definitionScope( target )._parent;
|
|
1229
|
-
const name = (parent) ? absolute.substring( parent.name.
|
|
1231
|
+
const name = (parent) ? absolute.substring( parent.name.id.length + 1 ) : absolute;
|
|
1230
1232
|
// no need for dedot here (as opposed to deprecated._longAutoexposed), as
|
|
1231
1233
|
// the name for dependent entities have already been created using `_` then
|
|
1232
|
-
return `${ service.name.
|
|
1234
|
+
return `${ service.name.id }.${ name }`;
|
|
1233
1235
|
}
|
|
1234
1236
|
if (isDeprecatedEnabled( options, '_longAutoexposed' ))
|
|
1235
|
-
return `${ service.name.
|
|
1237
|
+
return `${ service.name.id }.${ absolute }`;
|
|
1236
1238
|
const base = definitionScope( target );
|
|
1237
1239
|
if (base === target)
|
|
1238
|
-
return `${ service.name.
|
|
1240
|
+
return `${ service.name.id }.${ absolute.substring( absolute.lastIndexOf( '.' ) + 1 ) }`;
|
|
1239
1241
|
// for scoped (e.g. calculated) entities, use exposed name of base:
|
|
1240
1242
|
const exposed = minimalExposure( base, service, elemScope );
|
|
1241
|
-
// console.log(exposed.map( a => a.name.
|
|
1243
|
+
// console.log(exposed.map( a => a.name.id ));
|
|
1242
1244
|
const sbasename = (exposed.length === 1 && exposed[0] !== base) // same with no/failed expose
|
|
1243
|
-
? exposed[0].name.
|
|
1245
|
+
? exposed[0].name.id
|
|
1244
1246
|
: autoExposedName( base, service, elemScope );
|
|
1245
|
-
return sbasename + absolute.slice( base.name.
|
|
1247
|
+
return sbasename + absolute.slice( base.name.id.length );
|
|
1246
1248
|
}
|
|
1247
1249
|
|
|
1248
1250
|
|
|
@@ -1268,7 +1270,7 @@ function populate( model ) {
|
|
|
1268
1270
|
!annotationVal( autoexposed['@cds.autoexposed'] )) {
|
|
1269
1271
|
// existing def not auto-exposed, or un-scoped auto-exposed: should not happen
|
|
1270
1272
|
if (options.testMode)
|
|
1271
|
-
throw new CompilerAssertion( `Tried to auto-expose ${ target.name.
|
|
1273
|
+
throw new CompilerAssertion( `Tried to auto-expose ${ target.name.id } twice`);
|
|
1272
1274
|
}
|
|
1273
1275
|
return autoexposed;
|
|
1274
1276
|
}
|
|
@@ -1292,10 +1294,10 @@ function populate( model ) {
|
|
|
1292
1294
|
}
|
|
1293
1295
|
// console.log(absolute)
|
|
1294
1296
|
const { location } = target.name;
|
|
1295
|
-
const from = augmentPath( location, target.name.
|
|
1297
|
+
const from = augmentPath( location, target.name.id );
|
|
1296
1298
|
let art = {
|
|
1297
1299
|
kind: 'entity',
|
|
1298
|
-
name: { location,
|
|
1300
|
+
name: { location, id: absolute },
|
|
1299
1301
|
location: target.location,
|
|
1300
1302
|
query: { location, op: { val: 'SELECT', location }, from },
|
|
1301
1303
|
$syntax: 'projection',
|
|
@@ -238,6 +238,8 @@ function propagate( model ) {
|
|
|
238
238
|
return;
|
|
239
239
|
if (prop === 'params' && target.$inferred !== 'proxy' && target.$inferred !== 'include')
|
|
240
240
|
return;
|
|
241
|
+
if (prop === 'foreignKeys' && target.on)
|
|
242
|
+
return; // e.g. published associations with filters
|
|
241
243
|
const location = target.type && !target.type.$inferred && target.type.location ||
|
|
242
244
|
target.location ||
|
|
243
245
|
target._outer && target._outer.location;
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -46,8 +46,7 @@ const {
|
|
|
46
46
|
isDeprecatedEnabled,
|
|
47
47
|
} = require('../base/model');
|
|
48
48
|
const { dictAdd } = require('../base/dictionaries');
|
|
49
|
-
const { dictLocation } = require('../base/location');
|
|
50
|
-
const { weakLocation } = require('../base/messages');
|
|
49
|
+
const { dictLocation, weakLocation } = require('../base/location');
|
|
51
50
|
const { combinedLocation } = require('../base/location');
|
|
52
51
|
const { typeParameters } = require('./builtins');
|
|
53
52
|
|
|
@@ -414,7 +413,7 @@ function resolve( model ) {
|
|
|
414
413
|
obj.items = items;
|
|
415
414
|
obj.$expand = 'origin';
|
|
416
415
|
}
|
|
417
|
-
if (obj.items) { // TODO: make this a while in
|
|
416
|
+
if (obj.items) { // TODO: make this a while in v5 (also items proxy)
|
|
418
417
|
obj = obj.items || obj; // the object which has type properties
|
|
419
418
|
effectiveType( obj );
|
|
420
419
|
}
|
|
@@ -440,11 +439,11 @@ function resolve( model ) {
|
|
|
440
439
|
}
|
|
441
440
|
|
|
442
441
|
// Check if relational type is missing its target or if it's used directly.
|
|
443
|
-
if (elemtype.category === 'relation' &&
|
|
442
|
+
if (elemtype.category === 'relation' &&
|
|
444
443
|
!obj.target && !obj.targetAspect) {
|
|
445
444
|
const isCsn = (obj._block && obj._block.$frontend === 'json');
|
|
446
445
|
error( 'type-missing-target', [ obj.type.location, obj ],
|
|
447
|
-
{ '#': isCsn ? 'csn' : 'std', type: elemtype
|
|
446
|
+
{ '#': isCsn ? 'csn' : 'std', type: elemtype }, {
|
|
448
447
|
// We don't say "use 'association to <target>" because the type could be used
|
|
449
448
|
// in action parameters, etc. as well.
|
|
450
449
|
std: 'The type $(TYPE) can\'t be used directly because it\'s compiler internal',
|
|
@@ -589,20 +588,23 @@ function resolve( model ) {
|
|
|
589
588
|
if (specifiedElement.foreignKeys) {
|
|
590
589
|
const sKeys = Object.keys( specifiedElement.foreignKeys );
|
|
591
590
|
/** @type {any} */
|
|
592
|
-
let
|
|
591
|
+
let iAssoc = inferredElement;
|
|
593
592
|
if (inferredElement._effectiveType !== 0) {
|
|
594
|
-
while (
|
|
595
|
-
|
|
593
|
+
while (iAssoc._origin && !iAssoc.foreignKeys && !iAssoc.on)
|
|
594
|
+
iAssoc = iAssoc._origin;
|
|
596
595
|
}
|
|
597
|
-
iKeys = Object.keys(
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
name: user.name.id,
|
|
604
|
-
|
|
605
|
-
|
|
596
|
+
const iKeys = Object.keys( iAssoc.foreignKeys || {} );
|
|
597
|
+
const loc = [
|
|
598
|
+
specifiedElement.foreignKeys[$location] || specifiedElement.location, user,
|
|
599
|
+
];
|
|
600
|
+
if (iAssoc.on) {
|
|
601
|
+
error( 'query-mismatched-element', loc, {
|
|
602
|
+
'#': 'unmanagedToManaged', name: user.name.id,
|
|
603
|
+
} );
|
|
604
|
+
}
|
|
605
|
+
else if (sKeys.length !== iKeys.length || sKeys.some( fkey => !iKeys.includes( fkey ) )) {
|
|
606
|
+
error( 'query-mismatched-element', loc, {
|
|
607
|
+
'#': 'foreignKeys', name: user.name.id,
|
|
606
608
|
} );
|
|
607
609
|
}
|
|
608
610
|
}
|
|
@@ -960,7 +962,7 @@ function resolve( model ) {
|
|
|
960
962
|
} );
|
|
961
963
|
// TODO: also warning if inside structure
|
|
962
964
|
}
|
|
963
|
-
else {
|
|
965
|
+
else { // if (obj.target._artifact)
|
|
964
966
|
// TODO: extra with $inferred (to avoid messages)?
|
|
965
967
|
resolveExpr( obj.on, art.kind === 'mixin' ? 'mixin-on' : 'on', art );
|
|
966
968
|
}
|
|
@@ -1187,9 +1189,7 @@ function resolve( model ) {
|
|
|
1187
1189
|
const nav = elem._main && elem._main.query && elem.value && pathNavigation( elem.value );
|
|
1188
1190
|
if (nav && nav.item !== elem.value.path[elem.value.path.length - 1]) {
|
|
1189
1191
|
if (!elem.on && origType.on) {
|
|
1190
|
-
error( 'rewrite-not-supported', [ elem.target.location, elem ]
|
|
1191
|
-
// TODO: Better text ?
|
|
1192
|
-
'The ON-condition is not rewritten here - provide an explicit ON-condition' );
|
|
1192
|
+
error( 'rewrite-not-supported', [ elem.target.location, elem ] );
|
|
1193
1193
|
return;
|
|
1194
1194
|
}
|
|
1195
1195
|
}
|
|
@@ -1285,10 +1285,10 @@ function resolve( model ) {
|
|
|
1285
1285
|
|
|
1286
1286
|
// eslint-disable-next-line no-shadow
|
|
1287
1287
|
function findOrig( chain, alias, art ) {
|
|
1288
|
-
if (!art || dict[art.name.
|
|
1288
|
+
if (!art || dict[art.name.id])
|
|
1289
1289
|
// some include ref or query source cannot be found, or cyclic ref
|
|
1290
1290
|
return true;
|
|
1291
|
-
dict[art.name.
|
|
1291
|
+
dict[art.name.id] = true;
|
|
1292
1292
|
|
|
1293
1293
|
if (art.includes) {
|
|
1294
1294
|
news.push( {
|
|
@@ -1378,14 +1378,18 @@ function resolve( model ) {
|
|
|
1378
1378
|
art;
|
|
1379
1379
|
if (step.args)
|
|
1380
1380
|
resolveParams( step.args, art, entity, expected, user, step.location );
|
|
1381
|
-
|
|
1381
|
+
|
|
1382
|
+
// Publishing an association with filters is ok in column
|
|
1383
|
+
const publishAssoc = art.kind === 'entity' && expected === 'column';
|
|
1384
|
+
|
|
1385
|
+
if (entity || publishAssoc) {
|
|
1382
1386
|
if (step.where) {
|
|
1383
1387
|
setLink( step, '_user', user._user || user );
|
|
1384
1388
|
const inCalc = (expected === 'calc' || expected === 'calc-filter');
|
|
1385
1389
|
resolveExpr( step.where, (inCalc ? 'calc-filter' : 'filter'), step );
|
|
1386
1390
|
}
|
|
1387
1391
|
}
|
|
1388
|
-
else if (step.where || step.cardinality
|
|
1392
|
+
else if (step.where || step.cardinality) {
|
|
1389
1393
|
const location = combinedLocation( step.where, step.cardinality );
|
|
1390
1394
|
let variant = alias ? 'tableAlias' : 'std';
|
|
1391
1395
|
if (expected === 'from')
|
package/lib/compiler/shared.js
CHANGED
|
@@ -227,7 +227,9 @@ function fns( model ) {
|
|
|
227
227
|
dynamic: parentElements,
|
|
228
228
|
navigation: assocOnNavigation,
|
|
229
229
|
notFound: undefinedParentElement,
|
|
230
|
+
rewriteProjectionToSelf: true,
|
|
230
231
|
}),
|
|
232
|
+
rewriteProjectionToSelf: true,
|
|
231
233
|
},
|
|
232
234
|
'mixin-on': {
|
|
233
235
|
lexical: tableAliasesAndSelf,
|
|
@@ -313,10 +315,9 @@ function fns( model ) {
|
|
|
313
315
|
|
|
314
316
|
// Return absolute name for unchecked path `ref`. We first try searching for
|
|
315
317
|
// the path root starting from `env`. If it exists, return its absolute name
|
|
316
|
-
// appended with the name of the rest of the path
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
// Used for collecting artifact extension, and annotation assignments.
|
|
318
|
+
// appended with the name of the rest of the path. Otherwise, complain if
|
|
319
|
+
// `unchecked` is false, and set `ref.absolute` to the path name of `ref`.
|
|
320
|
+
// Used for collecting artifact extension.
|
|
320
321
|
//
|
|
321
322
|
// Return '' if the ref is good, but points to an element.
|
|
322
323
|
function resolveUncheckedPath( ref, refCtx, user ) {
|
|
@@ -325,6 +326,11 @@ function fns( model ) {
|
|
|
325
326
|
return undefined;
|
|
326
327
|
|
|
327
328
|
const semantics = referenceSemantics[refCtx];
|
|
329
|
+
if (!semantics.isMainRef)
|
|
330
|
+
throw new CompilerAssertion( `resolveUncheckedPath() called for reference ctx '${ refCtx }'` );
|
|
331
|
+
if (!definedViaCdl( user ))
|
|
332
|
+
return (path.length === 1) ? path[0].id : '';
|
|
333
|
+
|
|
328
334
|
let art = getPathRoot( ref, semantics, user );
|
|
329
335
|
if (ref.scope && ref.scope !== 'global')
|
|
330
336
|
return ''; // TYPE OF, Main:elem
|
|
@@ -333,9 +339,9 @@ function fns( model ) {
|
|
|
333
339
|
art = art[0];
|
|
334
340
|
if (!art)
|
|
335
341
|
return (semantics.dynamic !== modelDefinitions) ? art : pathName( path );
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return `${
|
|
342
|
+
|
|
343
|
+
const first = (art.kind === 'using' ? art.extern : art.name).id;
|
|
344
|
+
return (path.length === 1) ? first : `${ first }.${ pathName( ref.path.slice(1) ) }`;
|
|
339
345
|
}
|
|
340
346
|
|
|
341
347
|
/**
|
|
@@ -502,12 +508,12 @@ function fns( model ) {
|
|
|
502
508
|
}
|
|
503
509
|
}
|
|
504
510
|
|
|
505
|
-
// Search in $special (
|
|
511
|
+
// Search in $special (excluding $self/$projection) and dynamic environment:
|
|
506
512
|
const dynamicDict = semantics.dynamic( ruser, user._user && user._artifact );
|
|
507
513
|
if (!dynamicDict) // avoid consequential errors
|
|
508
514
|
return setArtifactLink( head, null );
|
|
509
515
|
const isVar = (semantics.dollar && head.id.charAt( 0 ) === '$');
|
|
510
|
-
const dict = (isVar) ? model.$magicVariables.
|
|
516
|
+
const dict = (isVar) ? model.$magicVariables.elements : dynamicDict;
|
|
511
517
|
const r = dict[head.id];
|
|
512
518
|
if (r)
|
|
513
519
|
return setArtifactLink( head, r );
|
|
@@ -515,7 +521,7 @@ function fns( model ) {
|
|
|
515
521
|
if (!semantics.dollar)
|
|
516
522
|
valid.push( dynamicDict );
|
|
517
523
|
else
|
|
518
|
-
valid.push( model.$magicVariables.
|
|
524
|
+
valid.push( model.$magicVariables.elements, removeDollarNames( dynamicDict ) );
|
|
519
525
|
// TODO: streamline function arguments (probably: user, path, semantics )
|
|
520
526
|
const undef = semantics.notFound( ruser, head, valid, dynamicDict,
|
|
521
527
|
!isMainRef && user._user && user._artifact, path );
|
|
@@ -590,11 +596,16 @@ function fns( model ) {
|
|
|
590
596
|
function acceptLexical( art, path, semantics, user ) {
|
|
591
597
|
if (semantics.isMainRef || !art)
|
|
592
598
|
return !!art;
|
|
599
|
+
|
|
593
600
|
// Non-global lexical are table aliases, mixins and $self, $projection, $parameters,
|
|
594
601
|
// Do not accept a lonely table alias and `$projection`
|
|
595
602
|
// TODO: test table alias and mixin named `$projection`
|
|
596
|
-
if (path.length !== 1 || user.expand || user.inline)
|
|
603
|
+
if (path.length !== 1 || user.expand || user.inline) {
|
|
604
|
+
// Rewrite $projection to $self
|
|
605
|
+
if (semantics.rewriteProjectionToSelf && art.kind === '$self' && path[0].id === '$projection')
|
|
606
|
+
path[0].id = '$self';
|
|
597
607
|
return art.name?.$inferred !== '$internal'; // not a compiler-generated internal alias
|
|
608
|
+
}
|
|
598
609
|
|
|
599
610
|
// allow mixins, $self, and `up_` in anonymous target aspect (is $navElement):
|
|
600
611
|
return art.kind === 'mixin' ||
|
|
@@ -612,7 +623,7 @@ function fns( model ) {
|
|
|
612
623
|
|
|
613
624
|
switch (art.kind) {
|
|
614
625
|
case 'using': {
|
|
615
|
-
const def = model.definitions[art.
|
|
626
|
+
const def = model.definitions[art.extern.id];
|
|
616
627
|
if (!def)
|
|
617
628
|
return def;
|
|
618
629
|
if (def.$duplicates)
|
|
@@ -663,7 +674,7 @@ function fns( model ) {
|
|
|
663
674
|
const uniqueNames = arr.filter( e => !e.$duplicates );
|
|
664
675
|
if (uniqueNames.length) {
|
|
665
676
|
const names = uniqueNames.filter( e => e._parent.$inferred !== '$internal' )
|
|
666
|
-
.map( e => `${ e.name.
|
|
677
|
+
.map( e => `${ e._parent.name.id }.${ e.name.id }` );
|
|
667
678
|
let variant = names.length === uniqueNames.length ? 'std' : 'few';
|
|
668
679
|
if (names.length === 0)
|
|
669
680
|
variant = 'none';
|
|
@@ -929,7 +940,7 @@ function fns( model ) {
|
|
|
929
940
|
if (id.charAt( 0 ) === '$') {
|
|
930
941
|
const tableAlias = dynamicDict[id]?._parent;
|
|
931
942
|
// TODO: probably better to pass param `semantics` and calculate dynamic dict explicitly
|
|
932
|
-
const alias = tableAlias?.kind === '$tableAlias' ? tableAlias.name?.
|
|
943
|
+
const alias = tableAlias?.kind === '$tableAlias' ? tableAlias.name?.id : null;
|
|
933
944
|
// TODO: mention $self without query
|
|
934
945
|
signalNotFound( 'ref-undefined-var', [ head.location, user ], valid,
|
|
935
946
|
{ '#': (alias ? 'alias' : 'std'), alias, id } );
|
|
@@ -966,10 +977,10 @@ function fns( model ) {
|
|
|
966
977
|
const { id } = head;
|
|
967
978
|
const src = id.charAt( 0 ) !== '$' && user._combined?.[id];
|
|
968
979
|
if (src && !Array.isArray( src )) {
|
|
969
|
-
path.$prefix = src.name.
|
|
980
|
+
path.$prefix = src._parent.name.id; // pushing it to path directly could be problematic
|
|
970
981
|
// configurable error:
|
|
971
982
|
signalNotFound( 'ref-deprecated-orderby', [ head.location, user ], valid,
|
|
972
|
-
{ id: head.id, newcode: `${
|
|
983
|
+
{ id: head.id, newcode: `${ path.$prefix }.${ head.id }` } );
|
|
973
984
|
return src;
|
|
974
985
|
}
|
|
975
986
|
undefinedParentElement( user, head, valid, dynamicDict );
|
|
@@ -984,7 +995,8 @@ function fns( model ) {
|
|
|
984
995
|
}
|
|
985
996
|
|
|
986
997
|
function undefinedItemElement( user, item, valid, _dict, art, path ) {
|
|
987
|
-
|
|
998
|
+
const query = userQuery( art );
|
|
999
|
+
if (query?.name?.id > 1) {
|
|
988
1000
|
// TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
|
|
989
1001
|
// and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
|
|
990
1002
|
// both as text variants to ref-undefined-element
|
|
@@ -996,21 +1008,35 @@ function fns( model ) {
|
|
|
996
1008
|
valid, { art: art._main, id: item.id } );
|
|
997
1009
|
}
|
|
998
1010
|
else if (art.kind === 'builtin') { // magic variable / replacement variable
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1011
|
+
// $magic.{ var } is a configurable error,
|
|
1012
|
+
// TODO: if it becomes non-configurable, we can omit this warning
|
|
1013
|
+
let id = pathName( path );
|
|
1014
|
+
let head = path[0]._artifact || { _parent: art };
|
|
1015
|
+
// eslint-disable-next-line no-cond-assign
|
|
1016
|
+
while ((head = head?._parent) && head.kind === 'builtin')
|
|
1017
|
+
id = `${ head.name.id }.${ id }`;
|
|
1002
1018
|
signalNotFound( ( art.$uncheckedElements ? 'ref-unknown-var' : 'ref-undefined-var'),
|
|
1003
|
-
[ item.location, user ], valid,
|
|
1004
|
-
{ id: `${ art.name.element }.${ id }` } );
|
|
1019
|
+
[ item.location, user ], valid, { id } );
|
|
1005
1020
|
}
|
|
1006
|
-
else {
|
|
1007
|
-
const variant = art.kind === 'aspect' && !art.name && 'aspect';
|
|
1021
|
+
else if (art.kind === 'aspect' && !art.name) { // anonymous target aspect
|
|
1008
1022
|
signalNotFound( 'ref-undefined-element', [ item.location, user ],
|
|
1009
|
-
valid, {
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1023
|
+
valid, { '#': 'aspect', id: item.id } );
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
const target = art._effectiveType?.target;
|
|
1027
|
+
if (target?._artifact) {
|
|
1028
|
+
signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
|
|
1029
|
+
{ '#': 'target', art: target, id: item.id } );
|
|
1030
|
+
}
|
|
1031
|
+
else if (!target) {
|
|
1032
|
+
const variant = art.kind === 'aspect' && !art.name && 'aspect';
|
|
1033
|
+
signalNotFound( 'ref-undefined-element', [ item.location, user ],
|
|
1034
|
+
valid, {
|
|
1035
|
+
'#': variant,
|
|
1036
|
+
art: (variant ? '' : searchName( art, item.id, 'element' )),
|
|
1037
|
+
id: item.id,
|
|
1038
|
+
} );
|
|
1039
|
+
}
|
|
1014
1040
|
}
|
|
1015
1041
|
return null;
|
|
1016
1042
|
}
|
|
@@ -1364,7 +1390,7 @@ function fns( model ) {
|
|
|
1364
1390
|
return false; // param ref `:$self` is not $self
|
|
1365
1391
|
// $self in entity (TODO: mixin? new assoc in col? in aspect?)
|
|
1366
1392
|
const kind = right._artifact?.kind;
|
|
1367
|
-
if (kind !== 'entity' && kind !== 'aspect' && kind !== 'select')
|
|
1393
|
+
if (kind !== 'entity' && kind !== 'aspect' && kind !== 'select' && kind !== 'event')
|
|
1368
1394
|
return false; // (ok, this would also return `false` for `:$self`)
|
|
1369
1395
|
const { path } = left;
|
|
1370
1396
|
// TODO: we might return true and issue an extra error here
|
|
@@ -1436,7 +1462,7 @@ function fns( model ) {
|
|
|
1436
1462
|
|
|
1437
1463
|
for (let set = query._main.query; set.op; set = set.args[0]) {
|
|
1438
1464
|
const right = set.args[1];
|
|
1439
|
-
if (query.name.
|
|
1465
|
+
if (query.name.id >= (right._leadingQuery || right).name.id)
|
|
1440
1466
|
return { txt: 'setQuery', op: set.op.val };
|
|
1441
1467
|
}
|
|
1442
1468
|
throw new CompilerAssertion( 'Did we pass the leading query as argument?' );
|