semantifold 0.3.0 → 0.4.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 +58 -15
- package/build/index.d.ts +48 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +57 -8
- package/build/src/artifact-path.d.ts +23 -1
- package/build/src/artifact-path.d.ts.map +1 -1
- package/build/src/artifact-path.js +45 -3
- package/build/src/artifacts.d.ts.map +1 -1
- package/build/src/artifacts.js +66 -1
- package/build/src/backends/c-validation.d.ts.map +1 -1
- package/build/src/backends/c-validation.js +7 -3
- package/build/src/backends/cpp-validation.d.ts.map +1 -1
- package/build/src/backends/cpp-validation.js +7 -3
- package/build/src/backends/csharp.js +7 -1
- package/build/src/backends/dart-runtime.d.ts +2 -0
- package/build/src/backends/dart-runtime.d.ts.map +1 -0
- package/build/src/backends/dart-runtime.js +41 -0
- package/build/src/backends/dart.d.ts +20 -0
- package/build/src/backends/dart.d.ts.map +1 -0
- package/build/src/backends/dart.js +321 -0
- package/build/src/backends/effects.d.ts +60 -0
- package/build/src/backends/effects.d.ts.map +1 -0
- package/build/src/backends/effects.js +746 -0
- package/build/src/backends/go.js +18 -4
- package/build/src/backends/identifiers.d.ts +36 -4
- package/build/src/backends/identifiers.d.ts.map +1 -1
- package/build/src/backends/identifiers.js +154 -10
- package/build/src/backends/index.d.ts.map +1 -1
- package/build/src/backends/index.js +27 -4
- package/build/src/backends/ios.d.ts +68 -0
- package/build/src/backends/ios.d.ts.map +1 -0
- package/build/src/backends/ios.js +1503 -0
- package/build/src/backends/java.d.ts.map +1 -1
- package/build/src/backends/java.js +429 -36
- package/build/src/backends/javascript.d.ts +6 -0
- package/build/src/backends/javascript.d.ts.map +1 -1
- package/build/src/backends/javascript.js +380 -24
- package/build/src/backends/kotlin-runtime.d.ts +3 -0
- package/build/src/backends/kotlin-runtime.d.ts.map +1 -0
- package/build/src/backends/kotlin-runtime.js +29 -0
- package/build/src/backends/kotlin.d.ts +23 -0
- package/build/src/backends/kotlin.d.ts.map +1 -0
- package/build/src/backends/kotlin.js +136 -0
- package/build/src/backends/native-validation.d.ts +2 -2
- package/build/src/backends/native-validation.d.ts.map +1 -1
- package/build/src/backends/native-validation.js +2 -2
- package/build/src/backends/ordered-expressions.d.ts.map +1 -1
- package/build/src/backends/ordered-expressions.js +39 -7
- package/build/src/backends/php-stdlib-support.d.ts +19 -0
- package/build/src/backends/php-stdlib-support.d.ts.map +1 -0
- package/build/src/backends/php-stdlib-support.js +227 -0
- package/build/src/backends/php.d.ts.map +1 -1
- package/build/src/backends/php.js +448 -33
- package/build/src/backends/program.d.ts +81 -0
- package/build/src/backends/program.d.ts.map +1 -0
- package/build/src/backends/program.js +1308 -0
- package/build/src/backends/python.js +7 -1
- package/build/src/backends/ruby.d.ts.map +1 -1
- package/build/src/backends/ruby.js +310 -24
- package/build/src/backends/rust-runtime.d.ts +7 -0
- package/build/src/backends/rust-runtime.d.ts.map +1 -0
- package/build/src/backends/rust-runtime.js +32 -0
- package/build/src/backends/rust-validation.d.ts +9 -0
- package/build/src/backends/rust-validation.d.ts.map +1 -0
- package/build/src/backends/rust-validation.js +78 -0
- package/build/src/backends/rust.d.ts +24 -0
- package/build/src/backends/rust.d.ts.map +1 -0
- package/build/src/backends/rust.js +344 -0
- package/build/src/backends/scalars.d.ts +14 -6
- package/build/src/backends/scalars.d.ts.map +1 -1
- package/build/src/backends/scalars.js +167 -17
- package/build/src/backends/shared.d.ts +23 -4
- package/build/src/backends/shared.d.ts.map +1 -1
- package/build/src/backends/shared.js +2158 -62
- package/build/src/backends/stdlib-support.d.ts +14 -0
- package/build/src/backends/stdlib-support.d.ts.map +1 -0
- package/build/src/backends/stdlib-support.js +237 -0
- package/build/src/backends/swift-runtime.d.ts +3 -0
- package/build/src/backends/swift-runtime.d.ts.map +1 -0
- package/build/src/backends/swift-runtime.js +14 -0
- package/build/src/backends/swift.d.ts +8 -0
- package/build/src/backends/swift.d.ts.map +1 -0
- package/build/src/backends/swift.js +127 -0
- package/build/src/backends/typescript.d.ts.map +1 -1
- package/build/src/backends/typescript.js +344 -24
- package/build/src/backends/wasm.d.ts +43 -0
- package/build/src/backends/wasm.d.ts.map +1 -0
- package/build/src/backends/wasm.js +2164 -0
- package/build/src/backends/writer.d.ts +235 -4
- package/build/src/backends/writer.d.ts.map +1 -1
- package/build/src/backends/writer.js +468 -3
- package/build/src/diagnostic.d.ts +4 -3
- package/build/src/diagnostic.d.ts.map +1 -1
- package/build/src/diagnostic.js +5 -3
- package/build/src/frontends/c.d.ts.map +1 -1
- package/build/src/frontends/c.js +5 -3
- package/build/src/frontends/cpp.d.ts.map +1 -1
- package/build/src/frontends/cpp.js +5 -3
- package/build/src/frontends/csharp.d.ts.map +1 -1
- package/build/src/frontends/csharp.js +7 -3
- package/build/src/frontends/dart.d.ts +10 -0
- package/build/src/frontends/dart.d.ts.map +1 -0
- package/build/src/frontends/dart.js +913 -0
- package/build/src/frontends/index.d.ts +3 -1
- package/build/src/frontends/index.d.ts.map +1 -1
- package/build/src/frontends/index.js +15 -5
- package/build/src/frontends/java.d.ts +110 -1
- package/build/src/frontends/java.d.ts.map +1 -1
- package/build/src/frontends/java.js +1725 -70
- package/build/src/frontends/javascript-typescript.d.ts +69 -1
- package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
- package/build/src/frontends/javascript-typescript.js +1825 -93
- package/build/src/frontends/kotlin.d.ts +10 -0
- package/build/src/frontends/kotlin.d.ts.map +1 -0
- package/build/src/frontends/kotlin.js +685 -0
- package/build/src/frontends/php.d.ts +118 -1
- package/build/src/frontends/php.d.ts.map +1 -1
- package/build/src/frontends/php.js +1583 -76
- package/build/src/frontends/program.d.ts +74 -0
- package/build/src/frontends/program.d.ts.map +1 -0
- package/build/src/frontends/program.js +989 -0
- package/build/src/frontends/ruby.d.ts +68 -1
- package/build/src/frontends/ruby.d.ts.map +1 -1
- package/build/src/frontends/ruby.js +1446 -101
- package/build/src/frontends/rust-parser.d.ts +11 -0
- package/build/src/frontends/rust-parser.d.ts.map +1 -0
- package/build/src/frontends/rust-parser.js +15 -0
- package/build/src/frontends/rust.d.ts +14 -0
- package/build/src/frontends/rust.d.ts.map +1 -0
- package/build/src/frontends/rust.js +715 -0
- package/build/src/frontends/scalars.d.ts +10 -0
- package/build/src/frontends/scalars.d.ts.map +1 -1
- package/build/src/frontends/scalars.js +28 -2
- package/build/src/frontends/swift.d.ts +10 -0
- package/build/src/frontends/swift.d.ts.map +1 -0
- package/build/src/frontends/swift.js +860 -0
- package/build/src/frontends/types.d.ts +186 -0
- package/build/src/frontends/types.d.ts.map +1 -0
- package/build/src/frontends/types.js +634 -0
- package/build/src/language-registry.d.ts +46 -4
- package/build/src/language-registry.d.ts.map +1 -1
- package/build/src/language-registry.js +167 -13
- package/build/src/mapping.d.ts +2 -2
- package/build/src/mapping.d.ts.map +1 -1
- package/build/src/mapping.js +31 -10
- package/build/src/semantic/capabilities.d.ts +66 -0
- package/build/src/semantic/capabilities.d.ts.map +1 -0
- package/build/src/semantic/capabilities.js +649 -0
- package/build/src/semantic/effects.d.ts +26 -0
- package/build/src/semantic/effects.d.ts.map +1 -0
- package/build/src/semantic/effects.js +764 -0
- package/build/src/semantic/generics.d.ts +23 -0
- package/build/src/semantic/generics.d.ts.map +1 -0
- package/build/src/semantic/generics.js +61 -0
- package/build/src/semantic/lifetime.d.ts +37 -0
- package/build/src/semantic/lifetime.d.ts.map +1 -0
- package/build/src/semantic/lifetime.js +527 -0
- package/build/src/semantic/provenance.d.ts +9 -2
- package/build/src/semantic/provenance.d.ts.map +1 -1
- package/build/src/semantic/provenance.js +462 -20
- package/build/src/semantic/scalars.d.ts +11 -0
- package/build/src/semantic/scalars.d.ts.map +1 -1
- package/build/src/semantic/scalars.js +16 -1
- package/build/src/semantic/stdlib.d.ts +254 -0
- package/build/src/semantic/stdlib.d.ts.map +1 -0
- package/build/src/semantic/stdlib.js +880 -0
- package/build/src/semantic/types.d.ts +2952 -202
- package/build/src/semantic/types.d.ts.map +1 -1
- package/build/src/semantic/types.js +688 -15
- package/build/src/semantic/validate.d.ts +85 -5
- package/build/src/semantic/validate.d.ts.map +1 -1
- package/build/src/semantic/validate.js +2576 -98
- package/build/src/stdlib-facades.d.ts +304 -0
- package/build/src/stdlib-facades.d.ts.map +1 -0
- package/build/src/stdlib-facades.js +1167 -0
- package/build/src/stdlib-negotiation.d.ts +69 -0
- package/build/src/stdlib-negotiation.d.ts.map +1 -0
- package/build/src/stdlib-negotiation.js +403 -0
- package/build/src/stdlib-providers.d.ts +72 -0
- package/build/src/stdlib-providers.d.ts.map +1 -0
- package/build/src/stdlib-providers.js +339 -0
- package/build/src/toolchains.d.ts +55 -1
- package/build/src/toolchains.d.ts.map +1 -1
- package/build/src/toolchains.js +15 -4
- package/docs/architecture.md +45 -13
- package/docs/dart.md +60 -0
- package/docs/goals.md +3 -3
- package/docs/ios.md +121 -0
- package/docs/kotlin.md +42 -0
- package/docs/language-support.md +231 -26
- package/docs/multi-file-modules.md +52 -0
- package/docs/parser-qualification.md +121 -8
- package/docs/plans/2026-09-05-standard-library-portability.md +10 -10
- package/docs/rust.md +88 -0
- package/docs/source-maps.md +15 -7
- package/docs/standard-library-portability.md +39 -8
- package/docs/swift.md +44 -0
- package/docs/testing.md +366 -4
- package/docs/webassembly.md +52 -0
- package/index.js +63 -8
- package/node_modules/semantifold-tree-sitter-legacy-internal/README.md +2 -2
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/LICENSE +21 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/README.md +45 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/binding.gyp +30 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/binding.cc +20 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/binding_test.js +9 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/index.d.ts +28 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/index.js +11 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/grammar.js +1630 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/package.json +64 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/darwin-arm64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/darwin-x64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/linux-arm64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/linux-x64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/win32-arm64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/win32-x64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/queries/highlights.scm +159 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/queries/injections.scm +9 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/queries/tags.scm +60 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/grammar.json +9405 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/node-types.json +5399 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/parser.c +192569 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/scanner.c +393 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/tree_sitter/alloc.h +54 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/tree_sitter/array.h +290 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/tree_sitter/parser.h +266 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/tree-sitter-rust.wasm +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/tree-sitter.json +53 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/package.json +3 -2
- package/node_modules/semantifold-tree-sitter-legacy-internal/src/c.js +5 -4
- package/package.json +5 -2
- package/src/artifact-path.js +47 -2
- package/src/artifacts.js +67 -0
- package/src/backends/c-validation.js +6 -1
- package/src/backends/cpp-validation.js +6 -1
- package/src/backends/csharp.js +3 -0
- package/src/backends/dart-runtime.js +41 -0
- package/src/backends/dart.js +326 -0
- package/src/backends/effects.js +705 -0
- package/src/backends/go.js +14 -2
- package/src/backends/identifiers.js +160 -9
- package/src/backends/index.js +27 -2
- package/src/backends/ios.js +1612 -0
- package/src/backends/java.js +454 -36
- package/src/backends/javascript.js +389 -23
- package/src/backends/kotlin-runtime.js +29 -0
- package/src/backends/kotlin.js +141 -0
- package/src/backends/native-validation.js +1 -1
- package/src/backends/ordered-expressions.js +40 -6
- package/src/backends/php-stdlib-support.js +239 -0
- package/src/backends/php.js +469 -32
- package/src/backends/program.js +1407 -0
- package/src/backends/python.js +3 -0
- package/src/backends/ruby.js +326 -23
- package/src/backends/rust-runtime.js +34 -0
- package/src/backends/rust-validation.js +75 -0
- package/src/backends/rust.js +335 -0
- package/src/backends/scalars.js +141 -16
- package/src/backends/shared.js +2304 -124
- package/src/backends/stdlib-support.js +234 -0
- package/src/backends/swift-runtime.js +14 -0
- package/src/backends/swift.js +132 -0
- package/src/backends/typescript.js +349 -23
- package/src/backends/wasm.js +2208 -0
- package/src/backends/writer.js +520 -2
- package/src/diagnostic.js +4 -2
- package/src/frontends/c.js +4 -2
- package/src/frontends/cpp.js +4 -2
- package/src/frontends/csharp.js +4 -2
- package/src/frontends/dart.js +963 -0
- package/src/frontends/index.js +14 -4
- package/src/frontends/java.js +1977 -69
- package/src/frontends/javascript-typescript.js +2046 -102
- package/src/frontends/kotlin.js +713 -0
- package/src/frontends/php.js +1725 -79
- package/src/frontends/program.js +1062 -0
- package/src/frontends/ruby.js +1624 -98
- package/src/frontends/rust-parser.js +18 -0
- package/src/frontends/rust.js +714 -0
- package/src/frontends/scalars.js +31 -1
- package/src/frontends/swift.js +893 -0
- package/src/frontends/types.js +750 -0
- package/src/language-registry.js +172 -12
- package/src/mapping.js +33 -9
- package/src/semantic/capabilities.js +671 -0
- package/src/semantic/effects.js +737 -0
- package/src/semantic/generics.js +58 -0
- package/src/semantic/lifetime.js +517 -0
- package/src/semantic/provenance.js +412 -19
- package/src/semantic/scalars.js +17 -0
- package/src/semantic/stdlib.js +921 -0
- package/src/semantic/types.js +765 -14
- package/src/semantic/validate.js +2934 -209
- package/src/stdlib-facades.js +1211 -0
- package/src/stdlib-negotiation.js +446 -0
- package/src/stdlib-providers.js +377 -0
- package/src/toolchains.js +16 -3
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds an ordered substitution map for a closed generic record application.
|
|
5
|
+
* @param {import("./types.js").RecordType} type - Closed record application.
|
|
6
|
+
* @param {import("./types.js").RecordDeclaration} declaration - Resolved declaration.
|
|
7
|
+
* @returns {Map<string, import("./types.js").SemanticValueType>} Substitutions.
|
|
8
|
+
*/
|
|
9
|
+
export function recordTypeSubstitutions(type, declaration) {
|
|
10
|
+
return new Map((declaration.typeParameters ?? []).map((parameter, index) => [
|
|
11
|
+
/** @type {string} */ (parameter.id),
|
|
12
|
+
/** @type {import("./types.js").SemanticValueType} */ (type.arguments?.[index])
|
|
13
|
+
]))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Applies complete recursive substitution without mutating declaration-owned types.
|
|
18
|
+
* @param {import("./types.js").SemanticValueType} type - Open semantic type.
|
|
19
|
+
* @param {Map<string, import("./types.js").SemanticValueType>} substitutions - Closed substitutions.
|
|
20
|
+
* @returns {import("./types.js").SemanticValueType} Substituted type.
|
|
21
|
+
*/
|
|
22
|
+
export function substituteValueType(type, substitutions) {
|
|
23
|
+
if (type.kind == "TypeVariableReference") return substitutions.get(type.parameterId) ?? type
|
|
24
|
+
if (type.kind == "ListType") return {...type, elementType: substituteValueType(type.elementType, substitutions)}
|
|
25
|
+
if (type.kind == "MapType" || type.kind == "OrderedMapType") return {
|
|
26
|
+
...type,
|
|
27
|
+
keyType: /** @type {import("./types.js").TypeReference} */ (substituteValueType(type.keyType, substitutions)),
|
|
28
|
+
valueType: substituteValueType(type.valueType, substitutions)
|
|
29
|
+
}
|
|
30
|
+
if (type.kind == "OptionalType") return {...type, valueType: substituteValueType(type.valueType, substitutions)}
|
|
31
|
+
if (type.kind == "RecordType" && type.arguments) {
|
|
32
|
+
return {...type, arguments: type.arguments.map((argument) => substituteValueType(argument, substitutions))}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return type
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Checks whether a recursive type contains any declaration-scoped variable
|
|
40
|
+
* outside an explicitly visible identity set.
|
|
41
|
+
* @param {import("./types.js").SemanticValueType} type - Candidate open type.
|
|
42
|
+
* @param {Set<string>} [visibleParameterIds] - Exact variable identities permitted by the caller's lexical scope.
|
|
43
|
+
* @returns {boolean} Whether any non-visible type variable occurs.
|
|
44
|
+
*/
|
|
45
|
+
export function typeContainsAnyVariable(type, visibleParameterIds = new Set()) {
|
|
46
|
+
if (type.kind == "TypeVariableReference") return !visibleParameterIds.has(type.parameterId)
|
|
47
|
+
if (type.kind == "RecordType") {
|
|
48
|
+
return type.arguments?.some((argument) => typeContainsAnyVariable(argument, visibleParameterIds)) ?? false
|
|
49
|
+
}
|
|
50
|
+
if (type.kind == "ListType") return typeContainsAnyVariable(type.elementType, visibleParameterIds)
|
|
51
|
+
if (type.kind == "MapType" || type.kind == "OrderedMapType") {
|
|
52
|
+
return typeContainsAnyVariable(type.keyType, visibleParameterIds) ||
|
|
53
|
+
typeContainsAnyVariable(type.valueType, visibleParameterIds)
|
|
54
|
+
}
|
|
55
|
+
if (type.kind == "OptionalType") return typeContainsAnyVariable(type.valueType, visibleParameterIds)
|
|
56
|
+
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/** @typedef {(code: string, detail: string, location: import("./types.js").SourceLocation) => never} LifetimeFail */
|
|
4
|
+
/** @typedef {{kind: "open", originId: string, origin: import("./types.js").SourceLocation} | {kind: "moved", originId: string, origin: import("./types.js").SourceLocation, destination: string} | {kind: "terminal", originId: string, origin: import("./types.js").SourceLocation, closeSiteId: string | import("./types.js").SourceLocation}} OwnerState */
|
|
5
|
+
/** @typedef {{kind: "normal" | "return" | "raise" | "break" | "continue", state: Map<string, OwnerState>, location?: import("./types.js").SourceLocation, errorId?: string}} LifetimeExit */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Performs path-sensitive structured lifetime validation for Task 034 owned values.
|
|
9
|
+
* @param {import("./types.js").SemanticModule} module - Fully type-resolved module.
|
|
10
|
+
* @param {{functions: Map<string, Set<string>>, methods: Map<string, Set<string>>}} errorEffects - Complete escaping error sets per function and method.
|
|
11
|
+
* @param {LifetimeFail} fail - Located failure.
|
|
12
|
+
* @returns {void}
|
|
13
|
+
*/
|
|
14
|
+
export function validateResourceLifetimes(module, errorEffects, fail) {
|
|
15
|
+
if (!(module.capabilities ?? []).some(({resources}) => resources.length > 0) && !moduleContainsOwnedType(module)) return
|
|
16
|
+
|
|
17
|
+
for (const declaration of module.classes ?? []) {
|
|
18
|
+
analyzeOwner(declaration.constructor.parameters, declaration.constructor.body, fail, undefined, errorEffects)
|
|
19
|
+
for (const method of declaration.methods) analyzeOwner(method.parameters, method.body, fail,
|
|
20
|
+
declaration.ownership?.kind == "ownedResource" ? declaration.ownership.fieldId : undefined, errorEffects)
|
|
21
|
+
}
|
|
22
|
+
for (const declaration of module.functions) analyzeOwner(declaration.parameters, declaration.body, fail, undefined, errorEffects)
|
|
23
|
+
analyzeOwner([], module.entryPoint.body, fail, undefined, errorEffects)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Analyzes one function, constructor, method, or entry-point owner.
|
|
28
|
+
* @param {import("./types.js").Parameter[]} parameters - Owned parameters.
|
|
29
|
+
* @param {import("./types.js").Block} body - Structured body.
|
|
30
|
+
* @param {LifetimeFail} fail - Located failure.
|
|
31
|
+
* @param {string | undefined} borrowedFieldId - Owned receiver field, when present.
|
|
32
|
+
* @param {{functions: Map<string, Set<string>>, methods: Map<string, Set<string>>}} errorEffects - Complete escaping error sets per function and method.
|
|
33
|
+
* @returns {void}
|
|
34
|
+
*/
|
|
35
|
+
function analyzeOwner(parameters, body, fail, borrowedFieldId, errorEffects) {
|
|
36
|
+
/** @type {Map<string, OwnerState>} */
|
|
37
|
+
const initial = new Map()
|
|
38
|
+
const obligations = new Set(parameters.filter(({type}) => isOwned(type)).map(({name}) => name))
|
|
39
|
+
|
|
40
|
+
for (const parameter of parameters) {
|
|
41
|
+
if (isOwned(parameter.type)) initial.set(parameter.name, openState(`parameter:${parameter.name}`, parameter.location))
|
|
42
|
+
}
|
|
43
|
+
if (borrowedFieldId) initial.set(`this.${borrowedFieldId}`, openState(`receiver:${borrowedFieldId}`, body.location))
|
|
44
|
+
const exits = analyzeBlock(body, [{kind: "normal", state: initial}], fail, errorEffects)
|
|
45
|
+
|
|
46
|
+
for (const exit of exits) checkLeaks(exit.state, obligations, exit.location ?? body.location, fail)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Evaluates one lexical block while retaining every abrupt exit separately.
|
|
51
|
+
* @param {import("./types.js").Block} block - Structured block.
|
|
52
|
+
* @param {LifetimeExit[]} incoming - Incoming control-flow states.
|
|
53
|
+
* @param {LifetimeFail} fail - Located failure.
|
|
54
|
+
* @param {{functions: Map<string, Set<string>>, methods: Map<string, Set<string>>}} errorEffects - Complete escaping error sets per function and method.
|
|
55
|
+
* @returns {LifetimeExit[]} Reachable exits.
|
|
56
|
+
*/
|
|
57
|
+
function analyzeBlock(block, incoming, fail, errorEffects) {
|
|
58
|
+
let active = incoming.filter(({kind}) => kind == "normal")
|
|
59
|
+
const departed = incoming.filter(({kind}) => kind != "normal")
|
|
60
|
+
/** @type {Set<string>} */
|
|
61
|
+
const locals = new Set()
|
|
62
|
+
|
|
63
|
+
for (const statement of block.statements) {
|
|
64
|
+
if (active.length == 0) break
|
|
65
|
+
const next = []
|
|
66
|
+
|
|
67
|
+
for (const flow of active) next.push(...analyzeStatement(statement, flow.state, fail, locals, errorEffects))
|
|
68
|
+
departed.push(...next.filter(({kind}) => kind != "normal"))
|
|
69
|
+
active = mergeEquivalentNormals(next.filter(({kind}) => kind == "normal"), statement.location, fail)
|
|
70
|
+
}
|
|
71
|
+
const exits = [...departed, ...active.map((flow) => ({...flow, location: flow.location ?? block.location}))]
|
|
72
|
+
|
|
73
|
+
for (const exit of exits) {
|
|
74
|
+
checkLeaks(exit.state, locals, exit.location ?? block.location, fail)
|
|
75
|
+
for (const name of locals) exit.state.delete(name)
|
|
76
|
+
}
|
|
77
|
+
return exits
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Evaluates one statement from one normal state.
|
|
82
|
+
* @param {import("./types.js").Statement} statement - Statement.
|
|
83
|
+
* @param {Map<string, OwnerState>} state - Incoming ownership state.
|
|
84
|
+
* @param {LifetimeFail} fail - Located failure.
|
|
85
|
+
* @param {Set<string>} locals - Locals introduced in the current block.
|
|
86
|
+
* @param {{functions: Map<string, Set<string>>, methods: Map<string, Set<string>>}} errorEffects - Complete escaping error sets per function and method.
|
|
87
|
+
* @returns {LifetimeExit[]} Reachable exits.
|
|
88
|
+
*/
|
|
89
|
+
function analyzeStatement(statement, state, fail, locals, errorEffects) {
|
|
90
|
+
if (statement.kind == "LocalDeclaration") {
|
|
91
|
+
const flows = evaluateExpression(statement.initializer, cloneState(state), fail, errorEffects)
|
|
92
|
+
|
|
93
|
+
if (isOwned(statement.type)) {
|
|
94
|
+
locals.add(statement.name)
|
|
95
|
+
for (const flow of flows) {
|
|
96
|
+
if (flow.kind != "normal") continue
|
|
97
|
+
const origin = statement.initializer.kind == "OwnedMoveExpression"
|
|
98
|
+
? flow.state.get(statement.initializer.expression.name)?.origin ?? statement.initializer.location
|
|
99
|
+
: statement.initializer.location
|
|
100
|
+
flow.state.set(statement.name, openState(`local:${statement.name}`, origin))
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return flows.map((flow) => ({...flow, location: statement.location}))
|
|
104
|
+
}
|
|
105
|
+
if (statement.kind == "ExpressionStatement" || statement.kind == "PrintStatement") {
|
|
106
|
+
return evaluateExpression(statement.expression, cloneState(state), fail, errorEffects)
|
|
107
|
+
.map((flow) => ({...flow, location: statement.location}))
|
|
108
|
+
}
|
|
109
|
+
if (statement.kind == "AssignmentStatement" || statement.kind == "PrivateFieldWriteStatement") {
|
|
110
|
+
return evaluateExpression(statement.expression, cloneState(state), fail, errorEffects)
|
|
111
|
+
.map((flow) => ({...flow, location: statement.location}))
|
|
112
|
+
}
|
|
113
|
+
if (statement.kind == "ReturnStatement") {
|
|
114
|
+
const flows = statement.expression ? evaluateExpression(statement.expression, cloneState(state), fail, errorEffects) :
|
|
115
|
+
/** @type {LifetimeExit[]} */ ([{kind: "normal", state: cloneState(state)}])
|
|
116
|
+
return flows.map((flow) => flow.kind == "normal"
|
|
117
|
+
? {kind: "return", location: statement.location, state: flow.state}
|
|
118
|
+
: {...flow, location: statement.location})
|
|
119
|
+
}
|
|
120
|
+
if (statement.kind == "RaiseStatement") {
|
|
121
|
+
const flows = evaluateExpression(statement.error.message, cloneState(state), fail, errorEffects)
|
|
122
|
+
return flows.map((flow) => flow.kind == "normal"
|
|
123
|
+
? {errorId: statement.error.error.declarationId, kind: "raise", location: statement.location, state: flow.state}
|
|
124
|
+
: {...flow, location: statement.location})
|
|
125
|
+
}
|
|
126
|
+
if (statement.kind == "IfStatement") {
|
|
127
|
+
const conditions = evaluateExpression(statement.condition, cloneState(state), fail, errorEffects)
|
|
128
|
+
/** @type {LifetimeExit[]} */
|
|
129
|
+
const result = conditions.filter(({kind}) => kind != "normal")
|
|
130
|
+
|
|
131
|
+
for (const condition of conditions.filter(({kind}) => kind == "normal")) {
|
|
132
|
+
result.push(...analyzeBlock(statement.consequent, [{kind: "normal", state: cloneState(condition.state)}], fail,
|
|
133
|
+
errorEffects))
|
|
134
|
+
const alternate = statement.alternate
|
|
135
|
+
? analyzeBlock(statement.alternate, [{kind: "normal", state: cloneState(condition.state)}], fail, errorEffects)
|
|
136
|
+
: /** @type {LifetimeExit[]} */ ([{kind: "normal", location: statement.location, state: cloneState(condition.state)}])
|
|
137
|
+
|
|
138
|
+
result.push(...alternate)
|
|
139
|
+
}
|
|
140
|
+
return mergeEquivalentNormals(result, statement.location, fail)
|
|
141
|
+
}
|
|
142
|
+
if (statement.kind == "TryStatement") {
|
|
143
|
+
const bodyExits = analyzeBlock(statement.body, [{kind: "normal", state: cloneState(state)}], fail, errorEffects)
|
|
144
|
+
/** @type {LifetimeExit[]} */
|
|
145
|
+
const result = bodyExits.filter((flow) => flow.kind != "raise" || flow.errorId != statement.catchType.declarationId)
|
|
146
|
+
|
|
147
|
+
for (const caught of bodyExits.filter((flow) => flow.kind == "raise" && flow.errorId == statement.catchType.declarationId)) {
|
|
148
|
+
result.push(...analyzeBlock(statement.catchBody, [{kind: "normal", state: cloneState(caught.state)}], fail, errorEffects))
|
|
149
|
+
}
|
|
150
|
+
return mergeEquivalentNormals(result, statement.location, fail)
|
|
151
|
+
}
|
|
152
|
+
if (statement.kind == "WhileStatement") {
|
|
153
|
+
const conditions = evaluateExpression(statement.condition, cloneState(state), fail, errorEffects)
|
|
154
|
+
/** @type {LifetimeExit[]} */
|
|
155
|
+
const result = conditions.filter(({kind}) => kind != "normal")
|
|
156
|
+
|
|
157
|
+
for (const condition of conditions.filter(({kind}) => kind == "normal")) {
|
|
158
|
+
const bodyExits = analyzeBlock(statement.body, [{kind: "normal", state: cloneState(condition.state)}], fail,
|
|
159
|
+
errorEffects)
|
|
160
|
+
for (const exit of bodyExits) {
|
|
161
|
+
if (exit.kind == "normal" || exit.kind == "continue") requireSameState(condition.state, exit.state, statement.location, fail)
|
|
162
|
+
else if (exit.kind == "break") {
|
|
163
|
+
requireSameState(condition.state, exit.state, statement.location, fail)
|
|
164
|
+
result.push({kind: "normal", location: statement.location, state: exit.state})
|
|
165
|
+
} else result.push(exit)
|
|
166
|
+
}
|
|
167
|
+
result.push({kind: "normal", location: statement.location, state: condition.state})
|
|
168
|
+
}
|
|
169
|
+
return mergeEquivalentNormals(result, statement.location, fail)
|
|
170
|
+
}
|
|
171
|
+
if (statement.kind == "ForEachStatement" || statement.kind == "ForEachMapStatement") {
|
|
172
|
+
const operand = statement.kind == "ForEachStatement" ? statement.list : statement.map
|
|
173
|
+
const operands = evaluateExpression(operand, cloneState(state), fail, errorEffects)
|
|
174
|
+
/** @type {LifetimeExit[]} */
|
|
175
|
+
const result = operands.filter(({kind}) => kind != "normal")
|
|
176
|
+
|
|
177
|
+
for (const evaluated of operands.filter(({kind}) => kind == "normal")) {
|
|
178
|
+
const bodyExits = analyzeBlock(statement.body, [{kind: "normal", state: cloneState(evaluated.state)}], fail,
|
|
179
|
+
errorEffects)
|
|
180
|
+
for (const exit of bodyExits) {
|
|
181
|
+
if (exit.kind == "normal" || exit.kind == "continue") requireSameState(evaluated.state, exit.state, statement.location, fail)
|
|
182
|
+
else if (exit.kind == "break") {
|
|
183
|
+
requireSameState(evaluated.state, exit.state, statement.location, fail)
|
|
184
|
+
result.push({kind: "normal", location: statement.location, state: exit.state})
|
|
185
|
+
} else result.push(exit)
|
|
186
|
+
}
|
|
187
|
+
result.push({kind: "normal", location: statement.location, state: evaluated.state})
|
|
188
|
+
}
|
|
189
|
+
return mergeEquivalentNormals(result, statement.location, fail)
|
|
190
|
+
}
|
|
191
|
+
if (statement.kind == "BreakStatement") return [{kind: "break", location: statement.location, state: cloneState(state)}]
|
|
192
|
+
if (statement.kind == "ContinueStatement") return [{kind: "continue", location: statement.location, state: cloneState(state)}]
|
|
193
|
+
|
|
194
|
+
return []
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Evaluates one expression with failure states at exact evaluation boundaries.
|
|
199
|
+
* @param {import("./types.js").Expression} expression - Expression.
|
|
200
|
+
* @param {Map<string, OwnerState>} state - Incoming ownership state.
|
|
201
|
+
* @param {LifetimeFail} fail - Located failure.
|
|
202
|
+
* @param {{functions: Map<string, Set<string>>, methods: Map<string, Set<string>>}} errorEffects - Complete escaping error sets per function and method.
|
|
203
|
+
* @returns {LifetimeExit[]} Reachable exits.
|
|
204
|
+
*/
|
|
205
|
+
function evaluateExpression(expression, state, fail, errorEffects) {
|
|
206
|
+
if (expression.kind == "OwnedMoveExpression") {
|
|
207
|
+
const owner = requireOwner(expression.expression.name, state, expression.location, fail)
|
|
208
|
+
|
|
209
|
+
if (owner.kind == "moved") fail("USE_AFTER_MOVE", `Resource '${expression.expression.name}' was already moved.`, expression.location)
|
|
210
|
+
if (owner.kind == "terminal") fail("USE_AFTER_CLOSE", `Resource '${expression.expression.name}' is terminal after close.`, expression.location)
|
|
211
|
+
state.set(expression.expression.name, {...owner, destination: "transfer", kind: "moved"})
|
|
212
|
+
return [{kind: "normal", state}]
|
|
213
|
+
}
|
|
214
|
+
if (expression.kind == "OwnedBorrowExpression") {
|
|
215
|
+
const name = placeName(expression.expression)
|
|
216
|
+
const owner = requireOwner(name, state, expression.location, fail)
|
|
217
|
+
|
|
218
|
+
if (owner.kind == "moved") fail("USE_AFTER_MOVE", `Resource '${name}' was moved before this borrow.`, expression.location)
|
|
219
|
+
return [{kind: "normal", state}]
|
|
220
|
+
}
|
|
221
|
+
if (expression.kind == "EffectCallExpression") return evaluateEffectCall(expression, state, fail, errorEffects)
|
|
222
|
+
if (expression.kind == "CallExpression" || expression.kind == "MethodCallExpression" || expression.kind == "ReferenceConstruction") {
|
|
223
|
+
return evaluateOrdinaryCall(expression, state, fail, errorEffects)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** @type {LifetimeExit[]} */
|
|
227
|
+
let flows = [{kind: "normal", state}]
|
|
228
|
+
for (const child of expressionChildren(expression)) flows = evaluateForFlows(child, flows, fail, errorEffects)
|
|
229
|
+
return flows
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Applies one capability operation's declared ownership and failure flow.
|
|
234
|
+
* @param {import("./types.js").EffectCallExpression} expression - Effect call.
|
|
235
|
+
* @param {Map<string, OwnerState>} state - Incoming ownership state.
|
|
236
|
+
* @param {LifetimeFail} fail - Located failure.
|
|
237
|
+
* @param {{functions: Map<string, Set<string>>, methods: Map<string, Set<string>>}} errorEffects - Complete escaping error sets per function and method.
|
|
238
|
+
* @returns {LifetimeExit[]} Reachable exits.
|
|
239
|
+
*/
|
|
240
|
+
function evaluateEffectCall(expression, state, fail, errorEffects) {
|
|
241
|
+
/** @type {LifetimeExit[]} */
|
|
242
|
+
let flows = [{kind: "normal", state}]
|
|
243
|
+
|
|
244
|
+
for (const argument of expression.arguments) flows = evaluateForFlows(argument, flows, fail, errorEffects)
|
|
245
|
+
const result = flows.filter(({kind}) => kind != "normal")
|
|
246
|
+
const resourceFlow = expression.resolution.resourceFlow
|
|
247
|
+
|
|
248
|
+
for (const flow of flows.filter(({kind}) => kind == "normal")) {
|
|
249
|
+
let operationState = cloneState(flow.state)
|
|
250
|
+
let terminalFailure
|
|
251
|
+
|
|
252
|
+
if (resourceFlow.kind == "borrow" || resourceFlow.kind == "close") {
|
|
253
|
+
const argument = expression.arguments[resourceFlow.parameterIndex]
|
|
254
|
+
if (argument.kind != "OwnedBorrowExpression") fail("INVALID_RESOURCE_BORROW", "Resource operation lacks an immediate borrow.", argument.location)
|
|
255
|
+
const name = placeName(argument.expression)
|
|
256
|
+
const owner = requireOwner(name, operationState, argument.location, fail)
|
|
257
|
+
|
|
258
|
+
if (owner.kind == "moved") fail("USE_AFTER_MOVE", `Resource '${name}' was moved before this operation.`, argument.location)
|
|
259
|
+
if (owner.kind == "terminal") terminalFailure = resourceFlow.terminalFailureId
|
|
260
|
+
else if (resourceFlow.kind == "close") operationState.set(name, {...owner, closeSiteId: expression.effectSiteId, kind: "terminal"})
|
|
261
|
+
}
|
|
262
|
+
if (terminalFailure) {
|
|
263
|
+
result.push({errorId: terminalFailure, kind: "raise", location: expression.location, state: operationState})
|
|
264
|
+
continue
|
|
265
|
+
}
|
|
266
|
+
const failureIds = expression.resolution.failureIds.filter((id) =>
|
|
267
|
+
resourceFlow.kind != "borrow" && resourceFlow.kind != "close" || id != resourceFlow.terminalFailureId)
|
|
268
|
+
|
|
269
|
+
result.push({kind: "normal", state: cloneState(operationState)})
|
|
270
|
+
for (const errorId of failureIds) {
|
|
271
|
+
result.push({errorId, kind: "raise", location: expression.location, state: cloneState(operationState)})
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return result
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Applies call-entry ownership commit semantics after ordered argument evaluation.
|
|
279
|
+
* @param {import("./types.js").CallExpression | import("./types.js").MethodCallExpression | import("./types.js").ReferenceConstruction} expression - Call.
|
|
280
|
+
* @param {Map<string, OwnerState>} state - Incoming ownership state.
|
|
281
|
+
* @param {LifetimeFail} fail - Located failure.
|
|
282
|
+
* @param {{functions: Map<string, Set<string>>, methods: Map<string, Set<string>>}} errorEffects - Complete escaping error sets per function and method.
|
|
283
|
+
* @returns {LifetimeExit[]} Reachable exits.
|
|
284
|
+
*/
|
|
285
|
+
function evaluateOrdinaryCall(expression, state, fail, errorEffects) {
|
|
286
|
+
const arguments_ = expression.kind == "MethodCallExpression" ? [expression.receiver, ...expression.arguments] : expression.arguments
|
|
287
|
+
/** @type {LifetimeExit[]} */
|
|
288
|
+
let flows = [{kind: "normal", state}]
|
|
289
|
+
/** @type {string[]} */
|
|
290
|
+
const reservedMoves = []
|
|
291
|
+
|
|
292
|
+
for (const argument of arguments_) {
|
|
293
|
+
if (argument.kind == "OwnedMoveExpression") {
|
|
294
|
+
for (const flow of flows.filter(({kind}) => kind == "normal")) {
|
|
295
|
+
const owner = requireOwner(argument.expression.name, flow.state, argument.location, fail)
|
|
296
|
+
if (owner.kind == "moved") fail("USE_AFTER_MOVE", `Resource '${argument.expression.name}' was already moved.`, argument.location)
|
|
297
|
+
if (owner.kind == "terminal") fail("USE_AFTER_CLOSE", `Resource '${argument.expression.name}' is terminal after close.`, argument.location)
|
|
298
|
+
}
|
|
299
|
+
if (reservedMoves.includes(argument.expression.name)) {
|
|
300
|
+
fail("RESOURCE_ALIAS", `Resource '${argument.expression.name}' cannot be transferred to two parameters.`, argument.location)
|
|
301
|
+
}
|
|
302
|
+
reservedMoves.push(argument.expression.name)
|
|
303
|
+
} else flows = evaluateForFlows(argument, flows, fail, errorEffects)
|
|
304
|
+
}
|
|
305
|
+
const result = flows.filter(({kind}) => kind != "normal")
|
|
306
|
+
|
|
307
|
+
for (const flow of flows.filter(({kind}) => kind == "normal")) {
|
|
308
|
+
const entered = cloneState(flow.state)
|
|
309
|
+
for (const name of reservedMoves) {
|
|
310
|
+
const owner = requireOwner(name, entered, expression.location, fail)
|
|
311
|
+
if (owner.kind == "moved") fail("USE_AFTER_MOVE", `Resource '${name}' was already moved.`, expression.location)
|
|
312
|
+
if (owner.kind == "terminal") fail("USE_AFTER_CLOSE", `Resource '${name}' is terminal after close.`, expression.location)
|
|
313
|
+
entered.set(name, {...owner, destination: "callee", kind: "moved"})
|
|
314
|
+
}
|
|
315
|
+
if (expression.kind == "MethodCallExpression" && expression.resolution?.resourceFlow) {
|
|
316
|
+
const receiver = expression.receiver.kind == "OwnedBorrowExpression" ? expression.receiver.expression : expression.receiver
|
|
317
|
+
if (receiver.kind != "IdentifierExpression" && receiver.kind != "ReceiverExpression" && receiver.kind != "PrivateFieldRead") {
|
|
318
|
+
fail("INVALID_RESOURCE_BORROW", "Owned method receiver must be an immediate borrowed place.", receiver.location)
|
|
319
|
+
}
|
|
320
|
+
const name = placeName(receiver)
|
|
321
|
+
const owner = requireOwner(name, entered, expression.location, fail)
|
|
322
|
+
|
|
323
|
+
if (owner.kind == "terminal") {
|
|
324
|
+
if (expression.resolution.resourceFlow.terminalFailureId) {
|
|
325
|
+
result.push({errorId: expression.resolution.resourceFlow.terminalFailureId, kind: "raise",
|
|
326
|
+
location: expression.location, state: entered})
|
|
327
|
+
continue
|
|
328
|
+
}
|
|
329
|
+
fail("USE_AFTER_CLOSE", `Resource '${name}' is terminal after close.`, expression.location)
|
|
330
|
+
}
|
|
331
|
+
if (expression.resolution.resourceFlow.kind == "terminal") {
|
|
332
|
+
entered.set(name, {...owner, closeSiteId: expression.effectSiteId ?? expression.location, kind: "terminal"})
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
result.push({kind: "normal", state: cloneState(entered)})
|
|
336
|
+
const escaping = new Set(expression.failureIds ?? [])
|
|
337
|
+
|
|
338
|
+
if (expression.kind == "CallExpression") {
|
|
339
|
+
for (const errorId of errorEffects.functions.get(expression.callee) ?? []) escaping.add(errorId)
|
|
340
|
+
} else if (expression.kind == "MethodCallExpression") {
|
|
341
|
+
for (const errorId of errorEffects.methods.get(expression.method) ?? []) escaping.add(errorId)
|
|
342
|
+
}
|
|
343
|
+
for (const errorId of escaping) {
|
|
344
|
+
result.push({errorId, kind: "raise", location: expression.location, state: cloneState(entered)})
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return result
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Evaluates a child expression only along normal incoming paths.
|
|
352
|
+
* @param {import("./types.js").Expression} expression - Child expression.
|
|
353
|
+
* @param {LifetimeExit[]} flows - Incoming paths.
|
|
354
|
+
* @param {LifetimeFail} fail - Located failure.
|
|
355
|
+
* @param {{functions: Map<string, Set<string>>, methods: Map<string, Set<string>>}} errorEffects - Complete escaping error sets per function and method.
|
|
356
|
+
* @returns {LifetimeExit[]} Reachable exits.
|
|
357
|
+
*/
|
|
358
|
+
function evaluateForFlows(expression, flows, fail, errorEffects) {
|
|
359
|
+
const result = flows.filter(({kind}) => kind != "normal")
|
|
360
|
+
for (const flow of flows.filter(({kind}) => kind == "normal")) {
|
|
361
|
+
result.push(...evaluateExpression(expression, cloneState(flow.state), fail, errorEffects))
|
|
362
|
+
}
|
|
363
|
+
return result
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Returns non-call expression children in evaluation order.
|
|
368
|
+
* @param {import("./types.js").Expression} expression - Parent expression.
|
|
369
|
+
* @returns {import("./types.js").Expression[]} Children.
|
|
370
|
+
*/
|
|
371
|
+
function expressionChildren(expression) {
|
|
372
|
+
if (expression.kind == "BinaryExpression") return [expression.left, expression.right]
|
|
373
|
+
if (expression.kind == "UnaryExpression") return [expression.operand]
|
|
374
|
+
if (expression.kind == "OptionalSome") return [expression.value]
|
|
375
|
+
if (expression.kind == "OptionalIsPresent" || expression.kind == "OptionalUnwrap") return [expression.operand]
|
|
376
|
+
if (expression.kind == "ListLiteral") return expression.elements
|
|
377
|
+
if (expression.kind == "MapLiteral" || expression.kind == "OrderedMapLiteral") return expression.entries.map(({value}) => value)
|
|
378
|
+
if (expression.kind == "ListIndexExpression") return [expression.collection, expression.index]
|
|
379
|
+
if (expression.kind == "MapLookupExpression") return [expression.collection, expression.key]
|
|
380
|
+
if (expression.kind == "CollectionSizeExpression") return [expression.collection]
|
|
381
|
+
if (expression.kind == "RecordConstruction") return expression.arguments
|
|
382
|
+
if (expression.kind == "MemberRead") return [expression.receiver]
|
|
383
|
+
if (expression.kind == "PrivateFieldRead") return [expression.receiver]
|
|
384
|
+
if (expression.kind == "ErrorMessageRead") return [expression.receiver]
|
|
385
|
+
return []
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Merges normal paths only after proving identical ownership states.
|
|
390
|
+
* @param {LifetimeExit[]} flows - Candidate paths.
|
|
391
|
+
* @param {import("./types.js").SourceLocation} location - Join location.
|
|
392
|
+
* @param {LifetimeFail} fail - Located failure.
|
|
393
|
+
* @returns {LifetimeExit[]} Merged paths.
|
|
394
|
+
*/
|
|
395
|
+
function mergeEquivalentNormals(flows, location, fail) {
|
|
396
|
+
const normal = flows.filter(({kind}) => kind == "normal")
|
|
397
|
+
if (normal.length > 1) {
|
|
398
|
+
for (const candidate of normal.slice(1)) requireSameState(normal[0].state, candidate.state, location, fail)
|
|
399
|
+
}
|
|
400
|
+
return [...flows.filter(({kind}) => kind != "normal"), ...(normal.length > 0 ? [normal[0]] : [])]
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Requires a finite ownership invariant at a branch or loop join.
|
|
405
|
+
* @param {Map<string, OwnerState>} left - First ownership state.
|
|
406
|
+
* @param {Map<string, OwnerState>} right - Second ownership state.
|
|
407
|
+
* @param {import("./types.js").SourceLocation} location - Join location.
|
|
408
|
+
* @param {LifetimeFail} fail - Located failure.
|
|
409
|
+
* @returns {void}
|
|
410
|
+
*/
|
|
411
|
+
function requireSameState(left, right, location, fail) {
|
|
412
|
+
const names = new Set([...left.keys(), ...right.keys()])
|
|
413
|
+
for (const name of names) {
|
|
414
|
+
const leftState = left.get(name)
|
|
415
|
+
const rightState = right.get(name)
|
|
416
|
+
if (leftState?.kind != rightState?.kind || leftState?.originId != rightState?.originId) {
|
|
417
|
+
fail("RESOURCE_STATE_MISMATCH", `Resource '${name}' has different ownership state across reachable paths.`, location)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Rejects every owned value that leaves its introducing scope open.
|
|
424
|
+
* @param {Map<string, OwnerState>} state - Ownership state.
|
|
425
|
+
* @param {Set<string>} names - Owners introduced in the scope.
|
|
426
|
+
* @param {import("./types.js").SourceLocation} location - Exit location.
|
|
427
|
+
* @param {LifetimeFail} fail - Located failure.
|
|
428
|
+
* @returns {void}
|
|
429
|
+
*/
|
|
430
|
+
function checkLeaks(state, names, location, fail) {
|
|
431
|
+
for (const name of names) {
|
|
432
|
+
const owner = state.get(name)
|
|
433
|
+
if (owner?.kind == "open") {
|
|
434
|
+
const origin = owner.origin
|
|
435
|
+
fail("LEAKED_RESOURCE", `Owned resource '${name}' remains open; acquired at ${origin.filename}:${origin.start.line}:${origin.start.column}.`, location)
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Resolves an owned place in the current scope.
|
|
442
|
+
* @param {string} name - Stable place name.
|
|
443
|
+
* @param {Map<string, OwnerState>} state - Ownership state.
|
|
444
|
+
* @param {import("./types.js").SourceLocation} location - Use location.
|
|
445
|
+
* @param {LifetimeFail} fail - Located failure.
|
|
446
|
+
* @returns {OwnerState} Owner state.
|
|
447
|
+
*/
|
|
448
|
+
function requireOwner(name, state, location, fail) {
|
|
449
|
+
const owner = state.get(name)
|
|
450
|
+
if (!owner) return fail("INVALID_RESOURCE_BORROW", `No owned resource place '${name}' is in scope.`, location)
|
|
451
|
+
return owner
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Converts an immediate owned expression to a stable place name.
|
|
456
|
+
* @param {import("./types.js").IdentifierExpression | import("./types.js").ReceiverExpression | import("./types.js").PrivateFieldRead} expression - Owned place.
|
|
457
|
+
* @returns {string} Place identity.
|
|
458
|
+
*/
|
|
459
|
+
function placeName(expression) {
|
|
460
|
+
if (expression.kind == "IdentifierExpression") return expression.name
|
|
461
|
+
if (expression.kind == "ReceiverExpression") return "this"
|
|
462
|
+
if (expression.kind == "PrivateFieldRead") return `this.${expression.field}`
|
|
463
|
+
return "<invalid>"
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Creates one open ownership state.
|
|
468
|
+
* @param {string} originId - Deterministic origin identity.
|
|
469
|
+
* @param {import("./types.js").SourceLocation} origin - Acquisition location.
|
|
470
|
+
* @returns {OwnerState} Open state.
|
|
471
|
+
*/
|
|
472
|
+
function openState(originId, origin) {
|
|
473
|
+
return {kind: "open", origin, originId}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Clones an ownership state for a control-flow fork.
|
|
478
|
+
* @param {Map<string, OwnerState>} state - State to clone.
|
|
479
|
+
* @returns {Map<string, OwnerState>} Detached state.
|
|
480
|
+
*/
|
|
481
|
+
function cloneState(state) {
|
|
482
|
+
return new Map([...state].map(([name, value]) => [name, {...value}]))
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Checks whether a type carries linear ownership.
|
|
487
|
+
* @param {import("./types.js").SemanticFunctionReturnType} type - Semantic type.
|
|
488
|
+
* @returns {boolean} Whether the type is owned.
|
|
489
|
+
*/
|
|
490
|
+
function isOwned(type) {
|
|
491
|
+
return type.kind == "OwnedResourceType" || type.kind == "OwnedReferenceType"
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Checks whether the module contains any owned semantic type.
|
|
496
|
+
* @param {import("./types.js").SemanticModule} module - Semantic module.
|
|
497
|
+
* @returns {boolean} Whether lifetime analysis is required.
|
|
498
|
+
*/
|
|
499
|
+
function moduleContainsOwnedType(module) {
|
|
500
|
+
const pending = [/** @type {unknown} */ (module)]
|
|
501
|
+
const seen = new Set()
|
|
502
|
+
|
|
503
|
+
while (pending.length > 0) {
|
|
504
|
+
const value = pending.pop()
|
|
505
|
+
|
|
506
|
+
if (!value || typeof value != "object" || seen.has(value)) continue
|
|
507
|
+
seen.add(value)
|
|
508
|
+
if (!Array.isArray(value) && (Reflect.get(value, "kind") == "OwnedResourceType" ||
|
|
509
|
+
Reflect.get(value, "kind") == "OwnedReferenceType")) return true
|
|
510
|
+
for (const [key, child] of Object.entries(value)) {
|
|
511
|
+
if (["location", "provenance", "sourceProvenance"].includes(key)) continue
|
|
512
|
+
if (Array.isArray(child)) pending.push(...child)
|
|
513
|
+
else if (child && typeof child == "object") pending.push(child)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return false
|
|
517
|
+
}
|