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.
Files changed (73) hide show
  1. package/README.md +2 -0
  2. package/build/src/backends/effects.js +5 -2
  3. package/build/src/backends/java.js +33 -14
  4. package/build/src/backends/javascript.js +83 -18
  5. package/build/src/backends/php.d.ts.map +1 -1
  6. package/build/src/backends/php.js +88 -16
  7. package/build/src/backends/ruby.js +37 -12
  8. package/build/src/backends/scalars.js +2 -2
  9. package/build/src/backends/shared.d.ts +2 -1
  10. package/build/src/backends/shared.d.ts.map +1 -1
  11. package/build/src/backends/shared.js +299 -42
  12. package/build/src/backends/typescript.js +83 -18
  13. package/build/src/backends/writer.d.ts +18 -4
  14. package/build/src/backends/writer.d.ts.map +1 -1
  15. package/build/src/backends/writer.js +40 -4
  16. package/build/src/frontends/java.d.ts +9 -1
  17. package/build/src/frontends/java.d.ts.map +1 -1
  18. package/build/src/frontends/java.js +90 -43
  19. package/build/src/frontends/javascript-typescript.d.ts +3 -1
  20. package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
  21. package/build/src/frontends/javascript-typescript.js +133 -52
  22. package/build/src/frontends/php.d.ts +13 -1
  23. package/build/src/frontends/php.d.ts.map +1 -1
  24. package/build/src/frontends/php.js +226 -59
  25. package/build/src/frontends/ruby.d.ts +4 -1
  26. package/build/src/frontends/ruby.d.ts.map +1 -1
  27. package/build/src/frontends/ruby.js +113 -37
  28. package/build/src/frontends/types.d.ts +9 -2
  29. package/build/src/frontends/types.d.ts.map +1 -1
  30. package/build/src/frontends/types.js +26 -9
  31. package/build/src/language-registry.d.ts.map +1 -1
  32. package/build/src/language-registry.js +14 -6
  33. package/build/src/mapping.d.ts.map +1 -1
  34. package/build/src/mapping.js +4 -2
  35. package/build/src/semantic/effects.d.ts.map +1 -1
  36. package/build/src/semantic/effects.js +20 -6
  37. package/build/src/semantic/lifetime.js +28 -4
  38. package/build/src/semantic/provenance.d.ts.map +1 -1
  39. package/build/src/semantic/provenance.js +61 -9
  40. package/build/src/semantic/types.d.ts +214 -23
  41. package/build/src/semantic/types.d.ts.map +1 -1
  42. package/build/src/semantic/types.js +58 -11
  43. package/build/src/semantic/validate.d.ts +31 -2
  44. package/build/src/semantic/validate.d.ts.map +1 -1
  45. package/build/src/semantic/validate.js +450 -60
  46. package/docs/adr/20261026-typed-error-handling-extension.md +55 -0
  47. package/docs/adr-bounded-subtyping.md +180 -0
  48. package/docs/adr-numeric-tower.md +156 -0
  49. package/docs/architecture.md +1 -1
  50. package/docs/language-support.md +8 -8
  51. package/docs/testing.md +5 -3
  52. package/package.json +1 -1
  53. package/src/backends/effects.js +3 -1
  54. package/src/backends/java.js +33 -14
  55. package/src/backends/javascript.js +83 -18
  56. package/src/backends/php.js +89 -16
  57. package/src/backends/ruby.js +36 -12
  58. package/src/backends/scalars.js +1 -1
  59. package/src/backends/shared.js +298 -47
  60. package/src/backends/typescript.js +83 -18
  61. package/src/backends/writer.js +41 -3
  62. package/src/frontends/java.js +95 -43
  63. package/src/frontends/javascript-typescript.js +145 -53
  64. package/src/frontends/php.js +243 -58
  65. package/src/frontends/ruby.js +117 -35
  66. package/src/frontends/types.js +26 -8
  67. package/src/language-registry.js +13 -5
  68. package/src/mapping.js +3 -1
  69. package/src/semantic/effects.js +18 -5
  70. package/src/semantic/lifetime.js +29 -3
  71. package/src/semantic/provenance.js +60 -14
  72. package/src/semantic/types.js +62 -10
  73. package/src/semantic/validate.js +481 -61
@@ -38,6 +38,7 @@ const task014Languages = new Set(["php", "ruby", "javascript", "typescript", "ja
38
38
  const task032Languages = new Set(["php", "ruby", "javascript", "typescript", "java"])
39
39
  const task033Languages = new Set(["php", "ruby", "javascript", "typescript", "java"])
40
40
  const task048ConditionalLanguages = new Set(["php", "ruby", "javascript", "typescript", "java"])
41
+ const boundedSubtypingLanguages = new Set(["php"])
41
42
  const javaReferenceMethodHooks = new Set(["clone", "equals", "finalize", "getClass", "hashCode", "notify", "notifyAll", "toString", "wait"])
42
43
  const javascriptReferenceMethodHooks = new Set([
43
44
  "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "__proto__", "constructor",
@@ -56,7 +57,7 @@ const javaObjectInstanceMethodSignatures = new Set([
56
57
  * Checks the intentionally narrow backend contract.
57
58
  * @param {import("../semantic/types.js").SemanticModule} module - Semantic module.
58
59
  * @param {import("../semantic/types.js").BackendLanguage} language - Backend language or binary target.
59
- * @param {{externalDeclarationIds?: Set<string>, program?: boolean, visibleFunctions?: Map<string, import("../semantic/types.js").FunctionDeclaration>, visibleRecords?: Map<string, import("../semantic/types.js").RecordDeclaration>, visibleClasses?: Map<string, import("../semantic/types.js").ClassDeclaration>, visibleErrors?: Map<string, import("../semantic/types.js").ErrorDeclaration>, visibleCallEffects?: Map<string, Set<string>>}} [options] - Resolved program validation context.
60
+ * @param {{externalDeclarationIds?: Set<string>, program?: boolean, visibleFunctions?: Map<string, import("../semantic/types.js").FunctionDeclaration>, visibleRecords?: Map<string, import("../semantic/types.js").RecordDeclaration>, visibleClasses?: Map<string, import("../semantic/types.js").ClassDeclaration>, visibleInterfaces?: Map<string, import("../semantic/types.js").InterfaceDeclaration>, visibleErrors?: Map<string, import("../semantic/types.js").ErrorDeclaration>, visibleCallEffects?: Map<string, Set<string>>}} [options] - Resolved program validation context.
60
61
  * @returns {void}
61
62
  */
62
63
  export function validateBackendModule(module, language, options = {}) {
@@ -89,11 +90,19 @@ export function validateBackendModule(module, language, options = {}) {
89
90
  if (classes.length > 0 && !task033Languages.has(language)) {
90
91
  unsupportedCapability(language, "Task 033 reference classes, methods, and constructors", classes[0]?.location ?? module.location)
91
92
  }
93
+ const declaredInterfaces = module.interfaces
94
+ const interfaces = declaredInterfaces === undefined ? [] : declaredInterfaces
95
+
96
+ if (!Array.isArray(interfaces)) unsupportedCapability(language, "missing or invalid interface declarations", module.location)
97
+ if (interfaces.length > 0 && !boundedSubtypingLanguages.has(language)) {
98
+ unsupportedCapability(language, "Task 048 bounded subtyping interfaces", interfaces[0]?.location ?? module.location)
99
+ }
92
100
  if (!task011Languages.has(language)) rejectTypedErrors(module, language)
93
101
  if (!task032Languages.has(language)) rejectConditionControlledLoops(module, language)
94
102
  if (!task014Languages.has(language)) rejectOrderedMapCapability(module, language)
95
103
  validateRecordTargets(records, module.functions, language, module.location)
96
- validateReferenceClassTargets(classes, records, errors, module.functions, language, module.location)
104
+ validateInterfaceTargets(interfaces, records, errors, module.functions, language, module.location)
105
+ validateReferenceClassTargets(classes, records, errors, module.functions, interfaces, language, module.location)
97
106
  if (!task008Languages.has(language)) rejectIterationStatements(module, language)
98
107
  if (!task007Languages.has(language)) rejectOptionalTypes(module, language)
99
108
  if (!task006Languages.has(language)) rejectCollectionTypes(module, language)
@@ -163,6 +172,7 @@ export function validateBackendModule(module, language, options = {}) {
163
172
  classes: options.visibleClasses,
164
173
  errors: options.visibleErrors,
165
174
  functions: options.visibleFunctions,
175
+ interfaces: options.visibleInterfaces,
166
176
  records: options.visibleRecords
167
177
  })
168
178
  }
@@ -382,7 +392,99 @@ function validateRecordTargets(records, functions, language, moduleLocation) {
382
392
  * @param {import("../semantic/types.js").SourceLocation | undefined} moduleLocation - Fallback location.
383
393
  * @returns {void}
384
394
  */
385
- function validateReferenceClassTargets(classes, records, errors, functions, language, moduleLocation) {
395
+ /**
396
+ * Validates the exact target-visible shape of every interface and its override contracts before output allocation.
397
+ * @param {import("../semantic/types.js").InterfaceDeclaration[]} interfaces - Candidate interfaces.
398
+ * @param {import("../semantic/types.js").RecordDeclaration[]} records - Existing records.
399
+ * @param {import("../semantic/types.js").ErrorDeclaration[]} errors - Existing typed errors.
400
+ * @param {import("../semantic/types.js").FunctionDeclaration[]} functions - Existing functions.
401
+ * @param {import("../semantic/types.js").BackendLanguage} language - Target language.
402
+ * @param {import("../semantic/types.js").SourceLocation | undefined} moduleLocation - Fallback location.
403
+ * @returns {void}
404
+ */
405
+ function validateInterfaceTargets(interfaces, records, errors, functions, language, moduleLocation) {
406
+ if (interfaces.length == 0) return
407
+ const byId = new Map(interfaces.map((declaration) => [declaration.id, declaration]))
408
+ const targetNames = new Set()
409
+
410
+ for (const declaration of [...records, ...errors, ...functions]) {
411
+ if (!declaration || typeof declaration != "object" || Array.isArray(declaration)) continue
412
+ const name = Reflect.get(declaration, "name")
413
+
414
+ if (typeof name == "string") targetNames.add(language == "php" ? name.toLowerCase() : name)
415
+ }
416
+
417
+ for (let interfaceIndex = 0; interfaceIndex < interfaces.length; interfaceIndex += 1) {
418
+ const declaration = interfaces[interfaceIndex]
419
+ const location = diagnosticLocation(declaration?.location, moduleLocation)
420
+ const keys = declaration && typeof declaration == "object" && !Array.isArray(declaration)
421
+ ? Object.keys(declaration).filter((key) => key != "sourceProvenance").sort().join(",") : ""
422
+
423
+ if (!declaration || declaration.kind != "InterfaceDeclaration" ||
424
+ !["extends,id,kind,location,methods,name", "id,kind,location,methods,name"].includes(keys) ||
425
+ !isDenseArray(declaration.methods) ||
426
+ !new RegExp(`^(?:[a-z][a-z0-9._-]*#)?interface:${interfaceIndex}$`, "u").test(String(declaration.id))) {
427
+ unsupportedCapability(language, "missing or invalid interface declaration", location)
428
+ }
429
+ const declarationLocation = requireSemanticLocation(declaration.location, language, "interface declaration location", location)
430
+
431
+ validateTargetTypeIdentifier(language, declaration.name, declarationLocation)
432
+ const targetName = language == "php" ? declaration.name.toLowerCase() : declaration.name
433
+
434
+ if (targetNames.has(targetName)) unsupportedCapability(language, `interface collision '${declaration.name}'`, declarationLocation)
435
+ targetNames.add(targetName)
436
+ const methodNames = new Set()
437
+
438
+ for (let methodIndex = 0; methodIndex < declaration.methods.length; methodIndex += 1) {
439
+ const method = declaration.methods[methodIndex]
440
+ const methodLocation = diagnosticLocation(method?.location, declarationLocation)
441
+ const methodKeys = method && typeof method == "object" && !Array.isArray(method)
442
+ ? Object.keys(method).filter((key) => key != "sourceProvenance").sort().join(",") : ""
443
+
444
+ if (!method || method.kind != "InterfaceMethodDeclaration" || methodKeys != "id,kind,location,name,parameters,returnType" ||
445
+ method.id != `${declaration.id}:method:${methodIndex}` || !isDenseArray(method.parameters)) {
446
+ unsupportedCapability(language, "missing or invalid interface method declaration", methodLocation)
447
+ }
448
+ const exactMethodLocation = requireSemanticLocation(method.location, language, "interface method declaration location", declarationLocation)
449
+
450
+ validateTargetIdentifier(language, method.name, "interface method", exactMethodLocation)
451
+ const normalized = language == "php" ? method.name.toLowerCase() : method.name
452
+
453
+ if (methodNames.has(normalized)) unsupportedCapability(language, `duplicate interface method '${method.name}'`, exactMethodLocation)
454
+ methodNames.add(normalized)
455
+ validateReferenceClassParameters(method.parameters, "interface method", language, exactMethodLocation)
456
+ }
457
+ const parents = declaration.extends ?? []
458
+
459
+ for (const parent of parents) {
460
+ const parentKeys = parent && typeof parent == "object" && !Array.isArray(parent)
461
+ ? Object.keys(parent).filter((key) => key != "sourceProvenance").sort().join(",") : ""
462
+
463
+ if (!parent || parent.kind != "InterfaceType" || parentKeys != "declarationId,kind" ||
464
+ !byId.has(parent.declarationId)) {
465
+ unsupportedCapability(language, "missing or invalid interface parent reference", declarationLocation)
466
+ }
467
+ }
468
+ }
469
+ for (const declaration of interfaces) {
470
+ for (const parent of declaration.extends ?? []) {
471
+ if (parent.declarationId == declaration.id) unsupportedCapability(language, "interface extends itself", declaration.location)
472
+ }
473
+ }
474
+ }
475
+
476
+ /**
477
+ * Validates the exact target-visible shape of every reference class before output allocation.
478
+ * @param {import("../semantic/types.js").ClassDeclaration[]} classes - Candidate classes.
479
+ * @param {import("../semantic/types.js").RecordDeclaration[]} records - Existing records.
480
+ * @param {import("../semantic/types.js").ErrorDeclaration[]} errors - Existing typed errors.
481
+ * @param {import("../semantic/types.js").FunctionDeclaration[]} functions - Existing functions.
482
+ * @param {import("../semantic/types.js").InterfaceDeclaration[]} interfaces - Existing interfaces.
483
+ * @param {import("../semantic/types.js").BackendLanguage} language - Target language.
484
+ * @param {import("../semantic/types.js").SourceLocation | undefined} moduleLocation - Fallback location.
485
+ * @returns {void}
486
+ */
487
+ function validateReferenceClassTargets(classes, records, errors, functions, interfaces, language, moduleLocation) {
386
488
  const targetNames = new Set()
387
489
 
388
490
  for (const declaration of [...records, ...errors, ...functions]) {
@@ -400,7 +502,8 @@ function validateReferenceClassTargets(classes, records, errors, functions, lang
400
502
  ? Object.keys(declaration).filter((key) => key != "sourceProvenance").sort().join(",") : ""
401
503
 
402
504
  if (!declaration || declaration.kind != "ClassDeclaration" ||
403
- !["constructor,fields,id,kind,location,methods,name", "constructor,fields,id,kind,location,methods,name,ownership"].includes(keys) ||
505
+ !["constructor,fields,id,implements,kind,location,methods,name", "constructor,fields,id,implements,kind,location,methods,name,ownership",
506
+ "constructor,fields,id,kind,location,methods,name", "constructor,fields,id,kind,location,methods,name,ownership"].includes(keys) ||
404
507
  !isDenseArray(declaration.fields) || declaration.fields.length == 0 || !isDenseArray(declaration.methods) ||
405
508
  !new RegExp(`^(?:[a-z][a-z0-9._-]*#)?class:${classIndex}$`, "u").test(String(declaration.id))) {
406
509
  unsupportedCapability(language, "missing or invalid reference class declaration", location)
@@ -459,7 +562,9 @@ function validateReferenceClassTargets(classes, records, errors, functions, lang
459
562
  ? Object.keys(method).filter((key) => key != "sourceProvenance").sort().join(",") : ""
460
563
 
461
564
  if (!method || method.kind != "MethodDeclaration" ||
462
- !["body,id,kind,location,name,parameters,returnType",
565
+ !["body,id,implements,kind,location,name,parameters,returnType",
566
+ "body,effects,failureIds,id,implements,kind,location,name,parameters,returnType",
567
+ "body,id,kind,location,name,parameters,returnType",
463
568
  "body,effects,failureIds,id,kind,location,name,parameters,returnType"].includes(methodKeys) ||
464
569
  method.id != `${declaration.id}:method:${methodIndex}` || !isDenseArray(method.parameters)) {
465
570
  unsupportedCapability(language, "missing or invalid method declaration", methodLocation)
@@ -492,13 +597,104 @@ function validateReferenceClassTargets(classes, records, errors, functions, lang
492
597
  methodNames.add(normalized)
493
598
  validateReferenceClassParameters(method.parameters, "method", language, exactMethodLocation)
494
599
  }
600
+ const implemented = declaration.implements ?? []
601
+
602
+ for (const reference of implemented) {
603
+ const referenceKeys = reference && typeof reference == "object" && !Array.isArray(reference)
604
+ ? Object.keys(reference).filter((key) => key != "sourceProvenance").sort().join(",") : ""
605
+
606
+ if (!reference || reference.kind != "InterfaceType" || referenceKeys != "declarationId,kind" ||
607
+ !interfaces.some((candidate) => candidate.id == reference.declarationId)) {
608
+ unsupportedCapability(language, "missing or invalid implemented interface reference", location)
609
+ }
610
+ }
611
+ const satisfied = new Set()
612
+
613
+ for (const reference of implemented) {
614
+ for (const member of flattenedTargetInterfaceMembers(reference.declarationId, interfaces, language, location)) {
615
+ const method = declaration.methods.find((candidate) => candidate.name == member.name)
616
+
617
+ if (!method) unsupportedCapability(language, `interface method '${member.name}' is not implemented`, location)
618
+ if (satisfied.has(member.name)) unsupportedCapability(language, `duplicate interface implementation '${member.name}'`, location)
619
+ if (method.parameters.length != member.parameters.length ||
620
+ method.parameters.some((parameter, index) => !sameTargetType(parameter.type, member.parameters[index].type))) {
621
+ unsupportedCapability(language, `interface method signature mismatch '${member.name}'`, location)
622
+ }
623
+ if (!sameTargetType(method.returnType, member.returnType)) {
624
+ unsupportedCapability(language, `interface method return mismatch '${member.name}'`, location)
625
+ }
626
+ satisfied.add(member.name)
627
+ }
628
+ }
629
+ }
630
+ }
631
+
632
+ /**
633
+ * Flattens one target-validated interface signature set in declaration order.
634
+ * @param {string} interfaceId - Interface identity.
635
+ * @param {import("../semantic/types.js").InterfaceDeclaration[]} interfaces - Interfaces in declaration order.
636
+ * @param {import("../semantic/types.js").BackendLanguage} language - Target language.
637
+ * @param {import("../semantic/types.js").SourceLocation | undefined} location - Diagnostic location.
638
+ * @param {Set<string>} [active] - Active extends path.
639
+ * @returns {import("../semantic/types.js").InterfaceMethodDeclaration[]} Flattened members.
640
+ */
641
+ function flattenedTargetInterfaceMembers(interfaceId, interfaces, language, location, active = new Set()) {
642
+ const declaration = interfaces.find((candidate) => candidate.id == interfaceId)
643
+
644
+ if (!declaration) unsupportedCapability(language, "missing or invalid implemented interface reference", location)
645
+ if (active.has(interfaceId)) unsupportedCapability(language, "interface extends itself", declaration.location)
646
+ active.add(interfaceId)
647
+ /** @type {import("../semantic/types.js").InterfaceMethodDeclaration[]} */
648
+ const members = []
649
+
650
+ for (const parent of declaration.extends ?? []) {
651
+ for (const member of flattenedTargetInterfaceMembers(parent.declarationId, interfaces, language, location, active)) {
652
+ if (!members.some((candidate) => candidate.name == member.name)) members.push(member)
653
+ }
495
654
  }
655
+ for (const method of declaration.methods) {
656
+ if (!members.some((candidate) => candidate.name == method.name)) members.push(method)
657
+ }
658
+ active.delete(interfaceId)
659
+
660
+ return members
661
+ }
662
+
663
+ /**
664
+ * Compares two backend-validated semantic types structurally.
665
+ * @param {import("../semantic/types.js").SemanticFunctionReturnType} left - First type.
666
+ * @param {import("../semantic/types.js").SemanticFunctionReturnType} right - Second type.
667
+ * @returns {boolean} Whether the types are identical.
668
+ */
669
+ function sameTargetType(left, right) {
670
+ return JSON.stringify(targetTypeIdentity(left)) == JSON.stringify(targetTypeIdentity(right))
671
+ }
672
+
673
+ /**
674
+ * Reduces one semantic type to its comparable target identity.
675
+ * @param {import("../semantic/types.js").SemanticFunctionReturnType} type - Semantic type.
676
+ * @returns {unknown} Comparable identity.
677
+ */
678
+ function targetTypeIdentity(type) {
679
+ if (typeof type == "string") return type
680
+ if (type.kind == "TypeReference") return type.name
681
+ if (type.kind == "TypeVariableReference") return {kind: "TypeVariableReference", parameterId: type.parameterId}
682
+ if (type.kind == "RecordType") return {declarationId: type.declarationId, kind: "RecordType"}
683
+ if (type.kind == "ReferenceType") return {declarationId: type.declarationId, kind: "ReferenceType"}
684
+ if (type.kind == "InterfaceType") return {declarationId: type.declarationId, kind: "InterfaceType"}
685
+ if (type.kind == "OwnedResourceType") return {kind: "OwnedResourceType", resourceId: type.resourceId}
686
+ if (type.kind == "OwnedReferenceType") return {declarationId: type.declarationId, kind: "OwnedReferenceType"}
687
+ if (type.kind == "ListType") return {elementType: targetTypeIdentity(type.elementType), kind: "ListType"}
688
+ if (type.kind == "OptionalType") return {kind: "OptionalType", valueType: targetTypeIdentity(type.valueType)}
689
+ if (type.kind == "OrderedMapType") return {keyType: targetTypeIdentity(type.keyType), kind: "OrderedMapType", valueType: targetTypeIdentity(type.valueType)}
690
+
691
+ return {keyType: targetTypeIdentity(type.keyType), kind: "MapType", valueType: targetTypeIdentity(type.valueType)}
496
692
  }
497
693
 
498
694
  /**
499
695
  * Validates one reference callable's exact parameter shape and target names.
500
696
  * @param {import("../semantic/types.js").Parameter[]} parameters - Candidate parameters.
501
- * @param {"constructor" | "method"} owner - Callable kind.
697
+ * @param {"constructor" | "interface method" | "method"} owner - Callable kind.
502
698
  * @param {import("../semantic/types.js").BackendLanguage} language - Target language.
503
699
  * @param {import("../semantic/types.js").SourceLocation | undefined} ownerLocation - Fallback location.
504
700
  * @returns {void}
@@ -627,7 +823,8 @@ function rejectBlockConditionLoop(block, language, seen = new Set()) {
627
823
  rejectBlockConditionLoop(Reflect.get(statement, "body"), language, seen)
628
824
  } else if (kind == "TryStatement") {
629
825
  rejectBlockConditionLoop(Reflect.get(statement, "body"), language, seen)
630
- rejectBlockConditionLoop(Reflect.get(statement, "catchBody"), language, seen)
826
+ for (const handler of Reflect.get(statement, "handlers") ?? []) rejectBlockConditionLoop(Reflect.get(handler, "catchBody"), language, seen)
827
+ rejectBlockConditionLoop(Reflect.get(statement, "finallyBody"), language, seen)
631
828
  }
632
829
  }
633
830
  }
@@ -658,7 +855,8 @@ function rejectBlockIteration(block, language, seen = new Set()) {
658
855
  rejectBlockIteration(Reflect.get(statement, "alternate"), language, seen)
659
856
  } else if (kind == "TryStatement") {
660
857
  rejectBlockIteration(Reflect.get(statement, "body"), language, seen)
661
- rejectBlockIteration(Reflect.get(statement, "catchBody"), language, seen)
858
+ for (const handler of Reflect.get(statement, "handlers") ?? []) rejectBlockIteration(Reflect.get(handler, "catchBody"), language, seen)
859
+ rejectBlockIteration(Reflect.get(statement, "finallyBody"), language, seen)
662
860
  }
663
861
  }
664
862
  }
@@ -713,7 +911,8 @@ function rejectBlockOptionalTypes(block, language) {
713
911
  if (statement.kind == "WhileStatement") rejectBlockOptionalTypes(statement.body, language)
714
912
  if (statement.kind == "TryStatement") {
715
913
  rejectBlockOptionalTypes(statement.body, language)
716
- rejectBlockOptionalTypes(statement.catchBody, language)
914
+ for (const handler of statement.handlers ?? []) rejectBlockOptionalTypes(handler.catchBody, language)
915
+ if (statement.finallyBody) rejectBlockOptionalTypes(statement.finallyBody, language)
717
916
  }
718
917
  }
719
918
  }
@@ -788,7 +987,8 @@ function rejectBlockCollectionTypes(block, language) {
788
987
  if (statement.kind == "WhileStatement") rejectBlockCollectionTypes(statement.body, language)
789
988
  if (statement.kind == "TryStatement") {
790
989
  rejectBlockCollectionTypes(statement.body, language)
791
- rejectBlockCollectionTypes(statement.catchBody, language)
990
+ for (const handler of statement.handlers ?? []) rejectBlockCollectionTypes(handler.catchBody, language)
991
+ if (statement.finallyBody) rejectBlockCollectionTypes(statement.finallyBody, language)
792
992
  }
793
993
  }
794
994
  }
@@ -856,9 +1056,13 @@ function validateScaffoldingNames(module, language) {
856
1056
  }
857
1057
  }
858
1058
  }
859
- if (statement.kind == "TryStatement" && ownedPrintReceiverNames.has(statement.catchBinding.name)) {
860
- unsupportedCapability(language, `${owner} catch binding '${statement.catchBinding.name}' captures backend scaffolding`,
861
- statement.catchBinding.location)
1059
+ if (statement.kind == "TryStatement") {
1060
+ for (const handler of statement.handlers ?? []) {
1061
+ if (ownedPrintReceiverNames.has(handler.catchBinding.name)) {
1062
+ unsupportedCapability(language, `${owner} catch binding '${handler.catchBinding.name}' captures backend scaffolding`,
1063
+ handler.catchBinding.location)
1064
+ }
1065
+ }
862
1066
  }
863
1067
  }
864
1068
  }
@@ -878,9 +1082,13 @@ function validateScaffoldingNames(module, language) {
878
1082
  }
879
1083
  }
880
1084
  }
881
- if (statement.kind == "TryStatement" && ownedEntryNames.has(statement.catchBinding.name)) {
882
- unsupportedCapability(language, `entry catch binding '${statement.catchBinding.name}' captures backend scaffolding`,
883
- statement.catchBinding.location)
1085
+ if (statement.kind == "TryStatement") {
1086
+ for (const handler of statement.handlers ?? []) {
1087
+ if (ownedEntryNames.has(handler.catchBinding.name)) {
1088
+ unsupportedCapability(language, `entry catch binding '${handler.catchBinding.name}' captures backend scaffolding`,
1089
+ handler.catchBinding.location)
1090
+ }
1091
+ }
884
1092
  }
885
1093
  }
886
1094
  for (const declaration of module.functions) {
@@ -949,12 +1157,13 @@ function validateRecordConstructorNames(module, language) {
949
1157
  }
950
1158
  }
951
1159
  }
952
- if (statement.kind == "TryStatement" && recordNames.has(statement.catchBinding.name)) {
953
- unsupportedCapability(
954
- language,
955
- `${owner} catch binding '${statement.catchBinding.name}' captures nominal constructor`,
956
- bindingNameLocation(statement.catchBinding)
957
- )
1160
+ if (statement.kind == "TryStatement") {
1161
+ const captured = (statement.handlers ?? []).find((handler) => recordNames.has(handler.catchBinding.name))
1162
+
1163
+ if (captured) {
1164
+ unsupportedCapability(language, `${owner} catch binding '${captured.catchBinding.name}' captures nominal constructor`,
1165
+ bindingNameLocation(captured.catchBinding))
1166
+ }
958
1167
  }
959
1168
  }
960
1169
  }
@@ -1052,12 +1261,15 @@ function validateJavaUtilFactoryNames(module) {
1052
1261
  }
1053
1262
  if (statement.kind == "TryStatement") {
1054
1263
  validateBlockNames(statement.body, capture, owner)
1055
- const catchCapture = statement.catchBinding.name == "java" ? {
1056
- detail: `${owner} catch binding 'java' captures java.util factory syntax`,
1057
- location: statement.catchBinding.location
1058
- } : capture
1264
+ for (const handler of statement.handlers ?? []) {
1265
+ const catchCapture = handler.catchBinding.name == "java" ? {
1266
+ detail: `${owner} catch binding 'java' captures java.util factory syntax`,
1267
+ location: handler.catchBinding.location
1268
+ } : capture
1059
1269
 
1060
- validateBlockNames(statement.catchBody, catchCapture, owner)
1270
+ validateBlockNames(handler.catchBody, catchCapture, owner)
1271
+ }
1272
+ if (statement.finallyBody) validateBlockNames(statement.finallyBody, capture, owner)
1061
1273
  }
1062
1274
  }
1063
1275
  }
@@ -1220,7 +1432,11 @@ function allStatements(block) {
1220
1432
  return [statement, ...allStatements(statement.body)]
1221
1433
  }
1222
1434
  if (statement.kind == "WhileStatement") return [statement, ...allStatements(statement.body)]
1223
- if (statement.kind == "TryStatement") return [statement, ...allStatements(statement.body), ...allStatements(statement.catchBody)]
1435
+ if (statement.kind == "TryStatement") {
1436
+ return [statement, ...allStatements(statement.body),
1437
+ ...(statement.handlers ?? []).flatMap((handler) => allStatements(handler.catchBody)),
1438
+ ...(statement.finallyBody ? allStatements(statement.finallyBody) : [])]
1439
+ }
1224
1440
 
1225
1441
  return [statement]
1226
1442
  })
@@ -1274,7 +1490,8 @@ function collectLoopOwners(module, language) {
1274
1490
  visit(Reflect.get(statement, "alternate"), owner, path)
1275
1491
  } else if (kind == "TryStatement") {
1276
1492
  visit(Reflect.get(statement, "body"), owner, path)
1277
- visit(Reflect.get(statement, "catchBody"), owner, path)
1493
+ for (const handler of Reflect.get(statement, "handlers") ?? []) visit(Reflect.get(handler, "catchBody"), owner, path)
1494
+ visit(Reflect.get(statement, "finallyBody"), owner, path)
1278
1495
  }
1279
1496
  }
1280
1497
  }
@@ -1536,30 +1753,55 @@ function validateStatement(statement, language, ownerLocation, loopContext, acti
1536
1753
  validateExpression(raised.message, language, raised.location, false, new Set(), externalDeclarationIds)
1537
1754
  return
1538
1755
  }
1756
+ if (kind == "RethrowStatement") {
1757
+ const rethrow = /** @type {import("../semantic/types.js").RethrowStatement} */ (statement)
1758
+ const rethrowLocation = requireSemanticLocation(ownLocation, language, "RethrowStatement location", ownerLocation)
1759
+
1760
+ if (Object.keys(rethrow).filter((key) => key != "sourceProvenance").sort().join(",") != "binding,bindingNode,kind,location" ||
1761
+ typeof rethrow.binding != "string" || rethrow.binding.length == 0 ||
1762
+ !rethrow.bindingNode || rethrow.bindingNode.kind != "CatchBinding" || rethrow.bindingNode.name != rethrow.binding) {
1763
+ unsupportedCapability(language, "malformed RethrowStatement", rethrowLocation)
1764
+ }
1765
+ validateTargetBindingIdentifier(language, rethrow.binding, "catch binding", rethrowLocation)
1766
+ return
1767
+ }
1539
1768
  if (kind == "TryStatement") {
1540
- const handler = /** @type {import("../semantic/types.js").TryStatement} */ (statement)
1769
+ const tryStatement = /** @type {import("../semantic/types.js").TryStatement} */ (statement)
1541
1770
  const tryLocation = requireSemanticLocation(ownLocation, language, "TryStatement location", ownerLocation)
1542
- const fields = Object.keys(handler).filter((key) => key != "sourceProvenance").sort().join(",")
1771
+ const fields = Object.keys(tryStatement).filter((key) => key != "sourceProvenance").sort().join(",")
1543
1772
 
1544
- if (fields != "body,catchBinding,catchBody,catchType,kind,location") {
1773
+ if (fields != "body,handlers,kind,location" && fields != "body,finallyBody,handlers,kind,location") {
1545
1774
  unsupportedCapability(language, "malformed TryStatement", tryLocation)
1546
1775
  }
1547
- validateErrorTypeShape(handler.catchType, language, tryLocation)
1548
- const binding = handler.catchBinding
1549
- const bindingFields = binding && typeof binding == "object" && !Array.isArray(binding)
1550
- ? Object.keys(binding).filter((key) => key != "sourceProvenance").sort().join(",") : ""
1551
- const bindingLocation = requireSemanticLocation(binding?.location, language, "catch binding location", tryLocation)
1552
-
1553
- if (!binding || binding.kind != "CatchBinding" || bindingFields != "kind,location,mutable,name,type" || binding.mutable !== false) {
1554
- unsupportedCapability(language, "malformed catch binding", bindingLocation)
1776
+ if (!Array.isArray(tryStatement.handlers) || (tryStatement.handlers.length == 0 && !tryStatement.finallyBody)) {
1777
+ unsupportedCapability(language, "TryStatement without an ordered handler list", tryLocation)
1555
1778
  }
1556
- validateTargetBindingIdentifier(language, binding.name, "catch binding", bindingLocation)
1557
- validateErrorTypeShape(binding.type, language, bindingLocation)
1558
- if (binding.type.declarationId != handler.catchType.declarationId) {
1559
- unsupportedCapability(language, "catch binding type mismatch", bindingLocation)
1779
+ validateBlock(tryStatement.body, language, tryLocation, loopContext, activePath, externalDeclarationIds)
1780
+ for (const handler of tryStatement.handlers) {
1781
+ const handlerLocation = requireSemanticLocation(handler?.location, language, "catch handler location", tryLocation)
1782
+ const handlerFields = handler && typeof handler == "object" && !Array.isArray(handler)
1783
+ ? Object.keys(handler).filter((key) => key != "sourceProvenance").sort().join(",") : ""
1784
+
1785
+ if (!handler || handler.kind != "CatchHandler" || handlerFields != "catchBinding,catchBody,catchType,kind,location") {
1786
+ unsupportedCapability(language, "malformed catch handler", handlerLocation)
1787
+ }
1788
+ validateErrorTypeShape(handler.catchType, language, handlerLocation)
1789
+ const binding = handler.catchBinding
1790
+ const bindingFields = binding && typeof binding == "object" && !Array.isArray(binding)
1791
+ ? Object.keys(binding).filter((key) => key != "sourceProvenance").sort().join(",") : ""
1792
+ const bindingLocation = requireSemanticLocation(binding?.location, language, "catch binding location", handlerLocation)
1793
+
1794
+ if (!binding || binding.kind != "CatchBinding" || bindingFields != "kind,location,mutable,name,type" || binding.mutable !== false) {
1795
+ unsupportedCapability(language, "malformed catch binding", bindingLocation)
1796
+ }
1797
+ validateTargetBindingIdentifier(language, binding.name, "catch binding", bindingLocation)
1798
+ validateErrorTypeShape(binding.type, language, bindingLocation)
1799
+ if (binding.type.declarationId != handler.catchType.declarationId) {
1800
+ unsupportedCapability(language, "catch binding type mismatch", bindingLocation)
1801
+ }
1802
+ validateBlock(handler.catchBody, language, handlerLocation, loopContext, activePath, externalDeclarationIds)
1560
1803
  }
1561
- validateBlock(handler.body, language, tryLocation, loopContext, activePath, externalDeclarationIds)
1562
- validateBlock(handler.catchBody, language, tryLocation, loopContext, activePath, externalDeclarationIds)
1804
+ if (tryStatement.finallyBody) validateBlock(tryStatement.finallyBody, language, tryLocation, loopContext, activePath, externalDeclarationIds)
1563
1805
  return
1564
1806
  }
1565
1807
 
@@ -2049,6 +2291,14 @@ export function emitType(writer, type, path, language, javaBoxed = false) {
2049
2291
  })
2050
2292
  return
2051
2293
  }
2294
+ if (type.kind == "InterfaceType") {
2295
+ const declaration = writer.interfaceForId(type.declarationId)
2296
+
2297
+ writer.mapped(writer.interfaceNameForId(type.declarationId), {
2298
+ mappingKind: "exact", name: declaration.name, node: type, path, role: "type"
2299
+ })
2300
+ return
2301
+ }
2052
2302
  if (type.kind == "OwnedResourceType") {
2053
2303
  const resource = writer.resourceForId(type.resourceId)
2054
2304
  writer.mapped(writer.capabilityNominalName(resource.name), {mappingKind: "exact", name: resource.name, node: type, path, role: "type"})
@@ -2664,7 +2914,8 @@ function blockContainsSignProducingOperation(block) {
2664
2914
  return expressionContainsSignProducingOperation(statement.condition) || blockContainsSignProducingOperation(statement.body)
2665
2915
  }
2666
2916
  if (statement.kind == "TryStatement") {
2667
- return blockContainsSignProducingOperation(statement.body) || blockContainsSignProducingOperation(statement.catchBody)
2917
+ return blockContainsSignProducingOperation(statement.body) || (statement.handlers ?? []).some((handler) => blockContainsSignProducingOperation(handler.catchBody)) ||
2918
+ (statement.finallyBody ? blockContainsSignProducingOperation(statement.finallyBody) : false)
2668
2919
  }
2669
2920
  if (statement.kind == "RaiseStatement") return expressionContainsSignProducingOperation(statement.error.message)
2670
2921
  if (statement.kind == "LocalDeclaration") return expressionContainsSignProducingOperation(statement.initializer)
@@ -376,10 +376,14 @@ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
376
376
  writer.synthetic(")\n", "error construction close", [statement.error], [`${path}/error`])
377
377
  return
378
378
  }
379
+ if (statement.kind == "RethrowStatement") {
380
+ writer.mapped("throw", {mappingKind: "anchor", node: statement, path})
381
+ writer.synthetic(" ", "throw spacing", [statement], [path])
382
+ writer.mapped(statement.binding, {mappingKind: "exact", node: statement.bindingNode, path: `${path}/bindingNode`, role: "name"})
383
+ writer.synthetic("\n", "line break", [statement], [path])
384
+ return
385
+ }
379
386
  if (statement.kind == "TryStatement") {
380
- const caught = writer.errorForId(statement.catchType.declarationId)
381
- const targetName = writer.errorNameForId(statement.catchType.declarationId)
382
-
383
387
  writer.mapped("try", {mappingKind: "anchor", node: statement, path})
384
388
  writer.synthetic(" ", "try spacing", [statement], [path])
385
389
  writer.mapped("{", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
@@ -387,21 +391,15 @@ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
387
391
  emitBlock(writer, statement.body, `${indent} `, `${path}/body`, canonicalizeZero)
388
392
  writer.synthetic(indent, "indentation", [statement], [path])
389
393
  writer.mapped("}", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
390
- writer.synthetic(" ", "catch spacing", [statement], [path])
391
- writer.mapped("catch", {mappingKind: "anchor", node: statement, path, role: "catch"})
392
- writer.synthetic(" (", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
393
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
394
- writer.synthetic(") {\n", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
395
- writer.synthetic(`${indent} if (!(`, "exact catch guard", [statement], [path])
396
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
397
- writer.synthetic(" instanceof ", "exact catch guard", [statement], [path])
398
- writer.mapped(targetName, {mappingKind: "exact", name: caught.name, node: statement.catchType, path: `${path}/catchType`, role: "type"})
399
- writer.synthetic(`)) {\n${indent} throw `, "unmatched error propagation", [statement], [path])
400
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
401
- writer.synthetic(`\n${indent} }\n`, "unmatched error propagation", [statement], [path])
402
- emitBlock(writer, statement.catchBody, `${indent} `, `${path}/catchBody`, canonicalizeZero)
403
- writer.synthetic(indent, "indentation", [statement], [path])
404
- writer.mapped("}", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
394
+ if (statement.handlers.length > 0) emitTypeScriptHandler(writer, statement, 0, indent, path, canonicalizeZero)
395
+ if (statement.finallyBody) {
396
+ writer.synthetic(" ", "finally spacing", [statement], [path])
397
+ writer.mapped("finally", {mappingKind: "anchor", node: statement, path, role: "finally"})
398
+ writer.synthetic(" {\n", "finally scaffolding", [statement], [path])
399
+ emitBlock(writer, statement.finallyBody, `${indent} `, `${path}/finallyBody`, canonicalizeZero)
400
+ writer.synthetic(indent, "indentation", [statement], [path])
401
+ writer.mapped("}", {mappingKind: "anchor", node: statement.finallyBody, path: `${path}/finallyBody`})
402
+ }
405
403
  writer.synthetic("\n", "line break", [statement], [path])
406
404
  return
407
405
  }
@@ -453,6 +451,73 @@ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
453
451
  writer.synthetic("\n", "line break", [statement], [path])
454
452
  }
455
453
 
454
+ /**
455
+ * Emits one ordered TypeScript catch handler with its exact nominal guard.
456
+ *
457
+ * TypeScript has no sibling catch clauses, so handler k > 0 hangs off handler k - 1's
458
+ * unmatched rethrow inside a nested try. Each handler body sits outside its own guard,
459
+ * so a raise from one handler body escapes outward instead of being caught by a later
460
+ * sibling handler, exactly like native ordered multi-catch.
461
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
462
+ * @param {import("../semantic/types.js").TryStatement} statement - Semantic try statement.
463
+ * @param {number} index - Zero-based ordered handler index.
464
+ * @param {string} indent - Leading indentation for this catch clause.
465
+ * @param {string} path - Exact JSON Pointer for the owning try statement.
466
+ * @param {boolean} canonicalizeZero - Whether scalar output canonicalizes zero.
467
+ * @returns {void}
468
+ */
469
+ function emitTypeScriptHandler(writer, statement, index, indent, path, canonicalizeZero) {
470
+ const handler = statement.handlers[index]
471
+ const handlerPath = `${path}/handlers/${index}`
472
+ const bodyIndent = `${indent} `
473
+ const caught = writer.errorForId(handler.catchType.declarationId)
474
+ const targetName = writer.errorNameForId(handler.catchType.declarationId)
475
+
476
+ writer.synthetic(" ", "catch spacing", [statement], [path])
477
+ writer.mapped("catch", {mappingKind: "anchor", node: handler, path: handlerPath, role: "catch"})
478
+ writer.synthetic(" (", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
479
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
480
+ writer.synthetic(") {\n", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
481
+ writer.synthetic(`${bodyIndent}if (!(`, "exact catch guard", [handler], [handlerPath])
482
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
483
+ writer.synthetic(" instanceof ", "exact catch guard", [handler], [handlerPath])
484
+ writer.mapped(targetName, {mappingKind: "exact", name: caught.name, node: handler.catchType, path: `${handlerPath}/catchType`, role: "type"})
485
+ writer.synthetic(")) {\n", "exact catch guard", [handler], [handlerPath])
486
+ if (index + 1 < statement.handlers.length) {
487
+ emitTypeScriptDispatch(writer, statement, index, indent, path, canonicalizeZero)
488
+ writer.synthetic(`${bodyIndent}} else {\n`, "matched error handling", [handler], [handlerPath])
489
+ emitBlock(writer, handler.catchBody, `${bodyIndent} `, `${handlerPath}/catchBody`, canonicalizeZero)
490
+ writer.synthetic(`${bodyIndent}}\n`, "matched error handling close", [handler], [handlerPath])
491
+ } else {
492
+ writer.synthetic(`${bodyIndent} throw `, "unmatched error propagation", [handler], [handlerPath])
493
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
494
+ writer.synthetic(`\n${bodyIndent}}\n`, "unmatched error propagation", [handler], [handlerPath])
495
+ emitBlock(writer, handler.catchBody, bodyIndent, `${handlerPath}/catchBody`, canonicalizeZero)
496
+ }
497
+ writer.synthetic(indent, "indentation", [handler], [handlerPath])
498
+ writer.mapped("}", {mappingKind: "anchor", node: handler.catchBody, path: `${handlerPath}/catchBody`})
499
+ }
500
+
501
+ /**
502
+ * Emits the nested re-dispatch of one unmatched caught value to the remaining ordered handlers.
503
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
504
+ * @param {import("../semantic/types.js").TryStatement} statement - Semantic try statement.
505
+ * @param {number} index - Index of the handler whose value is re-dispatched.
506
+ * @param {string} indent - Leading indentation for this dispatch try.
507
+ * @param {string} path - Exact JSON Pointer for the owning try statement.
508
+ * @param {boolean} canonicalizeZero - Whether scalar output canonicalizes zero.
509
+ * @returns {void}
510
+ */
511
+ function emitTypeScriptDispatch(writer, statement, index, indent, path, canonicalizeZero) {
512
+ writer.synthetic(`${indent} try {\n`, "ordered handler dispatch", [statement], [path])
513
+ writer.synthetic(`${indent} throw `, "ordered handler dispatch", [statement], [path])
514
+ writer.mapped(statement.handlers[index].catchBinding.name, {
515
+ mappingKind: "exact", node: statement.handlers[index].catchBinding, path: `${path}/handlers/${index}/catchBinding`, role: "name"
516
+ })
517
+ writer.synthetic(`\n${indent} }`, "ordered handler dispatch", [statement], [path])
518
+ emitTypeScriptHandler(writer, statement, index + 1, `${indent} `, path, canonicalizeZero)
519
+ }
520
+
456
521
  /**
457
522
  * Emits one TypeScript local statement.
458
523
  * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.