semantifold 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/build/src/backends/effects.js +5 -2
- package/build/src/backends/java.js +33 -14
- package/build/src/backends/javascript.js +83 -18
- package/build/src/backends/php.d.ts.map +1 -1
- package/build/src/backends/php.js +88 -16
- package/build/src/backends/ruby.js +37 -12
- package/build/src/backends/scalars.js +2 -2
- package/build/src/backends/shared.d.ts +2 -1
- package/build/src/backends/shared.d.ts.map +1 -1
- package/build/src/backends/shared.js +299 -42
- package/build/src/backends/typescript.js +83 -18
- package/build/src/backends/writer.d.ts +18 -4
- package/build/src/backends/writer.d.ts.map +1 -1
- package/build/src/backends/writer.js +40 -4
- package/build/src/frontends/java.d.ts +9 -1
- package/build/src/frontends/java.d.ts.map +1 -1
- package/build/src/frontends/java.js +90 -43
- package/build/src/frontends/javascript-typescript.d.ts +3 -1
- package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
- package/build/src/frontends/javascript-typescript.js +133 -52
- package/build/src/frontends/php.d.ts +13 -1
- package/build/src/frontends/php.d.ts.map +1 -1
- package/build/src/frontends/php.js +226 -59
- package/build/src/frontends/ruby.d.ts +4 -1
- package/build/src/frontends/ruby.d.ts.map +1 -1
- package/build/src/frontends/ruby.js +113 -37
- package/build/src/frontends/types.d.ts +9 -2
- package/build/src/frontends/types.d.ts.map +1 -1
- package/build/src/frontends/types.js +26 -9
- package/build/src/language-registry.d.ts.map +1 -1
- package/build/src/language-registry.js +14 -6
- package/build/src/mapping.d.ts.map +1 -1
- package/build/src/mapping.js +4 -2
- package/build/src/semantic/effects.d.ts.map +1 -1
- package/build/src/semantic/effects.js +20 -6
- package/build/src/semantic/lifetime.js +28 -4
- package/build/src/semantic/provenance.d.ts.map +1 -1
- package/build/src/semantic/provenance.js +61 -9
- package/build/src/semantic/types.d.ts +214 -23
- package/build/src/semantic/types.d.ts.map +1 -1
- package/build/src/semantic/types.js +58 -11
- package/build/src/semantic/validate.d.ts +31 -2
- package/build/src/semantic/validate.d.ts.map +1 -1
- package/build/src/semantic/validate.js +450 -60
- package/docs/adr/20261026-typed-error-handling-extension.md +55 -0
- package/docs/adr-bounded-subtyping.md +180 -0
- package/docs/adr-numeric-tower.md +156 -0
- package/docs/architecture.md +1 -1
- package/docs/language-support.md +8 -8
- package/docs/testing.md +5 -3
- package/package.json +1 -1
- package/src/backends/effects.js +3 -1
- package/src/backends/java.js +33 -14
- package/src/backends/javascript.js +83 -18
- package/src/backends/php.js +89 -16
- package/src/backends/ruby.js +36 -12
- package/src/backends/scalars.js +1 -1
- package/src/backends/shared.js +298 -47
- package/src/backends/typescript.js +83 -18
- package/src/backends/writer.js +41 -3
- package/src/frontends/java.js +95 -43
- package/src/frontends/javascript-typescript.js +145 -53
- package/src/frontends/php.js +243 -58
- package/src/frontends/ruby.js +117 -35
- package/src/frontends/types.js +26 -8
- package/src/language-registry.js +13 -5
- package/src/mapping.js +3 -1
- package/src/semantic/effects.js +18 -5
- package/src/semantic/lifetime.js +29 -3
- package/src/semantic/provenance.js +60 -14
- package/src/semantic/types.js +62 -10
- package/src/semantic/validate.js +481 -61
package/src/semantic/validate.js
CHANGED
|
@@ -12,9 +12,23 @@ const task005Languages = new Set(["php", "ruby", "javascript", "typescript", "ja
|
|
|
12
12
|
|
|
13
13
|
/** @typedef {Map<string, import("./types.js").RecordDeclaration>} RecordRegistry */
|
|
14
14
|
/** @typedef {Map<string, import("./types.js").ClassDeclaration>} ClassRegistry */
|
|
15
|
+
/** @typedef {Map<string, import("./types.js").InterfaceDeclaration>} InterfaceRegistry */
|
|
15
16
|
/** @typedef {Map<string, import("./types.js").ErrorDeclaration>} ErrorRegistry */
|
|
17
|
+
/**
|
|
18
|
+
* @typedef InterfaceContractMember
|
|
19
|
+
* @property {string} interfaceId - Declaring interface identity.
|
|
20
|
+
* @property {string} methodId - Declaring interface method identity.
|
|
21
|
+
* @property {string} name - Contract method name.
|
|
22
|
+
* @property {import("./types.js").Parameter[]} parameters - Source-ordered contract parameters.
|
|
23
|
+
* @property {import("./types.js").SemanticFunctionReturnType} returnType - Contract return type.
|
|
24
|
+
* @property {import("./types.js").SourceLocation} location - Declaring method source location.
|
|
25
|
+
*/
|
|
16
26
|
/** @type {WeakMap<RecordRegistry, ClassRegistry>} */
|
|
17
27
|
const referenceClassesByRecordRegistry = new WeakMap()
|
|
28
|
+
/** @type {WeakMap<RecordRegistry, InterfaceRegistry>} */
|
|
29
|
+
const interfacesByRecordRegistry = new WeakMap()
|
|
30
|
+
/** @type {WeakMap<import("./types.js").InterfaceDeclaration, InterfaceContractMember[]>} */
|
|
31
|
+
const flattenedInterfaceCache = new WeakMap()
|
|
18
32
|
/** @type {WeakMap<RecordRegistry, Set<string>>} */
|
|
19
33
|
const resourceIdsByRecordRegistry = new WeakMap()
|
|
20
34
|
|
|
@@ -52,7 +66,7 @@ const resourceIdsByRecordRegistry = new WeakMap()
|
|
|
52
66
|
* Enforces the coherent release-candidate semantic subset after adaptation.
|
|
53
67
|
* @param {import("./types.js").SemanticModule} module - Adapted semantic module.
|
|
54
68
|
* @param {import("./types.js").SemanticLanguage} language - Source language.
|
|
55
|
-
* @param {{classes?: Map<string, import("./types.js").ClassDeclaration>, functions?: Map<string, import("./types.js").FunctionDeclaration>, records?: Map<string, import("./types.js").RecordDeclaration>, errors?: Map<string, import("./types.js").ErrorDeclaration>, callEffects?: Map<string, Set<string>>}} [visible] - Program imports visible during validation.
|
|
69
|
+
* @param {{classes?: Map<string, import("./types.js").ClassDeclaration>, functions?: Map<string, import("./types.js").FunctionDeclaration>, interfaces?: Map<string, import("./types.js").InterfaceDeclaration>, records?: Map<string, import("./types.js").RecordDeclaration>, errors?: Map<string, import("./types.js").ErrorDeclaration>, callEffects?: Map<string, Set<string>>}} [visible] - Program imports visible during validation.
|
|
56
70
|
* @returns {import("./types.js").SemanticModule} Validated module.
|
|
57
71
|
*/
|
|
58
72
|
export function validateParsedModule(module, language, visible = {}) {
|
|
@@ -110,7 +124,7 @@ function normalizeParsedOwnedReferences(module, visibleClasses = new Map()) {
|
|
|
110
124
|
* Validates scalar types and bindings for a caller-supplied semantic module before emission.
|
|
111
125
|
* @param {import("./types.js").SemanticModule} module - Semantic module.
|
|
112
126
|
* @param {import("./types.js").BackendLanguage} language - Backend language or binary target.
|
|
113
|
-
* @param {{classes?: Map<string, import("./types.js").ClassDeclaration>, functions?: Map<string, import("./types.js").FunctionDeclaration>, records?: Map<string, import("./types.js").RecordDeclaration>, errors?: Map<string, import("./types.js").ErrorDeclaration>, callEffects?: Map<string, Set<string>>}} [visible] - Resolved program imports.
|
|
127
|
+
* @param {{classes?: Map<string, import("./types.js").ClassDeclaration>, functions?: Map<string, import("./types.js").FunctionDeclaration>, interfaces?: Map<string, import("./types.js").InterfaceDeclaration>, records?: Map<string, import("./types.js").RecordDeclaration>, errors?: Map<string, import("./types.js").ErrorDeclaration>, callEffects?: Map<string, Set<string>>}} [visible] - Resolved program imports.
|
|
114
128
|
* @returns {void}
|
|
115
129
|
*/
|
|
116
130
|
export function validateBackendTypes(module, language, visible = {}) {
|
|
@@ -160,7 +174,7 @@ function validateBlockShape(block, detail, fail) {
|
|
|
160
174
|
}
|
|
161
175
|
|
|
162
176
|
for (const statement of block.statements) {
|
|
163
|
-
if (!["AssignmentStatement", "BreakStatement", "ContinueStatement", "ExpressionStatement", "ForEachMapStatement", "ForEachStatement", "IfStatement", "LocalDeclaration", "PrivateFieldWriteStatement", "PrintStatement", "RaiseStatement", "ReturnStatement", "TryStatement", "WhileStatement"].includes(statement.kind)) {
|
|
177
|
+
if (!["AssignmentStatement", "BreakStatement", "ContinueStatement", "ExpressionStatement", "ForEachMapStatement", "ForEachStatement", "IfStatement", "LocalDeclaration", "PrivateFieldWriteStatement", "PrintStatement", "RaiseStatement", "RethrowStatement", "ReturnStatement", "TryStatement", "WhileStatement"].includes(statement.kind)) {
|
|
164
178
|
fail(`${detail} statement ${statement.kind}`, statement.location)
|
|
165
179
|
}
|
|
166
180
|
if (statement.kind == "IfStatement") {
|
|
@@ -172,7 +186,18 @@ function validateBlockShape(block, detail, fail) {
|
|
|
172
186
|
}
|
|
173
187
|
if (statement.kind == "TryStatement") {
|
|
174
188
|
validateBlockShape(statement.body, "try body", fail)
|
|
175
|
-
|
|
189
|
+
if (!Array.isArray(statement.handlers) || (statement.handlers.length == 0 && !statement.finallyBody)) {
|
|
190
|
+
fail(`${detail} try without an ordered handler list`, statement.location)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
for (const handler of statement.handlers ?? []) {
|
|
194
|
+
if (!handler || handler.kind != "CatchHandler" ||
|
|
195
|
+
Object.keys(handler).filter((key) => key != "sourceProvenance").sort().join(",") != "catchBinding,catchBody,catchType,kind,location") {
|
|
196
|
+
fail(`${detail} malformed catch handler`, handler?.location ?? statement.location)
|
|
197
|
+
}
|
|
198
|
+
validateBlockShape(handler.catchBody, "catch body", fail)
|
|
199
|
+
}
|
|
200
|
+
if (statement.finallyBody) validateBlockShape(statement.finallyBody, "finally body", fail)
|
|
176
201
|
}
|
|
177
202
|
}
|
|
178
203
|
}
|
|
@@ -183,12 +208,15 @@ function validateBlockShape(block, detail, fail) {
|
|
|
183
208
|
* @param {string} language - Source or backend language identity.
|
|
184
209
|
* @param {SemanticFail} fail - Diagnostic callback.
|
|
185
210
|
* @param {boolean} normalizeOperations - Whether to replace transient frontend operation intent.
|
|
186
|
-
* @param {{classes?: Map<string, import("./types.js").ClassDeclaration>, functions?: Map<string, import("./types.js").FunctionDeclaration>, records?: Map<string, import("./types.js").RecordDeclaration>, errors?: Map<string, import("./types.js").ErrorDeclaration>, callEffects?: Map<string, Set<string>>}} [visible] - Program imports visible during validation.
|
|
211
|
+
* @param {{classes?: Map<string, import("./types.js").ClassDeclaration>, functions?: Map<string, import("./types.js").FunctionDeclaration>, interfaces?: Map<string, import("./types.js").InterfaceDeclaration>, records?: Map<string, import("./types.js").RecordDeclaration>, errors?: Map<string, import("./types.js").ErrorDeclaration>, callEffects?: Map<string, Set<string>>}} [visible] - Program imports visible during validation.
|
|
187
212
|
* @returns {void}
|
|
188
213
|
*/
|
|
189
214
|
function validateModuleTypes(module, language, fail, normalizeOperations, visible = {}) {
|
|
190
|
-
const
|
|
215
|
+
const interfaces = registerInterfaceDeclarations(module.interfaces ?? [], fail, normalizeOperations, visible.interfaces)
|
|
216
|
+
const classes = registerClassDeclarations(module.classes ?? [], fail, normalizeOperations, visible.classes, interfaces)
|
|
191
217
|
const records = validateRecordDeclarations(module.records ?? [], fail, normalizeOperations, visible.records, classes)
|
|
218
|
+
|
|
219
|
+
interfacesByRecordRegistry.set(records, interfaces)
|
|
192
220
|
const errors = validateErrorDeclarations(module.errors ?? [], fail, normalizeOperations, visible.errors)
|
|
193
221
|
/** @type {Map<string, import("./types.js").FunctionDeclaration>} */
|
|
194
222
|
const functions = new Map(visible.functions ?? [])
|
|
@@ -226,6 +254,7 @@ function validateModuleTypes(module, language, fail, normalizeOperations, visibl
|
|
|
226
254
|
|
|
227
255
|
const reservedValueNames = new Set([
|
|
228
256
|
...functions.keys(),
|
|
257
|
+
...[...interfaces.values()].map((declaration) => declaration.name),
|
|
229
258
|
...[...records.values()].map((declaration) => declaration.name),
|
|
230
259
|
...[...classes.values()].map((declaration) => declaration.name),
|
|
231
260
|
...[...errors.values()].map((declaration) => declaration.name),
|
|
@@ -234,7 +263,7 @@ function validateModuleTypes(module, language, fail, normalizeOperations, visibl
|
|
|
234
263
|
|
|
235
264
|
const nominalNames = new Set()
|
|
236
265
|
|
|
237
|
-
for (const declaration of [...module.records ?? [], ...module.classes ?? [], ...module.errors ?? []]) {
|
|
266
|
+
for (const declaration of [...module.interfaces ?? [], ...module.records ?? [], ...module.classes ?? [], ...module.errors ?? []]) {
|
|
238
267
|
if (nominalNames.has(declaration.name)) {
|
|
239
268
|
fail("DUPLICATE_BINDING", `Duplicate nominal declaration '${declaration.name}'.`, roleLocation(declaration, "name"))
|
|
240
269
|
}
|
|
@@ -249,6 +278,9 @@ function validateModuleTypes(module, language, fail, normalizeOperations, visibl
|
|
|
249
278
|
const localFunctionNames = new Set(module.functions.map(({name}) => name))
|
|
250
279
|
const callEffects = inferCallEffects(functions, visible.callEffects, localFunctionNames)
|
|
251
280
|
|
|
281
|
+
for (const declaration of module.classes ?? []) {
|
|
282
|
+
validateInterfaceContracts(declaration, interfaces, records, fail, normalizeOperations)
|
|
283
|
+
}
|
|
252
284
|
for (const declaration of module.classes ?? []) {
|
|
253
285
|
validateClassDeclaration(declaration, functions, records, errors, callEffects, reservedValueNames, fail,
|
|
254
286
|
normalizeOperations, loopOwners)
|
|
@@ -310,7 +342,8 @@ function prepareLoopIdentities(module, normalizeOperations, fail) {
|
|
|
310
342
|
if (statement.alternate) visit(statement.alternate, owner, active)
|
|
311
343
|
} else if (statement.kind == "TryStatement") {
|
|
312
344
|
visit(statement.body, owner, active)
|
|
313
|
-
|
|
345
|
+
for (const handler of statement.handlers ?? []) visit(handler.catchBody, owner, active)
|
|
346
|
+
if (statement.finallyBody) visit(statement.finallyBody, owner, active)
|
|
314
347
|
}
|
|
315
348
|
}
|
|
316
349
|
}
|
|
@@ -339,9 +372,10 @@ export function moduleUncheckedErrorEffects(module, visible = {}) {
|
|
|
339
372
|
* @param {SemanticFail} fail - Diagnostic callback.
|
|
340
373
|
* @param {boolean} normalizeOperations - Whether parser-authored identities are assigned.
|
|
341
374
|
* @param {Map<string, import("./types.js").ClassDeclaration>} [visibleClasses] - Imported classes by identity.
|
|
375
|
+
* @param {Map<string, import("./types.js").InterfaceDeclaration>} [interfaces] - Interface declarations by identity.
|
|
342
376
|
* @returns {ClassRegistry} Classes by stable identity.
|
|
343
377
|
*/
|
|
344
|
-
function registerClassDeclarations(declarations, fail, normalizeOperations, visibleClasses = new Map()) {
|
|
378
|
+
function registerClassDeclarations(declarations, fail, normalizeOperations, visibleClasses = new Map(), interfaces = new Map()) {
|
|
345
379
|
if (!Array.isArray(declarations)) {
|
|
346
380
|
return fail("TYPE_MISMATCH", "Reference class declarations must be an ordered array.", /** @type {never} */ (undefined))
|
|
347
381
|
}
|
|
@@ -429,11 +463,235 @@ function registerClassDeclarations(declarations, fail, normalizeOperations, visi
|
|
|
429
463
|
}
|
|
430
464
|
methodNames.add(method.name)
|
|
431
465
|
}
|
|
466
|
+
if (declaration.implements !== undefined) {
|
|
467
|
+
if (!Array.isArray(declaration.implements)) {
|
|
468
|
+
fail("UNKNOWN_INTERFACE", `Class '${declaration.name}' has a malformed interface implementation set.`, declaration.location)
|
|
469
|
+
}
|
|
470
|
+
for (const implemented of declaration.implements) {
|
|
471
|
+
if (!implemented || implemented.kind != "InterfaceType" || typeof implemented.declarationId != "string" ||
|
|
472
|
+
!interfaces.has(implemented.declarationId)) {
|
|
473
|
+
fail("UNKNOWN_INTERFACE", `Class '${declaration.name}' implements unknown interface '${String(implemented?.declarationId)}'.`,
|
|
474
|
+
declaration.location)
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
432
478
|
}
|
|
433
479
|
|
|
434
480
|
return classes
|
|
435
481
|
}
|
|
436
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Checks the exact total override contract for one implementing class.
|
|
485
|
+
* @param {import("./types.js").ClassDeclaration} declaration - Class declaration.
|
|
486
|
+
* @param {InterfaceRegistry} interfaces - Interfaces by identity.
|
|
487
|
+
* @param {RecordRegistry} records - Record declarations by identity.
|
|
488
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
489
|
+
* @param {boolean} normalizeOperations - Whether resolved facts are assigned.
|
|
490
|
+
* @returns {void}
|
|
491
|
+
*/
|
|
492
|
+
function validateInterfaceContracts(declaration, interfaces, records, fail, normalizeOperations) {
|
|
493
|
+
const implemented = declaration.implements ?? []
|
|
494
|
+
|
|
495
|
+
if (implemented.length == 0) return
|
|
496
|
+
/** @type {InterfaceContractMember[]} */
|
|
497
|
+
const required = []
|
|
498
|
+
|
|
499
|
+
for (const reference of implemented) {
|
|
500
|
+
for (const member of flattenedInterfaceMembers(reference.declarationId, interfaces, fail, new Set(), declaration.location)) {
|
|
501
|
+
if (required.some((candidate) => candidate.name == member.name)) continue
|
|
502
|
+
required.push(member)
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
/** @type {Map<string, {parameterTypes: import("./types.js").SemanticValueType[], returnType: import("./types.js").SemanticFunctionReturnType}>} */
|
|
506
|
+
const contractSignatures = new Map()
|
|
507
|
+
|
|
508
|
+
for (const member of required) {
|
|
509
|
+
contractSignatures.set(member.name, {
|
|
510
|
+
parameterTypes: member.parameters.map((parameter) =>
|
|
511
|
+
validateValueTypeReference(parameter.type, parameter.location, fail, undefined, records)),
|
|
512
|
+
returnType: validateReturnTypeReference(member.returnType, member.location, fail, records)
|
|
513
|
+
})
|
|
514
|
+
}
|
|
515
|
+
/** @type {Map<string, InterfaceContractMember>} */
|
|
516
|
+
const satisfied = new Map()
|
|
517
|
+
|
|
518
|
+
for (const method of declaration.methods) {
|
|
519
|
+
const member = required.find((candidate) => candidate.name == method.name)
|
|
520
|
+
|
|
521
|
+
if (!member) continue
|
|
522
|
+
if (satisfied.has(member.name)) {
|
|
523
|
+
fail("INTERFACE_MEMBER_DUPLICATE", `Class '${declaration.name}' supplies method '${method.name}' more than once for interface contract membership.`,
|
|
524
|
+
roleLocation(method, "name"))
|
|
525
|
+
}
|
|
526
|
+
const methodParameterTypes = method.parameters.map((parameter) =>
|
|
527
|
+
validateValueTypeReference(parameter.type, parameter.location, fail, undefined, records))
|
|
528
|
+
const methodReturnType = validateReturnTypeReference(method.returnType, method.location, fail, records)
|
|
529
|
+
|
|
530
|
+
const contract = contractSignatures.get(member.name)
|
|
531
|
+
|
|
532
|
+
if (!contract || methodParameterTypes.length != contract.parameterTypes.length ||
|
|
533
|
+
methodParameterTypes.some((type, index) => !sameType(type, contract.parameterTypes[index])) ||
|
|
534
|
+
!sameType(methodReturnType, contract.returnType)) {
|
|
535
|
+
fail("INTERFACE_MEMBER_SIGNATURE_MISMATCH",
|
|
536
|
+
`Class '${declaration.name}' method '${method.name}' must exactly match interface '${interfaces.get(member.interfaceId)?.name}' signature ${interfaceSignatureDescription(member)}.`,
|
|
537
|
+
roleLocation(method, "name"))
|
|
538
|
+
}
|
|
539
|
+
satisfied.set(member.name, member)
|
|
540
|
+
if (normalizeOperations) method.implements = {interfaceId: member.interfaceId, methodId: member.methodId}
|
|
541
|
+
}
|
|
542
|
+
for (const member of required) {
|
|
543
|
+
if (satisfied.has(member.name)) continue
|
|
544
|
+
fail("INTERFACE_MEMBER_MISSING", `Class '${declaration.name}' does not implement interface '${interfaces.get(member.interfaceId)?.name}' method '${member.name}'.`,
|
|
545
|
+
declaration.location)
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Formats one flattened interface signature for diagnostics.
|
|
551
|
+
* @param {InterfaceContractMember} member - Flattened member.
|
|
552
|
+
* @returns {string} Stable signature spelling.
|
|
553
|
+
*/
|
|
554
|
+
function interfaceSignatureDescription(member) {
|
|
555
|
+
return `${member.name}(${member.parameters.map((parameter) => typeDescription(parameter.type)).join(", ")}): ${typeDescription(member.returnType)}`
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Registers interface declarations, their closed extends DAG, and their flattened signature sets.
|
|
560
|
+
* @param {unknown} declarations - Candidate ordered interface declarations.
|
|
561
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
562
|
+
* @param {boolean} normalizeOperations - Whether parser-authored identities are assigned.
|
|
563
|
+
* @param {InterfaceRegistry} [visibleInterfaces] - Imported interface declarations by identity.
|
|
564
|
+
* @returns {InterfaceRegistry} Validated interfaces by identity.
|
|
565
|
+
*/
|
|
566
|
+
function registerInterfaceDeclarations(declarations, fail, normalizeOperations, visibleInterfaces = new Map()) {
|
|
567
|
+
if (!Array.isArray(declarations)) {
|
|
568
|
+
return fail("TYPE_MISMATCH", "Interface declarations must be an ordered array.", /** @type {never} */ (undefined))
|
|
569
|
+
}
|
|
570
|
+
/** @type {InterfaceRegistry} */
|
|
571
|
+
const interfaces = new Map(visibleInterfaces)
|
|
572
|
+
const names = new Set([...visibleInterfaces.values()].map(({name}) => name))
|
|
573
|
+
|
|
574
|
+
for (let interfaceIndex = 0; interfaceIndex < declarations.length; interfaceIndex += 1) {
|
|
575
|
+
const declaration = /** @type {import("./types.js").InterfaceDeclaration} */ (declarations[interfaceIndex])
|
|
576
|
+
|
|
577
|
+
if (!declaration || declaration.kind != "InterfaceDeclaration" || !Array.isArray(declaration.methods) ||
|
|
578
|
+
(declaration.extends !== undefined && !Array.isArray(declaration.extends))) {
|
|
579
|
+
return fail("DUPLICATE_INTERFACE", "Malformed or invalid interface declaration.", declaration?.location)
|
|
580
|
+
}
|
|
581
|
+
if (normalizeOperations) declaration.id = `interface:${interfaceIndex}`
|
|
582
|
+
if (typeof declaration.id != "string" || !/^(?:[a-z][a-z0-9._-]*#)?interface:[0-9]+$/u.test(declaration.id) ||
|
|
583
|
+
interfaces.has(declaration.id) || typeof declaration.name != "string" || declaration.name.length == 0 ||
|
|
584
|
+
names.has(declaration.name)) {
|
|
585
|
+
fail("DUPLICATE_INTERFACE", `Duplicate or invalid interface '${String(declaration.name)}'.`, roleLocation(declaration, "name"))
|
|
586
|
+
}
|
|
587
|
+
names.add(declaration.name)
|
|
588
|
+
interfaces.set(declaration.id, declaration)
|
|
589
|
+
|
|
590
|
+
const methodNames = new Set()
|
|
591
|
+
|
|
592
|
+
for (let methodIndex = 0; methodIndex < declaration.methods.length; methodIndex += 1) {
|
|
593
|
+
const method = declaration.methods[methodIndex]
|
|
594
|
+
const expectedId = `${declaration.id}:method:${methodIndex}`
|
|
595
|
+
|
|
596
|
+
if (!method || method.kind != "InterfaceMethodDeclaration" || !Array.isArray(method.parameters) ||
|
|
597
|
+
Reflect.get(method, "body") !== undefined) {
|
|
598
|
+
fail("INTERFACE_MEMBER_DUPLICATE", "Malformed or invalid interface method declaration.", method?.location ?? declaration.location)
|
|
599
|
+
}
|
|
600
|
+
if (normalizeOperations) method.id = expectedId
|
|
601
|
+
if (method.id != expectedId || typeof method.name != "string" || method.name.length == 0 || methodNames.has(method.name)) {
|
|
602
|
+
fail("INTERFACE_MEMBER_DUPLICATE", `Duplicate or invalid interface method '${String(method.name)}'.`, roleLocation(method, "name"))
|
|
603
|
+
}
|
|
604
|
+
methodNames.add(method.name)
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
for (const declaration of interfaces.values()) {
|
|
609
|
+
for (const parent of declaration.extends ?? []) {
|
|
610
|
+
if (!parent || parent.kind != "InterfaceType" || typeof parent.declarationId != "string" ||
|
|
611
|
+
!interfaces.has(parent.declarationId)) {
|
|
612
|
+
fail("UNKNOWN_INTERFACE", `Unknown interface parent '${String(parent?.declarationId)}'.`, declaration.location)
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
flattenedInterfaceMembers(/** @type {string} */ (declaration.id), interfaces, fail)
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return interfaces
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Flattens one interface's transitive signature set: parents first in declaration order, then its own methods.
|
|
623
|
+
* @param {string} interfaceId - Interface identity.
|
|
624
|
+
* @param {InterfaceRegistry} interfaces - Interfaces by identity.
|
|
625
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
626
|
+
* @param {Set<string>} [active] - Active extends path.
|
|
627
|
+
* @param {import("./types.js").SourceLocation} [fallbackLocation] - Location for a missing-parent diagnostic.
|
|
628
|
+
* @returns {InterfaceContractMember[]} Flattened members.
|
|
629
|
+
*/
|
|
630
|
+
function flattenedInterfaceMembers(interfaceId, interfaces, fail, active = new Set(), fallbackLocation = undefined) {
|
|
631
|
+
const declaration = interfaces.get(interfaceId)
|
|
632
|
+
|
|
633
|
+
if (!declaration) return fail("UNKNOWN_INTERFACE", `Unknown interface declaration '${interfaceId}'.`, /** @type {import("./types.js").SourceLocation} */ (fallbackLocation))
|
|
634
|
+
if (active.has(interfaceId)) {
|
|
635
|
+
return fail("INTERFACE_CYCLE", `Interface '${declaration.name}' extends itself through a cycle.`, declaration.location)
|
|
636
|
+
}
|
|
637
|
+
const cached = flattenedInterfaceCache.get(declaration)
|
|
638
|
+
|
|
639
|
+
if (cached) return cached
|
|
640
|
+
active.add(interfaceId)
|
|
641
|
+
/** @type {InterfaceContractMember[]} */
|
|
642
|
+
const members = []
|
|
643
|
+
|
|
644
|
+
for (const parent of declaration.extends ?? []) {
|
|
645
|
+
for (const member of flattenedInterfaceMembers(parent.declarationId, interfaces, fail, active)) {
|
|
646
|
+
const inherited = members.find((candidate) => candidate.name == member.name)
|
|
647
|
+
|
|
648
|
+
if (inherited) {
|
|
649
|
+
if (!sameInterfaceSignature(inherited, member)) {
|
|
650
|
+
fail("INTERFACE_MEMBER_DUPLICATE", `Interface '${declaration.name}' inherits conflicting signatures for '${member.name}'.`,
|
|
651
|
+
declaration.location)
|
|
652
|
+
}
|
|
653
|
+
continue
|
|
654
|
+
}
|
|
655
|
+
members.push(member)
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
for (const method of declaration.methods) {
|
|
659
|
+
const inherited = members.find((candidate) => candidate.name == method.name)
|
|
660
|
+
|
|
661
|
+
if (inherited) {
|
|
662
|
+
if (!sameInterfaceSignature(inherited, method)) {
|
|
663
|
+
fail("INTERFACE_MEMBER_DUPLICATE", `Interface '${declaration.name}' redeclares inherited method '${method.name}' with a different signature.`,
|
|
664
|
+
roleLocation(method, "name"))
|
|
665
|
+
}
|
|
666
|
+
continue
|
|
667
|
+
}
|
|
668
|
+
members.push({
|
|
669
|
+
interfaceId: /** @type {string} */ (declaration.id),
|
|
670
|
+
location: method.location,
|
|
671
|
+
methodId: /** @type {string} */ (method.id),
|
|
672
|
+
name: method.name,
|
|
673
|
+
parameters: method.parameters,
|
|
674
|
+
returnType: method.returnType
|
|
675
|
+
})
|
|
676
|
+
}
|
|
677
|
+
active.delete(interfaceId)
|
|
678
|
+
flattenedInterfaceCache.set(declaration, members)
|
|
679
|
+
|
|
680
|
+
return members
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Compares two flattened interface signatures exactly.
|
|
685
|
+
* @param {{parameters: import("./types.js").Parameter[], returnType: import("./types.js").SemanticFunctionReturnType}} left - First signature.
|
|
686
|
+
* @param {{parameters: import("./types.js").Parameter[], returnType: import("./types.js").SemanticFunctionReturnType}} right - Second signature.
|
|
687
|
+
* @returns {boolean} Whether the signatures are identical.
|
|
688
|
+
*/
|
|
689
|
+
function sameInterfaceSignature(left, right) {
|
|
690
|
+
return left.parameters.length == right.parameters.length &&
|
|
691
|
+
left.parameters.every((parameter, index) => sameTypeIdentity(typeIdentity(parameter.type), typeIdentity(right.parameters[index].type))) &&
|
|
692
|
+
sameTypeIdentity(typeIdentity(left.returnType), typeIdentity(right.returnType))
|
|
693
|
+
}
|
|
694
|
+
|
|
437
695
|
/**
|
|
438
696
|
* Validates exact construction, private state, and method bodies for one class.
|
|
439
697
|
* @param {import("./types.js").ClassDeclaration} declaration - Class declaration.
|
|
@@ -762,11 +1020,11 @@ function validateBlock(block, scope, returnType, functions, records, errors, cal
|
|
|
762
1020
|
)
|
|
763
1021
|
|
|
764
1022
|
scope.pending.delete(statement.name)
|
|
765
|
-
if (!
|
|
1023
|
+
if (!assignableType(initializerType, declaredType, records)) {
|
|
766
1024
|
if (initializerType.kind == "OptionalType" && declaredType.kind != "OptionalType") {
|
|
767
1025
|
fail("FAILURE_ABSENCE_CONFLATION", "Optional absence cannot be coerced into a value or typed failure.", statement.initializer.location)
|
|
768
1026
|
}
|
|
769
|
-
fail(
|
|
1027
|
+
fail(nominalMismatchCode(declaredType), `Initializer type ${typeDescription(initializerType)}; expected ${typeDescription(declaredType)}.`, statement.initializer.location)
|
|
770
1028
|
}
|
|
771
1029
|
declareBinding(statement.name, {
|
|
772
1030
|
knownValue: knownValueForExpression(statement.initializer, scope),
|
|
@@ -792,8 +1050,8 @@ function validateBlock(block, scope, returnType, functions, records, errors, cal
|
|
|
792
1050
|
if (!binding.mutable) {
|
|
793
1051
|
fail("IMMUTABLE_ASSIGNMENT", `Cannot assign to immutable binding '${statement.target.name}'.`, statement.target.location)
|
|
794
1052
|
}
|
|
795
|
-
if (!
|
|
796
|
-
fail(
|
|
1053
|
+
if (!assignableType(expressionType, binding.type, records)) {
|
|
1054
|
+
fail(nominalMismatchCode(binding.type), `Assignment type ${typeDescription(expressionType)}; expected ${typeDescription(binding.type)}.`, statement.expression.location)
|
|
797
1055
|
}
|
|
798
1056
|
binding.knownValue = knownValueForExpression(statement.expression, scope)
|
|
799
1057
|
scope.presenceProofs.delete(binding)
|
|
@@ -890,8 +1148,8 @@ function validateBlock(block, scope, returnType, functions, records, errors, cal
|
|
|
890
1148
|
returnType
|
|
891
1149
|
)
|
|
892
1150
|
|
|
893
|
-
if (!
|
|
894
|
-
fail(
|
|
1151
|
+
if (!assignableType(actualType, returnType, records)) {
|
|
1152
|
+
fail(nominalMismatchCode(returnType), `Return type ${typeDescription(actualType)}; expected ${typeDescription(returnType)}.`, statement.expression.location)
|
|
895
1153
|
}
|
|
896
1154
|
}
|
|
897
1155
|
if (statement.expression) addExpressionEffects(raises, statement.expression, callEffects)
|
|
@@ -1086,44 +1344,87 @@ function validateBlock(block, scope, returnType, functions, records, errors, cal
|
|
|
1086
1344
|
normal = false
|
|
1087
1345
|
continue
|
|
1088
1346
|
}
|
|
1089
|
-
if (statement.kind == "
|
|
1090
|
-
const
|
|
1347
|
+
if (statement.kind == "RethrowStatement") {
|
|
1348
|
+
const binding = findBinding(statement.binding, scope)
|
|
1091
1349
|
|
|
1092
|
-
if (!
|
|
1093
|
-
fail("
|
|
1350
|
+
if (!binding || binding.mutable !== false || binding.type.kind != "ErrorType") {
|
|
1351
|
+
fail("INVALID_RETHROW", "Rethrow requires a catch binding in scope.", statement.location)
|
|
1094
1352
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1353
|
+
raises.add(/** @type {import("./types.js").ErrorType} */ (binding.type).declarationId)
|
|
1354
|
+
normal = false
|
|
1355
|
+
continue
|
|
1356
|
+
}
|
|
1357
|
+
if (statement.kind == "TryStatement") {
|
|
1358
|
+
if (!Array.isArray(statement.handlers) || (statement.handlers.length == 0 && !statement.finallyBody)) {
|
|
1359
|
+
fail("INVALID_ERROR_HANDLER", "Try requires at least one exact typed handler or a finally block.", statement.location)
|
|
1099
1360
|
}
|
|
1100
1361
|
const bodyAssignedOuterBindings = outerMutableBindingsAssignedBy(statement.body, scope)
|
|
1101
1362
|
const assignedOuterBindings = new Set(bodyAssignedOuterBindings)
|
|
1102
1363
|
|
|
1103
|
-
|
|
1364
|
+
for (const handler of statement.handlers) outerMutableBindingsAssignedBy(handler.catchBody, scope, assignedOuterBindings)
|
|
1365
|
+
if (statement.finallyBody) outerMutableBindingsAssignedBy(statement.finallyBody, scope, assignedOuterBindings)
|
|
1104
1366
|
const bodyScope = createScope(scope, statement.body.statements)
|
|
1105
1367
|
const bodyFlow = validateBlock(statement.body, bodyScope, returnType, functions, records, errors, callEffects, fail, normalizeOperations, loopContext)
|
|
1106
|
-
const catchScope = createScope(scope, statement.catchBody.statements)
|
|
1107
1368
|
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1369
|
+
let remainingRaises = new Set(bodyFlow.raises)
|
|
1370
|
+
let normalCompletion = bodyFlow.normal
|
|
1371
|
+
const handlerRaises = new Set()
|
|
1372
|
+
const seenHandlerNames = new Set()
|
|
1112
1373
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
mutable: false,
|
|
1116
|
-
type: bindingType
|
|
1117
|
-
}, roleLocation(statement.catchBinding, "name"), catchScope, fail)
|
|
1118
|
-
const catchFlow = validateBlock(statement.catchBody, catchScope, returnType, functions, records, errors, callEffects, fail, normalizeOperations, loopContext)
|
|
1374
|
+
for (const handler of statement.handlers) {
|
|
1375
|
+
const caughtType = validateErrorType(handler.catchType, handler.location, errors, fail)
|
|
1119
1376
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1377
|
+
if (!handler.catchBinding || handler.catchBinding.kind != "CatchBinding" || handler.catchBinding.mutable !== false) {
|
|
1378
|
+
fail("INVALID_ERROR_HANDLER", "Catch requires one immutable typed binding.", handler.catchBinding?.location ?? handler.location)
|
|
1379
|
+
}
|
|
1380
|
+
const bindingType = validateErrorType(handler.catchBinding.type, handler.catchBinding.location, errors, fail)
|
|
1381
|
+
|
|
1382
|
+
if (bindingType.declarationId != caughtType.declarationId) {
|
|
1383
|
+
fail("INVALID_ERROR_HANDLER", "Catch binding type must exactly match its handler type.", handler.catchBinding.location)
|
|
1384
|
+
}
|
|
1385
|
+
if (!remainingRaises.has(caughtType.declarationId)) {
|
|
1386
|
+
fail("UNREACHABLE_HANDLER", `Handler for '${errors.get(caughtType.declarationId)?.name}' cannot be reached.`,
|
|
1387
|
+
typeLocation(handler.catchType, handler.location))
|
|
1388
|
+
}
|
|
1389
|
+
if (seenHandlerNames.has(handler.catchBinding.name)) {
|
|
1390
|
+
fail("DUPLICATE_BINDING", `Duplicate or shadowed binding '${handler.catchBinding.name}'.`, handler.catchBinding.location)
|
|
1391
|
+
}
|
|
1392
|
+
seenHandlerNames.add(handler.catchBinding.name)
|
|
1393
|
+
remainingRaises.delete(caughtType.declarationId)
|
|
1394
|
+
const catchScope = createScope(scope, handler.catchBody.statements)
|
|
1395
|
+
|
|
1396
|
+
for (const binding of bodyAssignedOuterBindings) {
|
|
1397
|
+
binding.knownValue = undefined
|
|
1398
|
+
catchScope.presenceProofs.delete(binding)
|
|
1399
|
+
}
|
|
1400
|
+
declareBinding(handler.catchBinding.name, {
|
|
1401
|
+
knownValue: undefined,
|
|
1402
|
+
mutable: false,
|
|
1403
|
+
type: bindingType
|
|
1404
|
+
}, roleLocation(handler.catchBinding, "name"), catchScope, fail)
|
|
1405
|
+
const catchFlow = validateBlock(handler.catchBody, catchScope, returnType, functions, records, errors, callEffects, fail,
|
|
1406
|
+
normalizeOperations, loopContext)
|
|
1407
|
+
|
|
1408
|
+
normalCompletion = normalCompletion || catchFlow.normal
|
|
1409
|
+
addAll(handlerRaises, catchFlow.raises)
|
|
1410
|
+
}
|
|
1411
|
+
const pendingRaises = new Set(remainingRaises)
|
|
1412
|
+
|
|
1413
|
+
addAll(pendingRaises, handlerRaises)
|
|
1414
|
+
if (statement.finallyBody) {
|
|
1415
|
+
const finallyScope = createScope(scope, statement.finallyBody.statements)
|
|
1416
|
+
const finallyFlow = validateBlock(statement.finallyBody, finallyScope, returnType, functions, records, errors, callEffects,
|
|
1417
|
+
fail, normalizeOperations, loopContext)
|
|
1418
|
+
// `raises` is the enclosing block's accumulator. A completion block replaces only this try's
|
|
1419
|
+
// pending completion; escapes contributed by earlier statements of the enclosing block survive
|
|
1420
|
+
// whichever way the completion block completes.
|
|
1421
|
+
if (finallyFlow.normal) addAll(raises, pendingRaises)
|
|
1422
|
+
addAll(raises, finallyFlow.raises)
|
|
1423
|
+
normal = finallyFlow.normal ? normalCompletion : false
|
|
1424
|
+
} else {
|
|
1425
|
+
addAll(raises, pendingRaises)
|
|
1426
|
+
normal = normalCompletion
|
|
1122
1427
|
}
|
|
1123
|
-
bodyFlow.raises.delete(caughtType.declarationId)
|
|
1124
|
-
addAll(raises, bodyFlow.raises)
|
|
1125
|
-
addAll(raises, catchFlow.raises)
|
|
1126
|
-
normal = bodyFlow.normal || catchFlow.normal
|
|
1127
1428
|
for (const binding of assignedOuterBindings) {
|
|
1128
1429
|
binding.knownValue = undefined
|
|
1129
1430
|
scope.presenceProofs.delete(binding)
|
|
@@ -1271,14 +1572,27 @@ function syntacticBlockEffects(block, callEffects, methodEffects = new Map()) {
|
|
|
1271
1572
|
}
|
|
1272
1573
|
continue
|
|
1273
1574
|
}
|
|
1575
|
+
if (kind == "RethrowStatement") {
|
|
1576
|
+
const bindingNode = Reflect.get(statement, "bindingNode")
|
|
1577
|
+
const bindingType = bindingNode && typeof bindingNode == "object" ? Reflect.get(bindingNode, "type") : undefined
|
|
1578
|
+
const declarationId = bindingType && typeof bindingType == "object" ? Reflect.get(bindingType, "declarationId") : undefined
|
|
1579
|
+
|
|
1580
|
+
if (typeof declarationId == "string") result.add(declarationId)
|
|
1581
|
+
continue
|
|
1582
|
+
}
|
|
1274
1583
|
if (kind == "TryStatement") {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
const
|
|
1584
|
+
let bodyEffects = syntacticBlockEffects(Reflect.get(statement, "body"), callEffects, methodEffects)
|
|
1585
|
+
|
|
1586
|
+
for (const handler of Reflect.get(statement, "handlers") ?? []) {
|
|
1587
|
+
if (!handler || typeof handler != "object") continue
|
|
1588
|
+
const catchType = Reflect.get(handler, "catchType")
|
|
1589
|
+
const caughtId = catchType && typeof catchType == "object" ? Reflect.get(catchType, "declarationId") : undefined
|
|
1278
1590
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1591
|
+
if (typeof caughtId == "string" && bodyEffects.delete(caughtId)) {
|
|
1592
|
+
addAll(bodyEffects, syntacticBlockEffects(Reflect.get(handler, "catchBody"), callEffects, methodEffects))
|
|
1593
|
+
}
|
|
1281
1594
|
}
|
|
1595
|
+
addAll(bodyEffects, syntacticBlockEffects(Reflect.get(statement, "finallyBody"), callEffects, methodEffects))
|
|
1282
1596
|
addAll(result, bodyEffects)
|
|
1283
1597
|
continue
|
|
1284
1598
|
}
|
|
@@ -1555,6 +1869,14 @@ function validateTypeReference(type, location, fail, seen = new Set(), records =
|
|
|
1555
1869
|
|
|
1556
1870
|
return candidate
|
|
1557
1871
|
}
|
|
1872
|
+
if (candidate.kind == "InterfaceType") {
|
|
1873
|
+
if (Object.keys(candidate).filter((key) => key != "sourceProvenance").sort().join(",") != "declarationId,kind" ||
|
|
1874
|
+
typeof candidate.declarationId != "string" || !interfacesByRecordRegistry.get(records)?.has(candidate.declarationId)) {
|
|
1875
|
+
fail("UNKNOWN_INTERFACE", `Unknown interface declaration '${String(candidate.declarationId)}'.`, typeLocation(candidate, location))
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
return candidate
|
|
1879
|
+
}
|
|
1558
1880
|
if (candidate.kind == "OwnedResourceType") {
|
|
1559
1881
|
if (Object.keys(candidate).filter((key) => key != "sourceProvenance").sort().join(",") != "kind,resourceId" ||
|
|
1560
1882
|
typeof candidate.resourceId != "string" || !resourceIdsByRecordRegistry.get(records)?.has(candidate.resourceId)) {
|
|
@@ -1787,8 +2109,8 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
1787
2109
|
expression.value, scope, functions, records, fail, normalizeOperations, "an optional present value", expectedType.valueType
|
|
1788
2110
|
)
|
|
1789
2111
|
|
|
1790
|
-
if (!
|
|
1791
|
-
fail(
|
|
2112
|
+
if (!assignableType(valueType, expectedType.valueType, records)) {
|
|
2113
|
+
fail(nominalMismatchCode(expectedType.valueType), `Optional present type ${typeDescription(valueType)}; expected ${typeDescription(expectedType.valueType)}.`, expression.value.location)
|
|
1792
2114
|
}
|
|
1793
2115
|
|
|
1794
2116
|
return expectedType
|
|
@@ -1843,8 +2165,8 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
1843
2165
|
inferredElementType ??= actual
|
|
1844
2166
|
const requiredElementType = expectedType?.kind == "ListType" ? expectedType.elementType : inferredElementType
|
|
1845
2167
|
|
|
1846
|
-
if (!
|
|
1847
|
-
fail(
|
|
2168
|
+
if (!assignableType(actual, requiredElementType, records)) {
|
|
2169
|
+
fail(nominalMismatchCode(requiredElementType), `List element type ${typeDescription(actual)}; expected ${typeDescription(requiredElementType)}.`, element.location)
|
|
1848
2170
|
}
|
|
1849
2171
|
}
|
|
1850
2172
|
|
|
@@ -1879,8 +2201,8 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
1879
2201
|
inferredValueType ??= actual
|
|
1880
2202
|
const requiredValueType = expectedType?.kind == "MapType" ? expectedType.valueType : inferredValueType
|
|
1881
2203
|
|
|
1882
|
-
if (!
|
|
1883
|
-
fail(
|
|
2204
|
+
if (!assignableType(actual, requiredValueType, records)) {
|
|
2205
|
+
fail(nominalMismatchCode(requiredValueType), `Map value type ${typeDescription(actual)}; expected ${typeDescription(requiredValueType)}.`, entry.value.location)
|
|
1884
2206
|
}
|
|
1885
2207
|
}
|
|
1886
2208
|
|
|
@@ -1917,8 +2239,8 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
1917
2239
|
inferredValueType ??= actual
|
|
1918
2240
|
const requiredValueType = expectedType?.kind == "OrderedMapType" ? expectedType.valueType : inferredValueType
|
|
1919
2241
|
|
|
1920
|
-
if (!
|
|
1921
|
-
fail(
|
|
2242
|
+
if (!assignableType(actual, requiredValueType, records)) {
|
|
2243
|
+
fail(nominalMismatchCode(requiredValueType), `Ordered-map value type ${typeDescription(actual)}; expected ${typeDescription(requiredValueType)}.`,
|
|
1922
2244
|
entry.value.location)
|
|
1923
2245
|
}
|
|
1924
2246
|
}
|
|
@@ -2071,6 +2393,49 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
2071
2393
|
const receiverType = inferValueExpressionType(expression.receiver, scope, functions, records, fail,
|
|
2072
2394
|
normalizeOperations, "a method receiver")
|
|
2073
2395
|
|
|
2396
|
+
if (receiverType.kind == "InterfaceType") {
|
|
2397
|
+
const interfaces = interfacesByRecordRegistry.get(records) ?? new Map()
|
|
2398
|
+
const receiverInterface = interfaces.get(receiverType.declarationId)
|
|
2399
|
+
|
|
2400
|
+
if (!receiverInterface) return fail("UNKNOWN_INTERFACE", `Unknown interface '${receiverType.declarationId}'.`, expression.receiver.location)
|
|
2401
|
+
const members = flattenedInterfaceMembers(receiverType.declarationId, interfaces, fail, new Set(), expression.receiver.location)
|
|
2402
|
+
const member = members.find((candidate) => candidate.methodId == expression.method) ??
|
|
2403
|
+
(normalizeOperations ? members.find((candidate) => candidate.name == expression.method) : undefined)
|
|
2404
|
+
|
|
2405
|
+
if (!member) return fail("UNKNOWN_METHOD", `Interface '${receiverInterface.name}' has no method '${expression.method}'.`,
|
|
2406
|
+
roleLocation(expression, "member"))
|
|
2407
|
+
if (normalizeOperations) expression.method = member.methodId
|
|
2408
|
+
if (!isDenseArray(expression.arguments)) return fail("TYPE_MISMATCH", "Method arguments must be a dense ordered array.", expression.location)
|
|
2409
|
+
if (expression.arguments.length != member.parameters.length) {
|
|
2410
|
+
return fail("METHOD_ARITY_MISMATCH",
|
|
2411
|
+
`Method '${member.name}' has ${expression.arguments.length} arguments; expected ${member.parameters.length}.`, expression.location)
|
|
2412
|
+
}
|
|
2413
|
+
const parameterTypes = member.parameters.map((parameter) =>
|
|
2414
|
+
validateValueTypeReference(parameter.type, parameter.location, fail, undefined, records))
|
|
2415
|
+
|
|
2416
|
+
for (let index = 0; index < expression.arguments.length; index += 1) {
|
|
2417
|
+
expression.arguments[index] = normalizeOwnedMove(expression.arguments[index], parameterTypes[index], normalizeOperations, fail)
|
|
2418
|
+
const actual = inferValueExpressionType(expression.arguments[index], scope, functions, records, fail,
|
|
2419
|
+
normalizeOperations, "a method argument", parameterTypes[index])
|
|
2420
|
+
|
|
2421
|
+
if (!assignableType(actual, parameterTypes[index], records)) {
|
|
2422
|
+
fail(nominalMismatchCode(parameterTypes[index]), `Method argument type ${typeDescription(actual)}; expected ${typeDescription(parameterTypes[index])}.`,
|
|
2423
|
+
expression.arguments[index].location)
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
const returnType = validateReturnTypeReference(member.returnType, member.location, fail, records)
|
|
2427
|
+
const resolution = {
|
|
2428
|
+
declarationId: member.methodId,
|
|
2429
|
+
kind: /** @type {const} */ ("ResolvedMethodSignature"),
|
|
2430
|
+
parameterTypes: parameterTypes.map((type) => /** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(type))),
|
|
2431
|
+
returnType: typeIdentity(returnType)
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
if (normalizeOperations) expression.resolution = resolution
|
|
2435
|
+
else validateReferenceResolution(expression.resolution, resolution, expression.location, fail, "method")
|
|
2436
|
+
|
|
2437
|
+
return isVoidType(returnType) ? "void" : returnType
|
|
2438
|
+
}
|
|
2074
2439
|
if (receiverType.kind != "ReferenceType" && receiverType.kind != "OwnedReferenceType") {
|
|
2075
2440
|
return fail("INVALID_METHOD_RECEIVER", "Method call receiver must have a reference class type.", expression.receiver.location)
|
|
2076
2441
|
}
|
|
@@ -2096,8 +2461,8 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
2096
2461
|
const actual = inferValueExpressionType(expression.arguments[index], scope, functions, records, fail,
|
|
2097
2462
|
normalizeOperations, "a method argument", parameterTypes[index])
|
|
2098
2463
|
|
|
2099
|
-
if (!
|
|
2100
|
-
fail(
|
|
2464
|
+
if (!assignableType(actual, parameterTypes[index], records)) {
|
|
2465
|
+
fail(nominalMismatchCode(parameterTypes[index]), `Method argument type ${typeDescription(actual)}; expected ${typeDescription(parameterTypes[index])}.`,
|
|
2101
2466
|
expression.arguments[index].location)
|
|
2102
2467
|
}
|
|
2103
2468
|
}
|
|
@@ -2227,8 +2592,8 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
2227
2592
|
|
|
2228
2593
|
if ((functionDeclaration.typeParameters?.length ?? 0) > 0) {
|
|
2229
2594
|
inferTypeArguments(expectedType, actualType, substitutions, argument.location, fail)
|
|
2230
|
-
} else if (!
|
|
2231
|
-
fail(
|
|
2595
|
+
} else if (!assignableType(actualType, expectedType, records)) {
|
|
2596
|
+
fail(nominalMismatchCode(expectedType), `Call argument type ${typeDescription(actualType)}; expected ${typeDescription(expectedType)}.`, argument.location)
|
|
2232
2597
|
}
|
|
2233
2598
|
}
|
|
2234
2599
|
for (const parameter of functionDeclaration.typeParameters ?? []) {
|
|
@@ -2251,8 +2616,8 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
2251
2616
|
const actual = inferValueExpressionType(expression.arguments[index], scope, functions, records, fail,
|
|
2252
2617
|
normalizeOperations, "a call argument", expected)
|
|
2253
2618
|
|
|
2254
|
-
if (!
|
|
2255
|
-
fail(
|
|
2619
|
+
if (!assignableType(actual, expected, records)) {
|
|
2620
|
+
fail(nominalMismatchCode(expected), `Call argument type ${typeDescription(actual)}; expected ${typeDescription(expected)}.`, expression.arguments[index].location)
|
|
2256
2621
|
}
|
|
2257
2622
|
}
|
|
2258
2623
|
const resolution = signatureFor(functionDeclaration, records, fail, substitutions)
|
|
@@ -2767,6 +3132,7 @@ function typeIdentity(type) {
|
|
|
2767
3132
|
/** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(argument)))} : {})
|
|
2768
3133
|
}
|
|
2769
3134
|
if (type.kind == "ReferenceType") return {declarationId: type.declarationId, kind: "ReferenceType"}
|
|
3135
|
+
if (type.kind == "InterfaceType") return {declarationId: type.declarationId, kind: "InterfaceType"}
|
|
2770
3136
|
if (type.kind == "OwnedResourceType") return {kind: "OwnedResourceType", resourceId: type.resourceId}
|
|
2771
3137
|
if (type.kind == "OwnedReferenceType") return {declarationId: type.declarationId, kind: "OwnedReferenceType"}
|
|
2772
3138
|
if (type.kind == "ListType") return {elementType: /** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(type.elementType)), kind: "ListType"}
|
|
@@ -2823,6 +3189,9 @@ function validTypeIdentity(type, allowVoid, seen = new Set()) {
|
|
|
2823
3189
|
} else if (candidate.kind == "ReferenceType" && Object.keys(candidate).sort().join(",") == "declarationId,kind") {
|
|
2824
3190
|
valid = typeof candidate.declarationId == "string" &&
|
|
2825
3191
|
/^(?:[a-z][a-z0-9._-]*#)?class:[0-9]+$/u.test(candidate.declarationId)
|
|
3192
|
+
} else if (candidate.kind == "InterfaceType" && Object.keys(candidate).sort().join(",") == "declarationId,kind") {
|
|
3193
|
+
valid = typeof candidate.declarationId == "string" &&
|
|
3194
|
+
/^(?:[a-z][a-z0-9._-]*#)?interface:[0-9]+$/u.test(candidate.declarationId)
|
|
2826
3195
|
} else if (candidate.kind == "OwnedResourceType" && Object.keys(candidate).sort().join(",") == "kind,resourceId") {
|
|
2827
3196
|
valid = typeof candidate.resourceId == "string" && /^(?:module:[0-9]+\/)?capability:[0-9]+\/resource:[0-9]+$/u
|
|
2828
3197
|
.test(candidate.resourceId)
|
|
@@ -2873,6 +3242,7 @@ function sameTypeIdentity(left, right) {
|
|
|
2873
3242
|
leftArguments.every((argument, index) => sameTypeIdentity(argument, rightArguments[index]))
|
|
2874
3243
|
}
|
|
2875
3244
|
if (leftType.kind == "ReferenceType") return leftType.declarationId == rightType.declarationId
|
|
3245
|
+
if (leftType.kind == "InterfaceType") return leftType.declarationId == rightType.declarationId
|
|
2876
3246
|
if (leftType.kind == "OwnedResourceType") return leftType.resourceId == rightType.resourceId
|
|
2877
3247
|
if (leftType.kind == "OwnedReferenceType") return leftType.declarationId == rightType.declarationId
|
|
2878
3248
|
if (leftType.kind == "MapType" || leftType.kind == "OrderedMapType") {
|
|
@@ -2892,6 +3262,54 @@ function sameType(left, right) {
|
|
|
2892
3262
|
return sameTypeIdentity(typeIdentity(left), typeIdentity(right))
|
|
2893
3263
|
}
|
|
2894
3264
|
|
|
3265
|
+
/**
|
|
3266
|
+
* Reports whether one validated type may flow to a declared position.
|
|
3267
|
+
* @param {import("./types.js").SemanticFunctionReturnType} actual - Inferred source type.
|
|
3268
|
+
* @param {import("./types.js").SemanticFunctionReturnType} expected - Declared position type.
|
|
3269
|
+
* @param {RecordRegistry} records - Record and reference-class registries.
|
|
3270
|
+
* @returns {boolean} Whether the value flow is admitted.
|
|
3271
|
+
*/
|
|
3272
|
+
function assignableType(actual, expected, records) {
|
|
3273
|
+
if (sameType(actual, expected)) return true
|
|
3274
|
+
if (expected.kind != "InterfaceType") return false
|
|
3275
|
+
const interfaces = interfacesByRecordRegistry.get(records) ?? new Map()
|
|
3276
|
+
|
|
3277
|
+
if (actual.kind == "InterfaceType") return interfaceExtendsTransitively(actual.declarationId, expected.declarationId, interfaces)
|
|
3278
|
+
if (actual.kind == "ReferenceType") {
|
|
3279
|
+
const declaration = referenceClassesFor(records).get(actual.declarationId)
|
|
3280
|
+
|
|
3281
|
+
return Boolean(declaration?.implements?.some((implemented) =>
|
|
3282
|
+
implemented.declarationId == expected.declarationId ||
|
|
3283
|
+
interfaceExtendsTransitively(implemented.declarationId, expected.declarationId, interfaces)))
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3286
|
+
return false
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
/**
|
|
3290
|
+
* Reports whether one interface transitively extends another through the closed DAG.
|
|
3291
|
+
* @param {string} interfaceId - Descendant interface identity.
|
|
3292
|
+
* @param {string} ancestorId - Ancestor interface identity.
|
|
3293
|
+
* @param {InterfaceRegistry} interfaces - Interfaces by identity.
|
|
3294
|
+
* @returns {boolean} Whether the relation holds.
|
|
3295
|
+
*/
|
|
3296
|
+
function interfaceExtendsTransitively(interfaceId, ancestorId, interfaces) {
|
|
3297
|
+
const declaration = interfaces.get(interfaceId)
|
|
3298
|
+
|
|
3299
|
+
if (!declaration) return false
|
|
3300
|
+
return (declaration.extends ?? []).some((parent) => parent.declarationId == ancestorId ||
|
|
3301
|
+
interfaceExtendsTransitively(parent.declarationId, ancestorId, interfaces))
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
/**
|
|
3305
|
+
* Chooses the stable diagnostic code for one failed nominal value flow.
|
|
3306
|
+
* @param {import("./types.js").SemanticFunctionReturnType} expected - Declared position type.
|
|
3307
|
+
* @returns {string} Stable diagnostic code.
|
|
3308
|
+
*/
|
|
3309
|
+
function nominalMismatchCode(expected) {
|
|
3310
|
+
return expected.kind == "InterfaceType" ? "INTERFACE_TYPE_MISMATCH" : "TYPE_MISMATCH"
|
|
3311
|
+
}
|
|
3312
|
+
|
|
2895
3313
|
/**
|
|
2896
3314
|
* Formats one validated semantic type for diagnostics.
|
|
2897
3315
|
* @param {import("./types.js").SemanticFunctionReturnType | import("./types.js").ErrorType} type - Semantic type.
|
|
@@ -2903,6 +3321,7 @@ function typeDescription(type) {
|
|
|
2903
3321
|
if (type.kind == "RecordType") return `record<${type.declarationId}${type.arguments?.length
|
|
2904
3322
|
? `, ${type.arguments.map(typeDescription).join(", ")}` : ""}>`
|
|
2905
3323
|
if (type.kind == "ReferenceType") return `reference<${type.declarationId}>`
|
|
3324
|
+
if (type.kind == "InterfaceType") return `interface<${type.declarationId}>`
|
|
2906
3325
|
if (type.kind == "OwnedResourceType") return `owned-resource<${type.resourceId}>`
|
|
2907
3326
|
if (type.kind == "OwnedReferenceType") return `owned-reference<${type.declarationId}>`
|
|
2908
3327
|
if (type.kind == "ErrorType") return `error<${type.declarationId}>`
|
|
@@ -3046,7 +3465,8 @@ function outerMutableBindingsAssignedBy(block, outerScope, assigned = new Set())
|
|
|
3046
3465
|
outerMutableBindingsAssignedBy(statement.body, outerScope, assigned)
|
|
3047
3466
|
} else if (statement.kind == "TryStatement") {
|
|
3048
3467
|
outerMutableBindingsAssignedBy(statement.body, outerScope, assigned)
|
|
3049
|
-
|
|
3468
|
+
for (const handler of statement.handlers ?? []) outerMutableBindingsAssignedBy(handler.catchBody, outerScope, assigned)
|
|
3469
|
+
if (statement.finallyBody) outerMutableBindingsAssignedBy(statement.finallyBody, outerScope, assigned)
|
|
3050
3470
|
}
|
|
3051
3471
|
}
|
|
3052
3472
|
|