@sap/cds-compiler 2.13.6 → 2.15.4
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 +128 -4
- package/bin/cdsc.js +112 -37
- package/lib/api/main.js +20 -22
- package/lib/api/options.js +2 -3
- package/lib/api/validate.js +6 -6
- package/lib/base/message-registry.js +92 -17
- package/lib/base/messages.js +85 -64
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/validator.js +2 -4
- package/lib/compiler/assert-consistency.js +1 -0
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +11 -0
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +59 -11
- package/lib/compiler/extend.js +20 -3
- package/lib/compiler/finalize-parse-cdl.js +26 -20
- package/lib/compiler/index.js +75 -26
- package/lib/compiler/populate.js +6 -5
- package/lib/compiler/propagator.js +4 -1
- package/lib/compiler/resolve.js +104 -16
- package/lib/compiler/shared.js +61 -27
- package/lib/compiler/tweak-assocs.js +7 -1
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +216 -98
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +499 -423
- package/lib/edm/edmUtils.js +22 -22
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageParser.js +4636 -4368
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +3 -2
- package/lib/json/to-csn.js +0 -2
- package/lib/language/docCommentParser.js +2 -2
- package/lib/language/genericAntlrParser.js +47 -2
- package/lib/language/language.g4 +59 -27
- package/lib/main.d.ts +19 -1
- package/lib/main.js +6 -0
- package/lib/model/csnRefs.js +33 -6
- package/lib/model/csnUtils.js +193 -75
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +2 -2
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +62 -26
- package/lib/render/toCdl.js +844 -679
- package/lib/render/toHdbcds.js +189 -243
- package/lib/render/toSql.js +180 -198
- package/lib/render/utils/common.js +131 -15
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/constraints.js +3 -1
- package/lib/transform/db/expansion.js +15 -10
- package/lib/transform/db/flattening.js +95 -68
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +6 -3
- package/lib/transform/forHanaNew.js +43 -26
- package/lib/transform/forOdataNew.js +43 -42
- package/lib/transform/localized.js +12 -7
- package/lib/transform/odata/toFinalBaseType.js +8 -6
- package/lib/transform/odata/typesExposure.js +145 -197
- package/lib/transform/transformUtilsNew.js +9 -12
- package/lib/transform/translateAssocsToJoins.js +5 -1
- package/lib/transform/universalCsn/coreComputed.js +5 -3
- package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
- package/lib/utils/moduleResolve.js +13 -6
- package/package.json +1 -1
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -296
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
package/lib/compiler/index.js
CHANGED
|
@@ -128,30 +128,35 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
128
128
|
// if (Object.getPrototypeOf( fileCache ))
|
|
129
129
|
// fileCache = Object.assign( Object.create(null), fileCache );
|
|
130
130
|
dir = path.resolve(dir);
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
const model = { sources: a.sources, options };
|
|
131
|
+
const model = { sources: null, options };
|
|
134
132
|
model.$messageFunctions = createMessageFunctions( options, 'compile', model );
|
|
135
|
-
let
|
|
136
|
-
|
|
137
|
-
all =
|
|
133
|
+
let input = null;
|
|
134
|
+
|
|
135
|
+
let all = processFilenames( filenames, dir )
|
|
136
|
+
.then((processedInput) => {
|
|
137
|
+
input = processedInput;
|
|
138
|
+
model.sources = input.sources;
|
|
139
|
+
})
|
|
140
|
+
.then(() => promiseAllDoNotRejectImmediately( input.files.map(readAndParse) ))
|
|
138
141
|
.then( testInvocation, (reason) => {
|
|
139
142
|
// do not reject with PromiseAllError, use InvocationError:
|
|
140
143
|
const errs = reason.valuesOrErrors.filter( e => e instanceof Error );
|
|
141
144
|
// internal error if no file IO error (has property `path`)
|
|
142
145
|
return Promise.reject( errs.find( e => !e.path ) ||
|
|
143
|
-
new InvocationError( [ ...
|
|
146
|
+
new InvocationError( [ ...input.repeated, ...errs ]) );
|
|
144
147
|
});
|
|
148
|
+
|
|
145
149
|
if (!options.parseOnly && !options.parseCdl)
|
|
146
150
|
all = all.then( readDependencies );
|
|
151
|
+
|
|
147
152
|
return all.then( () => {
|
|
148
|
-
moduleLayers.setLayers(
|
|
153
|
+
moduleLayers.setLayers( input.sources );
|
|
149
154
|
return compileDoX( model );
|
|
150
155
|
});
|
|
151
156
|
|
|
152
157
|
// Read file `filename` and parse its content, return messages
|
|
153
158
|
async function readAndParse( filename ) {
|
|
154
|
-
const { sources } =
|
|
159
|
+
const { sources } = input;
|
|
155
160
|
if ( filename === false ) // module which has not been found
|
|
156
161
|
return [];
|
|
157
162
|
const rel = sources[filename] || path.relative( dir, filename );
|
|
@@ -173,9 +178,9 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
173
178
|
|
|
174
179
|
// Combine the parse results (if there are not file IO errors)
|
|
175
180
|
function testInvocation( values ) {
|
|
176
|
-
if (
|
|
181
|
+
if (input.repeated.length)
|
|
177
182
|
// repeated file names in invocation => just report these
|
|
178
|
-
return Promise.reject( new InvocationError(
|
|
183
|
+
return Promise.reject( new InvocationError(input.repeated) );
|
|
179
184
|
return values;
|
|
180
185
|
}
|
|
181
186
|
|
|
@@ -225,7 +230,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
225
230
|
// A non-proper dictionary (i.e. with prototype) is safe if the keys are
|
|
226
231
|
// absolute file names - they start with `/` or `\` or similar
|
|
227
232
|
dir = path.resolve(dir);
|
|
228
|
-
const a =
|
|
233
|
+
const a = processFilenamesSync( filenames, dir );
|
|
229
234
|
|
|
230
235
|
const model = { sources: a.sources, options };
|
|
231
236
|
model.$messageFunctions = createMessageFunctions( options, 'compile', model );
|
|
@@ -453,22 +458,44 @@ function compileDoX( model ) {
|
|
|
453
458
|
return propagator.propagate( model );
|
|
454
459
|
}
|
|
455
460
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
const
|
|
461
|
+
/**
|
|
462
|
+
* Process an array of `filenames`. Returns an object with properties:
|
|
463
|
+
* - `sources`: dictionary which has a filename as key (value is irrelevant)
|
|
464
|
+
* - `files`: the argument array without repeating the same name
|
|
465
|
+
* - `repeated`: array of filenames which have been repeatedly listed
|
|
466
|
+
* (listed only once here even if listed thrice)
|
|
467
|
+
*
|
|
468
|
+
* Note: there is nothing file-specific about the filenames, the filenames are
|
|
469
|
+
* not normalized - any strings work
|
|
470
|
+
*/
|
|
471
|
+
async function processFilenames( filenames, dir ) {
|
|
472
|
+
const filenameMap = Object.create(null);
|
|
468
473
|
|
|
474
|
+
const promises = [];
|
|
469
475
|
for (const originalName of filenames) {
|
|
470
|
-
|
|
476
|
+
const setName = (name) => {
|
|
477
|
+
filenameMap[originalName] = name;
|
|
478
|
+
};
|
|
479
|
+
// Resolve possible symbolic link; if the file does not exist
|
|
480
|
+
// we just continue using the original name because readFile()
|
|
481
|
+
// already handles non-existent files.
|
|
482
|
+
const promise = fs.promises.realpath(path.resolve(dir, originalName))
|
|
483
|
+
.then(setName, () => setName(originalName));
|
|
484
|
+
promises.push(promise);
|
|
485
|
+
}
|
|
471
486
|
|
|
487
|
+
await Promise.all(promises);
|
|
488
|
+
return createSourcesDict( filenames, filenameMap, dir );
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Synchronous version of processFilenames().
|
|
493
|
+
*/
|
|
494
|
+
function processFilenamesSync( filenames, dir ) {
|
|
495
|
+
const filenameMap = Object.create(null);
|
|
496
|
+
|
|
497
|
+
for (const originalName of filenames) {
|
|
498
|
+
let name = path.resolve(dir, originalName);
|
|
472
499
|
try {
|
|
473
500
|
// Resolve possible symbolic link; if the file does not exist
|
|
474
501
|
// we just continue using the original name because readFile()
|
|
@@ -478,7 +505,29 @@ function processFilenames( filenames, dir ) {
|
|
|
478
505
|
catch (e) {
|
|
479
506
|
// Ignore the not-found (ENOENT) error
|
|
480
507
|
}
|
|
508
|
+
filenameMap[originalName] = name;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return createSourcesDict( filenames, filenameMap, dir );
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Creates the sources dictionary as well as a list of absolute filenames.
|
|
516
|
+
* If files are repeated, `repeated` will contain ArgumentErrors for it.
|
|
517
|
+
*
|
|
518
|
+
* @param {string[]} filenames List of (possibly relative) filenames. Defines the file order.
|
|
519
|
+
* @param {Record<string, string>} filenameMap Map from original name to actual filename
|
|
520
|
+
* (e.g. from symlink to underlying path)
|
|
521
|
+
* @param {string} dir "Current working directory"
|
|
522
|
+
* @return {{sources: object, files: string[], repeated: ArgumentError[]}}
|
|
523
|
+
*/
|
|
524
|
+
function createSourcesDict( filenames, filenameMap, dir ) {
|
|
525
|
+
const sources = Object.create(null);
|
|
526
|
+
const files = [];
|
|
527
|
+
const repeated = [];
|
|
481
528
|
|
|
529
|
+
for (const originalName of filenames) {
|
|
530
|
+
const name = filenameMap[originalName];
|
|
482
531
|
if (!sources[name]) {
|
|
483
532
|
sources[name] = path.relative( dir, name );
|
|
484
533
|
files.push(name);
|
|
@@ -488,10 +537,10 @@ function processFilenames( filenames, dir ) {
|
|
|
488
537
|
repeated.push( new ArgumentError( name, msg ) );
|
|
489
538
|
}
|
|
490
539
|
}
|
|
540
|
+
|
|
491
541
|
return { sources, files, repeated };
|
|
492
542
|
}
|
|
493
543
|
|
|
494
|
-
|
|
495
544
|
module.exports = {
|
|
496
545
|
parseX,
|
|
497
546
|
compileX,
|
package/lib/compiler/populate.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
// used when resolving element references: when starting a references at a
|
|
6
6
|
// certain definition or element, which names are allowed next?
|
|
7
7
|
//
|
|
8
|
-
// To calculate that info, the compiler might
|
|
9
|
-
// definitions.
|
|
10
|
-
// algorithm where appropriate).
|
|
8
|
+
// To calculate that info, the compiler might need the same info for other
|
|
9
|
+
// definitions. In other words: it calls itself recursively (using an iterative
|
|
10
|
+
// algorithm where appropriate). To be able to calculate that info on demand,
|
|
11
11
|
// the definitions need to have enough information, which must have been set in
|
|
12
12
|
// an earlier compiler phase. It is essential to do things in the right order.
|
|
13
13
|
|
|
@@ -166,7 +166,7 @@ function populate( model ) {
|
|
|
166
166
|
const chain = [];
|
|
167
167
|
while (art && !('_effectiveType' in art) &&
|
|
168
168
|
(art.type || art._origin || art.value && art.value.path) &&
|
|
169
|
-
// TODO: really stop at art.enum?
|
|
169
|
+
// TODO: really stop at art.enum? See #8942
|
|
170
170
|
!art.target && !art.enum && !art.elements && !art.items) {
|
|
171
171
|
chain.push( art );
|
|
172
172
|
setLink( art, '_effectiveType', 0 ); // initial setting in case of cycles
|
|
@@ -564,7 +564,8 @@ function populate( model ) {
|
|
|
564
564
|
error( null, [ col.location, query ], { prop: (col.expand ? 'expand' : 'inline') },
|
|
565
565
|
'Unsupported nested $(PROP)' );
|
|
566
566
|
}
|
|
567
|
-
|
|
567
|
+
// If neither expression (value), expand nor new association.
|
|
568
|
+
if (!col.value && !col.expand && !(col.target && col.type))
|
|
568
569
|
continue; // error should have been reported by parser
|
|
569
570
|
if (col.inline) {
|
|
570
571
|
col.kind = '$inline';
|
|
@@ -117,6 +117,9 @@ function propagate( model ) {
|
|
|
117
117
|
function runMembers( art ) {
|
|
118
118
|
// console.log('MEMBERS:',refString(art), art.elements ? Object.keys(art.elements) : 0)
|
|
119
119
|
forEachMember( art, run ); // after propagation in parent!
|
|
120
|
+
// propagate to sub query elements even if not requested:
|
|
121
|
+
if (art.$queries)
|
|
122
|
+
art.$queries.forEach( run );
|
|
120
123
|
let obj = art;
|
|
121
124
|
if (art.returns) {
|
|
122
125
|
obj = art.returns;
|
|
@@ -230,7 +233,7 @@ function propagate( model ) {
|
|
|
230
233
|
}
|
|
231
234
|
|
|
232
235
|
function notWithExpand( prop, target, source ) {
|
|
233
|
-
if (!target.expand)
|
|
236
|
+
if (!target.expand || prop === 'type' && source.elements)
|
|
234
237
|
always( prop, target, source );
|
|
235
238
|
}
|
|
236
239
|
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -50,6 +50,7 @@ const { dictLocation } = require('../base/location');
|
|
|
50
50
|
const { searchName, weakLocation } = require('../base/messages');
|
|
51
51
|
const { combinedLocation } = require('../base/location');
|
|
52
52
|
const { forEachValue } = require('../utils/objectUtils');
|
|
53
|
+
const { typeParameters } = require('./builtins');
|
|
53
54
|
|
|
54
55
|
const { kindProperties } = require('./base');
|
|
55
56
|
const {
|
|
@@ -89,13 +90,13 @@ function resolve( model ) {
|
|
|
89
90
|
} = model.$messageFunctions;
|
|
90
91
|
const {
|
|
91
92
|
resolvePath,
|
|
92
|
-
resolveTypeArguments,
|
|
93
93
|
defineAnnotations,
|
|
94
94
|
attachAndEmitValidNames,
|
|
95
95
|
lateExtensions,
|
|
96
96
|
effectiveType,
|
|
97
97
|
directType,
|
|
98
98
|
resolveType,
|
|
99
|
+
resolveTypeArgumentsUnchecked,
|
|
99
100
|
populateQuery,
|
|
100
101
|
} = model.$functions;
|
|
101
102
|
const { environment } = model.$volatileFunctions;
|
|
@@ -371,8 +372,8 @@ function resolve( model ) {
|
|
|
371
372
|
// console.log(obj.name,obj._origin.name)
|
|
372
373
|
if (obj._origin && obj._origin.$inferred === 'REDIRECTED')
|
|
373
374
|
resolveTarget( art, obj._origin );
|
|
374
|
-
// console.log(
|
|
375
|
-
//
|
|
375
|
+
// console.log(error( 'test-target', [ obj.location, obj ],
|
|
376
|
+
// { target: obj.target, kind: obj.kind }, 'Target: $(TARGET), Kind $(KIND)'));
|
|
376
377
|
if (!obj.target.$inferred || obj.target.$inferred === 'aspect-composition')
|
|
377
378
|
resolveTarget( art, obj );
|
|
378
379
|
else
|
|
@@ -448,8 +449,10 @@ function resolve( model ) {
|
|
|
448
449
|
// propagation/rewrite has been done yet, cyclic dependency must have been
|
|
449
450
|
// checked before!
|
|
450
451
|
function getAssocSpec( type ) {
|
|
452
|
+
const cyclic = new Set(); // TODO(#8942): May not be necessary if effectiveType() is adapted.
|
|
451
453
|
// only to be called without cycles
|
|
452
|
-
while (type) {
|
|
454
|
+
while (type && !cyclic.has(type)) {
|
|
455
|
+
cyclic.add(type);
|
|
453
456
|
if (type.on || type.foreignKeys || type.targetAspect)
|
|
454
457
|
return type;
|
|
455
458
|
type = directType( type );
|
|
@@ -464,7 +467,7 @@ function resolve( model ) {
|
|
|
464
467
|
// op.val is also correctly set with CSN input
|
|
465
468
|
elem.type = { ...type, $inferred: 'cast' };
|
|
466
469
|
setArtifactLink( elem.type, type._artifact );
|
|
467
|
-
for (const prop of
|
|
470
|
+
for (const prop of typeParameters.list) {
|
|
468
471
|
if (elem.value[prop])
|
|
469
472
|
elem[prop] = { ...elem.value[prop], $inferred: 'cast' };
|
|
470
473
|
}
|
|
@@ -1026,6 +1029,15 @@ function resolve( model ) {
|
|
|
1026
1029
|
return;
|
|
1027
1030
|
}
|
|
1028
1031
|
const target = resolvePath( obj.target, 'target', art );
|
|
1032
|
+
|
|
1033
|
+
if (obj._pathHead && obj.type && !obj.type.$inferred && art._main && art._main.query) {
|
|
1034
|
+
// New association inside expand/inline: The on-condition can't be properly checked,
|
|
1035
|
+
// so abort early. See #8797
|
|
1036
|
+
error( 'query-unexpected-assoc', [ obj.name.location, art ], {},
|
|
1037
|
+
'Unexpected new association in expand/inline' );
|
|
1038
|
+
return; // avoid subsequent errors
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1029
1041
|
if (obj.on) {
|
|
1030
1042
|
if (!art._main || !art._parent.elements && !art._parent.items && !art._parent.targetAspect) {
|
|
1031
1043
|
// TODO: test of .items a bit unclear - we should somehow restrict the
|
|
@@ -1057,20 +1069,29 @@ function resolve( model ) {
|
|
|
1057
1069
|
else if (art.kind === 'mixin') {
|
|
1058
1070
|
error( 'assoc-in-mixin', [ obj.target.location, art ], {},
|
|
1059
1071
|
'Managed associations are not allowed for MIXIN elements' );
|
|
1072
|
+
return; // avoid subsequent errors
|
|
1073
|
+
}
|
|
1074
|
+
else if (obj.type && !obj.type.$inferred && art._parent && art._parent.kind === 'select') {
|
|
1075
|
+
// New association in views, i.e. parent is a query.
|
|
1076
|
+
error( 'query-expected-on-condition', [ obj.target.location, art ], {},
|
|
1077
|
+
'Expected on-condition for published association' );
|
|
1078
|
+
return; // avoid subsequent errors
|
|
1060
1079
|
}
|
|
1061
1080
|
else if (target && !obj.foreignKeys && target.kind === 'entity') {
|
|
1062
1081
|
if (obj.$inferred === 'REDIRECTED') {
|
|
1063
1082
|
addImplicitForeignKeys( art, obj, target );
|
|
1064
1083
|
}
|
|
1065
|
-
else if (
|
|
1066
|
-
|
|
1067
|
-
}
|
|
1068
|
-
else if (obj.type._artifact && obj.type._artifact.internal) { // cds.Association, ...
|
|
1084
|
+
else if (obj.type && obj.type._artifact && obj.type._artifact.internal) {
|
|
1085
|
+
// cds.Association, ...
|
|
1069
1086
|
addImplicitForeignKeys( art, obj, target );
|
|
1070
1087
|
}
|
|
1071
|
-
// else console.log( message( null,obj.location,obj, {target}, 'Info','NOTARGET').toString())
|
|
1072
1088
|
}
|
|
1073
|
-
|
|
1089
|
+
|
|
1090
|
+
if (target && !target.$inferred) {
|
|
1091
|
+
if (!obj.type || obj.type.$inferred || obj.target.$inferred) { // REDIRECTED
|
|
1092
|
+
resolveRedirected( art, target );
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1074
1095
|
}
|
|
1075
1096
|
|
|
1076
1097
|
function addImplicitForeignKeys( art, obj, target ) {
|
|
@@ -1130,7 +1151,9 @@ function resolve( model ) {
|
|
|
1130
1151
|
const assoc = directType( elem );
|
|
1131
1152
|
const origType = assoc && effectiveType( assoc );
|
|
1132
1153
|
if (!origType || !origType.target) {
|
|
1133
|
-
|
|
1154
|
+
const path = (elem.value && elem.value.path);
|
|
1155
|
+
const loc = (path && path[path.length - 1] || elem.value || elem).location;
|
|
1156
|
+
error( 'redirected-no-assoc', [ loc, elem ], {},
|
|
1134
1157
|
'Only an association can be redirected' );
|
|
1135
1158
|
return;
|
|
1136
1159
|
}
|
|
@@ -1138,7 +1161,7 @@ function resolve( model ) {
|
|
|
1138
1161
|
// .toString(), elem.value)
|
|
1139
1162
|
const nav = elem._main && elem._main.query && elem.value && pathNavigation( elem.value );
|
|
1140
1163
|
if (nav && nav.item !== elem.value.path[elem.value.path.length - 1]) {
|
|
1141
|
-
if (origType.on) {
|
|
1164
|
+
if (!elem.on && origType.on) {
|
|
1142
1165
|
error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
|
|
1143
1166
|
// TODO: Better text ?
|
|
1144
1167
|
'The ON condition is not rewritten here - provide an explicit ON condition' );
|
|
@@ -1151,7 +1174,9 @@ function resolve( model ) {
|
|
|
1151
1174
|
|
|
1152
1175
|
const chain = [];
|
|
1153
1176
|
if (target === origTarget) {
|
|
1154
|
-
if (!elem.target.$inferred) {
|
|
1177
|
+
if (!elem.target.$inferred && !elem.on && !elem.foreignKeys) {
|
|
1178
|
+
// Only a managed redirection gets this info message. Because otherwise
|
|
1179
|
+
// we'd have to check whether on-condition/foreignKeys are the same.
|
|
1155
1180
|
info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
|
|
1156
1181
|
'The redirected target is the original $(ART)' );
|
|
1157
1182
|
}
|
|
@@ -1265,8 +1290,71 @@ function resolve( model ) {
|
|
|
1265
1290
|
// Resolve the type and its arguments if applicable.
|
|
1266
1291
|
function resolveTypeExpr( art, user ) {
|
|
1267
1292
|
const typeArt = resolveType( art.type, user );
|
|
1268
|
-
if (typeArt)
|
|
1269
|
-
|
|
1293
|
+
if (typeArt) {
|
|
1294
|
+
resolveTypeArgumentsUnchecked( art, typeArt, user );
|
|
1295
|
+
checkTypeArguments( art );
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
/**
|
|
1300
|
+
* Check the type arguments on `artWithType`.
|
|
1301
|
+
* If the effective type is an array or structured type, an error is emitted.
|
|
1302
|
+
*/
|
|
1303
|
+
function checkTypeArguments( artWithType ) {
|
|
1304
|
+
// Note: `_effectiveType` may point to `artWithType` itself, if the type is structured.
|
|
1305
|
+
// Also: For enums, it points to the enum type, which is why this trick is needed.
|
|
1306
|
+
// TODO(#8942): May not be necessary if effectiveType() is adapted. Furthermore, the enum
|
|
1307
|
+
// trick may be removed if effectiveType() does not stop at enums.
|
|
1308
|
+
const cyclic = new Set();
|
|
1309
|
+
let effectiveTypeArt = effectiveType( artWithType );
|
|
1310
|
+
while (effectiveTypeArt && effectiveTypeArt.enum && !cyclic.has(effectiveTypeArt)) {
|
|
1311
|
+
cyclic.add(effectiveTypeArt);
|
|
1312
|
+
const underlyingEnumType = directType(effectiveTypeArt);
|
|
1313
|
+
if (underlyingEnumType)
|
|
1314
|
+
effectiveTypeArt = effectiveType(underlyingEnumType);
|
|
1315
|
+
else
|
|
1316
|
+
break;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
if (!effectiveTypeArt)
|
|
1320
|
+
return; // e.g. illegal definition references
|
|
1321
|
+
|
|
1322
|
+
const params = effectiveTypeArt.parameters &&
|
|
1323
|
+
effectiveTypeArt.parameters.map(p => p.name || p) || [];
|
|
1324
|
+
|
|
1325
|
+
for (const param of typeParameters.list) {
|
|
1326
|
+
if (artWithType[param] !== undefined) {
|
|
1327
|
+
if (!params.includes(param)) {
|
|
1328
|
+
// Whether the type ref itself is a builtin or a custom type with a builtin as base.
|
|
1329
|
+
const type = directType(artWithType);
|
|
1330
|
+
|
|
1331
|
+
let variant;
|
|
1332
|
+
if (type.builtin)
|
|
1333
|
+
// `.type` is already a builtin: use a nicer message.
|
|
1334
|
+
variant = 'builtin';
|
|
1335
|
+
else if (effectiveTypeArt.builtin)
|
|
1336
|
+
// base type is a builtin, i.e. a scalar
|
|
1337
|
+
variant = 'type';
|
|
1338
|
+
else
|
|
1339
|
+
// effectiveType is not a builtin -> array or structured
|
|
1340
|
+
variant = 'non-scalar';
|
|
1341
|
+
|
|
1342
|
+
error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
|
|
1343
|
+
'#': variant, prop: param, art: artWithType.type, type: effectiveTypeArt,
|
|
1344
|
+
});
|
|
1345
|
+
break; // Avoid spam: Only emit the first error.
|
|
1346
|
+
}
|
|
1347
|
+
else if (!typeParameters.expectedLiteralsFor[param].includes(artWithType[param].literal)) {
|
|
1348
|
+
error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
|
|
1349
|
+
'#': 'incorrect-type',
|
|
1350
|
+
prop: param,
|
|
1351
|
+
code: artWithType[param].literal,
|
|
1352
|
+
names: typeParameters.expectedLiteralsFor[param],
|
|
1353
|
+
});
|
|
1354
|
+
break; // Avoid spam: Only emit the first error.
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1270
1358
|
}
|
|
1271
1359
|
|
|
1272
1360
|
function resolveExpr( expr, expected, user, extDict, expandOrInline) {
|
package/lib/compiler/shared.js
CHANGED
|
@@ -157,8 +157,8 @@ function fns( model ) {
|
|
|
157
157
|
const VolatileFns = model.$volatileFunctions;
|
|
158
158
|
Object.assign( model.$functions, {
|
|
159
159
|
resolveUncheckedPath,
|
|
160
|
+
resolveTypeArgumentsUnchecked,
|
|
160
161
|
resolvePath,
|
|
161
|
-
resolveTypeArguments,
|
|
162
162
|
defineAnnotations,
|
|
163
163
|
attachAndEmitValidNames,
|
|
164
164
|
} );
|
|
@@ -459,35 +459,64 @@ function fns( model ) {
|
|
|
459
459
|
}
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
462
|
+
/**
|
|
463
|
+
* Resolve the type arguments of `artifact` according to the type `typeArtifact`.
|
|
464
|
+
* User is used for semantic message location.
|
|
465
|
+
*
|
|
466
|
+
* For builtins, for each property name `<prop>` in `typeArtifact.parameters`, we move a value
|
|
467
|
+
* in art.$typeArgs (a vector of numbers with locations) to `artifact.<prop>`.
|
|
468
|
+
*
|
|
469
|
+
* For non-builtins, we take either one or two arguments and interpret them
|
|
470
|
+
* as `length` or `precision`/`scale`.
|
|
471
|
+
*
|
|
472
|
+
* Left-over arguments are errors for non-builtins and warnings for builtins.
|
|
473
|
+
*
|
|
474
|
+
* @param {object} artifact
|
|
475
|
+
* @param {object} typeArtifact
|
|
476
|
+
* @param {CSN.Artifact} user
|
|
477
|
+
*/
|
|
478
|
+
function resolveTypeArgumentsUnchecked(artifact, typeArtifact, user) {
|
|
479
|
+
let args = artifact.$typeArgs || [];
|
|
473
480
|
const parameters = typeArtifact.parameters || [];
|
|
474
|
-
const parLength = parameters.length;
|
|
475
481
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
par =
|
|
480
|
-
|
|
481
|
-
|
|
482
|
+
if (parameters.length > 0) {
|
|
483
|
+
// For Builtins
|
|
484
|
+
for (let i = 0; i < parameters.length; ++i) {
|
|
485
|
+
let par = parameters[i];
|
|
486
|
+
if (!(par instanceof Object))
|
|
487
|
+
par = { name: par };
|
|
488
|
+
if (!artifact[par.name] && i < args.length)
|
|
489
|
+
artifact[par.name] = args[i];
|
|
490
|
+
}
|
|
491
|
+
args = args.slice(parameters.length);
|
|
482
492
|
}
|
|
483
|
-
if (args.length >
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
493
|
+
else if (args.length > 0 && !typeArtifact.builtin) {
|
|
494
|
+
// One or two arguments are interpreted as either length or precision/scale.
|
|
495
|
+
// For builtins, we know what arguments are expected, and we do not need this mapping.
|
|
496
|
+
// Also, we expect non-structured types.
|
|
497
|
+
if (args.length === 1) {
|
|
498
|
+
artifact.length = args[0];
|
|
499
|
+
args = args.slice(1);
|
|
500
|
+
}
|
|
501
|
+
else if (args.length === 2) {
|
|
502
|
+
artifact.precision = args[0];
|
|
503
|
+
artifact.scale = args[1];
|
|
504
|
+
args = args.slice(2);
|
|
505
|
+
}
|
|
487
506
|
}
|
|
488
|
-
|
|
489
|
-
|
|
507
|
+
|
|
508
|
+
if (!artifact.$typeArgs)
|
|
509
|
+
return;
|
|
510
|
+
|
|
511
|
+
// Warn about left-over arguments.
|
|
512
|
+
if (args.length > 0) {
|
|
513
|
+
const loc = [ args[args.length - 1].location, user ];
|
|
514
|
+
if (typeArtifact.builtin)
|
|
515
|
+
warning( 'type-ignoring-argument', loc, { art: typeArtifact } );
|
|
516
|
+
else
|
|
517
|
+
error( 'type-unexpected-argument', loc, { '#': 'std', art: typeArtifact });
|
|
490
518
|
}
|
|
519
|
+
artifact.$typeArgs = undefined;
|
|
491
520
|
}
|
|
492
521
|
|
|
493
522
|
// Return artifact or element referred by name `head`. The first environment
|
|
@@ -732,7 +761,7 @@ function fns( model ) {
|
|
|
732
761
|
signalNotFound( spec.undefinedDef || 'ref-undefined-def', [ item.location, user ],
|
|
733
762
|
[ env ], { art: a } );
|
|
734
763
|
}
|
|
735
|
-
else if (art.name.select && art.name.select > 1) {
|
|
764
|
+
else if (art.name && art.name.select && art.name.select > 1) {
|
|
736
765
|
// TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
|
|
737
766
|
// and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
|
|
738
767
|
// TODO: probably not extra messageId, but text variant
|
|
@@ -748,8 +777,13 @@ function fns( model ) {
|
|
|
748
777
|
{ param: 'Entity $(ART) has no parameter $(MEMBER)' } );
|
|
749
778
|
}
|
|
750
779
|
else {
|
|
780
|
+
const variant = art.kind === 'aspect' && !art.name && 'aspect';
|
|
751
781
|
signalNotFound( spec.undefinedDef || 'ref-undefined-element', [ item.location, user ],
|
|
752
|
-
[ env ], {
|
|
782
|
+
[ env ], {
|
|
783
|
+
'#': variant,
|
|
784
|
+
art: (variant ? '' : searchName( art, item.id, 'element' )),
|
|
785
|
+
id: item.id,
|
|
786
|
+
} );
|
|
753
787
|
}
|
|
754
788
|
return null;
|
|
755
789
|
}
|
|
@@ -459,7 +459,13 @@ function tweakAssocs( model ) {
|
|
|
459
459
|
if (!forKeys)
|
|
460
460
|
break;
|
|
461
461
|
setArtifactLink( item, null );
|
|
462
|
-
|
|
462
|
+
const culprit = elem.target && !elem.target.$inferred && elem.target ||
|
|
463
|
+
(elem.value && elem.value.path &&
|
|
464
|
+
elem.value.path[elem.value.path.length - 1]) ||
|
|
465
|
+
elem;
|
|
466
|
+
// TODO: probably better to collect the non-projected foreign keys
|
|
467
|
+
// and have one message for all
|
|
468
|
+
error( 'rewrite-undefined-key', [ weakLocation( culprit.location ), assoc ],
|
|
463
469
|
{ id: item.id, art: alias._main },
|
|
464
470
|
'Foreign key $(ID) has not been found in target $(ART)' );
|
|
465
471
|
return null;
|