@formaui-net/nexo 0.0.0 → 1.0.0-preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +148 -6
- package/package.json +23 -3
- package/src/common/ArrayUtils.mjs +16 -0
- package/src/common/ObjectUtils.mjs +33 -0
- package/src/language/DefinitionLocations.mjs +41 -0
- package/src/language/DefinitionValidator.mjs +1265 -0
- package/src/language/DefinitionWriter.mjs +475 -0
- package/src/language/Dictionary.mjs +52 -0
- package/src/language/Evaluator.mjs +3480 -0
- package/src/language/ImportedVocabulary.mjs +137 -0
- package/src/language/Imports.mjs +141 -0
- package/src/language/Index.mjs +29 -0
- package/src/language/LanguageResolver.mjs +4068 -0
- package/src/language/Limits.mjs +78 -0
- package/src/language/LineStructure.mjs +385 -0
- package/src/language/Lowering.mjs +453 -0
- package/src/language/MemberSelector.mjs +89 -0
- package/src/language/Names.mjs +65 -0
- package/src/language/NexoError.mjs +145 -0
- package/src/language/Pair.mjs +21 -0
- package/src/language/ParameterBindings.mjs +361 -0
- package/src/language/ParsedTexts.mjs +62 -0
- package/src/language/Parser.mjs +1817 -0
- package/src/language/Range.mjs +51 -0
- package/src/language/RecentlyUsed.mjs +50 -0
- package/src/language/ResolvedDefinition.mjs +219 -0
- package/src/language/ResultPath.mjs +101 -0
- package/src/language/ScopeLocals.mjs +102 -0
- package/src/language/StructureTypes.mjs +48 -0
- package/src/language/SyntaxTree.mjs +203 -0
- package/src/language/TextLocations.mjs +185 -0
- package/src/language/TextPositions.mjs +23 -0
- package/src/language/Tokenizer.mjs +917 -0
- package/src/language/Type.mjs +401 -0
- package/src/language/TypeChecker.mjs +449 -0
- package/src/language/ValueCapture.mjs +315 -0
- package/src/language/Versions.mjs +14 -0
- package/src/language/vocabulary/Declarations.mjs +64 -0
- package/src/language/vocabulary/ItemPositions.mjs +48 -0
- package/src/language/vocabulary/ListOrdering.mjs +145 -0
- package/src/language/vocabulary/MemberFailure.mjs +21 -0
- package/src/language/vocabulary/NexoModule.mjs +1119 -0
- package/src/language/vocabulary/NexoRegistry.mjs +1540 -0
- package/src/language/vocabulary/TypeNameScope.mjs +63 -0
- package/src/language/vocabulary/Vocabulary.mjs +640 -0
- package/src/nexo.d.ts +743 -0
- package/src/nexo.js +338 -0
|
@@ -0,0 +1,1540 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NexoRegistry — the declarations of every module registered with the registry, which requests resolve against
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ArrayUtils } from '../../common/ArrayUtils.mjs';
|
|
6
|
+
import { ObjectUtils } from '../../common/ObjectUtils.mjs';
|
|
7
|
+
import { Imports } from '../Imports.mjs';
|
|
8
|
+
import { Names } from '../Names.mjs';
|
|
9
|
+
import { NexoErrors } from '../NexoError.mjs';
|
|
10
|
+
import { MemberSelector } from '../MemberSelector.mjs';
|
|
11
|
+
import { Type } from '../Type.mjs';
|
|
12
|
+
import { TypeChecker } from '../TypeChecker.mjs';
|
|
13
|
+
import { NexoModule } from './NexoModule.mjs';
|
|
14
|
+
import { TypeNameScope } from './TypeNameScope.mjs';
|
|
15
|
+
import { Vocabulary } from './Vocabulary.mjs';
|
|
16
|
+
|
|
17
|
+
/** The receiver types of a module member, which no type contains. */
|
|
18
|
+
const NO_RECEIVER_TYPES = Object.freeze([]);
|
|
19
|
+
|
|
20
|
+
export class NexoRegistry {
|
|
21
|
+
/** @type {Map<string, import('./NexoModule.mjs').NexoModule>} */
|
|
22
|
+
#modules = new Map();
|
|
23
|
+
|
|
24
|
+
/** @type {Map<string, object[]>} */
|
|
25
|
+
#receiverMembers = new Map();
|
|
26
|
+
|
|
27
|
+
/** Every unnamed indexer registered, across every module, in the order registered: an indexer declared without a name, which no name finds. @type {object[]} */
|
|
28
|
+
#unnamedIndexers = [];
|
|
29
|
+
|
|
30
|
+
/** Each value type a module declares, reference and structured, by its identity, its module's name and its local name. @type {Map<string, object>} */
|
|
31
|
+
#types = new Map();
|
|
32
|
+
|
|
33
|
+
/** Each extension a module declares, by its identity, as a type's: never a value's type, so no notation names one. @type {Map<string, object>} */
|
|
34
|
+
#extensions = new Map();
|
|
35
|
+
|
|
36
|
+
/** Each type selector, by its name. @type {Map<string, object>} */
|
|
37
|
+
#typeSelectors = new Map();
|
|
38
|
+
|
|
39
|
+
/** Each selector's keys, by the selector's name and then the key, each the mapping a module declared. @type {Map<string, Map<string, object>>} */
|
|
40
|
+
#mappings = new Map();
|
|
41
|
+
|
|
42
|
+
/** @type {Map<string, object>} */
|
|
43
|
+
#observationTypes = new Map();
|
|
44
|
+
|
|
45
|
+
/** Every operator registered, by its name, `cast` or `convert`, in the order registered: looked up apart from the members, which the language's syntax never confuses with one. @type {Map<string, object[]>} */
|
|
46
|
+
#operators = new Map();
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The module each declaration belongs to, by the declaration: the name of the module that declares it, which its notations are read in, and the modules a structured type imports, which they read unqualified too.
|
|
50
|
+
* Written for every declaration of a module as its registration starts, so its own declarations read its own types, and removed for each where the module is refused.
|
|
51
|
+
* and the type parameters its notations read as parameters.
|
|
52
|
+
* @type {WeakMap<object, { moduleName: string, imports: readonly string[], typeParameters: ReadonlySet<string> }>}
|
|
53
|
+
*/
|
|
54
|
+
#declaringModules = new WeakMap();
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The type or the extension each member is declared within, by the member, which gives its receiver: a type a module declares, one of Nexo's own value types, or an extension. Written for every such member as its
|
|
58
|
+
* module's registration starts, beside its declaring module, and removed for each where the module is refused. @type {WeakMap<object, object>}
|
|
59
|
+
*/
|
|
60
|
+
#containingTypes = new WeakMap();
|
|
61
|
+
|
|
62
|
+
/** The type each notation of a declaration writes, every name in it resolved to its identity, by the declaration and then the notation. @type {WeakMap<object, Map<string, Type>>} */
|
|
63
|
+
#declaredTypeCache = new WeakMap();
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The types each member a type contains is read on, by the member, settled at the first time they are asked for. A member's containing type never changes once written, so an entry is valid until the member's
|
|
67
|
+
* module is refused, which removes it with the containing type, and is released with the member; a member no type contains has none, never kept. Building them anew for each request measured a tenth of
|
|
68
|
+
* resolving.
|
|
69
|
+
* @type {WeakMap<object, readonly Type[]>}
|
|
70
|
+
*/
|
|
71
|
+
#receiverTypes = new WeakMap();
|
|
72
|
+
|
|
73
|
+
/** The data shape of each structured type, by its declaration: valid for good, since a declaration is frozen and the types its notations write are kept for good, and released with the declaration. @type {WeakMap<object, ReadonlyMap<string, Type>>} */
|
|
74
|
+
#dataShapeCache = new WeakMap();
|
|
75
|
+
|
|
76
|
+
/** The module being registered and its value types, by identity, which its own declarations name before it is registered; null between registrations. */
|
|
77
|
+
#pending = null;
|
|
78
|
+
|
|
79
|
+
/** @param {import('./NexoModule.mjs').NexoModule[]} modules The modules registered at the start. */
|
|
80
|
+
constructor(modules = []) {
|
|
81
|
+
if (!Array.isArray(modules)) {
|
|
82
|
+
throw NexoErrors.module('NEXO4546', 'A registry is created from a list of modules, each as createModule makes it', {});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
modules.forEach(module => this.registerModule(module));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The checker of the relations between the types the registry's modules declare and Nexo's own. */
|
|
89
|
+
get typeChecker() {
|
|
90
|
+
return this.#typeChecker;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#typeChecker = new TypeChecker(this);
|
|
94
|
+
|
|
95
|
+
/** The selection of a declaration among those of a name for a receiver, by the owner of its type and the specificity of its extensions. */
|
|
96
|
+
get memberSelector() {
|
|
97
|
+
return this.#memberSelector;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#memberSelector = new MemberSelector(this, this.#typeChecker);
|
|
101
|
+
|
|
102
|
+
/** The number of modules registered, which changes whenever the vocabulary does. */
|
|
103
|
+
get revision() {
|
|
104
|
+
return this.#modules.size;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The revision the last module declaring a `typeOf` was registered at, 0 where none is, since a registration changes a resolution made before it through recognition alone. */
|
|
108
|
+
get recognitionRevision() {
|
|
109
|
+
return this.#recognitionRevision;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#recognitionRevision = 0;
|
|
113
|
+
|
|
114
|
+
/** The names of the modules registered. */
|
|
115
|
+
get moduleNames() {
|
|
116
|
+
return [...this.#modules.keys()];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Registers a module's declarations, refusing a module whose names are taken or whose declarations break the registry's rules, before registering any of them. It calls no host code: a module's `isInstance`, `typeOf`
|
|
121
|
+
* and implementations are called only as values are bound or evaluated.
|
|
122
|
+
*/
|
|
123
|
+
registerModule(module) {
|
|
124
|
+
if (!NexoModule.isModule(module)) {
|
|
125
|
+
throw NexoErrors.module('NEXO4546', 'A module is registered as createModule makes it', typeof module?.name === 'string' ? { module: module.name } : {});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const fail = (code, message, declarationName, declarationKey, typeName) => {
|
|
129
|
+
throw NexoErrors.module(code, message, {
|
|
130
|
+
module: module.name,
|
|
131
|
+
...(typeName === undefined ? {} : { type: typeName }),
|
|
132
|
+
...(declarationName === undefined ? {} : { declaration: declarationName }),
|
|
133
|
+
...(declarationKey === undefined ? {} : { declarationKey }),
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
// A member declared within a type is located at its type as well, and an unnamed indexer as its access is written.
|
|
137
|
+
const failAt = declaration => (code, message, declarationName, declarationKey) => fail(code, message, declarationName ?? NexoRegistry.#locatedNameOf(declaration), declarationKey, this.#containingTypes.get(declaration)?.name);
|
|
138
|
+
|
|
139
|
+
// Module names are compared case-insensitively for uniqueness, each keeping its own spelling.
|
|
140
|
+
const taken = [...this.#modules.keys()].find(name => name.toLowerCase() === module.name.toLowerCase());
|
|
141
|
+
|
|
142
|
+
if (taken !== undefined) {
|
|
143
|
+
fail('NEXO4035', `A module is registered once under a name compared case-insensitively, and '${taken}' is registered already`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// A type is named once within its module, which creating it checks, and two modules may each declare a type of one name, since its module is part of its identity.
|
|
147
|
+
// An extension is no value's type, so a notation names its module's value types alone.
|
|
148
|
+
const valueTypes = module.types.filter(type => type.kind !== 'extension');
|
|
149
|
+
|
|
150
|
+
for (const type of module.types) {
|
|
151
|
+
|
|
152
|
+
for (const moduleName of type.import ?? []) {
|
|
153
|
+
if (!this.#modules.has(moduleName)) {
|
|
154
|
+
fail('NEXO4015', `The type imports the module '${moduleName}', which is not registered`, type.name, 'import');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// A module's members are its module members, declared flat, and those each of its types and extensions declares within it; Nexo's own module owns its value types, declared through Nexo's internal form,
|
|
160
|
+
// so the members of lists are its own.
|
|
161
|
+
const ownValueTypes = module === Vocabulary.module ? Vocabulary.valueTypes : [];
|
|
162
|
+
const typesOfMembers = new Map([...ownValueTypes, ...module.types].flatMap(type => (type.members ?? []).map(member => [member, type])));
|
|
163
|
+
const members = [...module.members, ...typesOfMembers.keys()];
|
|
164
|
+
|
|
165
|
+
// Each member is located at the type that holds it from here on, so a refusal of it below names that type.
|
|
166
|
+
typesOfMembers.forEach((type, member) => this.#containingTypes.set(member, type));
|
|
167
|
+
|
|
168
|
+
NexoRegistry.#validateTypeSelections(module, members, this.#typeSelectors, this.#mappings, declaration => this.getDeclaringModuleName(declaration), fail, failAt);
|
|
169
|
+
|
|
170
|
+
for (const type of module.observationTypes) {
|
|
171
|
+
if (this.#observationTypes.has(type.name)) {
|
|
172
|
+
fail('NEXO4007', `The observation type '${type.name}' is declared already`, type.name);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const observationTypes = new Map([...this.#observationTypes, ...module.observationTypes.map(type => [type.name, type])]);
|
|
177
|
+
|
|
178
|
+
// A target belongs to the module that declares the observation type observing it, this module's own types observing this module's own targets.
|
|
179
|
+
const ownObservationTypes = new Set(module.observationTypes);
|
|
180
|
+
const getDeclaringModule = type => (ownObservationTypes.has(type) ? module : this.#modules.get(this.getDeclaringModuleName(type)));
|
|
181
|
+
|
|
182
|
+
// Every declaration's notations are read in its module: its own types, Nexo's, and those of a module a type imports, unqualified, and any other qualified by its module; a member a type declares within it reads
|
|
183
|
+
// its type's imports, which cover the type's whole declaration.
|
|
184
|
+
const declarations = [...module.types, ...members, ...module.typeSelectors, ...module.mappings, ...module.observationTypes];
|
|
185
|
+
|
|
186
|
+
for (const declaration of declarations) {
|
|
187
|
+
const type = module.types.includes(declaration) ? declaration : typesOfMembers.get(declaration);
|
|
188
|
+
|
|
189
|
+
this.#declaringModules.set(declaration, { moduleName: module.name, imports: type?.import ?? [], typeParameters: NexoRegistry.#typeParameterScopeOf(declaration, type) });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this.#pending = { module, types: new Map(valueTypes.map(type => [`${module.name}.${type.name}`, type])) };
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
this.#resolveDeclaredTypes(module, members);
|
|
196
|
+
this.#validateDeclaredTypes(module, members, fail, failAt);
|
|
197
|
+
|
|
198
|
+
for (const declaration of members) {
|
|
199
|
+
NexoRegistry.#validateObservations(declaration, this.takesReceiver(declaration), observationTypes, getDeclaringModule, failAt(declaration));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
this.#validateMemberCollisions(members, failAt);
|
|
203
|
+
this.#validateAccessibility(module, members, fail, failAt);
|
|
204
|
+
this.#validateTypeParameters(module, members, fail, failAt);
|
|
205
|
+
this.#validateOperators(module, members, failAt);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
// A refused module's declarations belong to no module of the registry, and its members to none of its types.
|
|
208
|
+
declarations.forEach(declaration => this.#declaringModules.delete(declaration));
|
|
209
|
+
typesOfMembers.forEach((type, member) => {
|
|
210
|
+
this.#containingTypes.delete(member);
|
|
211
|
+
this.#receiverTypes.delete(member);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
throw error;
|
|
215
|
+
} finally {
|
|
216
|
+
this.#pending = null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.#modules.set(module.name, module);
|
|
220
|
+
|
|
221
|
+
if (module.typeOf !== undefined) {
|
|
222
|
+
this.#recognitionRevision = this.revision;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
for (const type of module.types) {
|
|
226
|
+
(type.kind === 'extension' ? this.#extensions : this.#types).set(`${module.name}.${type.name}`, type);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (const selector of module.typeSelectors) {
|
|
230
|
+
this.#typeSelectors.set(selector.name, selector);
|
|
231
|
+
this.#mappings.set(selector.name, new Map());
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (const mapping of module.mappings) {
|
|
235
|
+
this.#mappings.get(mapping.selector).set(mapping.key, mapping);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for (const type of module.observationTypes) {
|
|
239
|
+
this.#observationTypes.set(type.name, type);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// A module member has no receiver, so no receiver's member of its name finds it; an unnamed indexer has no name; and an operator is looked up by its operation alone.
|
|
243
|
+
for (const declaration of members) {
|
|
244
|
+
if (declaration.kind === 'operator') {
|
|
245
|
+
this.#operators.set(declaration.name, [...this.getOperators(declaration.name), declaration]);
|
|
246
|
+
} else if (NexoRegistry.#isUnnamedIndexer(declaration)) {
|
|
247
|
+
this.#unnamedIndexers.push(declaration);
|
|
248
|
+
} else if (this.takesReceiver(declaration)) {
|
|
249
|
+
this.#receiverMembers.set(declaration.name, [...this.getReceiverMembers(declaration.name), declaration]);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return this;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** The members named so that receivers answer, across every module. */
|
|
257
|
+
getReceiverMembers(name) {
|
|
258
|
+
return this.#receiverMembers.get(name) ?? [];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The members named so that the module's namespace reaches: its module members, and those its own types declare within them, each in the order it declares them; an extension's members, which extension access
|
|
263
|
+
* selects, and an operator, which its syntax alone invokes, are none of them.
|
|
264
|
+
*/
|
|
265
|
+
getMembersDeclaredBy(moduleName, name) {
|
|
266
|
+
const module = this.#modules.get(moduleName);
|
|
267
|
+
const ownTypes = module?.types.filter(type => type.kind !== 'extension') ?? [];
|
|
268
|
+
|
|
269
|
+
// An operator is reached through its syntax alone, never through a namespace.
|
|
270
|
+
return module === undefined ? [] : [...module.members, ...ownTypes.flatMap(type => type.members ?? [])].filter(declaration => declaration.name === name && declaration.kind !== 'operator');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** The type, Nexo's own value type or the extension the member is declared within, which gives its receiver and decides whether its module owns it; undefined for a module member. */
|
|
274
|
+
getContainingType(declaration) {
|
|
275
|
+
return this.#containingTypes.get(declaration);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The signatures of the indexer the declaration is one of: the indexers its type or its extension declares of its name, or unnamed where it is, in the order declared, each taking a key of its own type; the
|
|
280
|
+
* declaration alone where it is no indexer within one.
|
|
281
|
+
*/
|
|
282
|
+
getIndexerSignatures(declaration) {
|
|
283
|
+
const containing = this.#containingTypes.get(declaration);
|
|
284
|
+
|
|
285
|
+
if (declaration.kind !== 'indexer' || containing === undefined) {
|
|
286
|
+
return [declaration];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return containing.members.filter(member => member.kind === 'indexer' && member.name === declaration.name);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Whether the member takes a receiver: one declared within a type or an extension, whose receiver the type or the extension's target is; a module member takes none. */
|
|
293
|
+
takesReceiver(declaration) {
|
|
294
|
+
return this.#containingTypes.has(declaration);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* How the type the member is read on is written, as a message names it: its type's local name for a member declared within it, whose receiver is that type, written with its type parameters for Nexo's generic
|
|
299
|
+
* type, and its extension's target for one declared within an extension; none for a module member.
|
|
300
|
+
*/
|
|
301
|
+
#receiverNotationsOf(declaration) {
|
|
302
|
+
const type = this.#containingTypes.get(declaration);
|
|
303
|
+
|
|
304
|
+
if (type?.kind === 'extension') {
|
|
305
|
+
return [type.target];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (type !== undefined) {
|
|
309
|
+
return [type.kind === 'generic' ? `${type.name}<${type.typeParameters.join(', ')}>` : type.name];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return [];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** The names of every receiver member declared, across every module; a module member, read through its namespace, is none of them. */
|
|
316
|
+
get receiverMemberNames() {
|
|
317
|
+
return [...this.#receiverMembers.keys()];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** The module registered under the name, or undefined. */
|
|
321
|
+
getModule(name) {
|
|
322
|
+
return this.#modules.get(name);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* The type parameters a declaration's notations read as parameters: an extension's own, and an extension member's with its own; a member's own, with those of the generic type it is declared within, as a list's
|
|
327
|
+
* member reads its `T`; and none for any other declaration, whose names are types'.
|
|
328
|
+
*/
|
|
329
|
+
static #typeParameterScopeOf(declaration, type) {
|
|
330
|
+
if (declaration === type) {
|
|
331
|
+
return new Set(declaration.typeParameters ?? []);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// A reference or a structured type declares none, so the type a member is declared within adds its parameters where it declares any.
|
|
335
|
+
return new Set([...(type?.typeParameters ?? []), ...(declaration.typeParameters ?? [])]);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** The unnamed indexers, each declared without a name for its receivers, across every module. */
|
|
339
|
+
getUnnamedIndexers() {
|
|
340
|
+
return this.#unnamedIndexers;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Whether the member is an unnamed indexer: an indexer declared without a name, which brackets after its receiver invoke. */
|
|
344
|
+
static #isUnnamedIndexer(declaration) {
|
|
345
|
+
return declaration.kind === 'indexer' && declaration.name === undefined;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** How a location names the declaration: an unnamed indexer, which is given no name, as its access is written, and any other by its name. */
|
|
349
|
+
static #locatedNameOf(declaration) {
|
|
350
|
+
// A mapping is named by its key, which no other mapping of its selector holds.
|
|
351
|
+
if (declaration.name === undefined && typeof declaration.key === 'string') {
|
|
352
|
+
return declaration.key;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return NexoRegistry.#isUnnamedIndexer(declaration) ? '[]' : declaration.name;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** The declaration key holding the notation, as a declaration writes it: its result, a parameter's type, its supertype, its target or a mapping's type; none where it holds the notation nowhere it can be told. */
|
|
359
|
+
static #declarationKeyOf(declaration, notation) {
|
|
360
|
+
if (declaration.result === notation) {
|
|
361
|
+
return 'result';
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if ((declaration.parameters ?? []).some(parameter => parameter.type === notation || (parameter.parameterTypes ?? []).includes(notation))) {
|
|
365
|
+
return 'parameters';
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return ['supertype', 'target', 'type'].find(key => declaration[key] === notation);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* The type the notation writes in the declaration, a member, one of its parameters' notations among them, a type, an extension or a mapping: each name resolved to its identity in the module that declares it. It is
|
|
373
|
+
* resolved once and kept by the declaration for good, since a registered module is never removed and its declarations are frozen, and it is released with the declaration; a notation that names no type the
|
|
374
|
+
* declaration's module can reach is refused at that module and the declaration's name, and kept by none. It calls no host code.
|
|
375
|
+
*/
|
|
376
|
+
getDeclaredType(declaration, notation) {
|
|
377
|
+
let types = this.#declaredTypeCache.get(declaration);
|
|
378
|
+
|
|
379
|
+
if (types === undefined) {
|
|
380
|
+
types = new Map();
|
|
381
|
+
this.#declaredTypeCache.set(declaration, types);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (!types.has(notation)) {
|
|
385
|
+
const typeName = this.#containingTypes.get(declaration)?.name;
|
|
386
|
+
const declarationName = NexoRegistry.#locatedNameOf(declaration);
|
|
387
|
+
const declarationKey = NexoRegistry.#declarationKeyOf(declaration, notation);
|
|
388
|
+
const fail = (code, message) => {
|
|
389
|
+
throw NexoErrors.module(code, message, { module: this.getDeclaringModuleName(declaration), ...(typeName === undefined ? {} : { type: typeName }), declaration: declarationName, ...(declarationKey === undefined ? {} : { declarationKey }) });
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
types.set(notation, this.#resolveNotation(notation, this.#declaringModules.get(declaration), fail));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return types.get(notation);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* The type the member is read on, as a list of it, none for a module member: the type it is declared within, Nexo's generic type written with its own type parameters, as `List<T>`, or its extension's target, a
|
|
400
|
+
* type parameter where the extension extends every type.
|
|
401
|
+
*/
|
|
402
|
+
getReceiverTypes(declaration) {
|
|
403
|
+
const type = this.#containingTypes.get(declaration);
|
|
404
|
+
|
|
405
|
+
if (type === undefined) {
|
|
406
|
+
return NO_RECEIVER_TYPES;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
let receiverTypes = this.#receiverTypes.get(declaration);
|
|
410
|
+
|
|
411
|
+
if (receiverTypes === undefined) {
|
|
412
|
+
receiverTypes = Object.freeze([this.#receiverTypeOf(declaration, type)]);
|
|
413
|
+
this.#receiverTypes.set(declaration, receiverTypes);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return receiverTypes;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** The type the member is read on within the type or the extension that contains it. */
|
|
420
|
+
#receiverTypeOf(declaration, type) {
|
|
421
|
+
// An extension's member is read on its extension's target, which the extension's own context resolves.
|
|
422
|
+
if (type.kind === 'extension') {
|
|
423
|
+
return this.getDeclaredType(type, type.target);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Nexo's own value types are named without a module, as a notation writes them; a member one declares as its own for a narrower receiver, as `List`'s `flatten` is for a list of lists, is read on that receiver.
|
|
427
|
+
if (type.kind === 'generic') {
|
|
428
|
+
const narrowed = type.receivers?.get(declaration);
|
|
429
|
+
|
|
430
|
+
return narrowed === undefined ? new Type('named', { name: type.name, typeArguments: type.typeParameters.map(name => Type.createTypeParameter(name)), isNullable: false }) : this.getDeclaredType(declaration, narrowed);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (type.kind === 'scalar') {
|
|
434
|
+
return Type.createNamedType(type.name);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return Type.createNamedType(type.name, false, this.getDeclaringModuleName(declaration));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** Whether the declaration is internal, available within its declaring module alone: a type or a member that says so, and a member within an internal type, whatever its own flag says, since its type bounds it. */
|
|
441
|
+
isInternal(declaration) {
|
|
442
|
+
return declaration.internal === true || this.#containingTypes.get(declaration)?.internal === true;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** Whether the member is a property of a structured type's data shape: a property the structured type declares directly without updates, which Nexo reads from the data and never invokes. */
|
|
446
|
+
isDataProperty(declaration) {
|
|
447
|
+
return declaration.kind === 'property' && declaration.updates === undefined && this.#containingTypes.get(declaration)?.kind === 'structured';
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* The declaration of the value type the named type is, nullable or not, whatever its type arguments, its kind an attribute: a type a module declares, registered or being registered, `reference` or `structured`, and
|
|
452
|
+
* one of Nexo's own, `scalar` or `generic`, its type parameters under `typeParameters`; undefined for any other type, an extension among them, which no value is of.
|
|
453
|
+
*/
|
|
454
|
+
getTypeDeclaration(type) {
|
|
455
|
+
if (type.kind !== 'named') {
|
|
456
|
+
return undefined;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (type.moduleName === null) {
|
|
460
|
+
return Vocabulary.getTypeDeclaration(type.name);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return this.#types.get(type.identity) ?? this.#pending?.types.get(type.identity);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** Whether the type is a reference type a module declares, nullable or not. */
|
|
467
|
+
isReferenceType(type) {
|
|
468
|
+
return this.getTypeDeclaration(type)?.kind === 'reference';
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Whether a dictionary's keys may be of the type, the types Nexo's key equality compares exactly: a scalar, by its value, a reference type, by its identity, and null, nullable or not, and a type parameter, which a
|
|
473
|
+
* dictionary binds to its own key type, and a never, an empty dictionary's; a list, an object, a structured value, a dictionary, a pair and a value of Any never are.
|
|
474
|
+
*/
|
|
475
|
+
isKeyType(type) {
|
|
476
|
+
switch (type.kind) {
|
|
477
|
+
case 'null':
|
|
478
|
+
case 'never':
|
|
479
|
+
case 'typeParameter':
|
|
480
|
+
return true;
|
|
481
|
+
case 'named':
|
|
482
|
+
return (type.moduleName === null && NexoModule.scalarTypeNames.includes(type.name)) || this.isReferenceType(type);
|
|
483
|
+
default:
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** The supertype the reference type names, made nullable where the type is, or null where it names none or is no reference type. */
|
|
489
|
+
getSupertype(type) {
|
|
490
|
+
const declared = this.getTypeDeclaration(type);
|
|
491
|
+
|
|
492
|
+
if (declared?.kind !== 'reference' || declared.supertype === undefined) {
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const supertype = this.getDeclaredType(declared, declared.supertype);
|
|
497
|
+
|
|
498
|
+
return type.isNullable ? supertype.toNullable() : supertype;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Whether the value belongs to the reference type: where the `isInstance` of the type and of every supertype above it answers true, each asked in turn until one answers false, on every call, no answer kept.
|
|
503
|
+
* An exception it throws (NEXO5027) and an answer that isn't a Boolean (NEXO5017) are faults of the module that declares the type, naming what asked.
|
|
504
|
+
* @param {unknown} value
|
|
505
|
+
* @param {Type} type
|
|
506
|
+
* @param {string} asked What asked whether the value belongs, as a message names it.
|
|
507
|
+
*/
|
|
508
|
+
belongsTo(value, type, asked) {
|
|
509
|
+
for (let current = type.toNonNullable(); current !== null; current = this.getSupertype(current)) {
|
|
510
|
+
const declared = this.getTypeDeclaration(current);
|
|
511
|
+
const location = { module: this.getDeclaringModuleName(declared), declaration: declared.name };
|
|
512
|
+
let answer;
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
answer = declared.isInstance(value);
|
|
516
|
+
} catch (error) {
|
|
517
|
+
throw NexoErrors.evaluation('NEXO5027', `The isInstance of ${current} threw as ${asked} asked whether a value belongs to it`, location, { cause: error });
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (typeof answer !== 'boolean') {
|
|
521
|
+
throw NexoErrors.evaluation('NEXO5017', `The isInstance of ${current} answered what isn't a Boolean as ${asked} asked whether a value belongs to it`, location);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (!answer) {
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* The reference types the registry's modules recognize the value as, each module's `typeOf` naming the most specific of its own reference types the value is of, or null; none for a value that isn't an object, which is data.
|
|
534
|
+
* It asks the `typeOf` of every module declaring one, in the order they were registered, and `belongsTo` of each type named, on every call, no answer kept. A name that is no reference type of its module, an abstract one,
|
|
535
|
+
* and one the value doesn't belong to (NEXO5002), and an exception `typeOf` throws (NEXO5026), are faults of the module, naming what asked.
|
|
536
|
+
* @param {unknown} value
|
|
537
|
+
* @param {string} asked What asked for the value's type, as a message names it.
|
|
538
|
+
* @returns {Type[]}
|
|
539
|
+
*/
|
|
540
|
+
recognize(value, asked) {
|
|
541
|
+
if (value === null || typeof value !== 'object') {
|
|
542
|
+
return [];
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const recognized = [];
|
|
546
|
+
|
|
547
|
+
for (const module of this.#modules.values()) {
|
|
548
|
+
if (module.typeOf === undefined) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
let name;
|
|
553
|
+
|
|
554
|
+
try {
|
|
555
|
+
name = module.typeOf(value);
|
|
556
|
+
} catch (error) {
|
|
557
|
+
throw NexoErrors.evaluation('NEXO5026', `The typeOf of the module '${module.name}' threw as ${asked} asked for a value's type`, { module: module.name }, { cause: error });
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (name === null) {
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// A typeOf answers a type's name or null, so one answering nothing, as one that never returns does, names no type, which recognizing nothing would hide.
|
|
565
|
+
if (name === undefined) {
|
|
566
|
+
throw NexoErrors.evaluation('NEXO5002', `The typeOf of the module '${module.name}' answered nothing, neither a type's name nor null, as ${asked} asked for a value's type`, { module: module.name });
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const type = typeof name === 'string' ? Type.createNamedType(name, false, module.name) : null;
|
|
570
|
+
const declaration = type === null ? undefined : this.getTypeDeclaration(type);
|
|
571
|
+
const declared = declaration?.kind === 'reference' ? declaration : undefined;
|
|
572
|
+
|
|
573
|
+
if (declared === undefined || declared.abstract === true || declared.internal === true || !this.belongsTo(value, type, asked)) {
|
|
574
|
+
const wrong = declared === undefined ? `${JSON.stringify(name) ?? String(name)}, which is no reference type of its own` : declared.abstract === true ? `the abstract type ${name}` : declared.internal === true ? `the internal type ${name}, which no script names` : `${name}, which the value doesn't belong to`;
|
|
575
|
+
|
|
576
|
+
throw NexoErrors.evaluation('NEXO5002', `The typeOf of the module '${module.name}' named ${wrong}, as ${asked} asked for a value's type`, { module: module.name });
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
recognized.push(type);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return recognized;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* The types a type name names in its scope, a declaration's, a declared binding's or a script's, the one path each reads, its own refusals its caller's. A qualified name, `Q.Name`, names the type of its local name
|
|
587
|
+
* the module `Q` names declares, `Q` the local name an import of the scope gives a module or, in a scope naming modules by their own names, a registered module's, Nexo's own among them, whose types `Nexo.Number`,
|
|
588
|
+
* `Nexo.Any` and `Nexo.Never` name, and its answer holds that module, undefined where `Q` names none. An unqualified name names the types of the first of the scope's tiers that holds any: a type import's of that
|
|
589
|
+
* local name, then the declaring module's own type, then Nexo's own, then those the modules the scope imports declare, in the order they were registered; in a scope reaching every module, Nexo's own and every
|
|
590
|
+
* module's alike. Internal types are among them, and an extension, which no type name names, is none.
|
|
591
|
+
* @param {string} name
|
|
592
|
+
* @param {import('./TypeNameScope.mjs').TypeNameScope} scope
|
|
593
|
+
* @returns {{ moduleName?: string, types: Type[] }}
|
|
594
|
+
*/
|
|
595
|
+
resolveTypeName(name, scope) {
|
|
596
|
+
const dot = name.indexOf('.');
|
|
597
|
+
|
|
598
|
+
if (dot !== -1) {
|
|
599
|
+
const localName = name.slice(dot + 1);
|
|
600
|
+
const moduleName = this.#moduleQualifiedBy(name.slice(0, dot), scope);
|
|
601
|
+
|
|
602
|
+
if (moduleName === Names.nexoModuleName) {
|
|
603
|
+
return { moduleName, types: NexoRegistry.#ownTypesNamed(localName) };
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
return { moduleName, types: moduleName !== undefined && this.#declaresType(moduleName, localName) ? [Type.createNamedType(localName, false, moduleName)] : [] };
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return { types: this.#typesNamedIn(name, scope) };
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** The module a type name's qualifier names in the scope: the one an import of it gives the qualifier as its local name, else, in a scope naming modules by their own names, the registered module of that name. */
|
|
613
|
+
#moduleQualifiedBy(qualifier, scope) {
|
|
614
|
+
const imported = scope.imports.find(entry => Imports.isModuleImport(entry) && Imports.localNameOf(entry) === qualifier);
|
|
615
|
+
|
|
616
|
+
if (imported !== undefined) {
|
|
617
|
+
return imported.module;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return scope.namesModulesByOwnName && (this.#modules.has(qualifier) || this.#pending?.module.name === qualifier) ? qualifier : undefined;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/** The one of Nexo's own types of the local name, as its module's name qualifies it: a value type, or `Any` or `Never`, the reserved types; none for any other name. */
|
|
624
|
+
static #ownTypesNamed(localName) {
|
|
625
|
+
if (localName === 'Any' || localName === 'Never') {
|
|
626
|
+
return [localName === 'Any' ? Type.any : Type.never];
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return Vocabulary.builtInTypeNames.includes(localName) ? [Type.createNamedType(localName)] : [];
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/** The types an unqualified name names in the scope, its first tier that holds any. */
|
|
633
|
+
#typesNamedIn(name, scope) {
|
|
634
|
+
const own = Vocabulary.builtInTypeNames.includes(name) ? [Type.createNamedType(name)] : [];
|
|
635
|
+
|
|
636
|
+
if (scope.reachesEveryModule) {
|
|
637
|
+
return [...own, ...this.getModuleNamesDeclaringType(name).map(moduleName => Type.createNamedType(name, false, moduleName))];
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const typeImports = scope.imports.filter(entry => !Imports.isModuleImport(entry) && Imports.localNameOf(entry) === name && this.getModuleType(entry.module, entry.type)?.kind !== 'extension');
|
|
641
|
+
|
|
642
|
+
if (typeImports.length > 0) {
|
|
643
|
+
return typeImports.map(entry => Type.createNamedType(entry.type, false, entry.module));
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (scope.declaringModule !== null && this.#declaresType(scope.declaringModule, name)) {
|
|
647
|
+
return [Type.createNamedType(name, false, scope.declaringModule)];
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (own.length > 0) {
|
|
651
|
+
return own;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const imported = new Set(scope.imports.filter(entry => Imports.isModuleImport(entry)).map(entry => entry.module));
|
|
655
|
+
|
|
656
|
+
return this.getModuleNamesDeclaringType(name).filter(moduleName => imported.has(moduleName)).map(moduleName => Type.createNamedType(name, false, moduleName));
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Returns the structured type's data properties and their types, in declaration order, or null if the supplied type does not name a structured type; a structured type of no data properties answers an empty map.
|
|
661
|
+
* The map is kept in the data-shape cache.
|
|
662
|
+
* @returns {ReadonlyMap<string, Type> | null}
|
|
663
|
+
*/
|
|
664
|
+
getDataShape(type) {
|
|
665
|
+
const structure = this.getTypeDeclaration(type);
|
|
666
|
+
|
|
667
|
+
if (structure?.kind !== 'structured') {
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
let shape = this.#dataShapeCache.get(structure);
|
|
672
|
+
|
|
673
|
+
if (shape === undefined) {
|
|
674
|
+
shape = new Map(NexoRegistry.#dataPropertiesOf(structure).map(property => [property.name, this.getDeclaredType(property, property.result)]));
|
|
675
|
+
this.#dataShapeCache.set(structure, shape);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
return shape;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/** The types of the data properties of the name, across every structured type, as each declares it. */
|
|
682
|
+
getDataPropertyTypes(propertyName) {
|
|
683
|
+
return [...this.#types.values()]
|
|
684
|
+
.filter(type => type.kind === 'structured')
|
|
685
|
+
.flatMap(type => NexoRegistry.#dataPropertiesOf(type).filter(property => property.name === propertyName).map(property => this.getDeclaredType(property, property.result)));
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/** The properties the structured type declares directly without updates, its data shape, in the order it declares them. */
|
|
689
|
+
static #dataPropertiesOf(structure) {
|
|
690
|
+
return (structure.members ?? []).filter(member => member.kind === 'property' && member.updates === undefined);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/** The type or the extension the registered module declares under the local name, as its `types` hold it, or undefined where it declares none. */
|
|
694
|
+
getModuleType(moduleName, name) {
|
|
695
|
+
return this.#types.get(`${moduleName}.${name}`) ?? this.#extensions.get(`${moduleName}.${name}`);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/** The names of the registered modules that declare a type of the local name, in the order they were registered. */
|
|
699
|
+
getModuleNamesDeclaringType(name) {
|
|
700
|
+
return [...this.#modules.keys()].filter(moduleName => this.#types.has(`${moduleName}.${name}`));
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/** The type selector declared so, or undefined. */
|
|
704
|
+
getTypeSelector(name) {
|
|
705
|
+
return this.#typeSelectors.get(name);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/** The mapping of the key in the selector, or undefined where the selector maps none. */
|
|
709
|
+
getMapping(selector, key) {
|
|
710
|
+
return this.#mappings.get(selector)?.get(key);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/** The type of observation declared so, or undefined. */
|
|
714
|
+
getObservationType(name) {
|
|
715
|
+
return this.#observationTypes.get(name);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/** The operators of the name, `cast` or `convert`, across every module, in the order registered, each within the type or the extension whose receiver it is invoked on. */
|
|
719
|
+
getOperators(name) {
|
|
720
|
+
return this.#operators.get(name) ?? [];
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/** The type an operator answers, its destination, which selects it beside its receiver's type. */
|
|
724
|
+
getOperatorDestination(declaration) {
|
|
725
|
+
return this.getDeclaredType(declaration, declaration.result);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/** How a message names an operator: its receiver's type and its destination, by the syntax that invokes it, as `String to Number` and `Meter as Device`. */
|
|
729
|
+
formatOperator(declaration) {
|
|
730
|
+
return `${this.getReceiverTypes(declaration)[0]} ${declaration.name === 'cast' ? 'as' : 'to'} ${declaration.result}`;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/** Whether the type is one a conversion converts between: a type of data Nexo defines, or a structured type that holds data alone, never null. */
|
|
734
|
+
isConvertible(type) {
|
|
735
|
+
return type.kind === 'named' && type.typeArguments.length === 0 && !type.isNullable && this.isData(type);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/** The name of the module that declares the declaration: a member, an operator among them, a type, an extension, a type selector, a mapping or an observation type. */
|
|
739
|
+
getDeclaringModuleName(declaration) {
|
|
740
|
+
return this.#declaringModules.get(declaration)?.moduleName;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/** The implementation that carries the declaration out, its `impl`, or undefined where its module gives it none. */
|
|
744
|
+
/** The implementation a declaration the registry holds carries out, undefined where its module gives none, and for anything else, as `getDeclaringModuleName` answers undefined for it. */
|
|
745
|
+
getImplementation(declaration) {
|
|
746
|
+
return this.getDeclaringModuleName(declaration) === undefined ? undefined : declaration.impl;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/** Whether the module, registered or being registered, declares a value type of the local name. */
|
|
750
|
+
#declaresType(moduleName, name) {
|
|
751
|
+
return this.getTypeDeclaration(Type.createNamedType(name, false, moduleName)) !== undefined;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* The type the notation writes in its context, each name resolved to its identity: a qualified name to the type its module declares; an unqualified one to a type of the declaring module, one of Nexo's own types,
|
|
756
|
+
* or the one type of its name among the modules a structured type imports, refusing one that several imported modules declare and one outside them, which is named qualified.
|
|
757
|
+
*/
|
|
758
|
+
#resolveNotation(notation, context, fail) {
|
|
759
|
+
// A name the declaration's scope declares is that parameter, and any other names a type, whatever its length.
|
|
760
|
+
const scope = context.typeParameters;
|
|
761
|
+
const resolve = type => {
|
|
762
|
+
if (type.kind === 'typeParameter' && type.name !== '@selected' && !scope.has(type.name)) {
|
|
763
|
+
return this.#typeOfName(type, notation, context, fail);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// A qualified name is a type's, whatever its local name, never a parameter's.
|
|
767
|
+
if (scope.has(type.name) && type.kind === 'named' && type.moduleName === null && type.typeArguments.length === 0) {
|
|
768
|
+
return Type.createTypeParameter(type.name, type.isNullable);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
switch (type.kind) {
|
|
772
|
+
case 'object':
|
|
773
|
+
return Type.createObjectType(new Map([...type.fieldTypes].map(([name, fieldType]) => [name, resolve(fieldType)])), type.isNullable);
|
|
774
|
+
// A generic value type is Nexo's own, and its type arguments are resolved in turn, a dictionary's key one of a key type.
|
|
775
|
+
case 'named': {
|
|
776
|
+
if (type.typeArguments.length === 0) {
|
|
777
|
+
return this.#typeOfName(type, notation, context, fail);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const typeArguments = type.typeArguments.map(resolve);
|
|
781
|
+
|
|
782
|
+
if (type.isDictionary && !this.isKeyType(typeArguments[0])) {
|
|
783
|
+
fail('NEXO4563', `'${notation}' names a dictionary whose keys are ${typeArguments[0]}, and a key is a number, a string, a Boolean, a reference or null, which Nexo's key equality compares exactly`);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
return new Type('named', { name: type.name, typeArguments, isNullable: type.isNullable });
|
|
787
|
+
}
|
|
788
|
+
// The declaring module's own type of the name comes before Nexo's own, `Any` and `Never` among them.
|
|
789
|
+
case 'any':
|
|
790
|
+
case 'never': {
|
|
791
|
+
const name = type.kind === 'any' ? 'Any' : 'Never';
|
|
792
|
+
|
|
793
|
+
return this.#declaresType(context.moduleName, name) ? Type.createNamedType(name, type.isNullable, context.moduleName) : type;
|
|
794
|
+
}
|
|
795
|
+
default:
|
|
796
|
+
return type;
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
return resolve(Type.parse(notation));
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* The type a binding's declared notation writes, each name in it read as a declared binding's is, in the binding scope, every registered module's public types alike: a name no type is (NEXO2036), one several types
|
|
805
|
+
* share unqualified (NEXO2037), a notation that is none, and a dictionary keyed by no key type (NEXO2044), are given to `fail`, which throws, with the code and the message. It reads no script's imports.
|
|
806
|
+
* @param {string} notation
|
|
807
|
+
* @param {(code: string, message: string) => never} fail
|
|
808
|
+
*/
|
|
809
|
+
resolveBindingType(notation, fail) {
|
|
810
|
+
// A notation that writes no type parses to none, and what is no string writes none.
|
|
811
|
+
const parsed = typeof notation === 'string' ? Type.parse(notation) : null;
|
|
812
|
+
|
|
813
|
+
if (parsed === null) {
|
|
814
|
+
fail('NEXO2044', `${JSON.stringify(notation)} is no type's notation`);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const resolve = type => {
|
|
818
|
+
switch (type.kind) {
|
|
819
|
+
case 'object':
|
|
820
|
+
return Type.createObjectType(new Map([...type.fieldTypes].map(([name, fieldType]) => [name, resolve(fieldType)])), type.isNullable);
|
|
821
|
+
case 'named':
|
|
822
|
+
case 'typeParameter': {
|
|
823
|
+
if (type.kind === 'named' && type.typeArguments.length > 0) {
|
|
824
|
+
const typeArguments = type.typeArguments.map(resolve);
|
|
825
|
+
|
|
826
|
+
if (type.isDictionary && !this.isKeyType(typeArguments[0])) {
|
|
827
|
+
fail('NEXO2044', `${notation} names a dictionary whose keys are ${typeArguments[0]}, and a key is a number, a string, a Boolean, a reference or null`);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return new Type('named', { name: type.name, typeArguments, isNullable: type.isNullable });
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// A binding declares a public type alone, since no script reaches an internal one, so an internal type of the name is no candidate.
|
|
834
|
+
const types = this.resolveTypeName(type.moduleName === null ? type.name : `${type.moduleName}.${type.name}`, TypeNameScope.bindingScope).types
|
|
835
|
+
.filter(candidate => candidate.moduleName === null || this.getModuleType(candidate.moduleName, candidate.name)?.internal !== true);
|
|
836
|
+
|
|
837
|
+
if (types.length === 0) {
|
|
838
|
+
fail('NEXO2036', `${notation} names ${type.moduleName === null ? type.name : `${type.moduleName}.${type.name}`}, which is no public type`);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (types.length > 1) {
|
|
842
|
+
fail('NEXO2037', `${notation} names ${type.name}, which is several types' name, so each is written as it alone is named: ${types.map(found => NexoRegistry.#writtenAloneOf(found)).join(' or ')}`);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
this.#refuseBareGeneric(types[0], notation, fail, 'NEXO2044');
|
|
846
|
+
|
|
847
|
+
return type.isNullable ? types[0].toNullable() : types[0];
|
|
848
|
+
}
|
|
849
|
+
default:
|
|
850
|
+
return type;
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
return resolve(parsed);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/** Whether the type is one of Nexo's own generic types written without its type arguments, as `Pair` alone. */
|
|
858
|
+
static #isBareGeneric(type) {
|
|
859
|
+
return type.moduleName === null && type.typeArguments.length === 0 && Object.hasOwn(Type.genericTypeArities, type.name);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* The type as a diagnostic writes it to name it alone: by its qualified name, or, one of Nexo's own generic types, which its qualified name alone writes none of, by its name and its type parameters, `List<T>`,
|
|
864
|
+
* since written with type arguments it is always Nexo's.
|
|
865
|
+
*/
|
|
866
|
+
static #writtenAloneOf(type) {
|
|
867
|
+
if (!NexoRegistry.#isBareGeneric(type)) {
|
|
868
|
+
return type.toString(true);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
return `${type.name}<${Type.genericTypeArities[type.name] === 1 ? 'T' : 'K, V'}>`;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Refuses a name that resolved to one of Nexo's own generic types written without its type arguments, as `Pair` alone: a generic type is written with them, `Pair<K, V>`, and its bare name names a type only where
|
|
876
|
+
* a nearer tier declares one of that name, as a module's own `Pair` does in its declarations; another module's is named qualified.
|
|
877
|
+
*/
|
|
878
|
+
#refuseBareGeneric(type, notation, fail, code) {
|
|
879
|
+
if (NexoRegistry.#isBareGeneric(type)) {
|
|
880
|
+
const foreign = this.getModuleNamesDeclaringType(type.name).map(moduleName => `${moduleName}.${type.name}`);
|
|
881
|
+
|
|
882
|
+
fail(code, `'${notation}' names Nexo's generic type ${NexoRegistry.#writtenAloneOf(type)}, which is written with its type arguments${foreign.length === 0 ? '' : `, and another module's ${type.name} qualified: ${foreign.join(' or ')}`}`);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* The type a name without type arguments names, nullable where it is written so, as its declaration's scope resolves it: a qualified one the type its module declares, Nexo's own as `Nexo.Number`, and an
|
|
888
|
+
* unqualified one the type of its scope's nearest tier.
|
|
889
|
+
*/
|
|
890
|
+
#typeOfName(type, notation, context, fail) {
|
|
891
|
+
const { name } = type;
|
|
892
|
+
const { moduleName, types } = this.resolveTypeName(type.moduleName === null ? name : `${type.moduleName}.${name}`, TypeNameScope.createDeclarationScope(context.moduleName, context.imports));
|
|
893
|
+
const written = found => (type.isNullable ? found.toNullable() : found);
|
|
894
|
+
|
|
895
|
+
if (type.moduleName !== null) {
|
|
896
|
+
if (moduleName === undefined) {
|
|
897
|
+
fail('NEXO4047', `'${notation}' names the type '${type.identity}', and no module named '${type.moduleName}' is registered`);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (types.length === 0) {
|
|
901
|
+
fail('NEXO4047', `'${notation}' names the type '${type.identity}', which the module '${type.moduleName}' does not declare`);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
this.#refuseBareGeneric(types[0], notation, fail, 'NEXO4070');
|
|
905
|
+
|
|
906
|
+
return written(types[0]);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// Only the imported modules' tier holds several types, Nexo's own and the declaring module's one each.
|
|
910
|
+
if (types.length > 1) {
|
|
911
|
+
fail('NEXO4048', `'${notation}' names the type '${name}', which the modules it imports each declare, so it is named qualified: ${types.map(found => `${found.moduleName}.${name}`).join(' or ')}`);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if (types.length === 1) {
|
|
915
|
+
this.#refuseBareGeneric(types[0], notation, fail, 'NEXO4070');
|
|
916
|
+
|
|
917
|
+
return written(types[0]);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const foreign = this.getModuleNamesDeclaringType(name);
|
|
921
|
+
|
|
922
|
+
if (foreign.length > 0) {
|
|
923
|
+
fail('NEXO4049', `'${notation}' names the type '${name}', which only another module declares, so it is named qualified: ${foreign.map(moduleName => `${moduleName}.${name}`).join(' or ')}`);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
fail('NEXO4047', `'${notation}' names the type '${name}', which neither Nexo nor a module registered with it declares`);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Refuses a type selector named already, by this module or one registered before it; a key of a selector neither declares; a key its selector maps already, which is never replaced; and a parameter that selects a type
|
|
931
|
+
* through a selector neither declares.
|
|
932
|
+
*/
|
|
933
|
+
static #validateTypeSelections(module, members, typeSelectors, mappings, getDeclaringModuleName, fail, failAt) {
|
|
934
|
+
for (const selector of module.typeSelectors) {
|
|
935
|
+
if (typeSelectors.has(selector.name)) {
|
|
936
|
+
fail('NEXO4007', `The type selector '${selector.name}' is declared already, by the module '${getDeclaringModuleName(typeSelectors.get(selector.name))}'`, selector.name, 'name');
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const isDeclared = name => typeSelectors.has(name) || module.typeSelectors.some(selector => selector.name === name);
|
|
941
|
+
|
|
942
|
+
module.mappings.forEach(mapping => {
|
|
943
|
+
if (!isDeclared(mapping.selector)) {
|
|
944
|
+
fail('NEXO4039', `The key '${mapping.key}' belongs to the type selector '${mapping.selector}', which no module registered declares`, mapping.key, 'selector');
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// A key its own module maps twice is refused when the module is created.
|
|
948
|
+
const registered = mappings.get(mapping.selector)?.get(mapping.key);
|
|
949
|
+
|
|
950
|
+
if (registered !== undefined) {
|
|
951
|
+
fail('NEXO4038', `The type selector '${mapping.selector}' maps the key '${mapping.key}' already, as the module '${getDeclaringModuleName(registered)}' declared it, and a key is never replaced`, mapping.key, 'key');
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
for (const declaration of members) {
|
|
956
|
+
for (const parameter of declaration.parameters.filter(candidate => candidate.selects === 'type')) {
|
|
957
|
+
if (!isDeclared(parameter.selector)) {
|
|
958
|
+
failAt(declaration)('NEXO4547', `The parameter '${parameter.name}' selects a type through '${parameter.selector}', which no module registered declares`, declaration.name, 'parameters');
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Resolves each notation in the module's declarations through the declared-type cache, in the order the module declares them, refusing one that names no type the declaration's module can reach. Type resolution
|
|
966
|
+
* precedes resolved-type validation, so a module whose notations name no type reports that before any rule that reads a resolved type, whichever declaration breaks it.
|
|
967
|
+
*/
|
|
968
|
+
#resolveDeclaredTypes(module, members) {
|
|
969
|
+
for (const type of module.types) {
|
|
970
|
+
if (type.supertype !== undefined) {
|
|
971
|
+
this.getDeclaredType(type, type.supertype);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
if (type.kind === 'extension') {
|
|
975
|
+
this.getDeclaredType(type, type.target);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
module.mappings.forEach(mapping => this.getDeclaredType(mapping, mapping.type));
|
|
980
|
+
|
|
981
|
+
// A member's receiver is the type it is declared within, or its extension's target, resolved with the extension; an operator's result is its destination.
|
|
982
|
+
for (const declaration of members) {
|
|
983
|
+
if (declaration.result !== undefined) {
|
|
984
|
+
this.getDeclaredType(declaration, declaration.result);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
for (const parameter of declaration.parameters) {
|
|
988
|
+
[parameter.type, ...(parameter.parameterTypes ?? [])].forEach(notation => this.getDeclaredType(declaration, notation));
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/** Refuses, every notation resolved, a supertype that is no reference type, a cycle of supertypes, a tolerance on a result that holds no number, and a default its parameter's type does not admit. */
|
|
995
|
+
#validateDeclaredTypes(module, members, fail, failAt) {
|
|
996
|
+
for (const type of module.types.filter(declared => declared.kind === 'extension')) {
|
|
997
|
+
const target = this.getDeclaredType(type, type.target);
|
|
998
|
+
const isOwnType = target.kind === 'named' && (target.moduleName === module.name || (target.moduleName === null && module === Vocabulary.module));
|
|
999
|
+
|
|
1000
|
+
// A type its module owns takes its members in its own declaration, so an extension's target is another module's.
|
|
1001
|
+
if (isOwnType) {
|
|
1002
|
+
fail('NEXO4045', `The extension '${type.name}' targets ${target}, a type its own module owns, which takes its members within its own declaration`, type.name, 'target');
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// An extension's indexers are named, read after ':' as its other members are, and brackets after a value alone read its type's owner's unnamed indexer.
|
|
1006
|
+
if (type.members.some(member => member.kind === 'indexer' && member.name === undefined)) {
|
|
1007
|
+
fail('NEXO4564', `The extension '${type.name}' declares an indexer without a name, and an extension's indexers are named, since brackets alone read the receiver's owner's indexer: name it, and read it as 'receiver:name[key]'`, type.name, 'members');
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
for (const type of module.types) {
|
|
1012
|
+
// A supertype is a reference type, one the module declares or one a module registered before it declares.
|
|
1013
|
+
if (type.supertype !== undefined && !this.isReferenceType(this.getDeclaredType(type, type.supertype))) {
|
|
1014
|
+
fail('NEXO4016', `The supertype of '${type.name}', ${type.supertype}, is no reference type`, type.name, 'supertype');
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// A module registered before names none of this module's types, so a hierarchy that cycles does within the module.
|
|
1019
|
+
for (const type of module.types.filter(declared => declared.supertype !== undefined)) {
|
|
1020
|
+
const visited = [type.name];
|
|
1021
|
+
|
|
1022
|
+
for (let current = this.getSupertype(Type.createNamedType(type.name, false, module.name)); current !== null && current.moduleName === module.name; current = this.getSupertype(current)) {
|
|
1023
|
+
if (visited.includes(current.name)) {
|
|
1024
|
+
fail('NEXO4050', `The hierarchy of '${type.name}' is a cycle: ${[...visited, current.name].join(' beneath ')}`, type.name, 'supertype');
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
visited.push(current.name);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
for (const declaration of members) {
|
|
1032
|
+
// A host call that supplies nothing declares no result.
|
|
1033
|
+
const resultType = declaration.result === undefined ? null : this.getDeclaredType(declaration, declaration.result);
|
|
1034
|
+
|
|
1035
|
+
// A tolerance applies to a numeric result, or to the numbers a structured result holds, which only now, every type known, is told apart from a reference type; a member with updates always declares a result.
|
|
1036
|
+
if (declaration.updates?.tolerance !== undefined && !(resultType.kind === 'named' && ((resultType.moduleName === null && resultType.name === 'Number') || this.getTypeDeclaration(resultType)?.kind === 'structured'))) {
|
|
1037
|
+
failAt(declaration)('NEXO4026', 'A tolerance applies to a numeric result, or to the numbers a structured result holds, and this result holds neither', declaration.name, 'updates.tolerance');
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
for (const parameter of declaration.parameters) {
|
|
1041
|
+
const type = this.getDeclaredType(declaration, parameter.type);
|
|
1042
|
+
|
|
1043
|
+
if (Object.hasOwn(parameter, 'default') && !this.#admits(parameter.default, type)) {
|
|
1044
|
+
// A type parameter binds to any type, so a default it writes holds for every call only where every type admits it.
|
|
1045
|
+
const typeParameters = NexoRegistry.#typeParametersOf(type);
|
|
1046
|
+
const whatever = typeParameters.length === 0 ? '' : `, whatever ${typeParameters.join(' and ')} binds to: null where it is nullable, or a list of none of its values`;
|
|
1047
|
+
|
|
1048
|
+
failAt(declaration)('NEXO4548', `The default of '${parameter.name}' is no ${parameter.type}${whatever}`, undefined, 'parameters');
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Refuses a member its module declares already on the same receiver type, an exact duplicate, and a second unnamed indexer of one module for a receiver type; a module member of a name its module declares already is
|
|
1056
|
+
* refused when the module is created, and one beside a member of its name on a receiver registers, as the namespace reaches both. Declarations of different modules never collide here, competing extensions among them:
|
|
1057
|
+
* each registers, and an access is resolved by the owner of its receiver's type and the specificity of its extensions, refused where it can't choose.
|
|
1058
|
+
*/
|
|
1059
|
+
#validateMemberCollisions(members, failAt) {
|
|
1060
|
+
// Two of one module's members on overlapping receivers, as a type and the type beneath it, both register, an access both take refused where it is resolved.
|
|
1061
|
+
members.forEach((declaration, index) => {
|
|
1062
|
+
const earlier = members.slice(0, index).filter(other => other.name === declaration.name && other.kind !== 'operator');
|
|
1063
|
+
const fail = failAt(declaration);
|
|
1064
|
+
|
|
1065
|
+
// An operator collides by its destination too, which its own check reads.
|
|
1066
|
+
if (!this.takesReceiver(declaration) || declaration.kind === 'operator') {
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
if (NexoRegistry.#isUnnamedIndexer(declaration)) {
|
|
1071
|
+
for (const other of members.slice(0, index).filter(member => NexoRegistry.#isUnnamedIndexer(member) && !this.#areDisjointSignatures(member, declaration))) {
|
|
1072
|
+
const shared = this.#getSharedReceiver(declaration, other);
|
|
1073
|
+
|
|
1074
|
+
if (shared !== undefined) {
|
|
1075
|
+
fail('NEXO4068', `An unnamed indexer of ${shared} is declared already, and a second is one of its signatures only within its type, taking a key of a type of its own`, undefined, 'kind');
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
const shared = earlier.filter(other => this.takesReceiver(other) && !this.#areDisjointSignatures(other, declaration)).map(other => this.#getSharedReceiver(other, declaration)).find(notation => notation !== undefined);
|
|
1083
|
+
|
|
1084
|
+
if (shared !== undefined) {
|
|
1085
|
+
fail('NEXO4029', `'${declaration.name}' is declared already for ${shared}${declaration.kind === 'indexer' ? ', and an indexer of its name is one of its signatures only within its type, taking a key of a type of its own' : ''}`, declaration.name, 'name');
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Refuses a declaration that names another module's internal type, as a supertype, an extension's target, a parameter's, a callback's, a result's, an operator's destination or a mapping's type (NEXO4018), and a
|
|
1092
|
+
* public declaration that exposes an internal type of its own module through what it writes of them (NEXO4052): a type's supertype, an extension's target, a member's parameters', callbacks' and result's types, an
|
|
1093
|
+
* operator's destination among them, and a mapping's type, through list items and nullable types. A member within an internal type, and an internal one, is no public declaration; within one module, declarations name each other's internal declarations freely.
|
|
1094
|
+
*/
|
|
1095
|
+
#validateAccessibility(module, members, fail, failAt) {
|
|
1096
|
+
const writtenTypesOf = declaration => {
|
|
1097
|
+
if (module.types.includes(declaration)) {
|
|
1098
|
+
return [...(declaration.supertype === undefined ? [] : [declaration.supertype]), ...(declaration.kind === 'extension' ? [declaration.target] : [])];
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
if (module.mappings.includes(declaration)) {
|
|
1102
|
+
return [declaration.type];
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
return [...(declaration.result === undefined ? [] : [declaration.result]), ...declaration.parameters.flatMap(parameter => [parameter.type, ...(parameter.parameterTypes ?? [])])];
|
|
1106
|
+
};
|
|
1107
|
+
const namedTypesOf = type => {
|
|
1108
|
+
if (type.kind === 'object') {
|
|
1109
|
+
return [...type.fieldTypes.values()].flatMap(namedTypesOf);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
if (type.kind !== 'named') {
|
|
1113
|
+
return [];
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
return [...(type.moduleName === null ? [] : [type]), ...type.typeArguments.flatMap(namedTypesOf)];
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
for (const declaration of [...module.types, ...members, ...module.mappings]) {
|
|
1120
|
+
const failHere = members.includes(declaration) ? failAt(declaration) : fail;
|
|
1121
|
+
const name = module.mappings.includes(declaration) ? declaration.key : declaration.name;
|
|
1122
|
+
|
|
1123
|
+
for (const type of writtenTypesOf(declaration).flatMap(notation => namedTypesOf(this.getDeclaredType(declaration, notation)))) {
|
|
1124
|
+
if (this.getTypeDeclaration(type)?.internal !== true) {
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
if (type.moduleName !== module.name) {
|
|
1129
|
+
failHere('NEXO4018', `It names ${type.identity}, an internal type of the module '${type.moduleName}', which its own module's declarations alone name`, name);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
if (!this.isInternal(declaration)) {
|
|
1133
|
+
failHere('NEXO4052', `It is public, and exposes ${type.name}, an internal type of its module: a public declaration writes public types alone`, name);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* Refuses a type parameter named as a type its declaration reaches unqualified, one of Nexo's own, `Any` and `Never` among them, one of its module's, or one of a module its type imports (NEXO4042); a member whose
|
|
1141
|
+
* result writes a parameter with no supported source to bind it from, its extension's target or its generic type's receiver, its value parameters' types or its callbacks' results (NEXO4011); and a member whose
|
|
1142
|
+
* callback's input writes a parameter whose only source is another callback's result, since callback results do not chain (NEXO4044).
|
|
1143
|
+
*/
|
|
1144
|
+
#validateTypeParameters(module, members, fail, failAt) {
|
|
1145
|
+
const parametersOf = (declaration, notation) => NexoRegistry.#typeParametersWrittenBy(this.getDeclaredType(declaration, notation));
|
|
1146
|
+
const isReachable = (declaration, name) => {
|
|
1147
|
+
const { moduleName, imports } = this.#declaringModules.get(declaration);
|
|
1148
|
+
|
|
1149
|
+
return Vocabulary.ownTypeNames.includes(name) || this.#declaresType(moduleName, name) || imports.some(imported => this.#declaresType(imported, name));
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
for (const declaration of [...module.types.filter(type => type.kind === 'extension'), ...members]) {
|
|
1153
|
+
const reached = (declaration.typeParameters ?? []).find(name => isReachable(declaration, name));
|
|
1154
|
+
|
|
1155
|
+
if (reached !== undefined) {
|
|
1156
|
+
(members.includes(declaration) ? failAt(declaration) : fail)('NEXO4042', `The type parameter '${reached}' is named as a type its declaration reaches unqualified, so a parameter of its name would hide it`, declaration.name, 'name');
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
for (const declaration of members) {
|
|
1161
|
+
// A receiver binds the parameters its extension's target writes, or those of the generic type the member is declared within.
|
|
1162
|
+
const type = this.#containingTypes.get(declaration);
|
|
1163
|
+
const fromTarget = type?.kind === 'extension' ? parametersOf(type, type.target) : new Set(type?.typeParameters ?? []);
|
|
1164
|
+
const fromValues = new Set(declaration.parameters.filter(parameter => parameter.kind === 'value').flatMap(parameter => [...parametersOf(declaration, parameter.type)]));
|
|
1165
|
+
const callbacks = declaration.parameters.filter(parameter => parameter.kind === 'function' || parameter.kind === 'expression');
|
|
1166
|
+
const fromCallbacks = new Set(callbacks.flatMap(parameter => [...parametersOf(declaration, parameter.type)]));
|
|
1167
|
+
const unbound = declaration.result === undefined ? undefined : [...parametersOf(declaration, declaration.result)].find(name => name !== '@selected' && !fromTarget.has(name) && !fromValues.has(name) && !fromCallbacks.has(name));
|
|
1168
|
+
const fail = failAt(declaration);
|
|
1169
|
+
|
|
1170
|
+
if (unbound !== undefined) {
|
|
1171
|
+
fail('NEXO4011', `Its result writes the type parameter '${unbound}', which nothing binds: neither its extension's target, its value parameters' types nor its callbacks' results write it`, declaration.name, 'result');
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
for (const callback of callbacks.filter(parameter => parameter.kind === 'function')) {
|
|
1175
|
+
const chained = callback.parameterTypes.flatMap(notation => [...parametersOf(declaration, notation)]).find(name => !fromTarget.has(name) && !fromValues.has(name));
|
|
1176
|
+
|
|
1177
|
+
if (chained !== undefined) {
|
|
1178
|
+
fail('NEXO4044', `The callback '${callback.name}' is called with the type parameter '${chained}', which only another callback's result binds, and callback results do not chain`, declaration.name, 'parameters');
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
/** The names of the type parameters the type writes, anywhere in it: its own, its type arguments' and its object fields'. */
|
|
1185
|
+
static #typeParametersWrittenBy(type) {
|
|
1186
|
+
if (type.kind === 'typeParameter') {
|
|
1187
|
+
return new Set([type.name]);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
const parts = [...type.typeArguments, ...(type.fieldTypes === null ? [] : type.fieldTypes.values())];
|
|
1191
|
+
|
|
1192
|
+
return new Set(parts.flatMap(part => [...NexoRegistry.#typeParametersWrittenBy(part)]));
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* Refuses an operator its receiver and its destination don't admit: a conversion between types that are not data, as each is named (NEXO4065), from a type to itself (NEXO4066), and between types none of which
|
|
1197
|
+
* its module owns (NEXO4028), Nexo's own module alone converting between the language's types; a cast on or to a type that is no reference type (NEXO4027), and one between types of one hierarchy, which the
|
|
1198
|
+
* hierarchy decides (NEXO4064); and an operator of a name and a destination its module declares already on its receiver type (NEXO4069).
|
|
1199
|
+
*/
|
|
1200
|
+
#validateOperators(module, members, failAt) {
|
|
1201
|
+
const operators = members.filter(declaration => declaration.kind === 'operator');
|
|
1202
|
+
|
|
1203
|
+
operators.forEach((operator, index) => {
|
|
1204
|
+
const fail = failAt(operator);
|
|
1205
|
+
const [source] = this.getReceiverTypes(operator);
|
|
1206
|
+
const destination = this.getOperatorDestination(operator);
|
|
1207
|
+
|
|
1208
|
+
if (operator.name === 'convert') {
|
|
1209
|
+
for (const [type, end] of [[source, 'its receiver'], [destination, 'its destination']]) {
|
|
1210
|
+
if (!this.isConvertible(type)) {
|
|
1211
|
+
fail('NEXO4065', `A conversion operator is from a type of data to another, each named as it is declared, Number, String, Boolean or a structured type holding data alone, and ${end}, ${type}, is none of them`, operator.name, type === source ? 'kind' : 'result');
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
if (source.equals(destination)) {
|
|
1216
|
+
fail('NEXO4066', `A value already of ${source} converts to itself, so no conversion operator is declared from a type to itself`, operator.name, 'result');
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (module !== Vocabulary.module && source.moduleName !== module.name && destination.moduleName !== module.name) {
|
|
1220
|
+
fail('NEXO4028', `A conversion operator converts to or from one of its module's own types, and ${this.formatOperator(operator)} does neither`, operator.name, 'result');
|
|
1221
|
+
}
|
|
1222
|
+
} else {
|
|
1223
|
+
if (!this.isReferenceType(source) || !this.isReferenceType(destination)) {
|
|
1224
|
+
fail('NEXO4027', `A cast operator is declared on a reference type and casts to another, and ${this.formatOperator(operator)} is not`, operator.name, this.isReferenceType(source) ? 'result' : 'kind');
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// A type and its supertypes and the types beneath it are cast through their hierarchy, which needs no operator.
|
|
1228
|
+
if (this.#typeChecker.isAssignable(source, destination) || this.#typeChecker.isAssignable(destination, source)) {
|
|
1229
|
+
fail('NEXO4064', `${this.formatOperator(operator)} is a cast within one hierarchy, which the hierarchy decides, so no cast operator is declared for it`, operator.name, 'result');
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
const isDeclaredEarlier = operators.slice(0, index).some(other => other.name === operator.name && this.getReceiverTypes(other)[0].equals(source) && this.getOperatorDestination(other).equals(destination));
|
|
1234
|
+
|
|
1235
|
+
if (isDeclaredEarlier) {
|
|
1236
|
+
fail('NEXO4069', `The operator ${this.formatOperator(operator)} is declared already on ${source}`, operator.name, 'result');
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* Whether values of the type are data: Number, String, Boolean, null and never; a list or another generic value type of data; an object type of data; and a structured type whose data shape holds data.
|
|
1243
|
+
* It classifies a type, and validates no JavaScript value: whether a value is data of a type is a separate check, as a default's and a supplied value's are. Being data does not make a type a conversion's endpoint,
|
|
1244
|
+
* which module creation and `isConvertible` decide apart.
|
|
1245
|
+
*/
|
|
1246
|
+
isData(type) {
|
|
1247
|
+
return this.#isData(type, new Set(), false);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* Whether the declaration gives a script a value of `Any`, the one way such a value reaches an evaluation: through its result, the arguments a function parameter's callback is called with, and, for a member
|
|
1252
|
+
* whose fixed argument selects its result's type, the type of any key its selector maps; each through lists, dictionaries, pairs, objects and a structure's data shape. A parameter's own type gives the script
|
|
1253
|
+
* nothing, since the script supplies its argument and the member receives it.
|
|
1254
|
+
*/
|
|
1255
|
+
givesAny(declaration) {
|
|
1256
|
+
const notations = [declaration.result, ...(declaration.parameters ?? []).flatMap(parameter => parameter.parameterTypes ?? [])].filter(notation => notation !== undefined);
|
|
1257
|
+
const selector = (declaration.parameters ?? []).find(parameter => parameter.selects === 'type')?.selector;
|
|
1258
|
+
const selected = selector === undefined ? [] : [...(this.#mappings.get(selector)?.values() ?? [])].map(mapping => this.getDeclaredType(mapping, mapping.type));
|
|
1259
|
+
|
|
1260
|
+
return [...notations.map(notation => this.getDeclaredType(declaration, notation)), ...selected].some(type => this.#holdsAny(type, new Set()));
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/**
|
|
1264
|
+
* Whether the type holds a nullable reference type anywhere: itself, a type argument, an object field, or a property of a structure's data shape, a structure already being walked counting as holding none. Capturing
|
|
1265
|
+
* a supplied value recognizes it there alone, telling a reference of another type, which is null, from mistyped data.
|
|
1266
|
+
*/
|
|
1267
|
+
holdsNullableReference(type) {
|
|
1268
|
+
return this.#holdsNullableReference(type, new Set());
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
#holdsNullableReference(type, walking) {
|
|
1272
|
+
switch (type.kind) {
|
|
1273
|
+
case 'object':
|
|
1274
|
+
return [...type.fieldTypes.values()].some(fieldType => this.#holdsNullableReference(fieldType, walking));
|
|
1275
|
+
case 'named': {
|
|
1276
|
+
if (type.typeArguments.length > 0) {
|
|
1277
|
+
return type.typeArguments.some(typeArgument => this.#holdsNullableReference(typeArgument, walking));
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
if (this.isReferenceType(type)) {
|
|
1281
|
+
return type.isNullable;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
const shape = this.getDataShape(type);
|
|
1285
|
+
|
|
1286
|
+
if (shape === null || walking.has(type.identity)) {
|
|
1287
|
+
return false;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
walking.add(type.identity);
|
|
1291
|
+
|
|
1292
|
+
return [...shape.values()].some(propertyType => this.#holdsNullableReference(propertyType, walking));
|
|
1293
|
+
}
|
|
1294
|
+
default:
|
|
1295
|
+
return false;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
/**
|
|
1300
|
+
* Whether a value of the type may hold a reference anywhere: the type is a reference type, `Any` or a type parameter, or a type argument, an object field or a property of a structure's data shape is, a structure
|
|
1301
|
+
* already being walked counting as holding none. Checking a value of a type that holds none asks no type's membership.
|
|
1302
|
+
*/
|
|
1303
|
+
mayHoldReference(type) {
|
|
1304
|
+
return this.#mayHoldReference(type, new Set());
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
#mayHoldReference(type, walking) {
|
|
1308
|
+
switch (type.kind) {
|
|
1309
|
+
case 'any':
|
|
1310
|
+
case 'typeParameter':
|
|
1311
|
+
return true;
|
|
1312
|
+
case 'object':
|
|
1313
|
+
return [...type.fieldTypes.values()].some(fieldType => this.#mayHoldReference(fieldType, walking));
|
|
1314
|
+
case 'named': {
|
|
1315
|
+
if (type.typeArguments.length > 0) {
|
|
1316
|
+
return type.typeArguments.some(typeArgument => this.#mayHoldReference(typeArgument, walking));
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
if (this.isReferenceType(type)) {
|
|
1320
|
+
return true;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
const shape = this.getDataShape(type);
|
|
1324
|
+
|
|
1325
|
+
if (shape === null || walking.has(type.identity)) {
|
|
1326
|
+
return false;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
walking.add(type.identity);
|
|
1330
|
+
|
|
1331
|
+
return [...shape.values()].some(propertyType => this.#mayHoldReference(propertyType, walking));
|
|
1332
|
+
}
|
|
1333
|
+
default:
|
|
1334
|
+
return false;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
/** Whether the type holds `Any` anywhere: itself, a type argument, an object field, or a property of a structure's data shape, a structure already being walked counting as holding none, since its properties are walked where it is first met. */
|
|
1339
|
+
#holdsAny(type, walking) {
|
|
1340
|
+
switch (type.kind) {
|
|
1341
|
+
case 'any':
|
|
1342
|
+
return true;
|
|
1343
|
+
case 'object':
|
|
1344
|
+
return [...type.fieldTypes.values()].some(fieldType => this.#holdsAny(fieldType, walking));
|
|
1345
|
+
case 'named': {
|
|
1346
|
+
if (type.typeArguments.length > 0) {
|
|
1347
|
+
return type.typeArguments.some(typeArgument => this.#holdsAny(typeArgument, walking));
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
const shape = this.getDataShape(type);
|
|
1351
|
+
|
|
1352
|
+
if (shape === null || walking.has(type.identity)) {
|
|
1353
|
+
return false;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
walking.add(type.identity);
|
|
1357
|
+
|
|
1358
|
+
return [...shape.values()].some(propertyType => this.#holdsAny(propertyType, walking));
|
|
1359
|
+
}
|
|
1360
|
+
default:
|
|
1361
|
+
return false;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/** Whether values of the type are data, as `isData` says, or are once each value of `Any` among them is checked as it is evaluated, which a result holding one asks for. */
|
|
1366
|
+
isDataOnceChecked(type) {
|
|
1367
|
+
return this.#isData(type, new Set(), true);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
/**
|
|
1371
|
+
* Whether values of the type are data, as `isData` says, a structure already being checked counting as data where it holds itself, since its other properties are checked where it is first met, and a value of `Any`
|
|
1372
|
+
* where its check is to come.
|
|
1373
|
+
*/
|
|
1374
|
+
#isData(type, checking, isAnyChecked) {
|
|
1375
|
+
switch (type.kind) {
|
|
1376
|
+
case 'null':
|
|
1377
|
+
case 'never':
|
|
1378
|
+
return true;
|
|
1379
|
+
case 'any':
|
|
1380
|
+
return isAnyChecked;
|
|
1381
|
+
case 'object':
|
|
1382
|
+
return [...type.fieldTypes.values()].every(fieldType => this.#isData(fieldType, checking, isAnyChecked));
|
|
1383
|
+
case 'named': {
|
|
1384
|
+
// A generic value type holds data where each of its type arguments does, as a list of data does.
|
|
1385
|
+
if (type.typeArguments.length > 0) {
|
|
1386
|
+
return type.typeArguments.every(typeArgument => this.#isData(typeArgument, checking, isAnyChecked));
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
if ((type.moduleName === null && NexoModule.scalarTypeNames.includes(type.name)) || checking.has(type.identity)) {
|
|
1390
|
+
return true;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
if (this.getTypeDeclaration(type)?.kind !== 'structured') {
|
|
1394
|
+
return false;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
checking.add(type.identity);
|
|
1398
|
+
|
|
1399
|
+
return [...this.getDataShape(type).values()].every(propertyType => this.#isData(propertyType, checking, isAnyChecked));
|
|
1400
|
+
}
|
|
1401
|
+
default:
|
|
1402
|
+
return false;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
/**
|
|
1407
|
+
* Whether the type admits a default: a value of data, its nullability, its items and a structure's fields as declared, and never a node. A number is finite and a list holds a value at every index, at any depth,
|
|
1408
|
+
* as every value an evaluation produces does. A type parameter binds to any type at the call, so it admits a default every type admits alone: null where it is nullable, and no other value, so a list of it admits one
|
|
1409
|
+
* of no items, or of nulls where its items are nullable, whatever the parameter binds to.
|
|
1410
|
+
*/
|
|
1411
|
+
#admits(value, type) {
|
|
1412
|
+
if (value === null) {
|
|
1413
|
+
return type.isNullable;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
switch (type.kind) {
|
|
1417
|
+
case 'typeParameter':
|
|
1418
|
+
return false;
|
|
1419
|
+
// A default of Any is any data.
|
|
1420
|
+
case 'any':
|
|
1421
|
+
return NexoRegistry.#isDataValue(value);
|
|
1422
|
+
case 'named': {
|
|
1423
|
+
if (type.isList) {
|
|
1424
|
+
return Array.isArray(value) && ArrayUtils.findMissingIndex(value) === -1 && value.every(item => this.#admits(item, type.itemType));
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
if (type.moduleName === null && type.name === 'Number') {
|
|
1428
|
+
return Number.isFinite(value);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// The scalars beside a number, which is finite, are admitted by their JavaScript type.
|
|
1432
|
+
if (type.moduleName === null && NexoModule.scalarTypeNames.includes(type.name)) {
|
|
1433
|
+
return typeof value === type.name.toLowerCase();
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
return this.getTypeDeclaration(type)?.kind === 'structured' && ObjectUtils.isNonArrayObject(value) && [...this.getDataShape(type)].every(([property, propertyType]) => this.#admits(value[property], propertyType));
|
|
1437
|
+
}
|
|
1438
|
+
default:
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
/** The type parameters the type writes, at any depth of its type arguments, each once, in the order written. */
|
|
1444
|
+
static #typeParametersOf(type) {
|
|
1445
|
+
const names = type.kind === 'typeParameter' ? [type.name] : type.typeArguments.flatMap(argument => NexoRegistry.#typeParametersOf(argument));
|
|
1446
|
+
|
|
1447
|
+
return [...new Set(names)];
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
/** Whether the value is data as a default holds it: null, a string, a Boolean, a finite number, a list holding a value at every index, or a plain object, each item and each field data in turn, at any depth. */
|
|
1451
|
+
static #isDataValue(value) {
|
|
1452
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') {
|
|
1453
|
+
return true;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
if (typeof value === 'number') {
|
|
1457
|
+
return Number.isFinite(value);
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
if (Array.isArray(value)) {
|
|
1461
|
+
return ArrayUtils.findMissingIndex(value) === -1 && value.every(item => NexoRegistry.#isDataValue(item));
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
return ObjectUtils.isPlainObject(value) && Object.values(value).every(field => NexoRegistry.#isDataValue(field));
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
/**
|
|
1468
|
+
* Refuses an observation of a type no module declares; one of a target its observation type's module declares none of, one of a target found from the receiver for a member without one,
|
|
1469
|
+
* and a maintained one of a target of several; and a member that reads maintained values without a maintained observation, or any other that has one.
|
|
1470
|
+
*/
|
|
1471
|
+
static #validateObservations(declaration, takesReceiver, observationTypes, getDeclaringModule, fail) {
|
|
1472
|
+
const { updates } = declaration;
|
|
1473
|
+
|
|
1474
|
+
if (updates === undefined) {
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
let hasMaintained = false;
|
|
1479
|
+
|
|
1480
|
+
for (const observationDeclaration of updates.observations) {
|
|
1481
|
+
const type = observationTypes.get(observationDeclaration.type);
|
|
1482
|
+
|
|
1483
|
+
if (type === undefined) {
|
|
1484
|
+
fail('NEXO4060', `The observation type '${observationDeclaration.type}' is declared by no module`, declaration.name, 'updates.observations');
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
if (typeof observationDeclaration.of === 'string' && observationDeclaration.of !== 'receiver') {
|
|
1488
|
+
NexoRegistry.#validateObservedTarget(declaration, takesReceiver, observationDeclaration, type, getDeclaringModule(type), fail);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
hasMaintained ||= type.contract === 'maintained';
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
if ((updates.reads === 'maintained') !== hasMaintained) {
|
|
1495
|
+
fail(updates.reads === 'maintained' ? 'NEXO4025' : 'NEXO4062', updates.reads === 'maintained' ? 'It reads maintained values, so it names a maintained observation' : 'It names a maintained observation, so it reads maintained values', declaration.name, 'updates');
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
/** Validates an observation's reference to a target, not the target's own declaration, which creating its module validates: refuses a target its observation type's module does not declare, one found from the receiver for a member without one, and one of several for a maintained observation, which gives its member one sample. */
|
|
1500
|
+
static #validateObservedTarget(declaration, takesReceiver, observationDeclaration, type, declaringModule, fail) {
|
|
1501
|
+
const target = declaringModule.observationTargets.find(candidate => candidate.name === observationDeclaration.of);
|
|
1502
|
+
|
|
1503
|
+
if (target === undefined) {
|
|
1504
|
+
fail('NEXO4024', `The observation '${type.name}' observes '${observationDeclaration.of}', and the module '${declaringModule.name}', which declares '${type.name}', declares no target of that name`, declaration.name, 'updates.observations');
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
if (target.from === 'receiver' && !takesReceiver) {
|
|
1508
|
+
fail('NEXO4057', `The observation '${type.name}' observes '${target.name}', a target found from the receiver, and a module member takes no receiver`, declaration.name, 'updates.observations');
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
if (type.contract === 'maintained' && target.count === 'many') {
|
|
1512
|
+
fail('NEXO4063', `The maintained observation '${type.name}' gives one sample, so it observes one target, never '${target.name}', a target of several`, declaration.name, 'updates.observations');
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/** The receiver type the one member is read on, as it is written, where the other is read on the same type, or undefined where they are read on different ones; a type and the type beneath it are two receiver types. */
|
|
1517
|
+
/**
|
|
1518
|
+
* Whether two indexers are signatures of one indexer: declared within one type or one extension, of one name or both unnamed, and taking keys of disjoint types, neither a type parameter nor `Any` and neither
|
|
1519
|
+
* assignable to the other, null among them where both admit it.
|
|
1520
|
+
*/
|
|
1521
|
+
#areDisjointSignatures(one, other) {
|
|
1522
|
+
if (one.kind !== 'indexer' || other.kind !== 'indexer' || this.#containingTypes.get(one) !== this.#containingTypes.get(other)) {
|
|
1523
|
+
return false;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
const [oneKey, otherKey] = [one, other].map(indexer => this.getDeclaredType(indexer, indexer.parameters[0].type));
|
|
1527
|
+
|
|
1528
|
+
if ([oneKey, otherKey].some(key => key.kind === 'any' || key.kind === 'typeParameter') || (oneKey.isNullable && otherKey.isNullable)) {
|
|
1529
|
+
return false;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
return !this.#typeChecker.fitsEitherWay(oneKey.toNonNullable(), otherKey.toNonNullable());
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
#getSharedReceiver(one, other) {
|
|
1536
|
+
const [mine] = this.getReceiverTypes(one);
|
|
1537
|
+
|
|
1538
|
+
return this.getReceiverTypes(other).some(type => mine.equals(type)) ? this.#receiverNotationsOf(one)[0] : undefined;
|
|
1539
|
+
}
|
|
1540
|
+
}
|