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
@@ -266,6 +266,7 @@ function semanticTypeForIdentity(identity) {
266
266
  if (identity.kind == "RecordType") return {arguments: identity.arguments?.map(semanticTypeForValueIdentity),
267
267
  declarationId: identity.declarationId, kind: "RecordType"}
268
268
  if (identity.kind == "ReferenceType") return {declarationId: identity.declarationId, kind: "ReferenceType"}
269
+ if (identity.kind == "InterfaceType") return {declarationId: identity.declarationId, kind: "InterfaceType"}
269
270
  if (identity.kind == "OwnedResourceType") return {kind: "OwnedResourceType", resourceId: identity.resourceId}
270
271
  if (identity.kind == "OwnedReferenceType") return {declarationId: identity.declarationId, kind: "OwnedReferenceType"}
271
272
  return {kind: "TypeVariableReference", location: /** @type {never} */ (undefined), parameterId: identity.parameterId}
@@ -478,7 +479,8 @@ function blockExcluded(block) {
478
479
  }
479
480
  const nested = statement.kind == "IfStatement" ? [statement.consequent, ...(statement.alternate ? [statement.alternate] : [])] :
480
481
  statement.kind == "ForEachStatement" || statement.kind == "ForEachMapStatement" || statement.kind == "WhileStatement" ? [statement.body] :
481
- statement.kind == "TryStatement" ? [statement.body, statement.catchBody] : []
482
+ statement.kind == "TryStatement" ? [statement.body, ...statement.handlers.map((handler) => handler.catchBody),
483
+ ...(statement.finallyBody ? [statement.finallyBody] : [])] : []
482
484
  for (const child of nested) {
483
485
  const found = blockExcluded(child)
484
486
  if (found) return found
@@ -391,9 +391,15 @@ function emitStatement(writer, statement, indent, path) {
391
391
  writer.synthetic(");\n", "error construction close", [statement.error], [`${path}/error`])
392
392
  return
393
393
  }
394
+ if (statement.kind == "RethrowStatement") {
395
+ writer.mapped("throw", {mappingKind: "anchor", node: statement, path})
396
+ writer.synthetic(" ", "throw spacing", [statement], [path])
397
+ writer.mapped(statement.binding, {mappingKind: "exact", node: statement.bindingNode, path: `${path}/bindingNode`, role: "name"})
398
+ writer.mapped(";", {mappingKind: "anchor", node: statement, path})
399
+ writer.synthetic("\n", "line break", [statement], [path])
400
+ return
401
+ }
394
402
  if (statement.kind == "TryStatement") {
395
- const caught = writer.errorForId(statement.catchType.declarationId)
396
-
397
403
  writer.mapped("try", {mappingKind: "anchor", node: statement, path})
398
404
  writer.synthetic(" ", "try spacing", [statement], [path])
399
405
  writer.mapped("{", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
@@ -401,18 +407,31 @@ function emitStatement(writer, statement, indent, path) {
401
407
  emitBlock(writer, statement.body, `${indent} `, `${path}/body`)
402
408
  writer.synthetic(indent, "indentation", [statement], [path])
403
409
  writer.mapped("}", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
404
- writer.synthetic(" ", "catch spacing", [statement], [path])
405
- writer.mapped("catch", {mappingKind: "anchor", node: statement, path, role: "catch"})
406
- writer.synthetic(" (", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
407
- writer.mapped(writer.errorNameForId(statement.catchType.declarationId), {mappingKind: "exact", name: caught.name, node: statement.catchType, path: `${path}/catchType`, role: "type"})
408
- writer.synthetic(" ", "catch binding spacing", [statement.catchBinding], [`${path}/catchBinding`])
409
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
410
- writer.synthetic(") ", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
411
- writer.mapped("{", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
412
- writer.synthetic("\n", "line break", [statement], [path])
413
- emitBlock(writer, statement.catchBody, `${indent} `, `${path}/catchBody`)
414
- writer.synthetic(indent, "indentation", [statement], [path])
415
- writer.mapped("}", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
410
+ for (const [index, handler] of statement.handlers.entries()) {
411
+ const handlerPath = `${path}/handlers/${index}`
412
+ const caught = writer.errorForId(handler.catchType.declarationId)
413
+
414
+ writer.synthetic(" ", "catch spacing", [handler], [handlerPath])
415
+ writer.mapped("catch", {mappingKind: "anchor", node: handler, path: handlerPath, role: "catch"})
416
+ writer.synthetic(" (", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
417
+ writer.mapped(writer.errorNameForId(handler.catchType.declarationId), {mappingKind: "exact", name: caught.name, node: handler.catchType, path: `${handlerPath}/catchType`, role: "type"})
418
+ writer.synthetic(" ", "catch binding spacing", [handler.catchBinding], [`${handlerPath}/catchBinding`])
419
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
420
+ writer.synthetic(") ", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
421
+ writer.mapped("{", {mappingKind: "anchor", node: handler.catchBody, path: `${handlerPath}/catchBody`})
422
+ writer.synthetic("\n", "line break", [handler], [handlerPath])
423
+ emitBlock(writer, handler.catchBody, `${indent} `, `${handlerPath}/catchBody`)
424
+ writer.synthetic(indent, "indentation", [handler], [handlerPath])
425
+ writer.mapped("}", {mappingKind: "anchor", node: handler.catchBody, path: `${handlerPath}/catchBody`})
426
+ }
427
+ if (statement.finallyBody) {
428
+ writer.synthetic(" ", "finally spacing", [statement], [path])
429
+ writer.mapped("finally", {mappingKind: "anchor", node: statement, path, role: "finally"})
430
+ writer.synthetic(" {\n", "finally scaffolding", [statement], [path])
431
+ emitBlock(writer, statement.finallyBody, `${indent} `, `${path}/finallyBody`)
432
+ writer.synthetic(indent, "indentation", [statement], [path])
433
+ writer.mapped("}", {mappingKind: "anchor", node: statement.finallyBody, path: `${path}/finallyBody`})
434
+ }
416
435
  writer.synthetic("\n", "line break", [statement], [path])
417
436
  return
418
437
  }
@@ -427,10 +427,14 @@ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
427
427
  writer.synthetic(")\n", "error construction close", [statement.error], [`${path}/error`])
428
428
  return
429
429
  }
430
+ if (statement.kind == "RethrowStatement") {
431
+ writer.mapped("throw", {mappingKind: "anchor", node: statement, path})
432
+ writer.synthetic(" ", "throw spacing", [statement], [path])
433
+ writer.mapped(statement.binding, {mappingKind: "exact", node: statement.bindingNode, path: `${path}/bindingNode`, role: "name"})
434
+ writer.synthetic("\n", "line break", [statement], [path])
435
+ return
436
+ }
430
437
  if (statement.kind == "TryStatement") {
431
- const caught = writer.errorForId(statement.catchType.declarationId)
432
- const targetName = writer.errorNameForId(statement.catchType.declarationId)
433
-
434
438
  writer.mapped("try", {mappingKind: "anchor", node: statement, path})
435
439
  writer.synthetic(" ", "try spacing", [statement], [path])
436
440
  writer.mapped("{", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
@@ -438,21 +442,15 @@ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
438
442
  emitBlock(writer, statement.body, `${indent} `, `${path}/body`, canonicalizeZero)
439
443
  writer.synthetic(indent, "indentation", [statement], [path])
440
444
  writer.mapped("}", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
441
- writer.synthetic(" ", "catch spacing", [statement], [path])
442
- writer.mapped("catch", {mappingKind: "anchor", node: statement, path, role: "catch"})
443
- writer.synthetic(" (", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
444
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
445
- writer.synthetic(") {\n", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
446
- writer.synthetic(`${indent} if (!(`, "exact catch guard", [statement], [path])
447
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
448
- writer.synthetic(" instanceof ", "exact catch guard", [statement], [path])
449
- writer.mapped(targetName, {mappingKind: "exact", name: caught.name, node: statement.catchType, path: `${path}/catchType`, role: "type"})
450
- writer.synthetic(`)) {\n${indent} throw `, "unmatched error propagation", [statement], [path])
451
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
452
- writer.synthetic(`\n${indent} }\n`, "unmatched error propagation", [statement], [path])
453
- emitBlock(writer, statement.catchBody, `${indent} `, `${path}/catchBody`, canonicalizeZero)
454
- writer.synthetic(indent, "indentation", [statement], [path])
455
- writer.mapped("}", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
445
+ if (statement.handlers.length > 0) emitJavaScriptHandler(writer, statement, 0, indent, path, canonicalizeZero)
446
+ if (statement.finallyBody) {
447
+ writer.synthetic(" ", "finally spacing", [statement], [path])
448
+ writer.mapped("finally", {mappingKind: "anchor", node: statement, path, role: "finally"})
449
+ writer.synthetic(" {\n", "finally scaffolding", [statement], [path])
450
+ emitBlock(writer, statement.finallyBody, `${indent} `, `${path}/finallyBody`, canonicalizeZero)
451
+ writer.synthetic(indent, "indentation", [statement], [path])
452
+ writer.mapped("}", {mappingKind: "anchor", node: statement.finallyBody, path: `${path}/finallyBody`})
453
+ }
456
454
  writer.synthetic("\n", "line break", [statement], [path])
457
455
  return
458
456
  }
@@ -505,6 +503,73 @@ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
505
503
  writer.synthetic("\n", "line break", [statement], [path])
506
504
  }
507
505
 
506
+ /**
507
+ * Emits one ordered JavaScript catch handler with its exact nominal guard.
508
+ *
509
+ * JavaScript has no sibling catch clauses, so handler k > 0 hangs off handler k - 1's
510
+ * unmatched rethrow inside a nested try. Each handler body sits outside its own guard,
511
+ * so a raise from one handler body escapes outward instead of being caught by a later
512
+ * sibling handler, exactly like native ordered multi-catch.
513
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
514
+ * @param {import("../semantic/types.js").TryStatement} statement - Semantic try statement.
515
+ * @param {number} index - Zero-based ordered handler index.
516
+ * @param {string} indent - Leading indentation for this catch clause.
517
+ * @param {string} path - Exact JSON Pointer for the owning try statement.
518
+ * @param {boolean} canonicalizeZero - Whether scalar output canonicalizes zero.
519
+ * @returns {void}
520
+ */
521
+ function emitJavaScriptHandler(writer, statement, index, indent, path, canonicalizeZero) {
522
+ const handler = statement.handlers[index]
523
+ const handlerPath = `${path}/handlers/${index}`
524
+ const bodyIndent = `${indent} `
525
+ const caught = writer.errorForId(handler.catchType.declarationId)
526
+ const targetName = writer.errorNameForId(handler.catchType.declarationId)
527
+
528
+ writer.synthetic(" ", "catch spacing", [statement], [path])
529
+ writer.mapped("catch", {mappingKind: "anchor", node: handler, path: handlerPath, role: "catch"})
530
+ writer.synthetic(" (", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
531
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
532
+ writer.synthetic(") {\n", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
533
+ writer.synthetic(`${bodyIndent}if (!(`, "exact catch guard", [handler], [handlerPath])
534
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
535
+ writer.synthetic(" instanceof ", "exact catch guard", [handler], [handlerPath])
536
+ writer.mapped(targetName, {mappingKind: "exact", name: caught.name, node: handler.catchType, path: `${handlerPath}/catchType`, role: "type"})
537
+ writer.synthetic(")) {\n", "exact catch guard", [handler], [handlerPath])
538
+ if (index + 1 < statement.handlers.length) {
539
+ emitJavaScriptDispatch(writer, statement, index, indent, path, canonicalizeZero)
540
+ writer.synthetic(`${bodyIndent}} else {\n`, "matched error handling", [handler], [handlerPath])
541
+ emitBlock(writer, handler.catchBody, `${bodyIndent} `, `${handlerPath}/catchBody`, canonicalizeZero)
542
+ writer.synthetic(`${bodyIndent}}\n`, "matched error handling close", [handler], [handlerPath])
543
+ } else {
544
+ writer.synthetic(`${bodyIndent} throw `, "unmatched error propagation", [handler], [handlerPath])
545
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
546
+ writer.synthetic(`\n${bodyIndent}}\n`, "unmatched error propagation", [handler], [handlerPath])
547
+ emitBlock(writer, handler.catchBody, bodyIndent, `${handlerPath}/catchBody`, canonicalizeZero)
548
+ }
549
+ writer.synthetic(indent, "indentation", [handler], [handlerPath])
550
+ writer.mapped("}", {mappingKind: "anchor", node: handler.catchBody, path: `${handlerPath}/catchBody`})
551
+ }
552
+
553
+ /**
554
+ * Emits the nested re-dispatch of one unmatched caught value to the remaining ordered handlers.
555
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
556
+ * @param {import("../semantic/types.js").TryStatement} statement - Semantic try statement.
557
+ * @param {number} index - Index of the handler whose value is re-dispatched.
558
+ * @param {string} indent - Leading indentation for this dispatch try.
559
+ * @param {string} path - Exact JSON Pointer for the owning try statement.
560
+ * @param {boolean} canonicalizeZero - Whether scalar output canonicalizes zero.
561
+ * @returns {void}
562
+ */
563
+ function emitJavaScriptDispatch(writer, statement, index, indent, path, canonicalizeZero) {
564
+ writer.synthetic(`${indent} try {\n`, "ordered handler dispatch", [statement], [path])
565
+ writer.synthetic(`${indent} throw `, "ordered handler dispatch", [statement], [path])
566
+ writer.mapped(statement.handlers[index].catchBinding.name, {
567
+ mappingKind: "exact", node: statement.handlers[index].catchBinding, path: `${path}/handlers/${index}/catchBinding`, role: "name"
568
+ })
569
+ writer.synthetic(`\n${indent} }`, "ordered handler dispatch", [statement], [path])
570
+ emitJavaScriptHandler(writer, statement, index + 1, `${indent} `, path, canonicalizeZero)
571
+ }
572
+
508
573
  /**
509
574
  * Emits one JavaScript local statement and its JSDoc type carrier.
510
575
  * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
@@ -15,6 +15,7 @@ export function generatePhp(module, writer) {
15
15
  emitEffectSupport(writer, module, "php")
16
16
  const records = module.records ?? []
17
17
  const classes = module.classes ?? []
18
+ const interfaces = module.interfaces ?? []
18
19
  const errors = module.errors ?? []
19
20
 
20
21
  errors.forEach((error, index) => {
@@ -72,6 +73,12 @@ export function generatePhp(module, writer) {
72
73
  })
73
74
  if (records.length > 0) writer.synthetic("\n\n", "record/function separator", [module])
74
75
 
76
+ interfaces.forEach((declaration, interfaceIndex) => {
77
+ if (interfaceIndex > 0) writer.synthetic("\n\n", "interface separator", [declaration], [`/interfaces/${interfaceIndex}`])
78
+ emitInterface(writer, declaration, interfaceIndex)
79
+ })
80
+ if (interfaces.length > 0) writer.synthetic("\n\n", "interface/class separator", [module])
81
+
75
82
  classes.forEach((declaration, classIndex) => {
76
83
  if (classIndex > 0) writer.synthetic("\n\n", "reference class separator", [declaration], [`/classes/${classIndex}`])
77
84
  emitReferenceClass(writer, declaration, classIndex)
@@ -156,6 +163,15 @@ function emitReferenceClass(writer, declaration, classIndex) {
156
163
  writer.mapped("final class", {mappingKind: "anchor", node: declaration, path})
157
164
  writer.synthetic(" ", "reference class spacing", [declaration], [path])
158
165
  writer.mapped(declaration.name, {mappingKind: "exact", node: declaration, path, role: "name"})
166
+ const implemented = declaration.implements ?? []
167
+
168
+ if (implemented.length > 0) {
169
+ writer.mapped(" implements ", {mappingKind: "anchor", node: declaration, path})
170
+ implemented.forEach((reference, referenceIndex) => {
171
+ if (referenceIndex > 0) writer.mapped(", ", {mappingKind: "anchor", node: declaration, path})
172
+ emitType(writer, reference, `${path}/implements/${referenceIndex}`, "php")
173
+ })
174
+ }
159
175
  writer.synthetic(" {\n", "reference class open", [declaration], [path])
160
176
  declaration.fields.forEach((field, index) => {
161
177
  const fieldPath = `${path}/fields/${index}`
@@ -191,6 +207,44 @@ function emitReferenceClass(writer, declaration, classIndex) {
191
207
  writer.mapped("}", {mappingKind: "anchor", node: declaration, path})
192
208
  }
193
209
 
210
+ /**
211
+ * Emits one PHP native interface declaration with signature-only members.
212
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
213
+ * @param {import("../semantic/types.js").InterfaceDeclaration} declaration - Semantic interface.
214
+ * @param {number} interfaceIndex - Declaration index.
215
+ * @returns {void}
216
+ */
217
+ function emitInterface(writer, declaration, interfaceIndex) {
218
+ const path = `/interfaces/${interfaceIndex}`
219
+
220
+ writer.mapped("interface", {mappingKind: "anchor", node: declaration, path})
221
+ writer.synthetic(" ", "interface spacing", [declaration], [path])
222
+ writer.mapped(declaration.name, {mappingKind: "exact", node: declaration, path, role: "name"})
223
+ const parents = declaration.extends ?? []
224
+
225
+ if (parents.length > 0) {
226
+ writer.mapped(" extends ", {mappingKind: "anchor", node: declaration, path})
227
+ parents.forEach((parent, parentIndex) => {
228
+ if (parentIndex > 0) writer.mapped(", ", {mappingKind: "anchor", node: declaration, path})
229
+ emitType(writer, parent, `${path}/extends/${parentIndex}`, "php")
230
+ })
231
+ }
232
+ writer.synthetic(" {\n", "interface open", [declaration], [path])
233
+ declaration.methods.forEach((method, methodIndex) => {
234
+ const methodPath = `${path}/methods/${methodIndex}`
235
+
236
+ if (methodIndex > 0) writer.synthetic("\n", "line break", [method], [methodPath])
237
+ writer.synthetic(" public function ", "interface method scaffolding", [method], [methodPath])
238
+ writer.mapped(method.name, {mappingKind: "exact", node: method, path: methodPath, role: "name"})
239
+ emitPhpParameters(writer, method.parameters, `${methodPath}/parameters`)
240
+ writer.synthetic(": ", "interface method return separator", [method], [methodPath])
241
+ emitNativePhpType(writer, method.returnType, `${methodPath}/returnType`)
242
+ writer.mapped(";", {mappingKind: "anchor", node: method, path: methodPath})
243
+ })
244
+ writer.synthetic("\n", "line break", [declaration], [path])
245
+ writer.mapped("}", {mappingKind: "anchor", node: declaration, path})
246
+ }
247
+
194
248
  /**
195
249
  * Emits one PHP parameter list.
196
250
  * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
@@ -355,7 +409,7 @@ function emitNativePhpType(writer, type, path) {
355
409
  })
356
410
  return
357
411
  }
358
- if (type.kind == "ReferenceType" || type.kind == "OwnedResourceType" || type.kind == "OwnedReferenceType") {
412
+ if (type.kind == "ReferenceType" || type.kind == "InterfaceType" || type.kind == "OwnedResourceType" || type.kind == "OwnedReferenceType") {
359
413
  emitType(writer, type, path, "php")
360
414
  return
361
415
  }
@@ -365,7 +419,7 @@ function emitNativePhpType(writer, type, path) {
365
419
  }
366
420
  if (type.kind == "OptionalType") {
367
421
  writer.mapped("?", {mappingKind: "exact", node: type, path, role: "type"})
368
- if (type.valueType.kind == "RecordType") emitNativePhpType(writer, type.valueType, `${path}/valueType`)
422
+ if (type.valueType.kind == "RecordType" || type.valueType.kind == "InterfaceType") emitNativePhpType(writer, type.valueType, `${path}/valueType`)
369
423
  else if (type.valueType.kind == "TypeReference") emitType(writer, type.valueType, `${path}/valueType`, "php")
370
424
  else writer.mapped("array", {mappingKind: "anchor", node: type, path})
371
425
  return
@@ -489,9 +543,15 @@ function emitStatement(writer, statement, indent, path) {
489
543
  writer.synthetic(");\n", "error construction close", [statement.error], [`${path}/error`])
490
544
  return
491
545
  }
546
+ if (statement.kind == "RethrowStatement") {
547
+ writer.mapped("throw", {mappingKind: "anchor", node: statement, path})
548
+ writer.synthetic(" ", "throw spacing", [statement], [path])
549
+ writer.mapped(`$${statement.binding}`, {mappingKind: "exact", node: statement.bindingNode, path: `${path}/bindingNode`, role: "name"})
550
+ writer.mapped(";", {mappingKind: "anchor", node: statement, path})
551
+ writer.synthetic("\n", "line break", [statement], [path])
552
+ return
553
+ }
492
554
  if (statement.kind == "TryStatement") {
493
- const caught = writer.errorForId(statement.catchType.declarationId)
494
-
495
555
  writer.mapped("try", {mappingKind: "anchor", node: statement, path})
496
556
  writer.synthetic(" ", "try spacing", [statement], [path])
497
557
  writer.mapped("{", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
@@ -499,18 +559,31 @@ function emitStatement(writer, statement, indent, path) {
499
559
  emitBlock(writer, statement.body, `${indent} `, `${path}/body`)
500
560
  writer.synthetic(indent, "indentation", [statement], [path])
501
561
  writer.mapped("}", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
502
- writer.synthetic(" ", "catch spacing", [statement], [path])
503
- writer.mapped("catch", {mappingKind: "anchor", node: statement, path, role: "catch"})
504
- writer.synthetic(" (", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
505
- writer.mapped(writer.errorNameForId(statement.catchType.declarationId), {mappingKind: "exact", name: caught.name, node: statement.catchType, path: `${path}/catchType`, role: "type"})
506
- writer.synthetic(" ", "catch binding spacing", [statement.catchBinding], [`${path}/catchBinding`])
507
- writer.mapped(`$${statement.catchBinding.name}`, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
508
- writer.synthetic(") ", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
509
- writer.mapped("{", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
510
- writer.synthetic("\n", "line break", [statement], [path])
511
- emitBlock(writer, statement.catchBody, `${indent} `, `${path}/catchBody`)
512
- writer.synthetic(indent, "indentation", [statement], [path])
513
- writer.mapped("}", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
562
+ for (const [index, handler] of statement.handlers.entries()) {
563
+ const handlerPath = `${path}/handlers/${index}`
564
+ const caught = writer.errorForId(handler.catchType.declarationId)
565
+
566
+ writer.synthetic(" ", "catch spacing", [handler], [handlerPath])
567
+ writer.mapped("catch", {mappingKind: "anchor", node: handler, path: handlerPath, role: "catch"})
568
+ writer.synthetic(" (", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
569
+ writer.mapped(writer.errorNameForId(handler.catchType.declarationId), {mappingKind: "exact", name: caught.name, node: handler.catchType, path: `${handlerPath}/catchType`, role: "type"})
570
+ writer.synthetic(" ", "catch binding spacing", [handler.catchBinding], [`${handlerPath}/catchBinding`])
571
+ writer.mapped(`$${handler.catchBinding.name}`, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
572
+ writer.synthetic(") ", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
573
+ writer.mapped("{", {mappingKind: "anchor", node: handler.catchBody, path: `${handlerPath}/catchBody`})
574
+ writer.synthetic("\n", "line break", [handler], [handlerPath])
575
+ emitBlock(writer, handler.catchBody, `${indent} `, `${handlerPath}/catchBody`)
576
+ writer.synthetic(indent, "indentation", [handler], [handlerPath])
577
+ writer.mapped("}", {mappingKind: "anchor", node: handler.catchBody, path: `${handlerPath}/catchBody`})
578
+ }
579
+ if (statement.finallyBody) {
580
+ writer.synthetic(" ", "finally spacing", [statement], [path])
581
+ writer.mapped("finally", {mappingKind: "anchor", node: statement, path, role: "finally"})
582
+ writer.synthetic(" {\n", "finally scaffolding", [statement], [path])
583
+ emitBlock(writer, statement.finallyBody, `${indent} `, `${path}/finallyBody`)
584
+ writer.synthetic(indent, "indentation", [statement], [path])
585
+ writer.mapped("}", {mappingKind: "anchor", node: statement.finallyBody, path: `${path}/finallyBody`})
586
+ }
514
587
  writer.synthetic("\n", "line break", [statement], [path])
515
588
  return
516
589
  }
@@ -364,22 +364,46 @@ function emitStatement(writer, statement, indent, path) {
364
364
  writer.synthetic("\n", "line break", [statement], [path])
365
365
  return
366
366
  }
367
+ if (statement.kind == "RethrowStatement") {
368
+ writer.mapped("raise", {mappingKind: "anchor", node: statement, path})
369
+ writer.synthetic(" ", "raise spacing", [statement], [path])
370
+ writer.mapped(statement.binding, {mappingKind: "exact", node: statement.bindingNode, path: `${path}/bindingNode`, role: "name"})
371
+ writer.synthetic("\n", "line break", [statement], [path])
372
+ return
373
+ }
367
374
  if (statement.kind == "TryStatement") {
368
- const caught = writer.errorForId(statement.catchType.declarationId)
369
-
370
375
  writer.mapped("begin", {mappingKind: "anchor", node: statement, path})
371
376
  writer.synthetic("\n", "line break", [statement], [path])
372
377
  emitBlock(writer, statement.body, `${indent} `, `${path}/body`)
373
- writer.synthetic(indent, "indentation", [statement], [path])
374
- writer.mapped("rescue", {mappingKind: "anchor", node: statement, path, role: "catch"})
375
- writer.synthetic(" ", "rescue spacing", [statement], [path])
376
- writer.mapped(writer.errorNameForId(statement.catchType.declarationId), {mappingKind: "exact", name: caught.name, node: statement.catchType, path: `${path}/catchType`, role: "type"})
377
- writer.synthetic(" => ", "catch binding separator", [statement.catchBinding], [`${path}/catchBinding`])
378
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
379
- writer.synthetic("\n", "line break", [statement], [path])
380
- emitBlock(writer, statement.catchBody, `${indent} `, `${path}/catchBody`)
381
- writer.synthetic(indent, "indentation", [statement], [path])
382
- writer.mapped("end", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
378
+ for (const [index, handler] of statement.handlers.entries()) {
379
+ const handlerPath = `${path}/handlers/${index}`
380
+ const caught = writer.errorForId(handler.catchType.declarationId)
381
+
382
+ writer.synthetic(indent, "indentation", [handler], [handlerPath])
383
+ writer.mapped("rescue", {mappingKind: "anchor", node: handler, path: handlerPath, role: "catch"})
384
+ writer.synthetic(" ", "rescue spacing", [handler], [handlerPath])
385
+ writer.mapped(writer.errorNameForId(handler.catchType.declarationId), {mappingKind: "exact", name: caught.name, node: handler.catchType, path: `${handlerPath}/catchType`, role: "type"})
386
+ writer.synthetic(" => ", "catch binding separator", [handler.catchBinding], [`${handlerPath}/catchBinding`])
387
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
388
+ writer.synthetic("\n", "line break", [handler], [handlerPath])
389
+ emitBlock(writer, handler.catchBody, `${indent} `, `${handlerPath}/catchBody`)
390
+ }
391
+ if (statement.finallyBody) {
392
+ writer.synthetic(indent, "indentation", [statement], [path])
393
+ writer.mapped("ensure", {mappingKind: "anchor", node: statement, path, role: "ensure"})
394
+ writer.synthetic("\n", "line break", [statement], [path])
395
+ emitBlock(writer, statement.finallyBody, `${indent} `, `${path}/finallyBody`)
396
+ writer.synthetic(indent, "indentation", [statement], [path])
397
+ writer.mapped("end", {mappingKind: "anchor", node: statement.finallyBody, path: `${path}/finallyBody`})
398
+ } else if (statement.handlers.length > 0) {
399
+ const lastIndex = statement.handlers.length - 1
400
+
401
+ writer.synthetic(indent, "indentation", [statement], [path])
402
+ writer.mapped("end", {mappingKind: "anchor", node: statement.handlers[lastIndex].catchBody, path: `${path}/handlers/${lastIndex}/catchBody`})
403
+ } else {
404
+ writer.synthetic(indent, "indentation", [statement], [path])
405
+ writer.mapped("end", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
406
+ }
383
407
  writer.synthetic("\n", "line break", [statement], [path])
384
408
  return
385
409
  }
@@ -37,7 +37,7 @@ export function emitScalarType(language, type) {
37
37
  * @returns {string} Exact target spelling.
38
38
  */
39
39
  export function emitSemanticType(language, type, javaBoxed = false) {
40
- if (type.kind == "RecordType" || type.kind == "ReferenceType") {
40
+ if (type.kind == "RecordType" || type.kind == "ReferenceType" || type.kind == "InterfaceType") {
41
41
  throw new TypeError("Nominal type emission requires declaration context.")
42
42
  }
43
43
  if (type.kind == "OwnedResourceType" || type.kind == "OwnedReferenceType") {