semantifold 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/build/src/backends/effects.js +5 -2
- package/build/src/backends/java.js +33 -14
- package/build/src/backends/javascript.js +83 -18
- package/build/src/backends/php.d.ts.map +1 -1
- package/build/src/backends/php.js +88 -16
- package/build/src/backends/ruby.js +37 -12
- package/build/src/backends/scalars.js +2 -2
- package/build/src/backends/shared.d.ts +2 -1
- package/build/src/backends/shared.d.ts.map +1 -1
- package/build/src/backends/shared.js +299 -42
- package/build/src/backends/typescript.js +83 -18
- package/build/src/backends/writer.d.ts +18 -4
- package/build/src/backends/writer.d.ts.map +1 -1
- package/build/src/backends/writer.js +40 -4
- package/build/src/frontends/java.d.ts +9 -1
- package/build/src/frontends/java.d.ts.map +1 -1
- package/build/src/frontends/java.js +90 -43
- package/build/src/frontends/javascript-typescript.d.ts +3 -1
- package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
- package/build/src/frontends/javascript-typescript.js +133 -52
- package/build/src/frontends/php.d.ts +13 -1
- package/build/src/frontends/php.d.ts.map +1 -1
- package/build/src/frontends/php.js +226 -59
- package/build/src/frontends/ruby.d.ts +4 -1
- package/build/src/frontends/ruby.d.ts.map +1 -1
- package/build/src/frontends/ruby.js +113 -37
- package/build/src/frontends/types.d.ts +9 -2
- package/build/src/frontends/types.d.ts.map +1 -1
- package/build/src/frontends/types.js +26 -9
- package/build/src/language-registry.d.ts.map +1 -1
- package/build/src/language-registry.js +14 -6
- package/build/src/mapping.d.ts.map +1 -1
- package/build/src/mapping.js +4 -2
- package/build/src/semantic/effects.d.ts.map +1 -1
- package/build/src/semantic/effects.js +20 -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 +61 -9
- package/build/src/semantic/types.d.ts +214 -23
- package/build/src/semantic/types.d.ts.map +1 -1
- package/build/src/semantic/types.js +58 -11
- package/build/src/semantic/validate.d.ts +31 -2
- package/build/src/semantic/validate.d.ts.map +1 -1
- package/build/src/semantic/validate.js +450 -60
- package/docs/adr/20261026-typed-error-handling-extension.md +55 -0
- package/docs/adr-bounded-subtyping.md +180 -0
- package/docs/adr-numeric-tower.md +156 -0
- package/docs/architecture.md +1 -1
- package/docs/language-support.md +8 -8
- package/docs/testing.md +5 -3
- package/package.json +1 -1
- package/src/backends/effects.js +3 -1
- package/src/backends/java.js +33 -14
- package/src/backends/javascript.js +83 -18
- package/src/backends/php.js +89 -16
- package/src/backends/ruby.js +36 -12
- package/src/backends/scalars.js +1 -1
- package/src/backends/shared.js +298 -47
- package/src/backends/typescript.js +83 -18
- package/src/backends/writer.js +41 -3
- package/src/frontends/java.js +95 -43
- package/src/frontends/javascript-typescript.js +145 -53
- package/src/frontends/php.js +243 -58
- package/src/frontends/ruby.js +117 -35
- package/src/frontends/types.js +26 -8
- package/src/language-registry.js +13 -5
- package/src/mapping.js +3 -1
- package/src/semantic/effects.js +18 -5
- package/src/semantic/lifetime.js +29 -3
- package/src/semantic/provenance.js +60 -14
- package/src/semantic/types.js +62 -10
- package/src/semantic/validate.js +481 -61
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 | import("../semantic/types.js").InterfaceDeclaration>, 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"],
|
|
@@ -357,6 +357,9 @@ function convertExpression(node, filename, source, context, expectedType, preser
|
|
|
357
357
|
reference: referenceType(/** @type {string} */ (declaration.id), nodeLocation(node.receiver, filename, source))},
|
|
358
358
|
{class: nodeLocation(node.receiver, filename, source)})
|
|
359
359
|
}
|
|
360
|
+
if (declaration.kind == "InterfaceDeclaration") {
|
|
361
|
+
return unsupportedSyntax("ruby", "construction of an interface", nodeLocation(node.receiver, filename, source))
|
|
362
|
+
}
|
|
360
363
|
if (declaration.kind == "EffectResourceDeclaration") {
|
|
361
364
|
return unsupportedSyntax("ruby", "owned resource construction", nodeLocation(node.receiver, filename, source))
|
|
362
365
|
}
|
|
@@ -817,7 +820,7 @@ function associatedComments(comments, node, source) {
|
|
|
817
820
|
* @param {LocalVariableWriteNode} node - Local write.
|
|
818
821
|
* @param {string} filename - Source filename.
|
|
819
822
|
* @param {string} source - Complete source.
|
|
820
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by source name.
|
|
823
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} recordNames - Nominal declarations by source name.
|
|
821
824
|
* @param {Map<string, import("../semantic/types.js").TypeParameter>} [typeParameters] - Declaration-scoped parameters.
|
|
822
825
|
* @returns {{immutable: boolean, type: import("../semantic/types.js").SemanticValueType} | undefined} Metadata when present.
|
|
823
826
|
*/
|
|
@@ -969,15 +972,38 @@ function convertStatement(node, comments, context, filename, source) {
|
|
|
969
972
|
* @param {RubyConversionContext} context - Typed lexical context.
|
|
970
973
|
* @param {string} filename - Source filename.
|
|
971
974
|
* @param {string} source - Complete source.
|
|
972
|
-
* @returns {import("../semantic/types.js").RaiseStatement} Semantic raise.
|
|
975
|
+
* @returns {import("../semantic/types.js").RaiseStatement | import("../semantic/types.js").RethrowStatement} Semantic raise or rethrow.
|
|
973
976
|
*/
|
|
974
977
|
function convertRubyRaise(node, context, filename, source) {
|
|
975
978
|
const location = nodeLocation(node, filename, source)
|
|
976
979
|
const arguments_ = node.arguments_?.arguments_ ?? []
|
|
980
|
+
|
|
981
|
+
if (arguments_.length == 0 && context.currentCatchBindingNode) {
|
|
982
|
+
return withParserRanges({
|
|
983
|
+
binding: context.currentCatchBindingNode.name,
|
|
984
|
+
bindingNode: context.currentCatchBindingNode,
|
|
985
|
+
kind: /** @type {const} */ ("RethrowStatement"),
|
|
986
|
+
location
|
|
987
|
+
}, {keyword: prismLocation(node.messageLoc ?? node.location, filename, source)})
|
|
988
|
+
}
|
|
989
|
+
if (arguments_.length == 1 && arguments_[0] instanceof LocalVariableReadNode &&
|
|
990
|
+
context.catchBindingNames?.has(arguments_[0].name) && context.bindings.get(arguments_[0].name)?.kind == "ErrorType") {
|
|
991
|
+
const bindingNode = context.catchBindingNodes?.get(arguments_[0].name)
|
|
992
|
+
|
|
993
|
+
if (!bindingNode) return unsupportedSyntax("ruby", "raise of a catch binding without an exact handler binding", location)
|
|
994
|
+
return withParserRanges({
|
|
995
|
+
binding: arguments_[0].name,
|
|
996
|
+
bindingNode,
|
|
997
|
+
kind: /** @type {const} */ ("RethrowStatement"),
|
|
998
|
+
location
|
|
999
|
+
}, {keyword: prismLocation(node.messageLoc ?? node.location, filename, source)})
|
|
1000
|
+
}
|
|
1001
|
+
if (node.block || node.callOperatorLoc || arguments_.length != 2) {
|
|
1002
|
+
return unsupportedSyntax("ruby", "raise other than exact declared error and message", location)
|
|
1003
|
+
}
|
|
977
1004
|
const errorNode = arguments_[0]
|
|
978
1005
|
|
|
979
|
-
if (
|
|
980
|
-
!(errorNode instanceof ConstantReadNode) && !(errorNode instanceof ConstantPathNode) ||
|
|
1006
|
+
if (!(errorNode instanceof ConstantReadNode) && !(errorNode instanceof ConstantPathNode) ||
|
|
981
1007
|
!context.errorNames.has(constantPathName(errorNode))) {
|
|
982
1008
|
return unsupportedSyntax("ruby", "raise other than exact declared error and message", location)
|
|
983
1009
|
}
|
|
@@ -997,48 +1023,104 @@ function convertRubyRaise(node, context, filename, source) {
|
|
|
997
1023
|
}
|
|
998
1024
|
|
|
999
1025
|
/**
|
|
1000
|
-
* Converts one exact
|
|
1026
|
+
* Converts one exact Prism ensure clause body.
|
|
1027
|
+
* @param {import("@ruby/prism").EnsureNode} node - Prism ensure clause.
|
|
1028
|
+
* @param {import("@ruby/prism/src/deserialize.js").Comment[]} comments - Prism comments.
|
|
1029
|
+
* @param {RubyConversionContext} context - Typed lexical context.
|
|
1030
|
+
* @param {string} filename - Source filename.
|
|
1031
|
+
* @param {string} source - Complete source.
|
|
1032
|
+
* @returns {import("../semantic/types.js").Block} Semantic finally block.
|
|
1033
|
+
*/
|
|
1034
|
+
function convertRubyEnsure(node, comments, context, filename, source) {
|
|
1035
|
+
// A completion block gets its own binding scope, like every other block in convertRubyTry: a local
|
|
1036
|
+
// declaration inside `ensure` must not leak back into the enclosing conversion context.
|
|
1037
|
+
return convertBlock(node.statements ?? null, comments, {...context, bindings: new Map(context.bindings)}, filename, source,
|
|
1038
|
+
nodeLocation(node, filename, source))
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Converts one begin block with ordered exact typed rescues and an optional ensure completion block.
|
|
1001
1043
|
* @param {BeginNode} node - Prism begin node.
|
|
1002
1044
|
* @param {import("@ruby/prism/src/deserialize.js").Comment[]} comments - Parser-owned comments.
|
|
1003
1045
|
* @param {RubyConversionContext} context - Typed lexical context.
|
|
1004
1046
|
* @param {string} filename - Source filename.
|
|
1005
1047
|
* @param {string} source - Complete source.
|
|
1006
|
-
* @returns {import("../semantic/types.js").TryStatement} Semantic exact handler.
|
|
1048
|
+
* @returns {import("../semantic/types.js").TryStatement} Semantic exact handler chain.
|
|
1007
1049
|
*/
|
|
1008
1050
|
function convertRubyTry(node, comments, context, filename, source) {
|
|
1009
1051
|
const location = nodeLocation(node, filename, source)
|
|
1010
|
-
const handler = node.rescueClause
|
|
1011
1052
|
|
|
1012
|
-
if (
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1053
|
+
if (node.elseClause) return unsupportedSyntax("ruby", "begin else clause", nodeLocation(node.elseClause, filename, source))
|
|
1054
|
+
/** @type {import("../semantic/types.js").CatchHandler[]} */
|
|
1055
|
+
const handlers = []
|
|
1056
|
+
const seenHandlerNames = new Set(context.catchBindingNames ?? [])
|
|
1057
|
+
let handler = node.rescueClause
|
|
1058
|
+
|
|
1059
|
+
if (!(handler instanceof RescueNode)) {
|
|
1060
|
+
if (node.ensureClause) {
|
|
1061
|
+
return withParserRanges({
|
|
1062
|
+
body: convertBlock(node.statements, comments, context, filename, source, location),
|
|
1063
|
+
handlers,
|
|
1064
|
+
kind: /** @type {const} */ ("TryStatement"),
|
|
1065
|
+
location,
|
|
1066
|
+
finallyBody: convertRubyEnsure(node.ensureClause, comments, context, filename, source)
|
|
1067
|
+
}, {ensure: nodeLocation(node.ensureClause, filename, source), try: location})
|
|
1068
|
+
}
|
|
1069
|
+
return unsupportedSyntax("ruby", "begin without rescue or ensure", location)
|
|
1017
1070
|
}
|
|
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
1071
|
const bodyContext = {...context, bindings: new Map(context.bindings)}
|
|
1025
|
-
|
|
1072
|
+
while (handler instanceof RescueNode) {
|
|
1073
|
+
if (handler.exceptions.length != 1 ||
|
|
1074
|
+
!(handler.exceptions[0] instanceof ConstantReadNode) && !(handler.exceptions[0] instanceof ConstantPathNode) ||
|
|
1075
|
+
!context.errorNames.has(constantPathName(handler.exceptions[0])) || !(handler.reference instanceof LocalVariableTargetNode)) {
|
|
1076
|
+
return unsupportedSyntax("ruby", "rescue without one exact typed exception and binding", nodeLocation(handler, filename, source))
|
|
1077
|
+
}
|
|
1078
|
+
if (seenHandlerNames.has(handler.reference.name)) {
|
|
1079
|
+
return unsupportedSyntax("ruby", `rescue binding '${handler.reference.name}' shadows an outer handler binding`,
|
|
1080
|
+
nodeLocation(handler.reference, filename, source))
|
|
1081
|
+
}
|
|
1082
|
+
seenHandlerNames.add(handler.reference.name)
|
|
1083
|
+
const errorNode = handler.exceptions[0]
|
|
1084
|
+
const declaration = /** @type {import("../semantic/types.js").ErrorDeclaration} */ (context.errorNames.get(constantPathName(errorNode)))
|
|
1085
|
+
const caughtLocation = nodeLocation(errorNode, filename, source)
|
|
1086
|
+
const catchType = withParserRanges({declarationId: /** @type {string} */ (declaration.id), kind: /** @type {const} */ ("ErrorType")}, {
|
|
1087
|
+
type: caughtLocation
|
|
1088
|
+
})
|
|
1089
|
+
const catchContext = {...context, bindings: new Map(context.bindings), catchBindingNames: new Set(seenHandlerNames),
|
|
1090
|
+
catchBindingNodes: new Map(context.catchBindingNodes)}
|
|
1026
1091
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
body: convertBlock(node.statements, comments, bodyContext, filename, source, location),
|
|
1030
|
-
catchBinding: withParserRanges({
|
|
1092
|
+
catchContext.bindings.set(handler.reference.name, catchType)
|
|
1093
|
+
const catchBindingNode = withParserRanges({
|
|
1031
1094
|
kind: /** @type {const} */ ("CatchBinding"),
|
|
1032
1095
|
location: nodeLocation(handler.reference, filename, source),
|
|
1033
1096
|
mutable: /** @type {const} */ (false),
|
|
1034
1097
|
name: handler.reference.name,
|
|
1035
1098
|
type: catchType
|
|
1036
|
-
}, {name: nodeLocation(handler.reference, filename, source)})
|
|
1037
|
-
|
|
1038
|
-
|
|
1099
|
+
}, {name: nodeLocation(handler.reference, filename, source)})
|
|
1100
|
+
|
|
1101
|
+
catchContext.catchBindingNodes.set(handler.reference.name, catchBindingNode)
|
|
1102
|
+
catchContext.currentCatchBindingNode = catchBindingNode
|
|
1103
|
+
handlers.push(withParserRanges({
|
|
1104
|
+
catchBinding: catchBindingNode,
|
|
1105
|
+
catchBody: convertBlock(handler.statements, comments, catchContext, filename, source, nodeLocation(handler, filename, source)),
|
|
1106
|
+
catchType,
|
|
1107
|
+
kind: /** @type {const} */ ("CatchHandler"),
|
|
1108
|
+
location: nodeLocation(handler, filename, source)
|
|
1109
|
+
}, {catch: nodeLocation(handler, filename, source)}))
|
|
1110
|
+
handler = handler.subsequent
|
|
1111
|
+
}
|
|
1112
|
+
const semantic = /** @type {import("../semantic/types.js").TryStatement} */ (withParserRanges({
|
|
1113
|
+
body: convertBlock(node.statements, comments, bodyContext, filename, source, location),
|
|
1114
|
+
handlers,
|
|
1039
1115
|
kind: /** @type {const} */ ("TryStatement"),
|
|
1040
1116
|
location
|
|
1041
|
-
}, {
|
|
1117
|
+
}, {try: location}))
|
|
1118
|
+
|
|
1119
|
+
if (node.ensureClause) {
|
|
1120
|
+
semantic.finallyBody = convertRubyEnsure(node.ensureClause, comments, context, filename, source)
|
|
1121
|
+
setParserRanges(semantic, {ensure: nodeLocation(node.ensureClause, filename, source), try: location})
|
|
1122
|
+
}
|
|
1123
|
+
return semantic
|
|
1042
1124
|
}
|
|
1043
1125
|
|
|
1044
1126
|
/**
|
|
@@ -1222,7 +1304,7 @@ function isImmediateCommentGap(gap) {
|
|
|
1222
1304
|
* @param {import("../semantic/types.js").SourceLocation} location - Source location.
|
|
1223
1305
|
* @param {string} source - Complete parser input.
|
|
1224
1306
|
* @param {import("../semantic/types.js").SourceLocation} [typeLocation] - Exact type comment token location.
|
|
1225
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} [recordNames] - Nominal declarations by source name.
|
|
1307
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} [recordNames] - Nominal declarations by source name.
|
|
1226
1308
|
* @param {Map<string, import("../semantic/types.js").TypeParameter>} [typeParameters] - Declaration-scoped parameters.
|
|
1227
1309
|
* @returns {import("../semantic/types.js").SemanticValueType} Semantic type.
|
|
1228
1310
|
*/
|
|
@@ -1236,7 +1318,7 @@ function convertType(sourceType, subject, location, source, typeLocation = locat
|
|
|
1236
1318
|
* @param {import("@ruby/prism/src/deserialize.js").Comment[]} comments - Prism comments.
|
|
1237
1319
|
* @param {string} filename - Source filename.
|
|
1238
1320
|
* @param {string} source - Complete source.
|
|
1239
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by source name.
|
|
1321
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} recordNames - Nominal declarations by source name.
|
|
1240
1322
|
* @param {string} ownerId - Stable function identity.
|
|
1241
1323
|
* @param {boolean} [allowBuiltinOutput] - Whether compiler authority admits the exact output facade function.
|
|
1242
1324
|
* @returns {RubyFunctionSignature} Semantic signature.
|
|
@@ -1301,7 +1383,7 @@ function convertFunctionSignature(node, comments, filename, source, recordNames,
|
|
|
1301
1383
|
* @param {DefNode} node - Prism definition.
|
|
1302
1384
|
* @param {RubyFunctionSignature} signature - Converted signature.
|
|
1303
1385
|
* @param {Map<string, RubyFunctionSignature>} functions - Module signatures.
|
|
1304
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by source name.
|
|
1386
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} recordNames - Nominal declarations by source name.
|
|
1305
1387
|
* @param {Map<string, import("../semantic/types.js").RecordDeclaration>} records - Record declarations by identity.
|
|
1306
1388
|
* @param {Map<string, import("../semantic/types.js").ClassDeclaration>} classes - Reference classes by identity.
|
|
1307
1389
|
* @param {Map<string, import("../semantic/types.js").ErrorDeclaration>} errorNames - Error declarations by source name.
|
|
@@ -1412,7 +1494,7 @@ function convertRubyError(node, declaration, filename, source) {
|
|
|
1412
1494
|
* Converts the bounded Ruby reference-class profile.
|
|
1413
1495
|
* @param {ClassNode} node - Prism class declaration.
|
|
1414
1496
|
* @param {import("../semantic/types.js").ClassDeclaration} declaration - Predeclared class identity.
|
|
1415
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} nominalNames - Visible nominal declarations.
|
|
1497
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} nominalNames - Visible nominal declarations.
|
|
1416
1498
|
* @param {Map<string, import("../semantic/types.js").RecordDeclaration>} records - Records by identity.
|
|
1417
1499
|
* @param {Map<string, import("../semantic/types.js").ClassDeclaration>} classes - Classes by identity.
|
|
1418
1500
|
* @param {Map<string, RubyFunctionSignature>} functions - Top-level signatures.
|
|
@@ -1528,7 +1610,7 @@ function convertRubyReferenceClass(node, declaration, nominalNames, records, cla
|
|
|
1528
1610
|
* Converts one exact typed-reader/initializer/freeze Ruby record profile.
|
|
1529
1611
|
* @param {ClassNode} node - Prism class declaration.
|
|
1530
1612
|
* @param {import("../semantic/types.js").RecordDeclaration} declaration - Predeclared nominal identity.
|
|
1531
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by name.
|
|
1613
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} recordNames - Nominal declarations by name.
|
|
1532
1614
|
* @param {import("@ruby/prism/src/deserialize.js").Comment[]} comments - Parser-owned comments.
|
|
1533
1615
|
* @param {string} filename - Source filename.
|
|
1534
1616
|
* @param {string} source - Complete source.
|
|
@@ -1815,7 +1897,7 @@ export function parseRuby({capabilities = [], filename, source, program}) {
|
|
|
1815
1897
|
classDeclarations.findIndex((candidate) => candidate.name == declaration.name) != index)
|
|
1816
1898
|
|
|
1817
1899
|
if (reopened) return unsupportedSyntax("ruby", `reference class reopening '${reopened.name}'`, reopened.location)
|
|
1818
|
-
const recordNames = /** @type {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} */ (
|
|
1900
|
+
const recordNames = /** @type {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} */ (
|
|
1819
1901
|
new Map(program?.records ?? []))
|
|
1820
1902
|
for (const [name, declaration] of program?.classes ?? []) recordNames.set(name, declaration)
|
|
1821
1903
|
for (const resource of capabilities.flatMap(({resources}) => resources)) recordNames.set(resource.name, resource)
|
package/src/frontends/types.js
CHANGED
|
@@ -55,6 +55,7 @@ export function iterationBindingType(type, location) {
|
|
|
55
55
|
if (type.kind == "RecordType") return recordType(type.declarationId, location,
|
|
56
56
|
type.arguments?.map((argument) => iterationBindingType(argument, location)))
|
|
57
57
|
if (type.kind == "ReferenceType") return referenceType(type.declarationId, location)
|
|
58
|
+
if (type.kind == "InterfaceType") return interfaceType(type.declarationId, location)
|
|
58
59
|
if (type.kind == "OwnedResourceType") return ownedResourceType(type.resourceId, location)
|
|
59
60
|
if (type.kind == "OwnedReferenceType") return ownedReferenceType(type.declarationId, location)
|
|
60
61
|
|
|
@@ -166,6 +167,19 @@ export function referenceType(declarationId, location) {
|
|
|
166
167
|
return type
|
|
167
168
|
}
|
|
168
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Builds one nominal interface type with a parser-owned type range.
|
|
172
|
+
* @param {string} declarationId - Stable interface declaration identity.
|
|
173
|
+
* @param {import("../semantic/types.js").SourceLocation} location - Complete type-expression range.
|
|
174
|
+
* @returns {import("../semantic/types.js").InterfaceType} Interface type.
|
|
175
|
+
*/
|
|
176
|
+
export function interfaceType(declarationId, location) {
|
|
177
|
+
const type = {declarationId, kind: /** @type {const} */ ("InterfaceType")}
|
|
178
|
+
|
|
179
|
+
setParserRanges(type, {type: location})
|
|
180
|
+
return type
|
|
181
|
+
}
|
|
182
|
+
|
|
169
183
|
/**
|
|
170
184
|
* Builds one compiler-owned resource type with a parser-owned name range.
|
|
171
185
|
* @param {string} resourceId - Stable capability resource identity.
|
|
@@ -392,7 +406,7 @@ function collectKnownSubstitutions(formal, actual, substitutions) {
|
|
|
392
406
|
* @param {"javascript" | "php" | "ruby"} input.language - Comment profile.
|
|
393
407
|
* @param {import("../semantic/types.js").SourceLocation} input.location - Exact type token range.
|
|
394
408
|
* @param {import("../semantic/types.js").SourceLocation} input.ownerLocation - Owning declaration range.
|
|
395
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} [input.records] - Available nominal declarations by source name.
|
|
409
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} [input.records] - Available nominal declarations by source name.
|
|
396
410
|
* @param {Map<string, import("../semantic/types.js").TypeParameter>} [input.typeParameters] - Declaration-scoped parameters by name.
|
|
397
411
|
* @param {string} input.source - Complete parser input for exact subranges.
|
|
398
412
|
* @param {string | undefined} input.sourceType - Exact comment-parser/Prism-owned type token.
|
|
@@ -451,9 +465,11 @@ export function documentedValueType({language, location, ownerLocation, records
|
|
|
451
465
|
|
|
452
466
|
if (directRecord?.id) return directRecord.kind == "ClassDeclaration"
|
|
453
467
|
? referenceType(directRecord.id, location)
|
|
454
|
-
: directRecord.kind == "
|
|
455
|
-
?
|
|
456
|
-
:
|
|
468
|
+
: directRecord.kind == "InterfaceDeclaration"
|
|
469
|
+
? interfaceType(directRecord.id, location)
|
|
470
|
+
: directRecord.kind == "EffectResourceDeclaration"
|
|
471
|
+
? ownedResourceType(directRecord.id, location)
|
|
472
|
+
: recordType(directRecord.id, location)
|
|
457
473
|
const directTypeParameter = typeParameters.get(unwrappedName)
|
|
458
474
|
|
|
459
475
|
if (directTypeParameter?.id) return typeVariable(directTypeParameter.id, location)
|
|
@@ -484,7 +500,7 @@ class DocumentedTypeParser {
|
|
|
484
500
|
* @param {string} text - Parser-owned type text.
|
|
485
501
|
* @param {import("../semantic/types.js").SourceLocation} location - Whole token location.
|
|
486
502
|
* @param {string} source - Complete source.
|
|
487
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} records - Available nominal declarations.
|
|
503
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} records - Available nominal declarations.
|
|
488
504
|
* @param {Map<string, import("../semantic/types.js").TypeParameter>} typeParameters - Declaration-scoped parameters.
|
|
489
505
|
*/
|
|
490
506
|
constructor(language, text, location, source, records, typeParameters) {
|
|
@@ -580,9 +596,11 @@ class DocumentedTypeParser {
|
|
|
580
596
|
if (record?.id && this.text[this.offset] != "<" && this.text[this.offset] != "[") {
|
|
581
597
|
const type = record.kind == "ClassDeclaration"
|
|
582
598
|
? referenceType(record.id, this.range(start, nameEnd))
|
|
583
|
-
: record.kind == "
|
|
584
|
-
?
|
|
585
|
-
:
|
|
599
|
+
: record.kind == "InterfaceDeclaration"
|
|
600
|
+
? interfaceType(record.id, this.range(start, nameEnd))
|
|
601
|
+
: record.kind == "EffectResourceDeclaration"
|
|
602
|
+
? ownedResourceType(record.id, this.range(start, nameEnd))
|
|
603
|
+
: recordType(record.id, this.range(start, nameEnd))
|
|
586
604
|
|
|
587
605
|
if (this.language == "ruby" && this.consume("?")) {
|
|
588
606
|
return optionalType(type, this.range(start, this.offset), this.typeRange(type, start, nameEnd))
|
package/src/language-registry.js
CHANGED
|
@@ -169,6 +169,7 @@ export function createLanguageRegistry(candidateRecords) {
|
|
|
169
169
|
}
|
|
170
170
|
if (typeof candidate.roundTrip != "boolean") invalidRegistry(`Registry record '${id}' requires a Boolean round-trip declaration.`, id)
|
|
171
171
|
const featuresCandidate = candidate.features ?? {
|
|
172
|
+
boundedSubtyping: false,
|
|
172
173
|
closedRecords: false,
|
|
173
174
|
conditionControlledLoops: false,
|
|
174
175
|
conditionalExpressions: false,
|
|
@@ -185,7 +186,8 @@ export function createLanguageRegistry(candidateRecords) {
|
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
if (!isPlainObject(featuresCandidate) ||
|
|
188
|
-
Object.keys(featuresCandidate).sort().join(",") != "closedRecords,conditionControlledLoops,conditionalExpressions,effectfulCapabilitiesAndResources,generalFunctionsAndCalls,immutableCollections,optionalValues,orderedListIteration,orderedMapIteration,referenceClasses,stringInterpolation,typeParametersAndGenerics,typedErrors" ||
|
|
189
|
+
Object.keys(featuresCandidate).sort().join(",") != "boundedSubtyping,closedRecords,conditionControlledLoops,conditionalExpressions,effectfulCapabilitiesAndResources,generalFunctionsAndCalls,immutableCollections,optionalValues,orderedListIteration,orderedMapIteration,referenceClasses,stringInterpolation,typeParametersAndGenerics,typedErrors" ||
|
|
190
|
+
typeof featuresCandidate.boundedSubtyping != "boolean" ||
|
|
189
191
|
typeof featuresCandidate.closedRecords != "boolean" ||
|
|
190
192
|
typeof featuresCandidate.conditionControlledLoops != "boolean" ||
|
|
191
193
|
typeof featuresCandidate.conditionalExpressions != "boolean" ||
|
|
@@ -196,12 +198,14 @@ export function createLanguageRegistry(candidateRecords) {
|
|
|
196
198
|
typeof featuresCandidate.orderedListIteration != "boolean" ||
|
|
197
199
|
typeof featuresCandidate.orderedMapIteration != "boolean" ||
|
|
198
200
|
typeof featuresCandidate.referenceClasses != "boolean" ||
|
|
201
|
+
(featuresCandidate.boundedSubtyping && !featuresCandidate.referenceClasses) ||
|
|
199
202
|
typeof featuresCandidate.stringInterpolation != "boolean" ||
|
|
200
203
|
typeof featuresCandidate.typedErrors != "boolean" ||
|
|
201
204
|
typeof featuresCandidate.typeParametersAndGenerics != "boolean") {
|
|
202
205
|
invalidRegistry(`Registry record '${id}' has an invalid feature declaration.`, id)
|
|
203
206
|
}
|
|
204
207
|
const features = deepFreeze(/** @type {import("./semantic/types.js").LanguageFeatureCapabilities} */ ({
|
|
208
|
+
boundedSubtyping: featuresCandidate.boundedSubtyping,
|
|
205
209
|
closedRecords: featuresCandidate.closedRecords,
|
|
206
210
|
conditionControlledLoops: featuresCandidate.conditionControlledLoops,
|
|
207
211
|
conditionalExpressions: featuresCandidate.conditionalExpressions,
|
|
@@ -582,6 +586,7 @@ const records = [
|
|
|
582
586
|
optionalValues: false,
|
|
583
587
|
orderedListIteration: false,
|
|
584
588
|
orderedMapIteration: false,
|
|
589
|
+
boundedSubtyping: false,
|
|
585
590
|
referenceClasses: false,
|
|
586
591
|
typedErrors: false,
|
|
587
592
|
typeParametersAndGenerics: false
|
|
@@ -621,6 +626,7 @@ const records = [
|
|
|
621
626
|
optionalValues: false,
|
|
622
627
|
orderedListIteration: false,
|
|
623
628
|
orderedMapIteration: false,
|
|
629
|
+
boundedSubtyping: false,
|
|
624
630
|
referenceClasses: false,
|
|
625
631
|
typedErrors: false,
|
|
626
632
|
typeParametersAndGenerics: false
|
|
@@ -646,6 +652,7 @@ const records = [
|
|
|
646
652
|
optionalValues: false,
|
|
647
653
|
orderedListIteration: false,
|
|
648
654
|
orderedMapIteration: false,
|
|
655
|
+
boundedSubtyping: false,
|
|
649
656
|
referenceClasses: false,
|
|
650
657
|
typedErrors: false,
|
|
651
658
|
typeParametersAndGenerics: false
|
|
@@ -661,7 +668,7 @@ const records = [
|
|
|
661
668
|
artifactMultiplicity: "multiple",
|
|
662
669
|
binaryBackend: generateBrowserWasm,
|
|
663
670
|
defaultFilename: "program.wasm",
|
|
664
|
-
features: {closedRecords: false, conditionControlledLoops: false, conditionalExpressions: false, stringInterpolation: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
671
|
+
features: {boundedSubtyping: false, closedRecords: false, conditionControlledLoops: false, conditionalExpressions: false, stringInterpolation: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
665
672
|
id: "wasm",
|
|
666
673
|
mapping: {binaryRanges: true, richText: true, sourceMapV3: true},
|
|
667
674
|
mediaType: "application/wasm",
|
|
@@ -671,7 +678,7 @@ const records = [
|
|
|
671
678
|
acceptance: {stages: ["generate", "compile", "validate", "instantiate", "execute"], toolchains: ["android"]},
|
|
672
679
|
applicationBackend: generateAndroidApplication,
|
|
673
680
|
artifactMultiplicity: "multiple",
|
|
674
|
-
features: {closedRecords: false, conditionControlledLoops: false, conditionalExpressions: false, stringInterpolation: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
681
|
+
features: {boundedSubtyping: false, closedRecords: false, conditionControlledLoops: false, conditionalExpressions: false, stringInterpolation: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
675
682
|
id: "android",
|
|
676
683
|
mapping: {binaryRanges: true, richText: true, sourceMapV3: true},
|
|
677
684
|
roundTrip: false
|
|
@@ -680,7 +687,7 @@ const records = [
|
|
|
680
687
|
acceptance: {stages: ["generate"], toolchains: []},
|
|
681
688
|
applicationBackend: generateIosApplication,
|
|
682
689
|
artifactMultiplicity: "multiple",
|
|
683
|
-
features: {closedRecords: false, conditionControlledLoops: false, conditionalExpressions: false, stringInterpolation: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
690
|
+
features: {boundedSubtyping: false, closedRecords: false, conditionControlledLoops: false, conditionalExpressions: false, stringInterpolation: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
684
691
|
id: "ios",
|
|
685
692
|
mapping: {binaryRanges: true, richText: true, sourceMapV3: true},
|
|
686
693
|
roundTrip: false
|
|
@@ -689,7 +696,7 @@ const records = [
|
|
|
689
696
|
acceptance: {stages: ["generate", "restore", "compile", "validate", "instantiate", "execute"], toolchains: ["flutter", "android"]},
|
|
690
697
|
applicationBackend: generateFlutterApplication,
|
|
691
698
|
artifactMultiplicity: "multiple",
|
|
692
|
-
features: {closedRecords: false, conditionControlledLoops: false, conditionalExpressions: false, stringInterpolation: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
699
|
+
features: {boundedSubtyping: false, closedRecords: false, conditionControlledLoops: false, conditionalExpressions: false, stringInterpolation: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
693
700
|
id: "flutter",
|
|
694
701
|
mapping: {binaryRanges: true, richText: true, sourceMapV3: true},
|
|
695
702
|
roundTrip: false
|
|
@@ -712,6 +719,7 @@ function language(values) {
|
|
|
712
719
|
return {
|
|
713
720
|
artifactMultiplicity: "single",
|
|
714
721
|
features: {
|
|
722
|
+
boundedSubtyping: String(values.id) == "php",
|
|
715
723
|
closedRecords: ["php", "ruby", "javascript", "typescript", "java"].includes(String(values.id)),
|
|
716
724
|
conditionControlledLoops: ["php", "ruby", "javascript", "typescript", "java"].includes(String(values.id)),
|
|
717
725
|
conditionalExpressions: ["php", "ruby", "javascript", "typescript", "java"].includes(String(values.id)),
|
package/src/mapping.js
CHANGED
|
@@ -764,7 +764,7 @@ export function validateMapping(value) {
|
|
|
764
764
|
for (const symbol of mapping.symbols) {
|
|
765
765
|
if (!symbol || typeof symbol != "object" || typeof symbol.id != "string" || symbol.id.length == 0 || symbolIds.has(symbol.id) ||
|
|
766
766
|
typeof symbol.name != "string" || symbol.name.length == 0 ||
|
|
767
|
-
!["capability", "resource", "failure", "operation", "record", "class", "error", "field", "constructor", "method", "function", "typeParameter", "parameter", "local", "iteration", "catch"].includes(symbol.kind) ||
|
|
767
|
+
!["capability", "resource", "failure", "operation", "record", "interface", "interfaceMethod", "class", "error", "field", "constructor", "method", "function", "typeParameter", "parameter", "local", "iteration", "catch"].includes(symbol.kind) ||
|
|
768
768
|
!nodeIds.has(symbol.declarationNodeId) || !Array.isArray(symbol.references) ||
|
|
769
769
|
symbol.semanticDeclarationId !== undefined && (typeof symbol.semanticDeclarationId != "string" || !(
|
|
770
770
|
symbol.kind == "function" && /^(?:[a-z][a-z0-9._-]*#)?function:[0-9]+$/u.test(symbol.semanticDeclarationId) ||
|
|
@@ -774,6 +774,8 @@ export function validateMapping(value) {
|
|
|
774
774
|
symbol.kind == "field" && /^(?:[a-z][a-z0-9._-]*#)?(?:record|class):[0-9]+:field:[0-9]+$/u.test(symbol.semanticDeclarationId) ||
|
|
775
775
|
symbol.kind == "constructor" && /^(?:[a-z][a-z0-9._-]*#)?class:[0-9]+:constructor$/u.test(symbol.semanticDeclarationId) ||
|
|
776
776
|
symbol.kind == "method" && /^(?:[a-z][a-z0-9._-]*#)?class:[0-9]+:method:[0-9]+$/u.test(symbol.semanticDeclarationId) ||
|
|
777
|
+
symbol.kind == "interface" && /^(?:[a-z][a-z0-9._-]*#)?interface:[0-9]+$/u.test(symbol.semanticDeclarationId) ||
|
|
778
|
+
symbol.kind == "interfaceMethod" && /^(?:[a-z][a-z0-9._-]*#)?interface:[0-9]+:method:[0-9]+$/u.test(symbol.semanticDeclarationId) ||
|
|
777
779
|
symbol.kind == "typeParameter" && /^(?:[a-z][a-z0-9._-]*#)?(?:function|record):[0-9]+:type:[0-9]+$/u.test(symbol.semanticDeclarationId) ||
|
|
778
780
|
symbol.kind == "capability" && /^(?:module:[0-9]+\/)?capability:[0-9]+$/u.test(symbol.semanticDeclarationId) ||
|
|
779
781
|
symbol.kind == "resource" && /^(?:module:[0-9]+\/)?capability:[0-9]+\/resource:[0-9]+$/u.test(symbol.semanticDeclarationId) ||
|
package/src/semantic/effects.js
CHANGED
|
@@ -326,6 +326,7 @@ function typeIdentity(type) {
|
|
|
326
326
|
if (type.kind == "RecordType") return {arguments: type.arguments?.map((argument) =>
|
|
327
327
|
/** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(argument))), declarationId: type.declarationId, kind: type.kind}
|
|
328
328
|
if (type.kind == "ReferenceType") return {declarationId: type.declarationId, kind: type.kind}
|
|
329
|
+
if (type.kind == "InterfaceType") return {declarationId: type.declarationId, kind: type.kind}
|
|
329
330
|
return {kind: type.kind, parameterId: type.parameterId}
|
|
330
331
|
}
|
|
331
332
|
|
|
@@ -379,10 +380,19 @@ function escapingBlockFailures(block, functionSummaries, methodSummaries, capabi
|
|
|
379
380
|
|
|
380
381
|
for (const statement of block.statements) {
|
|
381
382
|
if (statement.kind == "TryStatement") {
|
|
382
|
-
|
|
383
|
+
let body = escapingBlockFailures(statement.body, functionSummaries, methodSummaries, capabilityFailureIds)
|
|
383
384
|
|
|
384
|
-
|
|
385
|
-
|
|
385
|
+
for (const handler of statement.handlers ?? []) {
|
|
386
|
+
const caughtId = handler.catchType?.declarationId
|
|
387
|
+
|
|
388
|
+
if (typeof caughtId == "string" && body.delete(caughtId)) {
|
|
389
|
+
addFailures(body, escapingBlockFailures(handler.catchBody, functionSummaries, methodSummaries, capabilityFailureIds))
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (statement.finallyBody) {
|
|
393
|
+
// A completion block that can raise replaces the pending completion on the paths where it does,
|
|
394
|
+
// but pending failures still escape on the paths where it completes normally, so both are reachable.
|
|
395
|
+
addFailures(body, escapingBlockFailures(statement.finallyBody, functionSummaries, methodSummaries, capabilityFailureIds))
|
|
386
396
|
}
|
|
387
397
|
addFailures(result, body)
|
|
388
398
|
continue
|
|
@@ -639,7 +649,9 @@ function forEachBlockExpression(block, visitor, postorder) {
|
|
|
639
649
|
visit(statement.condition); forEachBlockExpression(statement.body, visitor, postorder)
|
|
640
650
|
} else if (statement.kind == "RaiseStatement") visit(statement.error.message)
|
|
641
651
|
else if (statement.kind == "TryStatement") {
|
|
642
|
-
forEachBlockExpression(statement.body, visitor, postorder)
|
|
652
|
+
forEachBlockExpression(statement.body, visitor, postorder)
|
|
653
|
+
for (const handler of statement.handlers) forEachBlockExpression(handler.catchBody, visitor, postorder)
|
|
654
|
+
if (statement.finallyBody) forEachBlockExpression(statement.finallyBody, visitor, postorder)
|
|
643
655
|
}
|
|
644
656
|
}
|
|
645
657
|
|
|
@@ -694,7 +706,8 @@ function statementReferencesProtectedEntry(statement) {
|
|
|
694
706
|
case "RaiseStatement":
|
|
695
707
|
return expressionReferencesProtectedEntry(statement.error.message)
|
|
696
708
|
case "TryStatement":
|
|
697
|
-
return blockReferencesProtectedEntry(statement.body) || blockReferencesProtectedEntry(
|
|
709
|
+
return blockReferencesProtectedEntry(statement.body) || statement.handlers.some((handler) => blockReferencesProtectedEntry(handler.catchBody)) ||
|
|
710
|
+
(statement.finallyBody ? blockReferencesProtectedEntry(statement.finallyBody) : false)
|
|
698
711
|
default:
|
|
699
712
|
return false
|
|
700
713
|
}
|
package/src/semantic/lifetime.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
148
|
-
|
|
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
|
}
|