semantifold 0.11.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 +2 -1
- package/build/src/backends/c.d.ts +2 -1
- package/build/src/backends/c.d.ts.map +1 -1
- package/build/src/backends/c.js +26 -5
- package/build/src/backends/cpp.js +3 -1
- 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/ordered-expressions.d.ts +19 -7
- package/build/src/backends/ordered-expressions.d.ts.map +1 -1
- package/build/src/backends/ordered-expressions.js +46 -11
- 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 +92 -37
- package/build/src/backends/typescript.js +83 -18
- package/build/src/frontends/c.d.ts +34 -1
- package/build/src/frontends/c.d.ts.map +1 -1
- package/build/src/frontends/c.js +128 -21
- 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/language-registry.d.ts.map +1 -1
- package/build/src/language-registry.js +16 -1
- 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 +106 -35
- 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/c.md +1 -1
- package/docs/language-support.md +10 -9
- package/docs/testing.md +8 -5
- package/package.json +1 -1
- package/src/backends/c.js +23 -4
- package/src/backends/cpp.js +1 -0
- package/src/backends/effects.js +2 -1
- package/src/backends/java.js +33 -14
- package/src/backends/javascript.js +83 -18
- package/src/backends/ordered-expressions.js +46 -10
- package/src/backends/php.js +33 -14
- package/src/backends/ruby.js +36 -12
- package/src/backends/shared.js +89 -42
- package/src/backends/typescript.js +83 -18
- package/src/frontends/c.js +135 -19
- 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/language-registry.js +15 -0
- 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 +105 -36
package/src/frontends/java.js
CHANGED
|
@@ -4,7 +4,7 @@ import {parser} from "@lezer/java"
|
|
|
4
4
|
import {SemantifoldDiagnostic, missingType, unsupportedSyntax} from "../diagnostic.js"
|
|
5
5
|
import {locationFromOffsets, moduleLocation} from "../semantic/location.js"
|
|
6
6
|
import {withAdaptedOperation} from "../semantic/operators.js"
|
|
7
|
-
import {withParserRanges} from "../semantic/provenance.js"
|
|
7
|
+
import {setParserRanges, withParserRanges} from "../semantic/provenance.js"
|
|
8
8
|
import {hasOnlyUnicodeScalars} from "../semantic/scalars.js"
|
|
9
9
|
import {isTask034ResourceProbe} from "../semantic/capabilities.js"
|
|
10
10
|
import {hasExactEffectSupport} from "../backends/effects.js"
|
|
@@ -41,6 +41,8 @@ const referenceMethodHooks = new Set(["clone", "equals", "finalize", "getClass",
|
|
|
41
41
|
/**
|
|
42
42
|
* @typedef JavaConversionContext
|
|
43
43
|
* @property {Map<string, import("../semantic/types.js").SemanticBindingType>} bindings - Explicitly typed visible bindings.
|
|
44
|
+
* @property {Set<string>} [catchBindingNames] - Lexically visible exact catch binding names.
|
|
45
|
+
* @property {Map<string, import("../semantic/types.js").CatchBinding>} [catchBindingNodes] - Exact catch binding nodes by visible name.
|
|
44
46
|
* @property {Map<string, import("../semantic/types.js").ClassDeclaration>} classes - Reference classes by identity.
|
|
45
47
|
* @property {import("../semantic/types.js").ClassDeclaration} [currentClass] - Enclosing reference class.
|
|
46
48
|
* @property {Map<string, import("../semantic/types.js").ErrorDeclaration>} errorNames - Errors by source name.
|
|
@@ -970,7 +972,7 @@ function convertStatement(statement, filename, source, context) {
|
|
|
970
972
|
* @param {string} filename - Source filename.
|
|
971
973
|
* @param {string} source - Complete source.
|
|
972
974
|
* @param {JavaConversionContext} context - Typed lexical context.
|
|
973
|
-
* @returns {import("../semantic/types.js").RaiseStatement} Semantic raise.
|
|
975
|
+
* @returns {import("../semantic/types.js").RaiseStatement | import("../semantic/types.js").RethrowStatement} Semantic raise or rethrow.
|
|
974
976
|
*/
|
|
975
977
|
function convertJavaRaise(statement, filename, source, context) {
|
|
976
978
|
const location = nodeLocation(statement, filename, source)
|
|
@@ -980,6 +982,23 @@ function convertJavaRaise(statement, filename, source, context) {
|
|
|
980
982
|
const arguments_ = argumentsNode ? structuralChildren(argumentsNode) : []
|
|
981
983
|
const declaration = typeNode ? context.errorNames.get(nodeText(typeNode, source)) : undefined
|
|
982
984
|
|
|
985
|
+
if (!construction && arguments_.length == 0) {
|
|
986
|
+
const throwNode = requiredChild(statement, "throw", filename, source)
|
|
987
|
+
const rethrown = structuralChildren(statement).find((candidate) => candidate.name == "Identifier")
|
|
988
|
+
const binding = rethrown ? context.bindings.get(nodeText(rethrown, source)) : undefined
|
|
989
|
+
|
|
990
|
+
if (rethrown && binding?.kind == "ErrorType" && context.catchBindingNames?.has(nodeText(rethrown, source))) {
|
|
991
|
+
const bindingNode = context.catchBindingNodes?.get(nodeText(rethrown, source))
|
|
992
|
+
|
|
993
|
+
if (!bindingNode) return unsupportedSyntax("java", "throw of a catch binding without an exact handler binding", location)
|
|
994
|
+
return withParserRanges({
|
|
995
|
+
binding: nodeText(rethrown, source),
|
|
996
|
+
bindingNode,
|
|
997
|
+
kind: /** @type {const} */ ("RethrowStatement"),
|
|
998
|
+
location
|
|
999
|
+
}, {keyword: nodeLocation(throwNode, filename, source)})
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
983
1002
|
if (!construction || !typeNode || !argumentsNode || !declaration || arguments_.length != 1 || construction.getChild("ClassBody")) {
|
|
984
1003
|
return unsupportedSyntax("java", "throw other than exact declared error construction", location)
|
|
985
1004
|
}
|
|
@@ -1009,46 +1028,76 @@ function convertJavaTry(statement, filename, source, context) {
|
|
|
1009
1028
|
const location = nodeLocation(statement, filename, source)
|
|
1010
1029
|
const body = statement.getChild("Block")
|
|
1011
1030
|
const catches = statement.getChildren("CatchClause")
|
|
1031
|
+
const finalizer = statement.getChild("FinallyClause")
|
|
1032
|
+
|
|
1033
|
+
if (!body || (catches.length == 0 && !finalizer) || statement.getChild("ResourceSpecification")) {
|
|
1034
|
+
return unsupportedSyntax("java", "try without catches or finally and without resources", location)
|
|
1035
|
+
}
|
|
1036
|
+
/** @type {import("../semantic/types.js").CatchHandler[]} */
|
|
1037
|
+
const handlers = []
|
|
1038
|
+
const seenHandlerNames = new Set(context.catchBindingNames ?? [])
|
|
1039
|
+
|
|
1040
|
+
for (const handler of catches) {
|
|
1041
|
+
const parameter = handler.getChild("CatchFormalParameter")
|
|
1042
|
+
const caught = parameter?.getChild("CatchType")
|
|
1043
|
+
const typeNodes = caught ? structuralChildren(caught) : []
|
|
1044
|
+
const definition = parameter?.getChild("Definition")
|
|
1045
|
+
const catchBody = handler.getChild("Block")
|
|
1046
|
+
const typeNode = typeNodes[0]
|
|
1047
|
+
const declaration = typeNode ? context.errorNames.get(nodeText(typeNode, source)) : undefined
|
|
1048
|
+
|
|
1049
|
+
if (!parameter || !caught || typeNodes.length != 1 || typeNode?.name != "TypeName" || !definition || !catchBody || !declaration ||
|
|
1050
|
+
parameter.getChild("Modifiers")) {
|
|
1051
|
+
return unsupportedSyntax("java", "broad, multiple, or malformed catch", nodeLocation(handler, filename, source))
|
|
1052
|
+
}
|
|
1053
|
+
const name = nodeText(definition, source)
|
|
1012
1054
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
const declaration = typeNode ? context.errorNames.get(nodeText(typeNode, source)) : undefined
|
|
1024
|
-
|
|
1025
|
-
if (!parameter || !caught || typeNodes.length != 1 || typeNode?.name != "TypeName" || !definition || !catchBody || !declaration ||
|
|
1026
|
-
parameter.getChild("Modifiers")) {
|
|
1027
|
-
return unsupportedSyntax("java", "broad, multiple, or malformed catch", nodeLocation(handler, filename, source))
|
|
1028
|
-
}
|
|
1029
|
-
const caughtLocation = nodeLocation(typeNode, filename, source)
|
|
1030
|
-
const catchType = withParserRanges({declarationId: /** @type {string} */ (declaration.id), kind: /** @type {const} */ ("ErrorType")}, {
|
|
1031
|
-
type: caughtLocation
|
|
1032
|
-
})
|
|
1033
|
-
const bodyContext = {...context, bindings: new Map(context.bindings), erasedBindingNames: new Set(context.erasedBindingNames)}
|
|
1034
|
-
const catchContext = {...context, bindings: new Map(context.bindings), erasedBindingNames: new Set(context.erasedBindingNames)}
|
|
1035
|
-
const name = nodeText(definition, source)
|
|
1055
|
+
if (seenHandlerNames.has(name)) {
|
|
1056
|
+
return unsupportedSyntax("java", `catch binding '${name}' shadows an outer handler binding`, nodeLocation(definition, filename, source))
|
|
1057
|
+
}
|
|
1058
|
+
seenHandlerNames.add(name)
|
|
1059
|
+
const caughtLocation = nodeLocation(typeNode, filename, source)
|
|
1060
|
+
const catchType = withParserRanges({declarationId: /** @type {string} */ (declaration.id), kind: /** @type {const} */ ("ErrorType")}, {
|
|
1061
|
+
type: caughtLocation
|
|
1062
|
+
})
|
|
1063
|
+
const catchContext = {...context, bindings: new Map(context.bindings), erasedBindingNames: new Set(context.erasedBindingNames),
|
|
1064
|
+
catchBindingNames: new Set(seenHandlerNames), catchBindingNodes: new Map(context.catchBindingNodes)}
|
|
1036
1065
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
body: convertBlock(body, filename, source, bodyContext),
|
|
1040
|
-
catchBinding: withParserRanges({
|
|
1066
|
+
catchContext.bindings.set(name, catchType)
|
|
1067
|
+
const catchBindingNode = withParserRanges({
|
|
1041
1068
|
kind: /** @type {const} */ ("CatchBinding"),
|
|
1042
1069
|
location: nodeLocation(definition, filename, source),
|
|
1043
1070
|
mutable: /** @type {const} */ (false),
|
|
1044
1071
|
name,
|
|
1045
1072
|
type: catchType
|
|
1046
|
-
}, {name: nodeLocation(definition, filename, source)})
|
|
1047
|
-
|
|
1048
|
-
|
|
1073
|
+
}, {name: nodeLocation(definition, filename, source)})
|
|
1074
|
+
|
|
1075
|
+
catchContext.catchBindingNodes.set(name, catchBindingNode)
|
|
1076
|
+
handlers.push(withParserRanges({
|
|
1077
|
+
catchBinding: catchBindingNode,
|
|
1078
|
+
catchBody: convertBlock(catchBody, filename, source, catchContext),
|
|
1079
|
+
catchType,
|
|
1080
|
+
kind: /** @type {const} */ ("CatchHandler"),
|
|
1081
|
+
location: nodeLocation(handler, filename, source)
|
|
1082
|
+
}, {catch: nodeLocation(handler, filename, source)}))
|
|
1083
|
+
}
|
|
1084
|
+
const bodyContext = {...context, bindings: new Map(context.bindings), erasedBindingNames: new Set(context.erasedBindingNames)}
|
|
1085
|
+
const semantic = /** @type {import("../semantic/types.js").TryStatement} */ (withParserRanges({
|
|
1086
|
+
body: convertBlock(body, filename, source, bodyContext),
|
|
1087
|
+
handlers,
|
|
1049
1088
|
kind: /** @type {const} */ ("TryStatement"),
|
|
1050
1089
|
location
|
|
1051
|
-
}, {
|
|
1090
|
+
}, {try: nodeLocation(requiredChild(statement, "try", filename, source), filename, source)}))
|
|
1091
|
+
|
|
1092
|
+
if (finalizer) {
|
|
1093
|
+
const finallyBlock = finalizer.getChild("Block")
|
|
1094
|
+
|
|
1095
|
+
if (!finallyBlock) return unsupportedSyntax("java", "malformed finally block", nodeLocation(finalizer, filename, source))
|
|
1096
|
+
semantic.finallyBody = convertBlock(finallyBlock, filename, source, bodyContext)
|
|
1097
|
+
setParserRanges(semantic, {finally: nodeLocation(finalizer, filename, source),
|
|
1098
|
+
try: nodeLocation(requiredChild(statement, "try", filename, source), filename, source)})
|
|
1099
|
+
}
|
|
1100
|
+
return semantic
|
|
1052
1101
|
}
|
|
1053
1102
|
|
|
1054
1103
|
/**
|
|
@@ -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
|
/**
|