semantifold 0.12.0 → 0.13.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 (53) hide show
  1. package/README.md +1 -0
  2. package/build/src/backends/effects.js +3 -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.js +33 -14
  6. package/build/src/backends/ruby.js +37 -12
  7. package/build/src/backends/shared.d.ts.map +1 -1
  8. package/build/src/backends/shared.js +91 -36
  9. package/build/src/backends/typescript.js +83 -18
  10. package/build/src/frontends/java.d.ts +8 -0
  11. package/build/src/frontends/java.d.ts.map +1 -1
  12. package/build/src/frontends/java.js +77 -33
  13. package/build/src/frontends/javascript-typescript.d.ts +2 -0
  14. package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
  15. package/build/src/frontends/javascript-typescript.js +118 -37
  16. package/build/src/frontends/php.d.ts +8 -0
  17. package/build/src/frontends/php.d.ts.map +1 -1
  18. package/build/src/frontends/php.js +68 -29
  19. package/build/src/frontends/ruby.d.ts +3 -0
  20. package/build/src/frontends/ruby.d.ts.map +1 -1
  21. package/build/src/frontends/ruby.js +103 -30
  22. package/build/src/semantic/effects.d.ts.map +1 -1
  23. package/build/src/semantic/effects.js +18 -6
  24. package/build/src/semantic/lifetime.js +28 -4
  25. package/build/src/semantic/provenance.d.ts.map +1 -1
  26. package/build/src/semantic/provenance.js +26 -7
  27. package/build/src/semantic/types.d.ts +76 -16
  28. package/build/src/semantic/types.d.ts.map +1 -1
  29. package/build/src/semantic/types.js +23 -7
  30. package/build/src/semantic/validate.d.ts.map +1 -1
  31. package/build/src/semantic/validate.js +105 -34
  32. package/docs/adr/20261026-typed-error-handling-extension.md +55 -0
  33. package/docs/adr-numeric-tower.md +156 -0
  34. package/docs/architecture.md +1 -1
  35. package/docs/language-support.md +7 -7
  36. package/docs/testing.md +5 -3
  37. package/package.json +1 -1
  38. package/src/backends/effects.js +2 -1
  39. package/src/backends/java.js +33 -14
  40. package/src/backends/javascript.js +83 -18
  41. package/src/backends/php.js +33 -14
  42. package/src/backends/ruby.js +36 -12
  43. package/src/backends/shared.js +88 -41
  44. package/src/backends/typescript.js +83 -18
  45. package/src/frontends/java.js +82 -33
  46. package/src/frontends/javascript-typescript.js +130 -38
  47. package/src/frontends/php.js +68 -27
  48. package/src/frontends/ruby.js +107 -28
  49. package/src/semantic/effects.js +17 -5
  50. package/src/semantic/lifetime.js +29 -3
  51. package/src/semantic/provenance.js +27 -12
  52. package/src/semantic/types.js +24 -6
  53. package/src/semantic/validate.js +104 -35
@@ -123,6 +123,10 @@ function analyzeStatement(statement, state, fail, locals, errorEffects) {
123
123
  ? {errorId: statement.error.error.declarationId, kind: "raise", location: statement.location, state: flow.state}
124
124
  : {...flow, location: statement.location})
125
125
  }
126
+ if (statement.kind == "RethrowStatement") {
127
+ // A rethrow re-raises the caught binding's exact nominal type, so an enclosing handler must see it.
128
+ return [{errorId: statement.bindingNode?.type?.declarationId, kind: "raise", location: statement.location, state: cloneState(state)}]
129
+ }
126
130
  if (statement.kind == "IfStatement") {
127
131
  const conditions = evaluateExpression(statement.condition, cloneState(state), fail, errorEffects)
128
132
  /** @type {LifetimeExit[]} */
@@ -142,10 +146,32 @@ function analyzeStatement(statement, state, fail, locals, errorEffects) {
142
146
  if (statement.kind == "TryStatement") {
143
147
  const bodyExits = analyzeBlock(statement.body, [{kind: "normal", state: cloneState(state)}], fail, errorEffects)
144
148
  /** @type {LifetimeExit[]} */
145
- const result = bodyExits.filter((flow) => flow.kind != "raise" || flow.errorId != statement.catchType.declarationId)
149
+ let result = bodyExits
150
+
151
+ for (const handler of statement.handlers ?? []) {
152
+ const caughtId = handler.catchType?.declarationId
153
+ const caught = result.filter((flow) => flow.kind == "raise" && flow.errorId == caughtId)
154
+
155
+ if (caught.length == 0) continue
156
+ result = result.filter((flow) => flow.kind != "raise" || flow.errorId != caughtId)
157
+ for (const flow of caught) {
158
+ result.push(...analyzeBlock(handler.catchBody, [{kind: "normal", state: cloneState(flow.state)}], fail, errorEffects))
159
+ }
160
+ }
161
+ if (statement.finallyBody) {
162
+ /** @type {LifetimeExit[]} */
163
+ const afterFinally = []
164
+
165
+ for (const flow of result) {
166
+ const finallyExits = analyzeBlock(statement.finallyBody, [{kind: "normal", state: cloneState(flow.state)}], fail, errorEffects)
146
167
 
147
- for (const caught of bodyExits.filter((flow) => flow.kind == "raise" && flow.errorId == statement.catchType.declarationId)) {
148
- result.push(...analyzeBlock(statement.catchBody, [{kind: "normal", state: cloneState(caught.state)}], fail, errorEffects))
168
+ for (const finallyExit of finallyExits) {
169
+ afterFinally.push(finallyExit.kind == "normal"
170
+ ? {...flow, location: statement.location}
171
+ : {...finallyExit, location: statement.location})
172
+ }
173
+ }
174
+ result = afterFinally
149
175
  }
150
176
  return mergeEquivalentNormals(result, statement.location, fail)
151
177
  }
@@ -580,8 +580,14 @@ export function semanticEntries(module) {
580
580
  visit(node.body, `${path}/body`, location)
581
581
  } else if (node.kind == "RaiseStatement") {
582
582
  visit(node.error, `${path}/error`, location)
583
+ } else if (node.kind == "RethrowStatement") {
584
+ visit(node.bindingNode, `${path}/bindingNode`, location)
585
+ visit(node.bindingNode, `${path}/bindingNode/name`, location)
583
586
  } else if (node.kind == "TryStatement") {
584
587
  visit(node.body, `${path}/body`, location)
588
+ node.handlers.forEach((child, index) => visit(child, `${path}/handlers/${index}`, location))
589
+ if (node.finallyBody) visit(node.finallyBody, `${path}/finallyBody`, location)
590
+ } else if (node.kind == "CatchHandler") {
585
591
  visit(node.catchType, `${path}/catchType`, location)
586
592
  visit(node.catchBinding, `${path}/catchBinding`, location)
587
593
  visit(node.catchBody, `${path}/catchBody`, location)
@@ -895,19 +901,28 @@ function resolveSymbols(module, records) {
895
901
  visitType(statement.error.error, `${statementPath}/error/error`)
896
902
  reference(statement.error, errorSymbols.get(statement.error.error.declarationId), "construct", `${statementPath}/error`)
897
903
  visitExpression(statement.error.message, scope, `${statementPath}/error/message`)
904
+ } else if (statement.kind == "RethrowStatement") {
905
+ const symbol = scope.get(statement.binding)
906
+
907
+ if (symbol) reference(statement, symbol, "read", statementPath)
898
908
  } else if (statement.kind == "TryStatement") {
899
909
  visitBlock(statement.body, scope, `${statementPath}/body`)
900
- visitType(statement.catchType, `${statementPath}/catchType`)
901
- visitType(statement.catchBinding.type, `${statementPath}/catchBinding/type`)
902
- const catchScope = new Map(scope)
903
-
904
- catchScope.set(statement.catchBinding.name, declare(
905
- statement.catchBinding,
906
- statement.catchBinding.name,
907
- "catch",
908
- `${statementPath}/catchBinding`
909
- ))
910
- visitBlock(statement.catchBody, catchScope, `${statementPath}/catchBody`)
910
+ for (const [index, handler] of statement.handlers.entries()) {
911
+ const handlerPath = `${statementPath}/handlers/${index}`
912
+
913
+ visitType(handler.catchType, `${handlerPath}/catchType`)
914
+ visitType(handler.catchBinding.type, `${handlerPath}/catchBinding/type`)
915
+ const catchScope = new Map(scope)
916
+
917
+ catchScope.set(handler.catchBinding.name, declare(
918
+ handler.catchBinding,
919
+ handler.catchBinding.name,
920
+ "catch",
921
+ `${handlerPath}/catchBinding`
922
+ ))
923
+ visitBlock(handler.catchBody, catchScope, `${handlerPath}/catchBody`)
924
+ }
925
+ if (statement.finallyBody) visitBlock(statement.finallyBody, scope, `${statementPath}/finallyBody`)
911
926
  }
912
927
  }
913
928
  }
@@ -1005,7 +1020,7 @@ function resolveSymbols(module, records) {
1005
1020
 
1006
1021
  /**
1007
1022
  * Attaches one resolved symbol reference.
1008
- * @param {import("./types.js").IdentifierExpression | import("./types.js").ReceiverExpression | import("./types.js").CallExpression | import("./types.js").EffectCallExpression | import("./types.js").MethodCallExpression | import("./types.js").TypeVariableReference | import("./types.js").RecordType | import("./types.js").ReferenceType | import("./types.js").OwnedReferenceType | import("./types.js").OwnedResourceType | import("./types.js").RecordConstruction | import("./types.js").ReferenceConstruction | import("./types.js").MemberRead | import("./types.js").PrivateFieldRead | import("./types.js").PrivateFieldWriteStatement | import("./types.js").ErrorType | import("./types.js").ErrorConstruction} node - Reference node.
1023
+ * @param {import("./types.js").IdentifierExpression | import("./types.js").ReceiverExpression | import("./types.js").CallExpression | import("./types.js").EffectCallExpression | import("./types.js").MethodCallExpression | import("./types.js").TypeVariableReference | import("./types.js").RecordType | import("./types.js").ReferenceType | import("./types.js").OwnedReferenceType | import("./types.js").OwnedResourceType | import("./types.js").RecordConstruction | import("./types.js").ReferenceConstruction | import("./types.js").MemberRead | import("./types.js").PrivateFieldRead | import("./types.js").PrivateFieldWriteStatement | import("./types.js").RethrowStatement | import("./types.js").ErrorType | import("./types.js").ErrorConstruction} node - Reference node.
1009
1024
  * @param {string | undefined} symbolId - Resolved symbol.
1010
1025
  * @param {"type" | "construct" | "member" | "read" | "write" | "call"} role - Reference role.
1011
1026
  * @param {string} path - Reference occurrence path.
@@ -1229,6 +1229,15 @@
1229
1229
  * @property {SemanticNodeSourceProvenance} [sourceProvenance] - Parser-owned raise range.
1230
1230
  */
1231
1231
 
1232
+ /**
1233
+ * @typedef RethrowStatement
1234
+ * @property {"RethrowStatement"} kind - Re-propagation of the exact caught binding in scope.
1235
+ * @property {string} binding - Name of the lexically visible immutable catch binding being re-raised.
1236
+ * @property {CatchBinding} bindingNode - Exact catch binding node being re-raised.
1237
+ * @property {SourceLocation} location - Complete rethrow source location.
1238
+ * @property {SemanticNodeSourceProvenance} [sourceProvenance] - Parser-owned rethrow range.
1239
+ */
1240
+
1232
1241
  /**
1233
1242
  * @typedef CatchBinding
1234
1243
  * @property {"CatchBinding"} kind - Exact-handler binding discriminator.
@@ -1240,18 +1249,27 @@
1240
1249
  */
1241
1250
 
1242
1251
  /**
1243
- * @typedef TryStatement
1244
- * @property {"TryStatement"} kind - One body and one exact typed handler.
1245
- * @property {Block} body - Protected body.
1252
+ * @typedef CatchHandler
1253
+ * @property {"CatchHandler"} kind - One exact nominal handler within an ordered handler list.
1246
1254
  * @property {ErrorType} catchType - Exact caught nominal error type.
1247
1255
  * @property {CatchBinding} catchBinding - Immutable handler-local binding.
1248
1256
  * @property {Block} catchBody - Handler body.
1257
+ * @property {SourceLocation} location - Complete handler source location.
1258
+ * @property {SemanticNodeSourceProvenance} [sourceProvenance] - Parser-owned catch ranges.
1259
+ */
1260
+
1261
+ /**
1262
+ * @typedef TryStatement
1263
+ * @property {"TryStatement"} kind - One body, an ordered list of exact typed handlers, and an optional completion block.
1264
+ * @property {Block} body - Protected body.
1265
+ * @property {CatchHandler[]} handlers - Ordered exact handlers, evaluated top to bottom with exact nominal matching.
1266
+ * @property {Block} [finallyBody] - Completion block executed on every normal and abrupt completion of the try.
1249
1267
  * @property {SourceLocation} location - Complete try/handler source location.
1250
- * @property {SemanticNodeSourceProvenance} [sourceProvenance] - Parser-owned try/catch ranges.
1268
+ * @property {SemanticNodeSourceProvenance} [sourceProvenance] - Parser-owned try/catch/finally ranges.
1251
1269
  */
1252
1270
 
1253
1271
  /** @typedef {LocalDeclaration | AssignmentStatement} LocalStatement */
1254
- /** @typedef {LocalStatement | PrivateFieldWriteStatement | ExpressionStatement | IfStatement | ForEachStatement | ForEachMapStatement | WhileStatement | BreakStatement | ContinueStatement | ReturnStatement | PrintStatement | RaiseStatement | TryStatement} Statement */
1272
+ /** @typedef {LocalStatement | PrivateFieldWriteStatement | ExpressionStatement | IfStatement | ForEachStatement | ForEachMapStatement | WhileStatement | BreakStatement | ContinueStatement | ReturnStatement | PrintStatement | RaiseStatement | RethrowStatement | TryStatement} Statement */
1255
1273
 
1256
1274
  /**
1257
1275
  * @typedef Block
@@ -1468,7 +1486,7 @@
1468
1486
  * @property {StdlibFacadeProgramDescriptor} [stdlibFacades] - Complete selected source-language facade closure.
1469
1487
  */
1470
1488
 
1471
- /** @typedef {SemanticModule | SemanticProgramModule | SemanticImport | SemanticExport | RecordDeclaration | ClassDeclaration | ErrorDeclaration | EffectCapabilityDeclaration | EffectResourceDeclaration | EffectFailureDeclaration | EffectOperationDeclaration | RecordField | PrivateField | ConstructorDeclaration | MethodDeclaration | FunctionDeclaration | TypeParameter | Parameter | ValueBinding | CatchBinding | Block | Statement | EntryPoint | Expression | ErrorConstruction | ErrorType | MapEntry | SemanticValueType | FunctionReturnTypeReference} SemanticNode */
1489
+ /** @typedef {SemanticModule | SemanticProgramModule | SemanticImport | SemanticExport | RecordDeclaration | ClassDeclaration | ErrorDeclaration | EffectCapabilityDeclaration | EffectResourceDeclaration | EffectFailureDeclaration | EffectOperationDeclaration | RecordField | PrivateField | ConstructorDeclaration | MethodDeclaration | FunctionDeclaration | TypeParameter | Parameter | ValueBinding | CatchBinding | CatchHandler | Block | Statement | EntryPoint | Expression | ErrorConstruction | ErrorType | MapEntry | SemanticValueType | FunctionReturnTypeReference} SemanticNode */
1472
1490
  /** @typedef {SemanticNode} SemanticNodeWithoutLocations */
1473
1491
 
1474
1492
  export {}
@@ -160,7 +160,7 @@ function validateBlockShape(block, detail, fail) {
160
160
  }
161
161
 
162
162
  for (const statement of block.statements) {
163
- if (!["AssignmentStatement", "BreakStatement", "ContinueStatement", "ExpressionStatement", "ForEachMapStatement", "ForEachStatement", "IfStatement", "LocalDeclaration", "PrivateFieldWriteStatement", "PrintStatement", "RaiseStatement", "ReturnStatement", "TryStatement", "WhileStatement"].includes(statement.kind)) {
163
+ if (!["AssignmentStatement", "BreakStatement", "ContinueStatement", "ExpressionStatement", "ForEachMapStatement", "ForEachStatement", "IfStatement", "LocalDeclaration", "PrivateFieldWriteStatement", "PrintStatement", "RaiseStatement", "RethrowStatement", "ReturnStatement", "TryStatement", "WhileStatement"].includes(statement.kind)) {
164
164
  fail(`${detail} statement ${statement.kind}`, statement.location)
165
165
  }
166
166
  if (statement.kind == "IfStatement") {
@@ -172,7 +172,18 @@ function validateBlockShape(block, detail, fail) {
172
172
  }
173
173
  if (statement.kind == "TryStatement") {
174
174
  validateBlockShape(statement.body, "try body", fail)
175
- validateBlockShape(statement.catchBody, "catch body", fail)
175
+ if (!Array.isArray(statement.handlers) || (statement.handlers.length == 0 && !statement.finallyBody)) {
176
+ fail(`${detail} try without an ordered handler list`, statement.location)
177
+ }
178
+
179
+ for (const handler of statement.handlers ?? []) {
180
+ if (!handler || handler.kind != "CatchHandler" ||
181
+ Object.keys(handler).filter((key) => key != "sourceProvenance").sort().join(",") != "catchBinding,catchBody,catchType,kind,location") {
182
+ fail(`${detail} malformed catch handler`, handler?.location ?? statement.location)
183
+ }
184
+ validateBlockShape(handler.catchBody, "catch body", fail)
185
+ }
186
+ if (statement.finallyBody) validateBlockShape(statement.finallyBody, "finally body", fail)
176
187
  }
177
188
  }
178
189
  }
@@ -310,7 +321,8 @@ function prepareLoopIdentities(module, normalizeOperations, fail) {
310
321
  if (statement.alternate) visit(statement.alternate, owner, active)
311
322
  } else if (statement.kind == "TryStatement") {
312
323
  visit(statement.body, owner, active)
313
- visit(statement.catchBody, owner, active)
324
+ for (const handler of statement.handlers ?? []) visit(handler.catchBody, owner, active)
325
+ if (statement.finallyBody) visit(statement.finallyBody, owner, active)
314
326
  }
315
327
  }
316
328
  }
@@ -1086,44 +1098,87 @@ function validateBlock(block, scope, returnType, functions, records, errors, cal
1086
1098
  normal = false
1087
1099
  continue
1088
1100
  }
1089
- if (statement.kind == "TryStatement") {
1090
- const caughtType = validateErrorType(statement.catchType, statement.location, errors, fail)
1101
+ if (statement.kind == "RethrowStatement") {
1102
+ const binding = findBinding(statement.binding, scope)
1091
1103
 
1092
- if (!statement.catchBinding || statement.catchBinding.kind != "CatchBinding" || statement.catchBinding.mutable !== false) {
1093
- fail("INVALID_ERROR_HANDLER", "Catch requires one immutable typed binding.", statement.catchBinding?.location ?? statement.location)
1104
+ if (!binding || binding.mutable !== false || binding.type.kind != "ErrorType") {
1105
+ fail("INVALID_RETHROW", "Rethrow requires a catch binding in scope.", statement.location)
1094
1106
  }
1095
- const bindingType = validateErrorType(statement.catchBinding.type, statement.catchBinding.location, errors, fail)
1096
-
1097
- if (bindingType.declarationId != caughtType.declarationId) {
1098
- fail("INVALID_ERROR_HANDLER", "Catch binding type must exactly match its handler type.", statement.catchBinding.location)
1107
+ raises.add(/** @type {import("./types.js").ErrorType} */ (binding.type).declarationId)
1108
+ normal = false
1109
+ continue
1110
+ }
1111
+ if (statement.kind == "TryStatement") {
1112
+ if (!Array.isArray(statement.handlers) || (statement.handlers.length == 0 && !statement.finallyBody)) {
1113
+ fail("INVALID_ERROR_HANDLER", "Try requires at least one exact typed handler or a finally block.", statement.location)
1099
1114
  }
1100
1115
  const bodyAssignedOuterBindings = outerMutableBindingsAssignedBy(statement.body, scope)
1101
1116
  const assignedOuterBindings = new Set(bodyAssignedOuterBindings)
1102
1117
 
1103
- outerMutableBindingsAssignedBy(statement.catchBody, scope, assignedOuterBindings)
1118
+ for (const handler of statement.handlers) outerMutableBindingsAssignedBy(handler.catchBody, scope, assignedOuterBindings)
1119
+ if (statement.finallyBody) outerMutableBindingsAssignedBy(statement.finallyBody, scope, assignedOuterBindings)
1104
1120
  const bodyScope = createScope(scope, statement.body.statements)
1105
1121
  const bodyFlow = validateBlock(statement.body, bodyScope, returnType, functions, records, errors, callEffects, fail, normalizeOperations, loopContext)
1106
- const catchScope = createScope(scope, statement.catchBody.statements)
1107
1122
 
1108
- for (const binding of bodyAssignedOuterBindings) {
1109
- binding.knownValue = undefined
1110
- catchScope.presenceProofs.delete(binding)
1111
- }
1123
+ let remainingRaises = new Set(bodyFlow.raises)
1124
+ let normalCompletion = bodyFlow.normal
1125
+ const handlerRaises = new Set()
1126
+ const seenHandlerNames = new Set()
1112
1127
 
1113
- declareBinding(statement.catchBinding.name, {
1114
- knownValue: undefined,
1115
- mutable: false,
1116
- type: bindingType
1117
- }, roleLocation(statement.catchBinding, "name"), catchScope, fail)
1118
- const catchFlow = validateBlock(statement.catchBody, catchScope, returnType, functions, records, errors, callEffects, fail, normalizeOperations, loopContext)
1128
+ for (const handler of statement.handlers) {
1129
+ const caughtType = validateErrorType(handler.catchType, handler.location, errors, fail)
1119
1130
 
1120
- if (!bodyFlow.raises.has(caughtType.declarationId)) {
1121
- fail("UNREACHABLE_HANDLER", `Handler for '${errors.get(caughtType.declarationId)?.name}' cannot be reached.`, typeLocation(statement.catchType, statement.location))
1131
+ if (!handler.catchBinding || handler.catchBinding.kind != "CatchBinding" || handler.catchBinding.mutable !== false) {
1132
+ fail("INVALID_ERROR_HANDLER", "Catch requires one immutable typed binding.", handler.catchBinding?.location ?? handler.location)
1133
+ }
1134
+ const bindingType = validateErrorType(handler.catchBinding.type, handler.catchBinding.location, errors, fail)
1135
+
1136
+ if (bindingType.declarationId != caughtType.declarationId) {
1137
+ fail("INVALID_ERROR_HANDLER", "Catch binding type must exactly match its handler type.", handler.catchBinding.location)
1138
+ }
1139
+ if (!remainingRaises.has(caughtType.declarationId)) {
1140
+ fail("UNREACHABLE_HANDLER", `Handler for '${errors.get(caughtType.declarationId)?.name}' cannot be reached.`,
1141
+ typeLocation(handler.catchType, handler.location))
1142
+ }
1143
+ if (seenHandlerNames.has(handler.catchBinding.name)) {
1144
+ fail("DUPLICATE_BINDING", `Duplicate or shadowed binding '${handler.catchBinding.name}'.`, handler.catchBinding.location)
1145
+ }
1146
+ seenHandlerNames.add(handler.catchBinding.name)
1147
+ remainingRaises.delete(caughtType.declarationId)
1148
+ const catchScope = createScope(scope, handler.catchBody.statements)
1149
+
1150
+ for (const binding of bodyAssignedOuterBindings) {
1151
+ binding.knownValue = undefined
1152
+ catchScope.presenceProofs.delete(binding)
1153
+ }
1154
+ declareBinding(handler.catchBinding.name, {
1155
+ knownValue: undefined,
1156
+ mutable: false,
1157
+ type: bindingType
1158
+ }, roleLocation(handler.catchBinding, "name"), catchScope, fail)
1159
+ const catchFlow = validateBlock(handler.catchBody, catchScope, returnType, functions, records, errors, callEffects, fail,
1160
+ normalizeOperations, loopContext)
1161
+
1162
+ normalCompletion = normalCompletion || catchFlow.normal
1163
+ addAll(handlerRaises, catchFlow.raises)
1164
+ }
1165
+ const pendingRaises = new Set(remainingRaises)
1166
+
1167
+ addAll(pendingRaises, handlerRaises)
1168
+ if (statement.finallyBody) {
1169
+ const finallyScope = createScope(scope, statement.finallyBody.statements)
1170
+ const finallyFlow = validateBlock(statement.finallyBody, finallyScope, returnType, functions, records, errors, callEffects,
1171
+ fail, normalizeOperations, loopContext)
1172
+ // `raises` is the enclosing block's accumulator. A completion block replaces only this try's
1173
+ // pending completion; escapes contributed by earlier statements of the enclosing block survive
1174
+ // whichever way the completion block completes.
1175
+ if (finallyFlow.normal) addAll(raises, pendingRaises)
1176
+ addAll(raises, finallyFlow.raises)
1177
+ normal = finallyFlow.normal ? normalCompletion : false
1178
+ } else {
1179
+ addAll(raises, pendingRaises)
1180
+ normal = normalCompletion
1122
1181
  }
1123
- bodyFlow.raises.delete(caughtType.declarationId)
1124
- addAll(raises, bodyFlow.raises)
1125
- addAll(raises, catchFlow.raises)
1126
- normal = bodyFlow.normal || catchFlow.normal
1127
1182
  for (const binding of assignedOuterBindings) {
1128
1183
  binding.knownValue = undefined
1129
1184
  scope.presenceProofs.delete(binding)
@@ -1271,14 +1326,27 @@ function syntacticBlockEffects(block, callEffects, methodEffects = new Map()) {
1271
1326
  }
1272
1327
  continue
1273
1328
  }
1329
+ if (kind == "RethrowStatement") {
1330
+ const bindingNode = Reflect.get(statement, "bindingNode")
1331
+ const bindingType = bindingNode && typeof bindingNode == "object" ? Reflect.get(bindingNode, "type") : undefined
1332
+ const declarationId = bindingType && typeof bindingType == "object" ? Reflect.get(bindingType, "declarationId") : undefined
1333
+
1334
+ if (typeof declarationId == "string") result.add(declarationId)
1335
+ continue
1336
+ }
1274
1337
  if (kind == "TryStatement") {
1275
- const bodyEffects = syntacticBlockEffects(Reflect.get(statement, "body"), callEffects, methodEffects)
1276
- const catchType = Reflect.get(statement, "catchType")
1277
- const caughtId = catchType && typeof catchType == "object" ? Reflect.get(catchType, "declarationId") : undefined
1338
+ let bodyEffects = syntacticBlockEffects(Reflect.get(statement, "body"), callEffects, methodEffects)
1339
+
1340
+ for (const handler of Reflect.get(statement, "handlers") ?? []) {
1341
+ if (!handler || typeof handler != "object") continue
1342
+ const catchType = Reflect.get(handler, "catchType")
1343
+ const caughtId = catchType && typeof catchType == "object" ? Reflect.get(catchType, "declarationId") : undefined
1278
1344
 
1279
- if (typeof caughtId == "string" && bodyEffects.delete(caughtId)) {
1280
- addAll(bodyEffects, syntacticBlockEffects(Reflect.get(statement, "catchBody"), callEffects, methodEffects))
1345
+ if (typeof caughtId == "string" && bodyEffects.delete(caughtId)) {
1346
+ addAll(bodyEffects, syntacticBlockEffects(Reflect.get(handler, "catchBody"), callEffects, methodEffects))
1347
+ }
1281
1348
  }
1349
+ addAll(bodyEffects, syntacticBlockEffects(Reflect.get(statement, "finallyBody"), callEffects, methodEffects))
1282
1350
  addAll(result, bodyEffects)
1283
1351
  continue
1284
1352
  }
@@ -3046,7 +3114,8 @@ function outerMutableBindingsAssignedBy(block, outerScope, assigned = new Set())
3046
3114
  outerMutableBindingsAssignedBy(statement.body, outerScope, assigned)
3047
3115
  } else if (statement.kind == "TryStatement") {
3048
3116
  outerMutableBindingsAssignedBy(statement.body, outerScope, assigned)
3049
- outerMutableBindingsAssignedBy(statement.catchBody, outerScope, assigned)
3117
+ for (const handler of statement.handlers ?? []) outerMutableBindingsAssignedBy(handler.catchBody, outerScope, assigned)
3118
+ if (statement.finallyBody) outerMutableBindingsAssignedBy(statement.finallyBody, outerScope, assigned)
3050
3119
  }
3051
3120
  }
3052
3121