semantifold 0.13.0 → 0.15.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 -1
- package/build/src/backends/effects.js +3 -1
- package/build/src/backends/php.d.ts.map +1 -1
- package/build/src/backends/php.js +56 -3
- package/build/src/backends/python.js +11 -7
- 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 +210 -8
- 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 +1 -1
- package/build/src/frontends/java.d.ts.map +1 -1
- package/build/src/frontends/java.js +14 -11
- package/build/src/frontends/javascript-typescript.d.ts +1 -1
- package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
- package/build/src/frontends/javascript-typescript.js +17 -17
- package/build/src/frontends/php.d.ts +5 -1
- package/build/src/frontends/php.d.ts.map +1 -1
- package/build/src/frontends/php.js +159 -31
- package/build/src/frontends/python.d.ts.map +1 -1
- package/build/src/frontends/python.js +37 -7
- package/build/src/frontends/ruby.d.ts +1 -1
- package/build/src/frontends/ruby.d.ts.map +1 -1
- package/build/src/frontends/ruby.js +12 -9
- 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 +30 -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 +3 -1
- package/build/src/semantic/provenance.d.ts.map +1 -1
- package/build/src/semantic/provenance.js +37 -4
- package/build/src/semantic/types.d.ts +140 -9
- package/build/src/semantic/types.d.ts.map +1 -1
- package/build/src/semantic/types.js +37 -6
- 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 +347 -28
- package/docs/adr-bounded-subtyping.md +180 -0
- package/docs/goals.md +1 -1
- package/docs/language-support.md +5 -4
- package/docs/testing.md +2 -2
- package/package.json +1 -1
- package/src/backends/effects.js +1 -0
- package/src/backends/php.js +56 -2
- package/src/backends/python.js +10 -4
- package/src/backends/scalars.js +1 -1
- package/src/backends/shared.js +211 -7
- package/src/backends/writer.js +41 -3
- package/src/frontends/java.js +13 -10
- package/src/frontends/javascript-typescript.js +16 -16
- package/src/frontends/php.js +175 -31
- package/src/frontends/python.js +39 -5
- package/src/frontends/ruby.js +11 -8
- package/src/frontends/types.js +26 -8
- package/src/language-registry.js +29 -5
- package/src/mapping.js +3 -1
- package/src/semantic/effects.js +1 -0
- package/src/semantic/provenance.js +34 -3
- package/src/semantic/types.js +39 -5
- package/src/semantic/validate.js +378 -27
package/src/semantic/validate.js
CHANGED
|
@@ -8,13 +8,27 @@ import {parserRangeFor} from "./provenance.js"
|
|
|
8
8
|
import {validateEffectGraph} from "./effects.js"
|
|
9
9
|
import {validateResourceLifetimes} from "./lifetime.js"
|
|
10
10
|
|
|
11
|
-
const task005Languages = new Set(["php", "ruby", "javascript", "typescript", "java", "c", "dart", "zig"])
|
|
11
|
+
const task005Languages = new Set(["php", "ruby", "javascript", "typescript", "java", "c", "dart", "zig", "python"])
|
|
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 = {}) {
|
|
@@ -194,12 +208,15 @@ function validateBlockShape(block, detail, fail) {
|
|
|
194
208
|
* @param {string} language - Source or backend language identity.
|
|
195
209
|
* @param {SemanticFail} fail - Diagnostic callback.
|
|
196
210
|
* @param {boolean} normalizeOperations - Whether to replace transient frontend operation intent.
|
|
197
|
-
* @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.
|
|
198
212
|
* @returns {void}
|
|
199
213
|
*/
|
|
200
214
|
function validateModuleTypes(module, language, fail, normalizeOperations, visible = {}) {
|
|
201
|
-
const
|
|
215
|
+
const interfaces = registerInterfaceDeclarations(module.interfaces ?? [], fail, normalizeOperations, visible.interfaces)
|
|
216
|
+
const classes = registerClassDeclarations(module.classes ?? [], fail, normalizeOperations, visible.classes, interfaces)
|
|
202
217
|
const records = validateRecordDeclarations(module.records ?? [], fail, normalizeOperations, visible.records, classes)
|
|
218
|
+
|
|
219
|
+
interfacesByRecordRegistry.set(records, interfaces)
|
|
203
220
|
const errors = validateErrorDeclarations(module.errors ?? [], fail, normalizeOperations, visible.errors)
|
|
204
221
|
/** @type {Map<string, import("./types.js").FunctionDeclaration>} */
|
|
205
222
|
const functions = new Map(visible.functions ?? [])
|
|
@@ -237,6 +254,7 @@ function validateModuleTypes(module, language, fail, normalizeOperations, visibl
|
|
|
237
254
|
|
|
238
255
|
const reservedValueNames = new Set([
|
|
239
256
|
...functions.keys(),
|
|
257
|
+
...[...interfaces.values()].map((declaration) => declaration.name),
|
|
240
258
|
...[...records.values()].map((declaration) => declaration.name),
|
|
241
259
|
...[...classes.values()].map((declaration) => declaration.name),
|
|
242
260
|
...[...errors.values()].map((declaration) => declaration.name),
|
|
@@ -245,7 +263,7 @@ function validateModuleTypes(module, language, fail, normalizeOperations, visibl
|
|
|
245
263
|
|
|
246
264
|
const nominalNames = new Set()
|
|
247
265
|
|
|
248
|
-
for (const declaration of [...module.records ?? [], ...module.classes ?? [], ...module.errors ?? []]) {
|
|
266
|
+
for (const declaration of [...module.interfaces ?? [], ...module.records ?? [], ...module.classes ?? [], ...module.errors ?? []]) {
|
|
249
267
|
if (nominalNames.has(declaration.name)) {
|
|
250
268
|
fail("DUPLICATE_BINDING", `Duplicate nominal declaration '${declaration.name}'.`, roleLocation(declaration, "name"))
|
|
251
269
|
}
|
|
@@ -260,6 +278,9 @@ function validateModuleTypes(module, language, fail, normalizeOperations, visibl
|
|
|
260
278
|
const localFunctionNames = new Set(module.functions.map(({name}) => name))
|
|
261
279
|
const callEffects = inferCallEffects(functions, visible.callEffects, localFunctionNames)
|
|
262
280
|
|
|
281
|
+
for (const declaration of module.classes ?? []) {
|
|
282
|
+
validateInterfaceContracts(declaration, interfaces, records, fail, normalizeOperations)
|
|
283
|
+
}
|
|
263
284
|
for (const declaration of module.classes ?? []) {
|
|
264
285
|
validateClassDeclaration(declaration, functions, records, errors, callEffects, reservedValueNames, fail,
|
|
265
286
|
normalizeOperations, loopOwners)
|
|
@@ -351,9 +372,10 @@ export function moduleUncheckedErrorEffects(module, visible = {}) {
|
|
|
351
372
|
* @param {SemanticFail} fail - Diagnostic callback.
|
|
352
373
|
* @param {boolean} normalizeOperations - Whether parser-authored identities are assigned.
|
|
353
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.
|
|
354
376
|
* @returns {ClassRegistry} Classes by stable identity.
|
|
355
377
|
*/
|
|
356
|
-
function registerClassDeclarations(declarations, fail, normalizeOperations, visibleClasses = new Map()) {
|
|
378
|
+
function registerClassDeclarations(declarations, fail, normalizeOperations, visibleClasses = new Map(), interfaces = new Map()) {
|
|
357
379
|
if (!Array.isArray(declarations)) {
|
|
358
380
|
return fail("TYPE_MISMATCH", "Reference class declarations must be an ordered array.", /** @type {never} */ (undefined))
|
|
359
381
|
}
|
|
@@ -441,11 +463,235 @@ function registerClassDeclarations(declarations, fail, normalizeOperations, visi
|
|
|
441
463
|
}
|
|
442
464
|
methodNames.add(method.name)
|
|
443
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
|
+
}
|
|
444
478
|
}
|
|
445
479
|
|
|
446
480
|
return classes
|
|
447
481
|
}
|
|
448
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
|
+
|
|
449
695
|
/**
|
|
450
696
|
* Validates exact construction, private state, and method bodies for one class.
|
|
451
697
|
* @param {import("./types.js").ClassDeclaration} declaration - Class declaration.
|
|
@@ -774,11 +1020,11 @@ function validateBlock(block, scope, returnType, functions, records, errors, cal
|
|
|
774
1020
|
)
|
|
775
1021
|
|
|
776
1022
|
scope.pending.delete(statement.name)
|
|
777
|
-
if (!
|
|
1023
|
+
if (!assignableType(initializerType, declaredType, records)) {
|
|
778
1024
|
if (initializerType.kind == "OptionalType" && declaredType.kind != "OptionalType") {
|
|
779
1025
|
fail("FAILURE_ABSENCE_CONFLATION", "Optional absence cannot be coerced into a value or typed failure.", statement.initializer.location)
|
|
780
1026
|
}
|
|
781
|
-
fail(
|
|
1027
|
+
fail(nominalMismatchCode(declaredType), `Initializer type ${typeDescription(initializerType)}; expected ${typeDescription(declaredType)}.`, statement.initializer.location)
|
|
782
1028
|
}
|
|
783
1029
|
declareBinding(statement.name, {
|
|
784
1030
|
knownValue: knownValueForExpression(statement.initializer, scope),
|
|
@@ -804,8 +1050,8 @@ function validateBlock(block, scope, returnType, functions, records, errors, cal
|
|
|
804
1050
|
if (!binding.mutable) {
|
|
805
1051
|
fail("IMMUTABLE_ASSIGNMENT", `Cannot assign to immutable binding '${statement.target.name}'.`, statement.target.location)
|
|
806
1052
|
}
|
|
807
|
-
if (!
|
|
808
|
-
fail(
|
|
1053
|
+
if (!assignableType(expressionType, binding.type, records)) {
|
|
1054
|
+
fail(nominalMismatchCode(binding.type), `Assignment type ${typeDescription(expressionType)}; expected ${typeDescription(binding.type)}.`, statement.expression.location)
|
|
809
1055
|
}
|
|
810
1056
|
binding.knownValue = knownValueForExpression(statement.expression, scope)
|
|
811
1057
|
scope.presenceProofs.delete(binding)
|
|
@@ -902,8 +1148,8 @@ function validateBlock(block, scope, returnType, functions, records, errors, cal
|
|
|
902
1148
|
returnType
|
|
903
1149
|
)
|
|
904
1150
|
|
|
905
|
-
if (!
|
|
906
|
-
fail(
|
|
1151
|
+
if (!assignableType(actualType, returnType, records)) {
|
|
1152
|
+
fail(nominalMismatchCode(returnType), `Return type ${typeDescription(actualType)}; expected ${typeDescription(returnType)}.`, statement.expression.location)
|
|
907
1153
|
}
|
|
908
1154
|
}
|
|
909
1155
|
if (statement.expression) addExpressionEffects(raises, statement.expression, callEffects)
|
|
@@ -1623,6 +1869,14 @@ function validateTypeReference(type, location, fail, seen = new Set(), records =
|
|
|
1623
1869
|
|
|
1624
1870
|
return candidate
|
|
1625
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
|
+
}
|
|
1626
1880
|
if (candidate.kind == "OwnedResourceType") {
|
|
1627
1881
|
if (Object.keys(candidate).filter((key) => key != "sourceProvenance").sort().join(",") != "kind,resourceId" ||
|
|
1628
1882
|
typeof candidate.resourceId != "string" || !resourceIdsByRecordRegistry.get(records)?.has(candidate.resourceId)) {
|
|
@@ -1855,8 +2109,8 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
1855
2109
|
expression.value, scope, functions, records, fail, normalizeOperations, "an optional present value", expectedType.valueType
|
|
1856
2110
|
)
|
|
1857
2111
|
|
|
1858
|
-
if (!
|
|
1859
|
-
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)
|
|
1860
2114
|
}
|
|
1861
2115
|
|
|
1862
2116
|
return expectedType
|
|
@@ -1911,8 +2165,8 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
1911
2165
|
inferredElementType ??= actual
|
|
1912
2166
|
const requiredElementType = expectedType?.kind == "ListType" ? expectedType.elementType : inferredElementType
|
|
1913
2167
|
|
|
1914
|
-
if (!
|
|
1915
|
-
fail(
|
|
2168
|
+
if (!assignableType(actual, requiredElementType, records)) {
|
|
2169
|
+
fail(nominalMismatchCode(requiredElementType), `List element type ${typeDescription(actual)}; expected ${typeDescription(requiredElementType)}.`, element.location)
|
|
1916
2170
|
}
|
|
1917
2171
|
}
|
|
1918
2172
|
|
|
@@ -1947,8 +2201,8 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
1947
2201
|
inferredValueType ??= actual
|
|
1948
2202
|
const requiredValueType = expectedType?.kind == "MapType" ? expectedType.valueType : inferredValueType
|
|
1949
2203
|
|
|
1950
|
-
if (!
|
|
1951
|
-
fail(
|
|
2204
|
+
if (!assignableType(actual, requiredValueType, records)) {
|
|
2205
|
+
fail(nominalMismatchCode(requiredValueType), `Map value type ${typeDescription(actual)}; expected ${typeDescription(requiredValueType)}.`, entry.value.location)
|
|
1952
2206
|
}
|
|
1953
2207
|
}
|
|
1954
2208
|
|
|
@@ -1985,8 +2239,8 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
1985
2239
|
inferredValueType ??= actual
|
|
1986
2240
|
const requiredValueType = expectedType?.kind == "OrderedMapType" ? expectedType.valueType : inferredValueType
|
|
1987
2241
|
|
|
1988
|
-
if (!
|
|
1989
|
-
fail(
|
|
2242
|
+
if (!assignableType(actual, requiredValueType, records)) {
|
|
2243
|
+
fail(nominalMismatchCode(requiredValueType), `Ordered-map value type ${typeDescription(actual)}; expected ${typeDescription(requiredValueType)}.`,
|
|
1990
2244
|
entry.value.location)
|
|
1991
2245
|
}
|
|
1992
2246
|
}
|
|
@@ -2139,6 +2393,49 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
2139
2393
|
const receiverType = inferValueExpressionType(expression.receiver, scope, functions, records, fail,
|
|
2140
2394
|
normalizeOperations, "a method receiver")
|
|
2141
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
|
+
}
|
|
2142
2439
|
if (receiverType.kind != "ReferenceType" && receiverType.kind != "OwnedReferenceType") {
|
|
2143
2440
|
return fail("INVALID_METHOD_RECEIVER", "Method call receiver must have a reference class type.", expression.receiver.location)
|
|
2144
2441
|
}
|
|
@@ -2164,8 +2461,8 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
2164
2461
|
const actual = inferValueExpressionType(expression.arguments[index], scope, functions, records, fail,
|
|
2165
2462
|
normalizeOperations, "a method argument", parameterTypes[index])
|
|
2166
2463
|
|
|
2167
|
-
if (!
|
|
2168
|
-
fail(
|
|
2464
|
+
if (!assignableType(actual, parameterTypes[index], records)) {
|
|
2465
|
+
fail(nominalMismatchCode(parameterTypes[index]), `Method argument type ${typeDescription(actual)}; expected ${typeDescription(parameterTypes[index])}.`,
|
|
2169
2466
|
expression.arguments[index].location)
|
|
2170
2467
|
}
|
|
2171
2468
|
}
|
|
@@ -2295,8 +2592,8 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
2295
2592
|
|
|
2296
2593
|
if ((functionDeclaration.typeParameters?.length ?? 0) > 0) {
|
|
2297
2594
|
inferTypeArguments(expectedType, actualType, substitutions, argument.location, fail)
|
|
2298
|
-
} else if (!
|
|
2299
|
-
fail(
|
|
2595
|
+
} else if (!assignableType(actualType, expectedType, records)) {
|
|
2596
|
+
fail(nominalMismatchCode(expectedType), `Call argument type ${typeDescription(actualType)}; expected ${typeDescription(expectedType)}.`, argument.location)
|
|
2300
2597
|
}
|
|
2301
2598
|
}
|
|
2302
2599
|
for (const parameter of functionDeclaration.typeParameters ?? []) {
|
|
@@ -2319,8 +2616,8 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
2319
2616
|
const actual = inferValueExpressionType(expression.arguments[index], scope, functions, records, fail,
|
|
2320
2617
|
normalizeOperations, "a call argument", expected)
|
|
2321
2618
|
|
|
2322
|
-
if (!
|
|
2323
|
-
fail(
|
|
2619
|
+
if (!assignableType(actual, expected, records)) {
|
|
2620
|
+
fail(nominalMismatchCode(expected), `Call argument type ${typeDescription(actual)}; expected ${typeDescription(expected)}.`, expression.arguments[index].location)
|
|
2324
2621
|
}
|
|
2325
2622
|
}
|
|
2326
2623
|
const resolution = signatureFor(functionDeclaration, records, fail, substitutions)
|
|
@@ -2835,6 +3132,7 @@ function typeIdentity(type) {
|
|
|
2835
3132
|
/** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(argument)))} : {})
|
|
2836
3133
|
}
|
|
2837
3134
|
if (type.kind == "ReferenceType") return {declarationId: type.declarationId, kind: "ReferenceType"}
|
|
3135
|
+
if (type.kind == "InterfaceType") return {declarationId: type.declarationId, kind: "InterfaceType"}
|
|
2838
3136
|
if (type.kind == "OwnedResourceType") return {kind: "OwnedResourceType", resourceId: type.resourceId}
|
|
2839
3137
|
if (type.kind == "OwnedReferenceType") return {declarationId: type.declarationId, kind: "OwnedReferenceType"}
|
|
2840
3138
|
if (type.kind == "ListType") return {elementType: /** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(type.elementType)), kind: "ListType"}
|
|
@@ -2891,6 +3189,9 @@ function validTypeIdentity(type, allowVoid, seen = new Set()) {
|
|
|
2891
3189
|
} else if (candidate.kind == "ReferenceType" && Object.keys(candidate).sort().join(",") == "declarationId,kind") {
|
|
2892
3190
|
valid = typeof candidate.declarationId == "string" &&
|
|
2893
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)
|
|
2894
3195
|
} else if (candidate.kind == "OwnedResourceType" && Object.keys(candidate).sort().join(",") == "kind,resourceId") {
|
|
2895
3196
|
valid = typeof candidate.resourceId == "string" && /^(?:module:[0-9]+\/)?capability:[0-9]+\/resource:[0-9]+$/u
|
|
2896
3197
|
.test(candidate.resourceId)
|
|
@@ -2941,6 +3242,7 @@ function sameTypeIdentity(left, right) {
|
|
|
2941
3242
|
leftArguments.every((argument, index) => sameTypeIdentity(argument, rightArguments[index]))
|
|
2942
3243
|
}
|
|
2943
3244
|
if (leftType.kind == "ReferenceType") return leftType.declarationId == rightType.declarationId
|
|
3245
|
+
if (leftType.kind == "InterfaceType") return leftType.declarationId == rightType.declarationId
|
|
2944
3246
|
if (leftType.kind == "OwnedResourceType") return leftType.resourceId == rightType.resourceId
|
|
2945
3247
|
if (leftType.kind == "OwnedReferenceType") return leftType.declarationId == rightType.declarationId
|
|
2946
3248
|
if (leftType.kind == "MapType" || leftType.kind == "OrderedMapType") {
|
|
@@ -2960,6 +3262,54 @@ function sameType(left, right) {
|
|
|
2960
3262
|
return sameTypeIdentity(typeIdentity(left), typeIdentity(right))
|
|
2961
3263
|
}
|
|
2962
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
|
+
|
|
2963
3313
|
/**
|
|
2964
3314
|
* Formats one validated semantic type for diagnostics.
|
|
2965
3315
|
* @param {import("./types.js").SemanticFunctionReturnType | import("./types.js").ErrorType} type - Semantic type.
|
|
@@ -2971,6 +3321,7 @@ function typeDescription(type) {
|
|
|
2971
3321
|
if (type.kind == "RecordType") return `record<${type.declarationId}${type.arguments?.length
|
|
2972
3322
|
? `, ${type.arguments.map(typeDescription).join(", ")}` : ""}>`
|
|
2973
3323
|
if (type.kind == "ReferenceType") return `reference<${type.declarationId}>`
|
|
3324
|
+
if (type.kind == "InterfaceType") return `interface<${type.declarationId}>`
|
|
2974
3325
|
if (type.kind == "OwnedResourceType") return `owned-resource<${type.resourceId}>`
|
|
2975
3326
|
if (type.kind == "OwnedReferenceType") return `owned-reference<${type.declarationId}>`
|
|
2976
3327
|
if (type.kind == "ErrorType") return `error<${type.declarationId}>`
|