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.
- package/README.md +1 -0
- package/build/src/backends/effects.js +3 -2
- package/build/src/backends/java.js +33 -14
- package/build/src/backends/javascript.js +83 -18
- package/build/src/backends/php.js +33 -14
- package/build/src/backends/ruby.js +37 -12
- package/build/src/backends/shared.d.ts.map +1 -1
- package/build/src/backends/shared.js +91 -36
- package/build/src/backends/typescript.js +83 -18
- package/build/src/frontends/java.d.ts +8 -0
- package/build/src/frontends/java.d.ts.map +1 -1
- package/build/src/frontends/java.js +77 -33
- package/build/src/frontends/javascript-typescript.d.ts +2 -0
- package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
- package/build/src/frontends/javascript-typescript.js +118 -37
- package/build/src/frontends/php.d.ts +8 -0
- package/build/src/frontends/php.d.ts.map +1 -1
- package/build/src/frontends/php.js +68 -29
- package/build/src/frontends/ruby.d.ts +3 -0
- package/build/src/frontends/ruby.d.ts.map +1 -1
- package/build/src/frontends/ruby.js +103 -30
- package/build/src/semantic/effects.d.ts.map +1 -1
- package/build/src/semantic/effects.js +18 -6
- package/build/src/semantic/lifetime.js +28 -4
- package/build/src/semantic/provenance.d.ts.map +1 -1
- package/build/src/semantic/provenance.js +26 -7
- package/build/src/semantic/types.d.ts +76 -16
- package/build/src/semantic/types.d.ts.map +1 -1
- package/build/src/semantic/types.js +23 -7
- package/build/src/semantic/validate.d.ts.map +1 -1
- package/build/src/semantic/validate.js +105 -34
- package/docs/adr/20261026-typed-error-handling-extension.md +55 -0
- package/docs/adr-numeric-tower.md +156 -0
- package/docs/architecture.md +1 -1
- package/docs/language-support.md +7 -7
- package/docs/testing.md +5 -3
- package/package.json +1 -1
- package/src/backends/effects.js +2 -1
- package/src/backends/java.js +33 -14
- package/src/backends/javascript.js +83 -18
- package/src/backends/php.js +33 -14
- package/src/backends/ruby.js +36 -12
- package/src/backends/shared.js +88 -41
- package/src/backends/typescript.js +83 -18
- package/src/frontends/java.js +82 -33
- package/src/frontends/javascript-typescript.js +130 -38
- package/src/frontends/php.js +68 -27
- package/src/frontends/ruby.js +107 -28
- package/src/semantic/effects.js +17 -5
- package/src/semantic/lifetime.js +29 -3
- package/src/semantic/provenance.js +27 -12
- package/src/semantic/types.js +24 -6
- package/src/semantic/validate.js +104 -35
|
@@ -6,7 +6,7 @@ import {missingType, parseFailure, SemantifoldDiagnostic, unsupportedSyntax} fro
|
|
|
6
6
|
import {blockReferencesProtectedEntry} from "../semantic/effects.js"
|
|
7
7
|
import {locationFromOffsets, moduleLocation} from "../semantic/location.js"
|
|
8
8
|
import {withAdaptedOperation} from "../semantic/operators.js"
|
|
9
|
-
import {withParserRanges} from "../semantic/provenance.js"
|
|
9
|
+
import {setParserRanges, withParserRanges} from "../semantic/provenance.js"
|
|
10
10
|
import {hasOnlyUnicodeScalars, scalarType} from "../semantic/scalars.js"
|
|
11
11
|
import {isTask034ResourceProbe} from "../semantic/capabilities.js"
|
|
12
12
|
import {hasExactEffectSupport} from "../backends/effects.js"
|
|
@@ -16,7 +16,7 @@ import {documentedValueType, instantiatedRecordFieldType, iterationBindingType,
|
|
|
16
16
|
/** @typedef {NonNullable<import("@babel/parser").ParseResult<import("@babel/types").File>["tokens"]>[number]} BabelToken */
|
|
17
17
|
/** @typedef {{byStart: Map<number, BabelToken>, tokens: BabelToken[]}} BabelTokenIndex */
|
|
18
18
|
/** @typedef {{name: string, nameLocation: import("../semantic/types.js").SourceLocation, parameters: import("../semantic/types.js").Parameter[], returnType: import("../semantic/types.js").SemanticFunctionReturnType, typeParameters?: import("../semantic/types.js").TypeParameter[], location: import("../semantic/types.js").SourceLocation}} JavaScriptFunctionSignature */
|
|
19
|
-
/** @typedef {{bindings: Map<string, import("../semantic/types.js").SemanticBindingType>, classes: Map<string, import("../semantic/types.js").ClassDeclaration>, currentClass?: import("../semantic/types.js").ClassDeclaration, errors: Map<string, import("../semantic/types.js").ErrorDeclaration>, errorNames: Map<string, import("../semantic/types.js").ErrorDeclaration>, functions: Map<string, JavaScriptFunctionSignature>, lexicalValueNames?: Set<string>, orderedMapDeclarations?: Set<object>, recordNames: Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>, records: Map<string, import("../semantic/types.js").RecordDeclaration>, returnType?: import("../semantic/types.js").SemanticFunctionReturnType, typeParameters?: Map<string, import("../semantic/types.js").TypeParameter>, valueRecordNames: Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration>}} JavaScriptConversionContext */
|
|
19
|
+
/** @typedef {{bindings: Map<string, import("../semantic/types.js").SemanticBindingType>, catchBindingNames?: Set<string>, catchBindingNodes?: Map<string, import("../semantic/types.js").CatchBinding>, classes: Map<string, import("../semantic/types.js").ClassDeclaration>, currentClass?: import("../semantic/types.js").ClassDeclaration, errors: Map<string, import("../semantic/types.js").ErrorDeclaration>, errorNames: Map<string, import("../semantic/types.js").ErrorDeclaration>, functions: Map<string, JavaScriptFunctionSignature>, lexicalValueNames?: Set<string>, orderedMapDeclarations?: Set<object>, recordNames: Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>, records: Map<string, import("../semantic/types.js").RecordDeclaration>, returnType?: import("../semantic/types.js").SemanticFunctionReturnType, typeParameters?: Map<string, import("../semantic/types.js").TypeParameter>, valueRecordNames: Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration>}} JavaScriptConversionContext */
|
|
20
20
|
|
|
21
21
|
/** @type {WeakMap<object, BabelTokenIndex>} */
|
|
22
22
|
const nodeTokens = new WeakMap()
|
|
@@ -966,12 +966,23 @@ function convertStatement(node, language, filename, source, canonicalZeroRequire
|
|
|
966
966
|
* @param {string} filename - Source filename.
|
|
967
967
|
* @param {string} source - Complete source.
|
|
968
968
|
* @param {JavaScriptConversionContext} context - Typed lexical context.
|
|
969
|
-
* @returns {import("../semantic/types.js").RaiseStatement} Semantic raise.
|
|
969
|
+
* @returns {import("../semantic/types.js").RaiseStatement | import("../semantic/types.js").RethrowStatement} Semantic raise or rethrow.
|
|
970
970
|
*/
|
|
971
971
|
function convertTypedThrow(node, language, filename, source, context) {
|
|
972
972
|
const location = nodeLocation(node, filename, source)
|
|
973
973
|
const argument = node.argument
|
|
974
974
|
|
|
975
|
+
if (argument?.type == "Identifier" && context.catchBindingNames?.has(argument.name) && context.bindings.get(argument.name)?.kind == "ErrorType") {
|
|
976
|
+
const bindingNode = context.catchBindingNodes?.get(argument.name)
|
|
977
|
+
|
|
978
|
+
if (!bindingNode) return unsupportedSyntax(language, "throw of a catch binding without an exact handler binding", location)
|
|
979
|
+
return withParserRanges({
|
|
980
|
+
binding: argument.name,
|
|
981
|
+
bindingNode,
|
|
982
|
+
kind: /** @type {const} */ ("RethrowStatement"),
|
|
983
|
+
location
|
|
984
|
+
}, {keyword: tokenLocation(node, "throw", node.start ?? 0, argument.start ?? node.end ?? source.length, filename, source)})
|
|
985
|
+
}
|
|
975
986
|
if (!argument || argument.type != "NewExpression" || argument.callee.type != "Identifier" ||
|
|
976
987
|
context.bindings.has(argument.callee.name) || !context.errorNames.has(argument.callee.name) || argument.arguments.length != 1 ||
|
|
977
988
|
argument.arguments[0].type == "SpreadElement" || argument.arguments[0].type == "ArgumentPlaceholder" ||
|
|
@@ -1006,53 +1017,134 @@ function convertTypedThrow(node, language, filename, source, context) {
|
|
|
1006
1017
|
function convertTypedTry(node, language, filename, source, canonicalZeroRequired, context) {
|
|
1007
1018
|
const location = nodeLocation(node, filename, source)
|
|
1008
1019
|
|
|
1009
|
-
if (
|
|
1010
|
-
return unsupportedSyntax(language, "try without
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
}, {type: caughtTypeLocation})
|
|
1020
|
+
if (!node.handler) {
|
|
1021
|
+
if (!node.finalizer) return unsupportedSyntax(language, "try without an explicit guarded catch", location)
|
|
1022
|
+
const bodyContext = {...context, bindings: new Map(context.bindings)}
|
|
1023
|
+
const semantic = /** @type {import("../semantic/types.js").TryStatement} */ (withParserRanges({
|
|
1024
|
+
body: convertBlock(node.block, language, filename, source, canonicalZeroRequired, bodyContext),
|
|
1025
|
+
handlers: [],
|
|
1026
|
+
kind: /** @type {const} */ ("TryStatement"),
|
|
1027
|
+
location
|
|
1028
|
+
}, {
|
|
1029
|
+
try: tokenLocation(node, "try", node.start ?? 0, node.block.start ?? node.end ?? source.length, filename, source)
|
|
1030
|
+
}))
|
|
1031
|
+
|
|
1032
|
+
semantic.finallyBody = convertBlock(node.finalizer.body, language, filename, source, canonicalZeroRequired,
|
|
1033
|
+
{...context, bindings: new Map(context.bindings)}, nodeLocation(node.finalizer, filename, source))
|
|
1034
|
+
setParserRanges(semantic, {finally: nodeLocation(node.finalizer, filename, source),
|
|
1035
|
+
try: tokenLocation(node, "try", node.start ?? 0, node.block.start ?? node.end ?? source.length, filename, source)})
|
|
1036
|
+
return semantic
|
|
1037
|
+
}
|
|
1038
|
+
/** @type {import("../semantic/types.js").CatchHandler[]} */
|
|
1039
|
+
const handlers = []
|
|
1040
|
+
const seenHandlerNames = new Set(context.catchBindingNames ?? [])
|
|
1031
1041
|
const bodyContext = {...context, bindings: new Map(context.bindings)}
|
|
1032
|
-
const catchContext = {...context, bindings: new Map(context.bindings)}
|
|
1033
1042
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1043
|
+
// JavaScript has no sibling catch clauses, so the exact native profile for ordered
|
|
1044
|
+
// multi-catch is a guarded dispatch chain: each unmatched guard rethrows into the next
|
|
1045
|
+
// clause. The chain flattens back to one ordered handler list here.
|
|
1046
|
+
/** @type {import("@babel/types").CatchClause | undefined} */
|
|
1047
|
+
let clause = node.handler
|
|
1048
|
+
|
|
1049
|
+
while (clause) {
|
|
1050
|
+
const binding = clause.param
|
|
1051
|
+
|
|
1052
|
+
if (binding?.type != "Identifier" || clause.body.directives.length > 0) {
|
|
1053
|
+
return unsupportedSyntax(language, "catch without one explicit binding", nodeLocation(clause, filename, source))
|
|
1054
|
+
}
|
|
1055
|
+
if (seenHandlerNames.has(binding.name)) {
|
|
1056
|
+
return unsupportedSyntax(language, `catch binding '${binding.name}' shadows an outer handler binding`,
|
|
1057
|
+
identifierLocation(binding, filename, source))
|
|
1058
|
+
}
|
|
1059
|
+
seenHandlerNames.add(binding.name)
|
|
1060
|
+
|
|
1061
|
+
/** @type {import("@babel/types").Statement | undefined} */
|
|
1062
|
+
const guard = clause.body.body[0]
|
|
1063
|
+
|
|
1064
|
+
if (!guard || guard.type != "IfStatement" || guard.test.type != "UnaryExpression" ||
|
|
1065
|
+
guard.test.operator != "!" || guard.test.argument.type != "BinaryExpression" || guard.test.argument.operator != "instanceof" ||
|
|
1066
|
+
guard.test.argument.left.type != "Identifier" || guard.test.argument.left.name != binding.name ||
|
|
1067
|
+
guard.test.argument.right.type != "Identifier" || guard.test.argument.right.name == binding.name ||
|
|
1068
|
+
context.bindings.has(guard.test.argument.right.name) || !context.errorNames.has(guard.test.argument.right.name) ||
|
|
1069
|
+
guard.consequent.type != "BlockStatement" || guard.consequent.body.length != 1 || guard.consequent.directives.length > 0 ||
|
|
1070
|
+
(guard.alternate && (guard.alternate.type != "BlockStatement" || guard.alternate.directives.length > 0))) {
|
|
1071
|
+
return unsupportedSyntax(language, "catch without exact instanceof guard", nodeLocation(guard ?? clause, filename, source))
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/** @type {import("@babel/types").Statement | undefined} */
|
|
1075
|
+
const dispatch = guard.consequent.body[0]
|
|
1076
|
+
/** @type {import("@babel/types").CatchClause | undefined} */
|
|
1077
|
+
let nextClause = undefined
|
|
1078
|
+
/** @type {import("@babel/types").Statement[]} */
|
|
1079
|
+
let handlerBodyStatements = clause.body.body.slice(1)
|
|
1080
|
+
let handlerBodyLocation = nodeLocation(clause.body, filename, source)
|
|
1081
|
+
|
|
1082
|
+
if (dispatch.type == "TryStatement") {
|
|
1083
|
+
if (dispatch.finalizer || !dispatch.handler || dispatch.block.body.length != 1 || dispatch.block.body[0].type != "ThrowStatement" ||
|
|
1084
|
+
dispatch.block.body[0].argument?.type != "Identifier" || dispatch.block.body[0].argument.name != binding.name ||
|
|
1085
|
+
!guard.alternate || clause.body.body.length != 1) {
|
|
1086
|
+
return unsupportedSyntax(language, "catch guard dispatch without exact rethrow of its own binding",
|
|
1087
|
+
nodeLocation(dispatch, filename, source))
|
|
1088
|
+
}
|
|
1089
|
+
nextClause = dispatch.handler
|
|
1090
|
+
handlerBodyStatements = /** @type {import("@babel/types").BlockStatement} */ (guard.alternate).body
|
|
1091
|
+
handlerBodyLocation = nodeLocation(guard.alternate, filename, source)
|
|
1092
|
+
} else if (dispatch.type != "ThrowStatement" || dispatch.argument?.type != "Identifier" || dispatch.argument.name != binding.name) {
|
|
1093
|
+
return unsupportedSyntax(language, "catch guard without exact unmatched rethrow", nodeLocation(dispatch, filename, source))
|
|
1094
|
+
} else if (guard.alternate) {
|
|
1095
|
+
if (clause.body.body.length != 1) {
|
|
1096
|
+
return unsupportedSyntax(language, "catch guard dispatch without exact rethrow of its own binding",
|
|
1097
|
+
nodeLocation(guard.alternate, filename, source))
|
|
1098
|
+
}
|
|
1099
|
+
handlerBodyStatements = /** @type {import("@babel/types").BlockStatement} */ (guard.alternate).body
|
|
1100
|
+
handlerBodyLocation = nodeLocation(guard.alternate, filename, source)
|
|
1101
|
+
}
|
|
1036
1102
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1103
|
+
const declaration = /** @type {import("../semantic/types.js").ErrorDeclaration} */ (context.errorNames.get(guard.test.argument.right.name))
|
|
1104
|
+
const caughtTypeLocation = identifierLocation(guard.test.argument.right, filename, source)
|
|
1105
|
+
const catchType = withParserRanges({
|
|
1106
|
+
declarationId: /** @type {string} */ (declaration.id),
|
|
1107
|
+
kind: /** @type {const} */ ("ErrorType")
|
|
1108
|
+
}, {type: caughtTypeLocation})
|
|
1109
|
+
const catchContext = {...context, bindings: new Map(context.bindings), catchBindingNames: new Set(seenHandlerNames),
|
|
1110
|
+
catchBindingNodes: new Map(context.catchBindingNodes)}
|
|
1111
|
+
|
|
1112
|
+
catchContext.bindings.set(binding.name, catchType)
|
|
1113
|
+
const catchBindingNode = withParserRanges({
|
|
1040
1114
|
kind: /** @type {const} */ ("CatchBinding"),
|
|
1041
1115
|
location: identifierLocation(binding, filename, source),
|
|
1042
1116
|
mutable: /** @type {const} */ (false),
|
|
1043
1117
|
name: binding.name,
|
|
1044
1118
|
type: catchType
|
|
1045
|
-
}, {name: identifierLocation(binding, filename, source)})
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1119
|
+
}, {name: identifierLocation(binding, filename, source)})
|
|
1120
|
+
|
|
1121
|
+
catchContext.catchBindingNodes.set(binding.name, catchBindingNode)
|
|
1122
|
+
handlers.push(withParserRanges({
|
|
1123
|
+
catchBinding: catchBindingNode,
|
|
1124
|
+
catchBody: convertBlock(handlerBodyStatements, language, filename, source, canonicalZeroRequired, catchContext,
|
|
1125
|
+
handlerBodyLocation),
|
|
1126
|
+
catchType,
|
|
1127
|
+
kind: /** @type {const} */ ("CatchHandler"),
|
|
1128
|
+
location: nodeLocation(clause, filename, source)
|
|
1129
|
+
}, {catch: tokenLocation(clause, "catch", clause.start ?? 0, binding.start ?? clause.end ?? source.length, filename, source)}))
|
|
1130
|
+
clause = nextClause
|
|
1131
|
+
}
|
|
1132
|
+
const semantic = /** @type {import("../semantic/types.js").TryStatement} */ (withParserRanges({
|
|
1133
|
+
body: convertBlock(node.block, language, filename, source, canonicalZeroRequired, bodyContext),
|
|
1134
|
+
handlers,
|
|
1049
1135
|
kind: /** @type {const} */ ("TryStatement"),
|
|
1050
1136
|
location
|
|
1051
1137
|
}, {
|
|
1052
|
-
catch: tokenLocation(node.handler, "catch", node.handler.start ?? 0, binding.start ?? node.handler.end ?? source.length, filename, source),
|
|
1053
|
-
guard: nodeLocation(guard, filename, source),
|
|
1054
1138
|
try: tokenLocation(node, "try", node.start ?? 0, node.block.start ?? node.end ?? source.length, filename, source)
|
|
1055
|
-
})
|
|
1139
|
+
}))
|
|
1140
|
+
|
|
1141
|
+
if (node.finalizer) {
|
|
1142
|
+
semantic.finallyBody = convertBlock(node.finalizer.body, language, filename, source, canonicalZeroRequired,
|
|
1143
|
+
{...context, bindings: new Map(context.bindings)}, nodeLocation(node.finalizer, filename, source))
|
|
1144
|
+
setParserRanges(semantic, {finally: nodeLocation(node.finalizer, filename, source),
|
|
1145
|
+
try: tokenLocation(node, "try", node.start ?? 0, node.block.start ?? node.end ?? source.length, filename, source)})
|
|
1146
|
+
}
|
|
1147
|
+
return semantic
|
|
1056
1148
|
}
|
|
1057
1149
|
|
|
1058
1150
|
/**
|
package/src/frontends/php.js
CHANGED
|
@@ -6,7 +6,7 @@ import {missingType, parseFailure, unsupportedSyntax} from "../diagnostic.js"
|
|
|
6
6
|
import {blockReferencesProtectedEntry} from "../semantic/effects.js"
|
|
7
7
|
import {locationFromOffsets, moduleLocation} from "../semantic/location.js"
|
|
8
8
|
import {withAdaptedOperation} from "../semantic/operators.js"
|
|
9
|
-
import {withParserRanges} from "../semantic/provenance.js"
|
|
9
|
+
import {setParserRanges, withParserRanges} from "../semantic/provenance.js"
|
|
10
10
|
import {isTask034ResourceProbe} from "../semantic/capabilities.js"
|
|
11
11
|
import {hasExactEffectSupport} from "../backends/effects.js"
|
|
12
12
|
import {hasOnlyUnicodeScalars, scalarType} from "../semantic/scalars.js"
|
|
@@ -39,6 +39,8 @@ const phpBinaryOperations = new Map([
|
|
|
39
39
|
/**
|
|
40
40
|
* @typedef PhpConversionContext
|
|
41
41
|
* @property {Map<string, import("../semantic/types.js").SemanticBindingType>} bindings - Explicitly typed visible bindings.
|
|
42
|
+
* @property {Set<string>} [catchBindingNames] - Lexically visible exact catch binding names.
|
|
43
|
+
* @property {Map<string, import("../semantic/types.js").CatchBinding>} [catchBindingNodes] - Exact catch binding nodes by visible name.
|
|
42
44
|
* @property {Map<string, import("../semantic/types.js").ClassDeclaration>} classes - Reference classes by identity.
|
|
43
45
|
* @property {import("../semantic/types.js").ClassDeclaration} [currentClass] - Enclosing reference class.
|
|
44
46
|
* @property {Map<string, import("../semantic/types.js").ErrorDeclaration>} errorNames - Error declarations by source name.
|
|
@@ -963,12 +965,24 @@ function convertStatement(node, filename, source, context) {
|
|
|
963
965
|
* @param {string} filename - Source filename.
|
|
964
966
|
* @param {string} source - Complete source.
|
|
965
967
|
* @param {PhpConversionContext} context - Typed lexical context.
|
|
966
|
-
* @returns {import("../semantic/types.js").RaiseStatement} Semantic raise.
|
|
968
|
+
* @returns {import("../semantic/types.js").RaiseStatement | import("../semantic/types.js").RethrowStatement} Semantic raise or rethrow.
|
|
967
969
|
*/
|
|
968
970
|
function convertPhpRaise(node, filename, source, context) {
|
|
969
971
|
const location = nodeLocation(node, filename, source)
|
|
970
972
|
const construction = Reflect.get(node, "what")
|
|
971
973
|
|
|
974
|
+
if (construction?.kind == "variable" && typeof construction.name == "string" && !construction.curly &&
|
|
975
|
+
context.bindings.get(construction.name)?.kind == "ErrorType" && context.catchBindingNames?.has(construction.name)) {
|
|
976
|
+
const bindingNode = context.catchBindingNodes?.get(construction.name)
|
|
977
|
+
|
|
978
|
+
if (!bindingNode) return unsupportedSyntax("php", "throw of a catch binding without an exact handler binding", location)
|
|
979
|
+
return withParserRanges({
|
|
980
|
+
binding: construction.name,
|
|
981
|
+
bindingNode,
|
|
982
|
+
kind: /** @type {const} */ ("RethrowStatement"),
|
|
983
|
+
location
|
|
984
|
+
}, {keyword: tokenLocation("throw", node.loc?.start.offset ?? 0, construction.loc?.start.offset ?? source.length, filename, source)})
|
|
985
|
+
}
|
|
972
986
|
if (!construction || construction.kind != "new" || construction.what?.kind != "name" ||
|
|
973
987
|
construction.what.resolution != "uqn" || !context.errorNames.has(construction.what.name) ||
|
|
974
988
|
!Array.isArray(construction.arguments) || construction.arguments.length != 1 ||
|
|
@@ -1002,42 +1016,69 @@ function convertPhpTry(node, filename, source, context) {
|
|
|
1002
1016
|
const location = nodeLocation(node, filename, source)
|
|
1003
1017
|
const catches = Reflect.get(node, "catches")
|
|
1004
1018
|
const body = Reflect.get(node, "body")
|
|
1019
|
+
const always = Reflect.get(node, "always")
|
|
1005
1020
|
|
|
1006
|
-
if (
|
|
1007
|
-
return unsupportedSyntax("php", "try without
|
|
1021
|
+
if (!Array.isArray(catches) || (catches.length == 0 && !always) || body?.kind != "block") {
|
|
1022
|
+
return unsupportedSyntax("php", "try without typed catches or finally", location)
|
|
1008
1023
|
}
|
|
1009
|
-
|
|
1010
|
-
const
|
|
1011
|
-
const
|
|
1024
|
+
/** @type {import("../semantic/types.js").CatchHandler[]} */
|
|
1025
|
+
const handlers = []
|
|
1026
|
+
const seenHandlerNames = new Set(context.catchBindingNames ?? [])
|
|
1012
1027
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
binding
|
|
1016
|
-
return unsupportedSyntax("php", "broad, multiple, or malformed catch", nodeLocation(handler, filename, source))
|
|
1017
|
-
}
|
|
1018
|
-
const declaration = /** @type {import("../semantic/types.js").ErrorDeclaration} */ (context.errorNames.get(caught.name))
|
|
1019
|
-
const caughtLocation = nodeLocation(caught, filename, source)
|
|
1020
|
-
const catchType = withParserRanges({declarationId: /** @type {string} */ (declaration.id), kind: /** @type {const} */ ("ErrorType")}, {
|
|
1021
|
-
type: caughtLocation
|
|
1022
|
-
})
|
|
1023
|
-
const bodyContext = {...context, bindings: new Map(context.bindings)}
|
|
1024
|
-
const catchContext = {...context, bindings: new Map(context.bindings)}
|
|
1028
|
+
for (const handler of catches) {
|
|
1029
|
+
const caught = handler.what?.[0]
|
|
1030
|
+
const binding = handler.variable
|
|
1025
1031
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1032
|
+
if (handler.what?.length != 1 || caught?.kind != "name" || caught.resolution != "uqn" ||
|
|
1033
|
+
!context.errorNames.has(caught.name) || binding?.kind != "variable" || typeof binding.name != "string" ||
|
|
1034
|
+
binding.curly || handler.body?.kind != "block") {
|
|
1035
|
+
return unsupportedSyntax("php", "broad, multiple, or malformed catch", nodeLocation(handler, filename, source))
|
|
1036
|
+
}
|
|
1037
|
+
if (seenHandlerNames.has(binding.name)) {
|
|
1038
|
+
return unsupportedSyntax("php", `catch binding '${binding.name}' shadows an outer handler binding`, nodeLocation(binding, filename, source))
|
|
1039
|
+
}
|
|
1040
|
+
seenHandlerNames.add(binding.name)
|
|
1041
|
+
const declaration = /** @type {import("../semantic/types.js").ErrorDeclaration} */ (context.errorNames.get(caught.name))
|
|
1042
|
+
const caughtLocation = nodeLocation(caught, filename, source)
|
|
1043
|
+
const catchType = withParserRanges({declarationId: /** @type {string} */ (declaration.id), kind: /** @type {const} */ ("ErrorType")}, {
|
|
1044
|
+
type: caughtLocation
|
|
1045
|
+
})
|
|
1046
|
+
const catchContext = {...context, bindings: new Map(context.bindings), catchBindingNames: new Set(seenHandlerNames),
|
|
1047
|
+
catchBindingNodes: new Map(context.catchBindingNodes)}
|
|
1048
|
+
|
|
1049
|
+
catchContext.bindings.set(binding.name, catchType)
|
|
1050
|
+
const catchBindingNode = withParserRanges({
|
|
1030
1051
|
kind: /** @type {const} */ ("CatchBinding"),
|
|
1031
1052
|
location: nodeLocation(binding, filename, source),
|
|
1032
1053
|
mutable: /** @type {const} */ (false),
|
|
1033
1054
|
name: binding.name,
|
|
1034
1055
|
type: catchType
|
|
1035
|
-
}, {name: nodeLocation(binding, filename, source)})
|
|
1036
|
-
|
|
1037
|
-
|
|
1056
|
+
}, {name: nodeLocation(binding, filename, source)})
|
|
1057
|
+
|
|
1058
|
+
catchContext.catchBindingNodes.set(binding.name, catchBindingNode)
|
|
1059
|
+
handlers.push(withParserRanges({
|
|
1060
|
+
catchBinding: catchBindingNode,
|
|
1061
|
+
catchBody: convertBlock(handler.body, filename, source, catchContext),
|
|
1062
|
+
catchType,
|
|
1063
|
+
kind: /** @type {const} */ ("CatchHandler"),
|
|
1064
|
+
location: nodeLocation(handler, filename, source)
|
|
1065
|
+
}, {catch: nodeLocation(handler, filename, source)}))
|
|
1066
|
+
}
|
|
1067
|
+
const bodyContext = {...context, bindings: new Map(context.bindings)}
|
|
1068
|
+
const semantic = /** @type {import("../semantic/types.js").TryStatement} */ (withParserRanges({
|
|
1069
|
+
body: convertBlock(body, filename, source, bodyContext),
|
|
1070
|
+
handlers,
|
|
1038
1071
|
kind: /** @type {const} */ ("TryStatement"),
|
|
1039
1072
|
location
|
|
1040
|
-
}, {
|
|
1073
|
+
}, {try: tokenLocation("try", node.loc?.start.offset ?? 0, body.loc?.start.offset ?? source.length, filename, source)}))
|
|
1074
|
+
|
|
1075
|
+
if (always) {
|
|
1076
|
+
if (always.kind != "block") return unsupportedSyntax("php", "malformed finally block", nodeLocation(always, filename, source))
|
|
1077
|
+
semantic.finallyBody = convertBlock(always, filename, source, bodyContext)
|
|
1078
|
+
setParserRanges(semantic, {finally: nodeLocation(always, filename, source),
|
|
1079
|
+
try: tokenLocation("try", node.loc?.start.offset ?? 0, body.loc?.start.offset ?? source.length, filename, source)})
|
|
1080
|
+
}
|
|
1081
|
+
return semantic
|
|
1041
1082
|
}
|
|
1042
1083
|
|
|
1043
1084
|
/**
|
package/src/frontends/ruby.js
CHANGED
|
@@ -49,7 +49,7 @@ import {missingType, SemantifoldDiagnostic, unsupportedSyntax} from "../diagnost
|
|
|
49
49
|
import {blockReferencesProtectedEntry} from "../semantic/effects.js"
|
|
50
50
|
import {locationFromOffsets, moduleLocation, utf8ByteOffsetToUtf16Offset} from "../semantic/location.js"
|
|
51
51
|
import {withAdaptedOperation} from "../semantic/operators.js"
|
|
52
|
-
import {withParserRanges} from "../semantic/provenance.js"
|
|
52
|
+
import {setParserRanges, withParserRanges} from "../semantic/provenance.js"
|
|
53
53
|
import {hasOnlyUnicodeScalars, scalarType} from "../semantic/scalars.js"
|
|
54
54
|
import {isTask034ResourceProbe} from "../semantic/capabilities.js"
|
|
55
55
|
import {hasExactEffectSupport} from "../backends/effects.js"
|
|
@@ -61,7 +61,7 @@ const referenceMethodHooks = new Set([
|
|
|
61
61
|
"respond_to_missing?", "send", "singleton_class", "size"
|
|
62
62
|
])
|
|
63
63
|
/** @typedef {{name: string, nameLocation: import("../semantic/types.js").SourceLocation, parameters: import("../semantic/types.js").Parameter[], returnType: import("../semantic/types.js").SemanticFunctionReturnType, typeParameters?: import("../semantic/types.js").TypeParameter[], location: import("../semantic/types.js").SourceLocation}} RubyFunctionSignature */
|
|
64
|
-
/** @typedef {{bindings: Map<string, import("../semantic/types.js").SemanticBindingType>, classes: Map<string, import("../semantic/types.js").ClassDeclaration>, currentClass?: import("../semantic/types.js").ClassDeclaration, errorNames: Map<string, import("../semantic/types.js").ErrorDeclaration>, errors: Map<string, import("../semantic/types.js").ErrorDeclaration>, functions: Map<string, RubyFunctionSignature>, orderedMapDeclarations?: Set<object>, records: Map<string, import("../semantic/types.js").RecordDeclaration>, recordNames: Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>, loopDepth?: number, returnType?: import("../semantic/types.js").SemanticFunctionReturnType, typeParameters?: Map<string, import("../semantic/types.js").TypeParameter>}} RubyConversionContext */
|
|
64
|
+
/** @typedef {{bindings: Map<string, import("../semantic/types.js").SemanticBindingType>, catchBindingNames?: Set<string>, classes: Map<string, import("../semantic/types.js").ClassDeclaration>, catchBindingNodes?: Map<string, import("../semantic/types.js").CatchBinding>, currentCatchBindingNode?: import("../semantic/types.js").CatchBinding, currentClass?: import("../semantic/types.js").ClassDeclaration, errorNames: Map<string, import("../semantic/types.js").ErrorDeclaration>, errors: Map<string, import("../semantic/types.js").ErrorDeclaration>, functions: Map<string, RubyFunctionSignature>, orderedMapDeclarations?: Set<object>, records: Map<string, import("../semantic/types.js").RecordDeclaration>, recordNames: Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>, loopDepth?: number, returnType?: import("../semantic/types.js").SemanticFunctionReturnType, typeParameters?: Map<string, import("../semantic/types.js").TypeParameter>}} RubyConversionContext */
|
|
65
65
|
const rubyBinaryOperations = new Map([
|
|
66
66
|
["+", "Add"],
|
|
67
67
|
["-", "Subtract"],
|
|
@@ -969,15 +969,38 @@ function convertStatement(node, comments, context, filename, source) {
|
|
|
969
969
|
* @param {RubyConversionContext} context - Typed lexical context.
|
|
970
970
|
* @param {string} filename - Source filename.
|
|
971
971
|
* @param {string} source - Complete source.
|
|
972
|
-
* @returns {import("../semantic/types.js").RaiseStatement} Semantic raise.
|
|
972
|
+
* @returns {import("../semantic/types.js").RaiseStatement | import("../semantic/types.js").RethrowStatement} Semantic raise or rethrow.
|
|
973
973
|
*/
|
|
974
974
|
function convertRubyRaise(node, context, filename, source) {
|
|
975
975
|
const location = nodeLocation(node, filename, source)
|
|
976
976
|
const arguments_ = node.arguments_?.arguments_ ?? []
|
|
977
|
+
|
|
978
|
+
if (arguments_.length == 0 && context.currentCatchBindingNode) {
|
|
979
|
+
return withParserRanges({
|
|
980
|
+
binding: context.currentCatchBindingNode.name,
|
|
981
|
+
bindingNode: context.currentCatchBindingNode,
|
|
982
|
+
kind: /** @type {const} */ ("RethrowStatement"),
|
|
983
|
+
location
|
|
984
|
+
}, {keyword: prismLocation(node.messageLoc ?? node.location, filename, source)})
|
|
985
|
+
}
|
|
986
|
+
if (arguments_.length == 1 && arguments_[0] instanceof LocalVariableReadNode &&
|
|
987
|
+
context.catchBindingNames?.has(arguments_[0].name) && context.bindings.get(arguments_[0].name)?.kind == "ErrorType") {
|
|
988
|
+
const bindingNode = context.catchBindingNodes?.get(arguments_[0].name)
|
|
989
|
+
|
|
990
|
+
if (!bindingNode) return unsupportedSyntax("ruby", "raise of a catch binding without an exact handler binding", location)
|
|
991
|
+
return withParserRanges({
|
|
992
|
+
binding: arguments_[0].name,
|
|
993
|
+
bindingNode,
|
|
994
|
+
kind: /** @type {const} */ ("RethrowStatement"),
|
|
995
|
+
location
|
|
996
|
+
}, {keyword: prismLocation(node.messageLoc ?? node.location, filename, source)})
|
|
997
|
+
}
|
|
998
|
+
if (node.block || node.callOperatorLoc || arguments_.length != 2) {
|
|
999
|
+
return unsupportedSyntax("ruby", "raise other than exact declared error and message", location)
|
|
1000
|
+
}
|
|
977
1001
|
const errorNode = arguments_[0]
|
|
978
1002
|
|
|
979
|
-
if (
|
|
980
|
-
!(errorNode instanceof ConstantReadNode) && !(errorNode instanceof ConstantPathNode) ||
|
|
1003
|
+
if (!(errorNode instanceof ConstantReadNode) && !(errorNode instanceof ConstantPathNode) ||
|
|
981
1004
|
!context.errorNames.has(constantPathName(errorNode))) {
|
|
982
1005
|
return unsupportedSyntax("ruby", "raise other than exact declared error and message", location)
|
|
983
1006
|
}
|
|
@@ -997,48 +1020,104 @@ function convertRubyRaise(node, context, filename, source) {
|
|
|
997
1020
|
}
|
|
998
1021
|
|
|
999
1022
|
/**
|
|
1000
|
-
* Converts one exact
|
|
1023
|
+
* Converts one exact Prism ensure clause body.
|
|
1024
|
+
* @param {import("@ruby/prism").EnsureNode} node - Prism ensure clause.
|
|
1025
|
+
* @param {import("@ruby/prism/src/deserialize.js").Comment[]} comments - Prism comments.
|
|
1026
|
+
* @param {RubyConversionContext} context - Typed lexical context.
|
|
1027
|
+
* @param {string} filename - Source filename.
|
|
1028
|
+
* @param {string} source - Complete source.
|
|
1029
|
+
* @returns {import("../semantic/types.js").Block} Semantic finally block.
|
|
1030
|
+
*/
|
|
1031
|
+
function convertRubyEnsure(node, comments, context, filename, source) {
|
|
1032
|
+
// A completion block gets its own binding scope, like every other block in convertRubyTry: a local
|
|
1033
|
+
// declaration inside `ensure` must not leak back into the enclosing conversion context.
|
|
1034
|
+
return convertBlock(node.statements ?? null, comments, {...context, bindings: new Map(context.bindings)}, filename, source,
|
|
1035
|
+
nodeLocation(node, filename, source))
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Converts one begin block with ordered exact typed rescues and an optional ensure completion block.
|
|
1001
1040
|
* @param {BeginNode} node - Prism begin node.
|
|
1002
1041
|
* @param {import("@ruby/prism/src/deserialize.js").Comment[]} comments - Parser-owned comments.
|
|
1003
1042
|
* @param {RubyConversionContext} context - Typed lexical context.
|
|
1004
1043
|
* @param {string} filename - Source filename.
|
|
1005
1044
|
* @param {string} source - Complete source.
|
|
1006
|
-
* @returns {import("../semantic/types.js").TryStatement} Semantic exact handler.
|
|
1045
|
+
* @returns {import("../semantic/types.js").TryStatement} Semantic exact handler chain.
|
|
1007
1046
|
*/
|
|
1008
1047
|
function convertRubyTry(node, comments, context, filename, source) {
|
|
1009
1048
|
const location = nodeLocation(node, filename, source)
|
|
1010
|
-
const handler = node.rescueClause
|
|
1011
1049
|
|
|
1012
|
-
if (
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1050
|
+
if (node.elseClause) return unsupportedSyntax("ruby", "begin else clause", nodeLocation(node.elseClause, filename, source))
|
|
1051
|
+
/** @type {import("../semantic/types.js").CatchHandler[]} */
|
|
1052
|
+
const handlers = []
|
|
1053
|
+
const seenHandlerNames = new Set(context.catchBindingNames ?? [])
|
|
1054
|
+
let handler = node.rescueClause
|
|
1055
|
+
|
|
1056
|
+
if (!(handler instanceof RescueNode)) {
|
|
1057
|
+
if (node.ensureClause) {
|
|
1058
|
+
return withParserRanges({
|
|
1059
|
+
body: convertBlock(node.statements, comments, context, filename, source, location),
|
|
1060
|
+
handlers,
|
|
1061
|
+
kind: /** @type {const} */ ("TryStatement"),
|
|
1062
|
+
location,
|
|
1063
|
+
finallyBody: convertRubyEnsure(node.ensureClause, comments, context, filename, source)
|
|
1064
|
+
}, {ensure: nodeLocation(node.ensureClause, filename, source), try: location})
|
|
1065
|
+
}
|
|
1066
|
+
return unsupportedSyntax("ruby", "begin without rescue or ensure", location)
|
|
1017
1067
|
}
|
|
1018
|
-
const errorNode = handler.exceptions[0]
|
|
1019
|
-
const declaration = /** @type {import("../semantic/types.js").ErrorDeclaration} */ (context.errorNames.get(constantPathName(errorNode)))
|
|
1020
|
-
const caughtLocation = nodeLocation(errorNode, filename, source)
|
|
1021
|
-
const catchType = withParserRanges({declarationId: /** @type {string} */ (declaration.id), kind: /** @type {const} */ ("ErrorType")}, {
|
|
1022
|
-
type: caughtLocation
|
|
1023
|
-
})
|
|
1024
1068
|
const bodyContext = {...context, bindings: new Map(context.bindings)}
|
|
1025
|
-
|
|
1069
|
+
while (handler instanceof RescueNode) {
|
|
1070
|
+
if (handler.exceptions.length != 1 ||
|
|
1071
|
+
!(handler.exceptions[0] instanceof ConstantReadNode) && !(handler.exceptions[0] instanceof ConstantPathNode) ||
|
|
1072
|
+
!context.errorNames.has(constantPathName(handler.exceptions[0])) || !(handler.reference instanceof LocalVariableTargetNode)) {
|
|
1073
|
+
return unsupportedSyntax("ruby", "rescue without one exact typed exception and binding", nodeLocation(handler, filename, source))
|
|
1074
|
+
}
|
|
1075
|
+
if (seenHandlerNames.has(handler.reference.name)) {
|
|
1076
|
+
return unsupportedSyntax("ruby", `rescue binding '${handler.reference.name}' shadows an outer handler binding`,
|
|
1077
|
+
nodeLocation(handler.reference, filename, source))
|
|
1078
|
+
}
|
|
1079
|
+
seenHandlerNames.add(handler.reference.name)
|
|
1080
|
+
const errorNode = handler.exceptions[0]
|
|
1081
|
+
const declaration = /** @type {import("../semantic/types.js").ErrorDeclaration} */ (context.errorNames.get(constantPathName(errorNode)))
|
|
1082
|
+
const caughtLocation = nodeLocation(errorNode, filename, source)
|
|
1083
|
+
const catchType = withParserRanges({declarationId: /** @type {string} */ (declaration.id), kind: /** @type {const} */ ("ErrorType")}, {
|
|
1084
|
+
type: caughtLocation
|
|
1085
|
+
})
|
|
1086
|
+
const catchContext = {...context, bindings: new Map(context.bindings), catchBindingNames: new Set(seenHandlerNames),
|
|
1087
|
+
catchBindingNodes: new Map(context.catchBindingNodes)}
|
|
1026
1088
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
body: convertBlock(node.statements, comments, bodyContext, filename, source, location),
|
|
1030
|
-
catchBinding: withParserRanges({
|
|
1089
|
+
catchContext.bindings.set(handler.reference.name, catchType)
|
|
1090
|
+
const catchBindingNode = withParserRanges({
|
|
1031
1091
|
kind: /** @type {const} */ ("CatchBinding"),
|
|
1032
1092
|
location: nodeLocation(handler.reference, filename, source),
|
|
1033
1093
|
mutable: /** @type {const} */ (false),
|
|
1034
1094
|
name: handler.reference.name,
|
|
1035
1095
|
type: catchType
|
|
1036
|
-
}, {name: nodeLocation(handler.reference, filename, source)})
|
|
1037
|
-
|
|
1038
|
-
|
|
1096
|
+
}, {name: nodeLocation(handler.reference, filename, source)})
|
|
1097
|
+
|
|
1098
|
+
catchContext.catchBindingNodes.set(handler.reference.name, catchBindingNode)
|
|
1099
|
+
catchContext.currentCatchBindingNode = catchBindingNode
|
|
1100
|
+
handlers.push(withParserRanges({
|
|
1101
|
+
catchBinding: catchBindingNode,
|
|
1102
|
+
catchBody: convertBlock(handler.statements, comments, catchContext, filename, source, nodeLocation(handler, filename, source)),
|
|
1103
|
+
catchType,
|
|
1104
|
+
kind: /** @type {const} */ ("CatchHandler"),
|
|
1105
|
+
location: nodeLocation(handler, filename, source)
|
|
1106
|
+
}, {catch: nodeLocation(handler, filename, source)}))
|
|
1107
|
+
handler = handler.subsequent
|
|
1108
|
+
}
|
|
1109
|
+
const semantic = /** @type {import("../semantic/types.js").TryStatement} */ (withParserRanges({
|
|
1110
|
+
body: convertBlock(node.statements, comments, bodyContext, filename, source, location),
|
|
1111
|
+
handlers,
|
|
1039
1112
|
kind: /** @type {const} */ ("TryStatement"),
|
|
1040
1113
|
location
|
|
1041
|
-
}, {
|
|
1114
|
+
}, {try: location}))
|
|
1115
|
+
|
|
1116
|
+
if (node.ensureClause) {
|
|
1117
|
+
semantic.finallyBody = convertRubyEnsure(node.ensureClause, comments, context, filename, source)
|
|
1118
|
+
setParserRanges(semantic, {ensure: nodeLocation(node.ensureClause, filename, source), try: location})
|
|
1119
|
+
}
|
|
1120
|
+
return semantic
|
|
1042
1121
|
}
|
|
1043
1122
|
|
|
1044
1123
|
/**
|
package/src/semantic/effects.js
CHANGED
|
@@ -379,10 +379,19 @@ function escapingBlockFailures(block, functionSummaries, methodSummaries, capabi
|
|
|
379
379
|
|
|
380
380
|
for (const statement of block.statements) {
|
|
381
381
|
if (statement.kind == "TryStatement") {
|
|
382
|
-
|
|
382
|
+
let body = escapingBlockFailures(statement.body, functionSummaries, methodSummaries, capabilityFailureIds)
|
|
383
383
|
|
|
384
|
-
|
|
385
|
-
|
|
384
|
+
for (const handler of statement.handlers ?? []) {
|
|
385
|
+
const caughtId = handler.catchType?.declarationId
|
|
386
|
+
|
|
387
|
+
if (typeof caughtId == "string" && body.delete(caughtId)) {
|
|
388
|
+
addFailures(body, escapingBlockFailures(handler.catchBody, functionSummaries, methodSummaries, capabilityFailureIds))
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (statement.finallyBody) {
|
|
392
|
+
// A completion block that can raise replaces the pending completion on the paths where it does,
|
|
393
|
+
// but pending failures still escape on the paths where it completes normally, so both are reachable.
|
|
394
|
+
addFailures(body, escapingBlockFailures(statement.finallyBody, functionSummaries, methodSummaries, capabilityFailureIds))
|
|
386
395
|
}
|
|
387
396
|
addFailures(result, body)
|
|
388
397
|
continue
|
|
@@ -639,7 +648,9 @@ function forEachBlockExpression(block, visitor, postorder) {
|
|
|
639
648
|
visit(statement.condition); forEachBlockExpression(statement.body, visitor, postorder)
|
|
640
649
|
} else if (statement.kind == "RaiseStatement") visit(statement.error.message)
|
|
641
650
|
else if (statement.kind == "TryStatement") {
|
|
642
|
-
forEachBlockExpression(statement.body, visitor, postorder)
|
|
651
|
+
forEachBlockExpression(statement.body, visitor, postorder)
|
|
652
|
+
for (const handler of statement.handlers) forEachBlockExpression(handler.catchBody, visitor, postorder)
|
|
653
|
+
if (statement.finallyBody) forEachBlockExpression(statement.finallyBody, visitor, postorder)
|
|
643
654
|
}
|
|
644
655
|
}
|
|
645
656
|
|
|
@@ -694,7 +705,8 @@ function statementReferencesProtectedEntry(statement) {
|
|
|
694
705
|
case "RaiseStatement":
|
|
695
706
|
return expressionReferencesProtectedEntry(statement.error.message)
|
|
696
707
|
case "TryStatement":
|
|
697
|
-
return blockReferencesProtectedEntry(statement.body) || blockReferencesProtectedEntry(
|
|
708
|
+
return blockReferencesProtectedEntry(statement.body) || statement.handlers.some((handler) => blockReferencesProtectedEntry(handler.catchBody)) ||
|
|
709
|
+
(statement.finallyBody ? blockReferencesProtectedEntry(statement.finallyBody) : false)
|
|
698
710
|
default:
|
|
699
711
|
return false
|
|
700
712
|
}
|