semantifold 0.3.0 → 0.5.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 +61 -15
- package/build/index.d.ts +52 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +60 -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/android.d.ts +49 -0
- package/build/src/backends/android.d.ts.map +1 -0
- package/build/src/backends/android.js +1274 -0
- 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 +160 -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 +33 -0
- package/build/src/backends/kotlin.d.ts.map +1 -0
- package/build/src/backends/kotlin.js +138 -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 +83 -0
- package/build/src/backends/program.d.ts.map +1 -0
- package/build/src/backends/program.js +1309 -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 +2174 -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 +255 -4
- package/build/src/backends/writer.d.ts.map +1 -1
- package/build/src/backends/writer.js +491 -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 +177 -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 +3108 -178
- package/build/src/semantic/types.d.ts.map +1 -1
- package/build/src/semantic/types.js +730 -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/android.md +79 -0
- package/docs/architecture.md +47 -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 +238 -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 +17 -7
- package/docs/specification.md +5 -0
- package/docs/standard-library-portability.md +39 -8
- package/docs/swift.md +44 -0
- package/docs/testing.md +381 -4
- package/docs/webassembly.md +52 -0
- package/index.js +66 -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/android.js +1370 -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 +166 -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 +144 -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 +1408 -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 +2314 -115
- 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 +545 -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 +182 -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 +810 -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
package/src/semantic/validate.js
CHANGED
|
@@ -1,21 +1,46 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import {semanticFailure, unsupportedCapability, unsupportedSyntax} from "../diagnostic.js"
|
|
4
|
-
import {
|
|
4
|
+
import {recordTypeSubstitutions, substituteValueType, typeContainsAnyVariable} from "./generics.js"
|
|
5
|
+
import {hasOnlyUnicodeScalars, isScalarTypeName, scalarType} from "./scalars.js"
|
|
5
6
|
import {adaptedOperationFor} from "./operators.js"
|
|
7
|
+
import {parserRangeFor} from "./provenance.js"
|
|
8
|
+
import {validateEffectGraph} from "./effects.js"
|
|
9
|
+
import {validateResourceLifetimes} from "./lifetime.js"
|
|
10
|
+
|
|
11
|
+
const task005Languages = new Set(["php", "ruby", "javascript", "typescript", "java", "dart"])
|
|
12
|
+
|
|
13
|
+
/** @typedef {Map<string, import("./types.js").RecordDeclaration>} RecordRegistry */
|
|
14
|
+
/** @typedef {Map<string, import("./types.js").ClassDeclaration>} ClassRegistry */
|
|
15
|
+
/** @typedef {Map<string, import("./types.js").ErrorDeclaration>} ErrorRegistry */
|
|
16
|
+
/** @type {WeakMap<RecordRegistry, ClassRegistry>} */
|
|
17
|
+
const referenceClassesByRecordRegistry = new WeakMap()
|
|
18
|
+
/** @type {WeakMap<RecordRegistry, Set<string>>} */
|
|
19
|
+
const resourceIdsByRecordRegistry = new WeakMap()
|
|
6
20
|
|
|
7
21
|
/**
|
|
8
22
|
* @typedef Binding
|
|
9
23
|
* @property {boolean} mutable - Whether assignment is allowed.
|
|
10
|
-
* @property {import("./types.js").
|
|
24
|
+
* @property {import("./types.js").SemanticBindingType} type - Binding type.
|
|
25
|
+
* @property {import("./types.js").Expression | undefined} knownValue - Current statically known immutable value.
|
|
11
26
|
*/
|
|
12
27
|
|
|
13
28
|
/**
|
|
14
29
|
* @typedef Scope
|
|
15
30
|
* @property {Map<string, Binding>} bindings - Bindings declared directly in this scope.
|
|
16
31
|
* @property {Set<string>} pending - Names declared later in this scope.
|
|
32
|
+
* @property {Set<Binding>} presenceProofs - Optional bindings proven present on this path.
|
|
17
33
|
* @property {Scope | undefined} parent - Enclosing lexical scope.
|
|
34
|
+
* @property {Set<string>} typeParameters - Type parameters visible throughout the enclosing declaration.
|
|
18
35
|
* @property {Set<string>} usedNames - All names used by the enclosing function or entry point.
|
|
36
|
+
* @property {import("./types.js").ClassDeclaration | undefined} currentClass - Declaring class for receiver-private access.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef LoopFlowContext
|
|
41
|
+
* @property {import("./types.js").ForEachStatement | import("./types.js").ForEachMapStatement | import("./types.js").WhileStatement} [nearest] - Nearest active loop.
|
|
42
|
+
* @property {object} owner - Enclosing function or entry-point identity.
|
|
43
|
+
* @property {Map<string, object>} owners - Loop owner by module-local loop identity.
|
|
19
44
|
*/
|
|
20
45
|
|
|
21
46
|
/**
|
|
@@ -27,34 +52,92 @@ import {adaptedOperationFor} from "./operators.js"
|
|
|
27
52
|
* Enforces the coherent release-candidate semantic subset after adaptation.
|
|
28
53
|
* @param {import("./types.js").SemanticModule} module - Adapted semantic module.
|
|
29
54
|
* @param {import("./types.js").SemanticLanguage} language - Source language.
|
|
55
|
+
* @param {{classes?: Map<string, import("./types.js").ClassDeclaration>, functions?: Map<string, import("./types.js").FunctionDeclaration>, records?: Map<string, import("./types.js").RecordDeclaration>, errors?: Map<string, import("./types.js").ErrorDeclaration>, callEffects?: Map<string, Set<string>>}} [visible] - Program imports visible during validation.
|
|
30
56
|
* @returns {import("./types.js").SemanticModule} Validated module.
|
|
31
57
|
*/
|
|
32
|
-
export function validateParsedModule(module, language) {
|
|
33
|
-
|
|
34
|
-
|
|
58
|
+
export function validateParsedModule(module, language, visible = {}) {
|
|
59
|
+
normalizeParsedOwnedReferences(module, visible.classes)
|
|
60
|
+
validateModuleShape(module, language, (detail, location) => unsupportedSyntax(language, detail, location))
|
|
61
|
+
/**
|
|
62
|
+
* Converts one parser failure into a located semantic diagnostic.
|
|
63
|
+
* @type {SemanticFail}
|
|
64
|
+
*/
|
|
65
|
+
const fail = (code, detail, location) => semanticFailure(language, code, detail, location)
|
|
66
|
+
|
|
67
|
+
validateModuleTypes(module, language, fail, true, visible)
|
|
68
|
+
validateResourceLifetimes(module, inferEscapingErrorEffects(module, visible.callEffects), fail)
|
|
35
69
|
|
|
36
70
|
return module
|
|
37
71
|
}
|
|
38
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Promotes parser-authored class spellings after all private field types are known.
|
|
75
|
+
* @param {import("./types.js").SemanticModule} module - Parser-authored module.
|
|
76
|
+
* @param {Map<string, import("./types.js").ClassDeclaration>} [visibleClasses] - Imported reference classes.
|
|
77
|
+
* @returns {void}
|
|
78
|
+
*/
|
|
79
|
+
function normalizeParsedOwnedReferences(module, visibleClasses = new Map()) {
|
|
80
|
+
const ownedClassIds = new Set([...visibleClasses.values()].filter(({ownership}) => ownership?.kind == "ownedResource")
|
|
81
|
+
.map(({id}) => /** @type {string} */ (id)))
|
|
82
|
+
|
|
83
|
+
for (const declaration of module.classes ?? []) {
|
|
84
|
+
const fields = declaration.fields.filter(({type}) => type?.kind == "OwnedResourceType")
|
|
85
|
+
if (fields.length != 1) continue
|
|
86
|
+
const field = /** @type {import("./types.js").PrivateField & {type: import("./types.js").OwnedResourceType}} */ (fields[0])
|
|
87
|
+
|
|
88
|
+
declaration.ownership = {fieldId: /** @type {string} */ (field.id), kind: "ownedResource", resourceId: field.type.resourceId}
|
|
89
|
+
ownedClassIds.add(/** @type {string} */ (declaration.id))
|
|
90
|
+
}
|
|
91
|
+
/** @type {unknown[]} */
|
|
92
|
+
const pending = [module]
|
|
93
|
+
/** @type {Set<object>} */
|
|
94
|
+
const seen = new Set()
|
|
95
|
+
|
|
96
|
+
while (pending.length > 0) {
|
|
97
|
+
const value = pending.pop()
|
|
98
|
+
if (!value || typeof value != "object" || seen.has(value)) continue
|
|
99
|
+
seen.add(value)
|
|
100
|
+
if (!Array.isArray(value) && Reflect.get(value, "kind") == "ReferenceType" &&
|
|
101
|
+
ownedClassIds.has(Reflect.get(value, "declarationId"))) Reflect.set(value, "kind", "OwnedReferenceType")
|
|
102
|
+
for (const [key, child] of Object.entries(value)) {
|
|
103
|
+
if (["location", "sourceProvenance"].includes(key)) continue
|
|
104
|
+
if (child && typeof child == "object") pending.push(child)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
39
109
|
/**
|
|
40
110
|
* Validates scalar types and bindings for a caller-supplied semantic module before emission.
|
|
41
111
|
* @param {import("./types.js").SemanticModule} module - Semantic module.
|
|
42
|
-
* @param {import("./types.js").
|
|
112
|
+
* @param {import("./types.js").BackendLanguage} language - Backend language or binary target.
|
|
113
|
+
* @param {{classes?: Map<string, import("./types.js").ClassDeclaration>, functions?: Map<string, import("./types.js").FunctionDeclaration>, records?: Map<string, import("./types.js").RecordDeclaration>, errors?: Map<string, import("./types.js").ErrorDeclaration>, callEffects?: Map<string, Set<string>>}} [visible] - Resolved program imports.
|
|
43
114
|
* @returns {void}
|
|
44
115
|
*/
|
|
45
|
-
export function validateBackendTypes(module, language) {
|
|
46
|
-
|
|
116
|
+
export function validateBackendTypes(module, language, visible = {}) {
|
|
117
|
+
/**
|
|
118
|
+
* Converts one backend validation failure into an unsupported-capability diagnostic.
|
|
119
|
+
* @type {SemanticFail}
|
|
120
|
+
*/
|
|
121
|
+
const fail = (_code, detail, location) => unsupportedCapability(language, detail, location)
|
|
122
|
+
|
|
123
|
+
validateModuleTypes(module, language, fail, false, visible)
|
|
124
|
+
validateResourceLifetimes(module, inferEscapingErrorEffects(module, visible.callEffects), fail)
|
|
47
125
|
}
|
|
48
126
|
|
|
49
127
|
/**
|
|
50
128
|
* Checks parser-authored block and statement layouts before semantic validation.
|
|
51
129
|
* @param {import("./types.js").SemanticModule} module - Semantic module.
|
|
130
|
+
* @param {import("./types.js").SemanticLanguage} language - Source language.
|
|
52
131
|
* @param {(detail: string, location: import("./types.js").SourceLocation) => never} fail - Shape failure.
|
|
53
132
|
* @returns {void}
|
|
54
133
|
*/
|
|
55
|
-
function validateModuleShape(module, fail) {
|
|
134
|
+
function validateModuleShape(module, language, fail) {
|
|
135
|
+
for (const declaration of module.classes ?? []) {
|
|
136
|
+
validateBlockShape(declaration.constructor?.body, "constructor body", fail)
|
|
137
|
+
for (const method of declaration.methods ?? []) validateBlockShape(method.body, "method body", fail)
|
|
138
|
+
}
|
|
56
139
|
for (const functionDeclaration of module.functions) {
|
|
57
|
-
if (functionDeclaration.parameters.length != 2) {
|
|
140
|
+
if (!task005Languages.has(language) && functionDeclaration.parameters.length != 2) {
|
|
58
141
|
fail("function parameter count other than two", functionDeclaration.location)
|
|
59
142
|
}
|
|
60
143
|
|
|
@@ -77,312 +160,2954 @@ function validateBlockShape(block, detail, fail) {
|
|
|
77
160
|
}
|
|
78
161
|
|
|
79
162
|
for (const statement of block.statements) {
|
|
80
|
-
if (!["AssignmentStatement", "IfStatement", "LocalDeclaration", "PrintStatement", "ReturnStatement"].includes(statement.kind)) {
|
|
163
|
+
if (!["AssignmentStatement", "BreakStatement", "ContinueStatement", "ExpressionStatement", "ForEachMapStatement", "ForEachStatement", "IfStatement", "LocalDeclaration", "PrivateFieldWriteStatement", "PrintStatement", "RaiseStatement", "ReturnStatement", "TryStatement", "WhileStatement"].includes(statement.kind)) {
|
|
81
164
|
fail(`${detail} statement ${statement.kind}`, statement.location)
|
|
82
165
|
}
|
|
83
166
|
if (statement.kind == "IfStatement") {
|
|
84
167
|
validateBlockShape(statement.consequent, "if consequent", fail)
|
|
85
168
|
if (statement.alternate) validateBlockShape(statement.alternate, "if alternate", fail)
|
|
86
169
|
}
|
|
170
|
+
if (statement.kind == "ForEachStatement" || statement.kind == "ForEachMapStatement" || statement.kind == "WhileStatement") {
|
|
171
|
+
validateBlockShape(statement.body, "loop body", fail)
|
|
172
|
+
}
|
|
173
|
+
if (statement.kind == "TryStatement") {
|
|
174
|
+
validateBlockShape(statement.body, "try body", fail)
|
|
175
|
+
validateBlockShape(statement.catchBody, "catch body", fail)
|
|
176
|
+
}
|
|
87
177
|
}
|
|
88
178
|
}
|
|
89
179
|
|
|
90
180
|
/**
|
|
91
181
|
* Validates declarations and expression types within lexical scopes.
|
|
92
182
|
* @param {import("./types.js").SemanticModule} module - Semantic module.
|
|
183
|
+
* @param {string} language - Source or backend language identity.
|
|
93
184
|
* @param {SemanticFail} fail - Diagnostic callback.
|
|
94
185
|
* @param {boolean} normalizeOperations - Whether to replace transient frontend operation intent.
|
|
186
|
+
* @param {{classes?: Map<string, import("./types.js").ClassDeclaration>, functions?: Map<string, import("./types.js").FunctionDeclaration>, records?: Map<string, import("./types.js").RecordDeclaration>, errors?: Map<string, import("./types.js").ErrorDeclaration>, callEffects?: Map<string, Set<string>>}} [visible] - Program imports visible during validation.
|
|
95
187
|
* @returns {void}
|
|
96
188
|
*/
|
|
97
|
-
function validateModuleTypes(module, fail, normalizeOperations) {
|
|
189
|
+
function validateModuleTypes(module, language, fail, normalizeOperations, visible = {}) {
|
|
190
|
+
const classes = registerClassDeclarations(module.classes ?? [], fail, normalizeOperations, visible.classes)
|
|
191
|
+
const records = validateRecordDeclarations(module.records ?? [], fail, normalizeOperations, visible.records, classes)
|
|
192
|
+
const errors = validateErrorDeclarations(module.errors ?? [], fail, normalizeOperations, visible.errors)
|
|
98
193
|
/** @type {Map<string, import("./types.js").FunctionDeclaration>} */
|
|
99
|
-
const functions = new Map()
|
|
100
|
-
|
|
101
|
-
|
|
194
|
+
const functions = new Map(visible.functions ?? [])
|
|
195
|
+
const declarationIds = new Set([...functions.values()].flatMap(({id}) => typeof id == "string" ? [id] : []))
|
|
196
|
+
|
|
197
|
+
for (const [index, functionDeclaration] of module.functions.entries()) {
|
|
198
|
+
if (normalizeOperations) functionDeclaration.id = `function:${index}`
|
|
199
|
+
if (typeof functionDeclaration.id != "string" || !/^(?:[a-z][a-z0-9._-]*#)?function:[0-9]+$/u.test(functionDeclaration.id) ||
|
|
200
|
+
declarationIds.has(functionDeclaration.id)) {
|
|
201
|
+
fail("DUPLICATE_BINDING", "Duplicate or invalid function declaration identity.", functionDeclaration.location)
|
|
202
|
+
}
|
|
102
203
|
if (functions.has(functionDeclaration.name)) {
|
|
103
|
-
fail("DUPLICATE_BINDING", `duplicate function '${functionDeclaration.name}'.`, functionDeclaration
|
|
204
|
+
fail("DUPLICATE_BINDING", `duplicate function '${functionDeclaration.name}'.`, roleLocation(functionDeclaration, "name"))
|
|
104
205
|
}
|
|
206
|
+
declarationIds.add(functionDeclaration.id)
|
|
207
|
+
validateTypeParameters(functionDeclaration, fail, normalizeOperations)
|
|
105
208
|
functions.set(functionDeclaration.name, functionDeclaration)
|
|
106
209
|
}
|
|
107
210
|
|
|
108
|
-
|
|
211
|
+
validateEffectGraph(module, language, functions, fail, normalizeOperations, classes)
|
|
212
|
+
const capabilityResources = (module.capabilities ?? []).flatMap(({resources}) => resources)
|
|
213
|
+
const capabilityFailures = (module.capabilities ?? []).flatMap(({failures}) => failures)
|
|
214
|
+
|
|
215
|
+
resourceIdsByRecordRegistry.set(records, new Set(capabilityResources.map(({id}) => id)))
|
|
216
|
+
const existingErrorNames = new Set([...errors.values()].map(({name}) => name))
|
|
217
|
+
for (const failure of capabilityFailures) {
|
|
218
|
+
if (errors.has(failure.id) || existingErrorNames.has(failure.name)) {
|
|
219
|
+
fail("DUPLICATE_ERROR", `Duplicate capability failure '${failure.name}'.`, module.location)
|
|
220
|
+
}
|
|
221
|
+
errors.set(failure.id, /** @type {import("./types.js").ErrorDeclaration} */ (/** @type {unknown} */ (failure)))
|
|
222
|
+
existingErrorNames.add(failure.name)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const loopOwners = prepareLoopIdentities(module, normalizeOperations, fail)
|
|
226
|
+
|
|
227
|
+
const reservedValueNames = new Set([
|
|
228
|
+
...functions.keys(),
|
|
229
|
+
...[...records.values()].map((declaration) => declaration.name),
|
|
230
|
+
...[...classes.values()].map((declaration) => declaration.name),
|
|
231
|
+
...[...errors.values()].map((declaration) => declaration.name),
|
|
232
|
+
...capabilityResources.map(({name}) => name)
|
|
233
|
+
])
|
|
234
|
+
|
|
235
|
+
const nominalNames = new Set()
|
|
236
|
+
|
|
237
|
+
for (const declaration of [...module.records ?? [], ...module.classes ?? [], ...module.errors ?? []]) {
|
|
238
|
+
if (nominalNames.has(declaration.name)) {
|
|
239
|
+
fail("DUPLICATE_BINDING", `Duplicate nominal declaration '${declaration.name}'.`, roleLocation(declaration, "name"))
|
|
240
|
+
}
|
|
241
|
+
nominalNames.add(declaration.name)
|
|
242
|
+
}
|
|
243
|
+
for (const declaration of [...capabilityResources, ...capabilityFailures]) {
|
|
244
|
+
if (nominalNames.has(declaration.name)) {
|
|
245
|
+
fail("DUPLICATE_BINDING", `Duplicate nominal declaration '${declaration.name}'.`, module.location)
|
|
246
|
+
}
|
|
247
|
+
nominalNames.add(declaration.name)
|
|
248
|
+
}
|
|
249
|
+
const localFunctionNames = new Set(module.functions.map(({name}) => name))
|
|
250
|
+
const callEffects = inferCallEffects(functions, visible.callEffects, localFunctionNames)
|
|
251
|
+
|
|
252
|
+
for (const declaration of module.classes ?? []) {
|
|
253
|
+
validateClassDeclaration(declaration, functions, records, errors, callEffects, reservedValueNames, fail,
|
|
254
|
+
normalizeOperations, loopOwners)
|
|
255
|
+
}
|
|
109
256
|
|
|
110
|
-
const
|
|
257
|
+
for (const functionDeclaration of module.functions) {
|
|
258
|
+
validateFunction(functionDeclaration, functions, records, errors, callEffects, reservedValueNames, fail, normalizeOperations, loopOwners)
|
|
259
|
+
}
|
|
111
260
|
|
|
112
|
-
const entryScope = createScope(undefined, module.entryPoint.body.statements,
|
|
261
|
+
const entryScope = createScope(undefined, module.entryPoint.body.statements, reservedValueNames)
|
|
113
262
|
|
|
114
|
-
validateBlock(module.entryPoint.body, entryScope, undefined, functions, fail,
|
|
263
|
+
validateBlock(module.entryPoint.body, entryScope, undefined, functions, records, errors, callEffects, fail,
|
|
264
|
+
normalizeOperations, {owner: module.entryPoint, owners: loopOwners})
|
|
265
|
+
if (normalizeOperations) validateEffectGraph(module, language, functions, fail, true, classes)
|
|
115
266
|
}
|
|
116
267
|
|
|
117
268
|
/**
|
|
118
|
-
*
|
|
119
|
-
* @param {import("./types.js").
|
|
120
|
-
* @param {
|
|
269
|
+
* Assigns parser-authored loop/control identities and validates caller-owned identities without crossing a function boundary.
|
|
270
|
+
* @param {import("./types.js").SemanticModule} module - Semantic module.
|
|
271
|
+
* @param {boolean} normalizeOperations - Whether parser-authored identities are assigned.
|
|
121
272
|
* @param {SemanticFail} fail - Diagnostic callback.
|
|
122
|
-
* @
|
|
123
|
-
* @returns {void}
|
|
273
|
+
* @returns {Map<string, object>} Loop owner by identity.
|
|
124
274
|
*/
|
|
125
|
-
function
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
275
|
+
function prepareLoopIdentities(module, normalizeOperations, fail) {
|
|
276
|
+
const owners = new Map()
|
|
277
|
+
let nextLoop = 0
|
|
278
|
+
|
|
279
|
+
const classOwners = (module.classes ?? []).flatMap((declaration) => [declaration.constructor, ...declaration.methods])
|
|
280
|
+
|
|
281
|
+
for (const owner of [...classOwners, ...module.functions, module.entryPoint]) visit(owner.body, owner, [])
|
|
282
|
+
|
|
283
|
+
return owners
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Visits one block in lexical semantic order.
|
|
287
|
+
* @param {import("./types.js").Block} block - Candidate block.
|
|
288
|
+
* @param {object} owner - Enclosing function or entry point.
|
|
289
|
+
* @param {(import("./types.js").ForEachStatement | import("./types.js").ForEachMapStatement | import("./types.js").WhileStatement)[]} active - Active loops.
|
|
290
|
+
* @returns {void}
|
|
291
|
+
*/
|
|
292
|
+
function visit(block, owner, active) {
|
|
293
|
+
if (!block || !Array.isArray(block.statements)) return
|
|
294
|
+
for (const statement of block.statements) {
|
|
295
|
+
if (!statement || typeof statement != "object") continue
|
|
296
|
+
if (["ForEachStatement", "ForEachMapStatement", "WhileStatement"].includes(statement.kind)) {
|
|
297
|
+
const loop = /** @type {import("./types.js").ForEachStatement | import("./types.js").ForEachMapStatement | import("./types.js").WhileStatement} */ (statement)
|
|
298
|
+
|
|
299
|
+
if (normalizeOperations) loop.id = `loop:${nextLoop}`
|
|
300
|
+
nextLoop += 1
|
|
301
|
+
if (typeof loop.id != "string" || !/^loop:[0-9]+$/u.test(loop.id) || owners.has(loop.id)) {
|
|
302
|
+
fail("INVALID_LOOP_TARGET", "Duplicate or invalid loop identity.", loop.location)
|
|
303
|
+
}
|
|
304
|
+
owners.set(loop.id, owner)
|
|
305
|
+
visit(loop.body, owner, [...active, loop])
|
|
306
|
+
} else if (statement.kind == "BreakStatement" || statement.kind == "ContinueStatement") {
|
|
307
|
+
if (normalizeOperations && active.length > 0) statement.targetLoopId = /** @type {string} */ (active.at(-1)?.id)
|
|
308
|
+
} else if (statement.kind == "IfStatement") {
|
|
309
|
+
visit(statement.consequent, owner, active)
|
|
310
|
+
if (statement.alternate) visit(statement.alternate, owner, active)
|
|
311
|
+
} else if (statement.kind == "TryStatement") {
|
|
312
|
+
visit(statement.body, owner, active)
|
|
313
|
+
visit(statement.catchBody, owner, active)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
132
316
|
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Derives validator-only unchecked-error effects for a validated module's local functions.
|
|
321
|
+
* These effects never enter semantic function declarations or public signatures.
|
|
322
|
+
* @param {import("./types.js").SemanticModule} module - Validated semantic module.
|
|
323
|
+
* @param {{functions?: Map<string, import("./types.js").FunctionDeclaration>, callEffects?: Map<string, Set<string>>}} [visible] - Imported functions and already-resolved effects by local binding.
|
|
324
|
+
* @returns {Map<string, Set<string>>} Local function effects by source-visible name.
|
|
325
|
+
*/
|
|
326
|
+
export function moduleUncheckedErrorEffects(module, visible = {}) {
|
|
327
|
+
const functions = new Map(visible.functions ?? [])
|
|
133
328
|
|
|
134
|
-
const
|
|
135
|
-
const
|
|
329
|
+
for (const declaration of module.functions) functions.set(declaration.name, declaration)
|
|
330
|
+
const localFunctionNames = new Set(module.functions.map(({name}) => name))
|
|
331
|
+
const effects = inferCallEffects(functions, visible.callEffects, localFunctionNames)
|
|
136
332
|
|
|
137
|
-
|
|
333
|
+
return new Map(module.functions.map((declaration) => [declaration.name, new Set(effects.get(declaration.name) ?? [])]))
|
|
138
334
|
}
|
|
139
335
|
|
|
140
336
|
/**
|
|
141
|
-
*
|
|
142
|
-
* @param {
|
|
143
|
-
* @param {Scope} scope - Scope belonging to this block.
|
|
144
|
-
* @param {import("./types.js").SemanticTypeName | undefined} returnType - Function return type, absent for entry points.
|
|
145
|
-
* @param {Map<string, import("./types.js").FunctionDeclaration>} functions - Function signatures.
|
|
337
|
+
* Assigns and validates the closed nominal identities owned by reference classes.
|
|
338
|
+
* @param {unknown} declarations - Candidate declarations.
|
|
146
339
|
* @param {SemanticFail} fail - Diagnostic callback.
|
|
147
|
-
* @param {boolean} normalizeOperations - Whether
|
|
148
|
-
* @
|
|
340
|
+
* @param {boolean} normalizeOperations - Whether parser-authored identities are assigned.
|
|
341
|
+
* @param {Map<string, import("./types.js").ClassDeclaration>} [visibleClasses] - Imported classes by identity.
|
|
342
|
+
* @returns {ClassRegistry} Classes by stable identity.
|
|
149
343
|
*/
|
|
150
|
-
function
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
344
|
+
function registerClassDeclarations(declarations, fail, normalizeOperations, visibleClasses = new Map()) {
|
|
345
|
+
if (!Array.isArray(declarations)) {
|
|
346
|
+
return fail("TYPE_MISMATCH", "Reference class declarations must be an ordered array.", /** @type {never} */ (undefined))
|
|
347
|
+
}
|
|
348
|
+
/** @type {ClassRegistry} */
|
|
349
|
+
const classes = new Map(visibleClasses)
|
|
350
|
+
const names = new Set([...visibleClasses.values()].map(({name}) => name))
|
|
155
351
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const initializerType = inferExpressionType(statement.initializer, scope, functions, fail, normalizeOperations)
|
|
352
|
+
for (let classIndex = 0; classIndex < declarations.length; classIndex += 1) {
|
|
353
|
+
const declaration = /** @type {import("./types.js").ClassDeclaration} */ (declarations[classIndex])
|
|
159
354
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
declareBinding(statement.name, {mutable: statement.mutable, type: declaredType}, statement.location, scope, fail)
|
|
165
|
-
continue
|
|
355
|
+
if (!declaration || declaration.kind != "ClassDeclaration" || !Array.isArray(declaration.fields) ||
|
|
356
|
+
!Array.isArray(declaration.methods) || !declaration.constructor || declaration.constructor.kind != "ConstructorDeclaration") {
|
|
357
|
+
return fail("DUPLICATE_CLASS", "Malformed or invalid reference class declaration.", declaration?.location)
|
|
166
358
|
}
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
359
|
+
if (declaration.fields.length == 0) {
|
|
360
|
+
fail("INCOMPLETE_INITIALIZATION", "Reference classes must declare private instance state.", declaration.location)
|
|
361
|
+
}
|
|
362
|
+
if (normalizeOperations) declaration.id = `class:${classIndex}`
|
|
363
|
+
if (typeof declaration.id != "string" || !/^(?:[a-z][a-z0-9._-]*#)?class:[0-9]+$/u.test(declaration.id) ||
|
|
364
|
+
classes.has(declaration.id) || typeof declaration.name != "string" || declaration.name.length == 0 || names.has(declaration.name)) {
|
|
365
|
+
fail("DUPLICATE_CLASS", `Duplicate or invalid reference class '${String(declaration.name)}'.`, roleLocation(declaration, "name"))
|
|
366
|
+
}
|
|
367
|
+
names.add(declaration.name)
|
|
368
|
+
classes.set(declaration.id, declaration)
|
|
170
369
|
|
|
171
|
-
|
|
172
|
-
|
|
370
|
+
const fieldNames = new Set()
|
|
371
|
+
|
|
372
|
+
for (let fieldIndex = 0; fieldIndex < declaration.fields.length; fieldIndex += 1) {
|
|
373
|
+
const field = declaration.fields[fieldIndex]
|
|
374
|
+
const expectedId = `${declaration.id}:field:${fieldIndex}`
|
|
375
|
+
|
|
376
|
+
if (!field || field.kind != "PrivateField") {
|
|
377
|
+
fail("DUPLICATE_PRIVATE_FIELD", "Malformed or invalid private field.", field?.location ?? declaration.location)
|
|
173
378
|
}
|
|
174
|
-
if (
|
|
175
|
-
|
|
379
|
+
if (normalizeOperations) field.id = expectedId
|
|
380
|
+
if (field.id != expectedId || typeof field.name != "string" || field.name.length == 0 || fieldNames.has(field.name)) {
|
|
381
|
+
fail("DUPLICATE_PRIVATE_FIELD", `Duplicate or invalid private field '${String(field.name)}'.`, roleLocation(field, "name"))
|
|
176
382
|
}
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
if (statement.kind == "PrintStatement") {
|
|
180
|
-
inferExpressionType(statement.expression, scope, functions, fail, normalizeOperations)
|
|
181
|
-
continue
|
|
383
|
+
fieldNames.add(field.name)
|
|
182
384
|
}
|
|
183
|
-
|
|
184
|
-
if (!returnType) fail("ILLEGAL_RETURN_CONTEXT", "Return statement outside a function.", statement.location)
|
|
185
|
-
const actualType = inferExpressionType(statement.expression, scope, functions, fail, normalizeOperations)
|
|
385
|
+
const invalidOwnedField = declaration.fields.find(({type}) => containsOwnedType(type) && type.kind != "OwnedResourceType")
|
|
186
386
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
alwaysReturns = true
|
|
191
|
-
continue
|
|
387
|
+
if (invalidOwnedField) {
|
|
388
|
+
fail("RESOURCE_ALIAS", `Class '${declaration.name}' may contain ownership only in one direct resource field.`, invalidOwnedField.location)
|
|
192
389
|
}
|
|
193
|
-
|
|
194
|
-
const conditionType = inferExpressionType(statement.condition, scope, functions, fail, normalizeOperations)
|
|
390
|
+
const ownedFields = declaration.fields.filter(({type}) => type?.kind == "OwnedResourceType")
|
|
195
391
|
|
|
196
|
-
|
|
197
|
-
|
|
392
|
+
if (ownedFields.length > 1) {
|
|
393
|
+
fail("RESOURCE_ALIAS", `Class '${declaration.name}' may own exactly one direct resource field.`, declaration.location)
|
|
394
|
+
}
|
|
395
|
+
if (ownedFields.length == 1) {
|
|
396
|
+
const field = /** @type {import("./types.js").PrivateField & {type: import("./types.js").OwnedResourceType}} */ (ownedFields[0])
|
|
397
|
+
const expected = {fieldId: /** @type {string} */ (field.id), kind: /** @type {const} */ ("ownedResource"),
|
|
398
|
+
resourceId: field.type.resourceId}
|
|
399
|
+
|
|
400
|
+
if (normalizeOperations) declaration.ownership = expected
|
|
401
|
+
else if (JSON.stringify(declaration.ownership) != JSON.stringify(expected)) {
|
|
402
|
+
fail("RESOURCE_TYPE_MISMATCH", `Class '${declaration.name}' has malformed resource ownership metadata.`, declaration.location)
|
|
198
403
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
404
|
+
} else if (declaration.ownership?.kind == "ownedResource") {
|
|
405
|
+
fail("RESOURCE_TYPE_MISMATCH", `Class '${declaration.name}' does not contain its declared resource field.`, declaration.location)
|
|
406
|
+
}
|
|
202
407
|
|
|
203
|
-
|
|
204
|
-
const alternateScope = createScope(scope, statement.alternate.statements)
|
|
408
|
+
const constructorId = `${declaration.id}:constructor`
|
|
205
409
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
410
|
+
if (normalizeOperations) declaration.constructor.id = constructorId
|
|
411
|
+
if (declaration.constructor.id != constructorId || !Array.isArray(declaration.constructor.parameters) ||
|
|
412
|
+
!declaration.constructor.body || declaration.constructor.body.kind != "Block") {
|
|
413
|
+
fail("DUPLICATE_CONSTRUCTOR", "Malformed or invalid constructor declaration.", declaration.constructor.location)
|
|
210
414
|
}
|
|
211
415
|
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
fail("UNSUPPORTED_STATEMENT", unexpected.kind, unexpected.location)
|
|
215
|
-
}
|
|
416
|
+
const methodNames = new Set()
|
|
216
417
|
|
|
217
|
-
|
|
218
|
-
|
|
418
|
+
for (let methodIndex = 0; methodIndex < declaration.methods.length; methodIndex += 1) {
|
|
419
|
+
const method = declaration.methods[methodIndex]
|
|
420
|
+
const expectedId = `${declaration.id}:method:${methodIndex}`
|
|
219
421
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
422
|
+
if (!method || method.kind != "MethodDeclaration") {
|
|
423
|
+
fail("DUPLICATE_METHOD", "Malformed or invalid method declaration.", method?.location ?? declaration.location)
|
|
424
|
+
}
|
|
425
|
+
if (normalizeOperations) method.id = expectedId
|
|
426
|
+
if (method.id != expectedId || typeof method.name != "string" || method.name.length == 0 || methodNames.has(method.name) ||
|
|
427
|
+
!Array.isArray(method.parameters) || !method.body || method.body.kind != "Block") {
|
|
428
|
+
fail("DUPLICATE_METHOD", `Duplicate or invalid method '${String(method.name)}'.`, roleLocation(method, "name"))
|
|
429
|
+
}
|
|
430
|
+
methodNames.add(method.name)
|
|
431
|
+
}
|
|
432
|
+
}
|
|
230
433
|
|
|
231
|
-
return
|
|
434
|
+
return classes
|
|
232
435
|
}
|
|
233
436
|
|
|
234
437
|
/**
|
|
235
|
-
*
|
|
236
|
-
* @param {
|
|
237
|
-
* @param {
|
|
238
|
-
* @param {
|
|
239
|
-
* @param {
|
|
438
|
+
* Validates exact construction, private state, and method bodies for one class.
|
|
439
|
+
* @param {import("./types.js").ClassDeclaration} declaration - Class declaration.
|
|
440
|
+
* @param {Map<string, import("./types.js").FunctionDeclaration>} functions - Function signatures.
|
|
441
|
+
* @param {RecordRegistry} records - Record and reference-class registries.
|
|
442
|
+
* @param {ErrorRegistry} errors - Error declarations.
|
|
443
|
+
* @param {Map<string, Set<string>>} callEffects - Function call effects.
|
|
444
|
+
* @param {Set<string>} reservedValueNames - Module-reserved names.
|
|
240
445
|
* @param {SemanticFail} fail - Diagnostic callback.
|
|
446
|
+
* @param {boolean} normalizeOperations - Whether parser-authored resolutions are assigned.
|
|
447
|
+
* @param {Map<string, object>} loopOwners - Loop ownership registry.
|
|
241
448
|
* @returns {void}
|
|
242
449
|
*/
|
|
243
|
-
function
|
|
244
|
-
|
|
450
|
+
function validateClassDeclaration(declaration, functions, records, errors, callEffects, reservedValueNames, fail,
|
|
451
|
+
normalizeOperations, loopOwners) {
|
|
452
|
+
for (const field of declaration.fields) {
|
|
453
|
+
validateValueTypeReference(field.type, field.location, fail, undefined, records)
|
|
454
|
+
}
|
|
245
455
|
|
|
246
|
-
|
|
247
|
-
|
|
456
|
+
const constructor = declaration.constructor
|
|
457
|
+
const acquisitionStatement = constructor.body.statements[0]
|
|
458
|
+
const acquisitionExpression = acquisitionStatement?.kind == "PrivateFieldWriteStatement"
|
|
459
|
+
? acquisitionStatement.expression : undefined
|
|
460
|
+
const ownedAcquisition = declaration.ownership?.kind == "ownedResource" && declaration.fields.length == 1 &&
|
|
461
|
+
constructor.body.statements.length == 1 && acquisitionStatement?.kind == "PrivateFieldWriteStatement" &&
|
|
462
|
+
acquisitionStatement.field == declaration.ownership.fieldId && acquisitionStatement.receiver.kind == "ReceiverExpression" &&
|
|
463
|
+
acquisitionStatement.receiver.classId == declaration.id && acquisitionExpression?.kind == "EffectCallExpression" &&
|
|
464
|
+
acquisitionExpression.resolution.resourceFlow.kind == "acquire" &&
|
|
465
|
+
acquisitionExpression.resolution.resourceFlow.resourceId == declaration.ownership.resourceId
|
|
466
|
+
|
|
467
|
+
if (syntacticBlockEffects(constructor.body, callEffects).size > 0 && !ownedAcquisition) {
|
|
468
|
+
fail("UNSUPPORTED_STATEMENT", "Reference constructors cannot expose unchecked-error effects in Task 033.", constructor.location)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (!ownedAcquisition &&
|
|
472
|
+
(constructor.parameters.length != declaration.fields.length || constructor.body.statements.length != declaration.fields.length)) {
|
|
473
|
+
fail("INCOMPLETE_INITIALIZATION", `Constructor for '${declaration.name}' must initialize every private field exactly once in declaration order.`,
|
|
474
|
+
constructor.location)
|
|
475
|
+
}
|
|
476
|
+
const constructorScope = createScope(undefined, constructor.body.statements, reservedValueNames, new Set(), declaration)
|
|
477
|
+
|
|
478
|
+
for (let index = 0; index < constructor.parameters.length; index += 1) {
|
|
479
|
+
const parameter = constructor.parameters[index]
|
|
480
|
+
const field = declaration.fields[index]
|
|
481
|
+
const type = validateValueTypeReference(parameter.type, parameter.location, fail, undefined, records)
|
|
482
|
+
|
|
483
|
+
if (!ownedAcquisition && !sameType(type, field.type)) {
|
|
484
|
+
fail("TYPE_MISMATCH", `Constructor parameter '${parameter.name}' must exactly match private field '${field.name}'.`, parameter.location)
|
|
485
|
+
}
|
|
486
|
+
declareBinding(parameter.name, {knownValue: undefined, mutable: false, type}, roleLocation(parameter, "name"), constructorScope, fail)
|
|
487
|
+
if (ownedAcquisition) continue
|
|
488
|
+
const statement = constructor.body.statements[index]
|
|
489
|
+
const initializer = statement?.kind == "PrivateFieldWriteStatement"
|
|
490
|
+
? statement.expression.kind == "OwnedMoveExpression" ? statement.expression.expression : statement.expression
|
|
491
|
+
: undefined
|
|
492
|
+
|
|
493
|
+
if (!statement || statement.kind != "PrivateFieldWriteStatement" || statement.field != field.id ||
|
|
494
|
+
statement.receiver?.kind != "ReceiverExpression" || statement.receiver.classId != declaration.id ||
|
|
495
|
+
initializer?.kind != "IdentifierExpression" || initializer.name != parameter.name) {
|
|
496
|
+
fail("INCOMPLETE_INITIALIZATION", `Constructor for '${declaration.name}' must initialize '${field.name}' from its corresponding parameter.`,
|
|
497
|
+
statement?.location ?? constructor.location)
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
validateBlock(constructor.body, constructorScope, {kind: "TypeReference", name: "void"}, functions, records,
|
|
501
|
+
errors, callEffects, fail, normalizeOperations, {owner: constructor, owners: loopOwners})
|
|
502
|
+
|
|
503
|
+
for (const method of declaration.methods) {
|
|
504
|
+
const unsupportedMethodEffects = [...syntacticBlockEffects(method.body, callEffects)]
|
|
505
|
+
.filter((id) => !/^(?:module:[0-9]+\/)?capability:[0-9]+\/failure:[0-9]+$/u.test(id))
|
|
506
|
+
|
|
507
|
+
if (declaration.ownership?.kind != "ownedResource" && unsupportedMethodEffects.length > 0) {
|
|
508
|
+
fail("UNSUPPORTED_STATEMENT", `Reference method '${method.name}' cannot expose unchecked-error effects in Task 033.`, method.location)
|
|
509
|
+
}
|
|
510
|
+
const scope = createScope(undefined, method.body.statements, reservedValueNames, new Set(), declaration)
|
|
511
|
+
|
|
512
|
+
for (const parameter of method.parameters) {
|
|
513
|
+
const type = validateValueTypeReference(parameter.type, parameter.location, fail, undefined, records)
|
|
514
|
+
|
|
515
|
+
declareBinding(parameter.name, {knownValue: undefined, mutable: false, type}, roleLocation(parameter, "name"), scope, fail)
|
|
516
|
+
}
|
|
517
|
+
const returnType = validateReturnTypeReference(method.returnType, method.location, fail, records)
|
|
518
|
+
const flow = validateBlock(method.body, scope, returnType, functions, records, errors, callEffects, fail,
|
|
519
|
+
normalizeOperations, {owner: method, owners: loopOwners})
|
|
520
|
+
|
|
521
|
+
if (declaration.ownership?.kind == "ownedResource") ownedMethodResourceFlow(declaration, method, fail)
|
|
522
|
+
|
|
523
|
+
if (!isVoidType(returnType) && flow.normal) {
|
|
524
|
+
fail("MISSING_RETURN", `Method '${method.name}' does not return on every reachable path.`, method.location)
|
|
525
|
+
}
|
|
526
|
+
}
|
|
248
527
|
}
|
|
249
528
|
|
|
250
529
|
/**
|
|
251
|
-
*
|
|
252
|
-
* @param {
|
|
253
|
-
* @param {import("./types.js").SourceLocation} location - Reference location.
|
|
254
|
-
* @param {Scope} scope - Current scope.
|
|
530
|
+
* Validates nominal unchecked-error declarations.
|
|
531
|
+
* @param {unknown} declarations - Candidate declarations.
|
|
255
532
|
* @param {SemanticFail} fail - Diagnostic callback.
|
|
256
|
-
* @
|
|
533
|
+
* @param {boolean} normalizeOperations - Whether parser-authored identities are assigned.
|
|
534
|
+
* @param {Map<string, import("./types.js").ErrorDeclaration>} [visibleErrors] - Imported error declarations by identity.
|
|
535
|
+
* @returns {ErrorRegistry} Validated declarations by identity.
|
|
257
536
|
*/
|
|
258
|
-
function
|
|
259
|
-
|
|
260
|
-
|
|
537
|
+
function validateErrorDeclarations(declarations, fail, normalizeOperations, visibleErrors = new Map()) {
|
|
538
|
+
if (!Array.isArray(declarations)) return fail("TYPE_MISMATCH", "Error declarations must be an ordered array.", /** @type {never} */ (undefined))
|
|
539
|
+
/** @type {ErrorRegistry} */
|
|
540
|
+
const errors = new Map(visibleErrors)
|
|
541
|
+
const names = new Set()
|
|
261
542
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
543
|
+
for (let index = 0; index < declarations.length; index += 1) {
|
|
544
|
+
const declaration = declarations[index]
|
|
545
|
+
|
|
546
|
+
if (!declaration || declaration.kind != "ErrorDeclaration") {
|
|
547
|
+
return fail("TYPE_MISMATCH", "Malformed error declaration.", declaration?.location)
|
|
548
|
+
}
|
|
549
|
+
if (normalizeOperations) declaration.id = `error:${index}`
|
|
550
|
+
if (typeof declaration.id != "string" || !/^(?:[a-z][a-z0-9._-]*#)?error:[0-9]+$/u.test(declaration.id) || errors.has(declaration.id)) {
|
|
551
|
+
fail("DUPLICATE_ERROR", "Duplicate or invalid error declaration identity.", declaration.location)
|
|
552
|
+
}
|
|
553
|
+
if (typeof declaration.name != "string" || names.has(declaration.name)) {
|
|
554
|
+
fail("DUPLICATE_ERROR", `Duplicate or invalid error '${String(declaration.name)}'.`, roleLocation(declaration, "name"))
|
|
265
555
|
}
|
|
556
|
+
names.add(declaration.name)
|
|
557
|
+
errors.set(declaration.id, declaration)
|
|
266
558
|
}
|
|
267
559
|
|
|
268
|
-
return
|
|
560
|
+
return errors
|
|
269
561
|
}
|
|
270
562
|
|
|
271
563
|
/**
|
|
272
|
-
*
|
|
273
|
-
* @param {
|
|
274
|
-
* @param {import("./types.js").SourceLocation} location - Owning source location.
|
|
564
|
+
* Assigns and validates one declaration's invariant unbounded type parameters.
|
|
565
|
+
* @param {import("./types.js").FunctionDeclaration | import("./types.js").RecordDeclaration} declaration - Owning declaration.
|
|
275
566
|
* @param {SemanticFail} fail - Diagnostic callback.
|
|
276
|
-
* @
|
|
567
|
+
* @param {boolean} normalizeOperations - Whether parser-authored identities are assigned.
|
|
568
|
+
* @returns {void}
|
|
277
569
|
*/
|
|
278
|
-
function
|
|
279
|
-
if (
|
|
280
|
-
|
|
570
|
+
function validateTypeParameters(declaration, fail, normalizeOperations) {
|
|
571
|
+
if (declaration.typeParameters === undefined) return
|
|
572
|
+
if (!Array.isArray(declaration.typeParameters)) {
|
|
573
|
+
fail("TYPE_MISMATCH", "Type parameters must be a dense ordered array.", declaration.location)
|
|
281
574
|
}
|
|
575
|
+
const names = new Set()
|
|
576
|
+
|
|
577
|
+
for (let index = 0; index < declaration.typeParameters.length; index += 1) {
|
|
578
|
+
const parameter = declaration.typeParameters[index]
|
|
579
|
+
const keys = parameter && typeof parameter == "object" && !Array.isArray(parameter)
|
|
580
|
+
? Object.keys(parameter).filter((key) => key != "sourceProvenance").sort().join(",") : ""
|
|
282
581
|
|
|
283
|
-
|
|
582
|
+
if (!Object.hasOwn(declaration.typeParameters, index) || !parameter || parameter.kind != "TypeParameter" ||
|
|
583
|
+
keys != "id,kind,location,name" || typeof parameter.name != "string" || parameter.name.length == 0) {
|
|
584
|
+
fail("TYPE_MISMATCH", "Malformed type parameter declaration.", parameter?.location ?? declaration.location)
|
|
585
|
+
}
|
|
586
|
+
if (names.has(parameter.name)) {
|
|
587
|
+
fail("DUPLICATE_TYPE_PARAMETER", `Duplicate type parameter '${parameter.name}'.`, roleLocation(parameter, "name"))
|
|
588
|
+
}
|
|
589
|
+
names.add(parameter.name)
|
|
590
|
+
const expectedId = `${declaration.id}:type:${index}`
|
|
284
591
|
|
|
285
|
-
|
|
286
|
-
|
|
592
|
+
if (normalizeOperations) parameter.id = expectedId
|
|
593
|
+
if (parameter.id != expectedId) {
|
|
594
|
+
fail("DUPLICATE_TYPE_PARAMETER", "Duplicate or invalid type parameter identity.", parameter.location)
|
|
595
|
+
}
|
|
287
596
|
}
|
|
597
|
+
}
|
|
288
598
|
|
|
289
|
-
|
|
599
|
+
/**
|
|
600
|
+
* Returns the closed identity scope owned by one validated declaration.
|
|
601
|
+
* @param {import("./types.js").FunctionDeclaration | import("./types.js").RecordDeclaration} declaration - Declaration.
|
|
602
|
+
* @returns {Set<string>} Type-parameter identities.
|
|
603
|
+
*/
|
|
604
|
+
function declarationTypeParameterIds(declaration) {
|
|
605
|
+
return new Set((declaration.typeParameters ?? []).map(({id}) => /** @type {string} */ (id)))
|
|
290
606
|
}
|
|
291
607
|
|
|
292
608
|
/**
|
|
293
|
-
*
|
|
294
|
-
* @param {
|
|
295
|
-
* @param {Scope} scope - Visible lexical scope.
|
|
296
|
-
* @param {Map<string, import("./types.js").FunctionDeclaration>} functions - Module function signatures.
|
|
609
|
+
* Validates nominal declarations and returns their stable identity registry.
|
|
610
|
+
* @param {unknown} declarations - Candidate ordered declarations.
|
|
297
611
|
* @param {SemanticFail} fail - Diagnostic callback.
|
|
298
|
-
* @param {boolean} normalizeOperations - Whether
|
|
299
|
-
* @
|
|
612
|
+
* @param {boolean} normalizeOperations - Whether parser-authored identities are assigned.
|
|
613
|
+
* @param {Map<string, import("./types.js").RecordDeclaration>} [visibleRecords] - Imported record declarations by identity.
|
|
614
|
+
* @param {ClassRegistry} [classes] - Module reference classes by identity.
|
|
615
|
+
* @returns {RecordRegistry} Validated declarations by identity.
|
|
300
616
|
*/
|
|
301
|
-
function
|
|
302
|
-
if (
|
|
617
|
+
function validateRecordDeclarations(declarations, fail, normalizeOperations, visibleRecords = new Map(), classes = new Map()) {
|
|
618
|
+
if (!Array.isArray(declarations)) return fail("TYPE_MISMATCH", "Record declarations must be an ordered array.", /** @type {never} */ (undefined))
|
|
619
|
+
/** @type {RecordRegistry} */
|
|
620
|
+
const records = new Map(visibleRecords)
|
|
303
621
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
return "integer"
|
|
307
|
-
}
|
|
308
|
-
if (expression.kind == "BooleanLiteral") {
|
|
309
|
-
if (typeof Reflect.get(expression, "value") != "boolean") {
|
|
310
|
-
fail("TYPE_MISMATCH", "Invalid boolean literal value.", expression.location)
|
|
311
|
-
}
|
|
312
|
-
return "boolean"
|
|
313
|
-
}
|
|
314
|
-
if (expression.kind == "StringLiteral") {
|
|
315
|
-
const value = Reflect.get(expression, "value")
|
|
622
|
+
referenceClassesByRecordRegistry.set(records, classes)
|
|
623
|
+
const names = new Set()
|
|
316
624
|
|
|
317
|
-
|
|
318
|
-
|
|
625
|
+
for (let recordIndex = 0; recordIndex < declarations.length; recordIndex += 1) {
|
|
626
|
+
const declaration = declarations[recordIndex]
|
|
627
|
+
|
|
628
|
+
if (!declaration || declaration.kind != "RecordDeclaration" || !Array.isArray(declaration.fields)) {
|
|
629
|
+
return fail("TYPE_MISMATCH", "Malformed record declaration.", declaration?.location)
|
|
630
|
+
}
|
|
631
|
+
if (normalizeOperations) declaration.id = `record:${recordIndex}`
|
|
632
|
+
if (typeof declaration.id != "string" || !/^(?:[a-z][a-z0-9._-]*#)?record:[0-9]+$/u.test(declaration.id) || records.has(declaration.id)) {
|
|
633
|
+
fail("DUPLICATE_RECORD", "Duplicate or invalid record declaration identity.", declaration.location)
|
|
319
634
|
}
|
|
320
|
-
|
|
635
|
+
if (names.has(declaration.name)) {
|
|
636
|
+
fail("DUPLICATE_RECORD", `Duplicate record '${declaration.name}'.`, roleLocation(declaration, "name"))
|
|
637
|
+
}
|
|
638
|
+
names.add(declaration.name)
|
|
639
|
+
validateTypeParameters(declaration, fail, normalizeOperations)
|
|
640
|
+
records.set(declaration.id, declaration)
|
|
321
641
|
}
|
|
322
|
-
if (expression.kind == "CallExpression") {
|
|
323
|
-
const functionDeclaration = functions.get(expression.callee)
|
|
324
642
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
for (let index = 0; index < expression.arguments.length; index++) {
|
|
330
|
-
const argument = expression.arguments[index]
|
|
331
|
-
const actualType = inferExpressionType(argument, scope, functions, fail, normalizeOperations)
|
|
332
|
-
const expectedType = validateTypeReference(functionDeclaration.parameters[index].type, functionDeclaration.parameters[index].location, fail)
|
|
643
|
+
for (const declaration of declarations) {
|
|
644
|
+
const fieldNames = new Set()
|
|
645
|
+
const typeParameters = declarationTypeParameterIds(declaration)
|
|
333
646
|
|
|
334
|
-
|
|
335
|
-
|
|
647
|
+
for (let fieldIndex = 0; fieldIndex < declaration.fields.length; fieldIndex += 1) {
|
|
648
|
+
const field = declaration.fields[fieldIndex]
|
|
649
|
+
|
|
650
|
+
if (!field || field.kind != "RecordField") fail("TYPE_MISMATCH", "Malformed record field.", field?.location ?? declaration.location)
|
|
651
|
+
if (normalizeOperations) field.id = `${declaration.id}:field:${fieldIndex}`
|
|
652
|
+
if (typeof field.id != "string" || field.id != `${declaration.id}:field:${fieldIndex}`) {
|
|
653
|
+
fail("DUPLICATE_FIELD", "Duplicate or invalid record field identity.", field.location)
|
|
654
|
+
}
|
|
655
|
+
if (fieldNames.has(field.name)) fail("DUPLICATE_FIELD", `Duplicate field '${field.name}'.`, roleLocation(field, "name"))
|
|
656
|
+
fieldNames.add(field.name)
|
|
657
|
+
if (containsOwnedType(field.type)) {
|
|
658
|
+
fail("RESOURCE_ALIAS", "Owned resources cannot be stored in immutable records.", field.location)
|
|
336
659
|
}
|
|
660
|
+
validateValueTypeReference(field.type, field.location, fail, undefined, records, typeParameters)
|
|
337
661
|
}
|
|
338
|
-
return validateTypeReference(functionDeclaration.returnType, functionDeclaration.location, fail)
|
|
339
662
|
}
|
|
340
|
-
|
|
341
|
-
const operandType = inferExpressionType(expression.operand, scope, functions, fail, normalizeOperations)
|
|
342
|
-
const operation = normalizeOperations
|
|
343
|
-
? normalizeUnaryOperation(adaptedOperationFor(expression), operandType, expression.operand.location, fail)
|
|
344
|
-
: expression.operation
|
|
663
|
+
validateDirectRecordRecursion(declarations, records, fail)
|
|
345
664
|
|
|
346
|
-
|
|
665
|
+
return records
|
|
666
|
+
}
|
|
347
667
|
|
|
348
|
-
|
|
668
|
+
/**
|
|
669
|
+
* Rejects cycles composed solely of unmediated record fields.
|
|
670
|
+
* @param {import("./types.js").RecordDeclaration[]} declarations - Valid declarations.
|
|
671
|
+
* @param {RecordRegistry} records - Declarations by identity.
|
|
672
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
673
|
+
* @returns {void}
|
|
674
|
+
*/
|
|
675
|
+
function validateDirectRecordRecursion(declarations, records, fail) {
|
|
676
|
+
const complete = new Set()
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Visits one declaration along an unmediated record path.
|
|
680
|
+
* @param {import("./types.js").RecordDeclaration} declaration - Current declaration.
|
|
681
|
+
* @param {Set<string>} active - Declaration identities active on this path.
|
|
682
|
+
* @returns {void}
|
|
683
|
+
*/
|
|
684
|
+
function visit(declaration, active) {
|
|
685
|
+
if (complete.has(/** @type {string} */ (declaration.id))) return
|
|
686
|
+
const next = new Set(active)
|
|
687
|
+
|
|
688
|
+
next.add(/** @type {string} */ (declaration.id))
|
|
689
|
+
for (const field of declaration.fields) {
|
|
690
|
+
if (field.type.kind != "RecordType") continue
|
|
691
|
+
if (next.has(field.type.declarationId)) {
|
|
692
|
+
fail("ILLEGAL_RECORD_RECURSION", `Direct value-recursive field '${field.name}'.`, typeLocation(field.type, field.location))
|
|
693
|
+
}
|
|
694
|
+
const referenced = records.get(field.type.declarationId)
|
|
349
695
|
|
|
350
|
-
|
|
351
|
-
if (operandType != signature.operand) {
|
|
352
|
-
return fail("INVALID_OPERAND_TYPE", `${operation} requires ${signature.operand}; received ${operandType}.`, expression.operand.location)
|
|
353
|
-
}
|
|
354
|
-
if (expression.type != signature.result) {
|
|
355
|
-
return fail("TYPE_MISMATCH", `${operation} result type ${String(expression.type)}; expected ${signature.result}.`, expression.location)
|
|
696
|
+
if (referenced) visit(referenced, next)
|
|
356
697
|
}
|
|
357
|
-
|
|
698
|
+
complete.add(/** @type {string} */ (declaration.id))
|
|
358
699
|
}
|
|
359
|
-
if (expression.kind == "BinaryExpression") {
|
|
360
|
-
const leftType = inferExpressionType(expression.left, scope, functions, fail, normalizeOperations)
|
|
361
|
-
const rightType = inferExpressionType(expression.right, scope, functions, fail, normalizeOperations)
|
|
362
|
-
const operation = normalizeOperations
|
|
363
|
-
? normalizeBinaryOperation(adaptedOperationFor(expression), leftType, rightType, expression.left.location, expression.right.location, expression.location, fail)
|
|
364
|
-
: expression.operation
|
|
365
700
|
|
|
366
|
-
|
|
701
|
+
for (const declaration of declarations) visit(declaration, new Set())
|
|
702
|
+
}
|
|
367
703
|
|
|
368
|
-
|
|
704
|
+
/**
|
|
705
|
+
* Validates one non-void function and its complete control flow.
|
|
706
|
+
* @param {import("./types.js").FunctionDeclaration} declaration - Function declaration.
|
|
707
|
+
* @param {Map<string, import("./types.js").FunctionDeclaration>} functions - Function signatures.
|
|
708
|
+
* @param {RecordRegistry} records - Record declarations by identity.
|
|
709
|
+
* @param {ErrorRegistry} errors - Error declarations by identity.
|
|
710
|
+
* @param {Map<string, Set<string>>} callEffects - Internally inferred unchecked-error effects.
|
|
711
|
+
* @param {Set<string>} reservedValueNames - Module functions and nominal constructors unavailable to lexical bindings.
|
|
712
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
713
|
+
* @param {boolean} normalizeOperations - Whether to replace transient frontend operation intent.
|
|
714
|
+
* @param {Map<string, object>} loopOwners - Loop owner by module-local identity.
|
|
715
|
+
* @returns {void}
|
|
716
|
+
*/
|
|
717
|
+
function validateFunction(declaration, functions, records, errors, callEffects, reservedValueNames, fail, normalizeOperations, loopOwners) {
|
|
718
|
+
const typeParameters = declarationTypeParameterIds(declaration)
|
|
719
|
+
const scope = createScope(undefined, declaration.body.statements, reservedValueNames, typeParameters)
|
|
369
720
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
if (rightType != signature.right) {
|
|
375
|
-
return fail("INVALID_OPERAND_TYPE", `${operation} requires ${signature.right} right operand; received ${rightType}.`, expression.right.location)
|
|
376
|
-
}
|
|
377
|
-
if (expression.type != signature.result) {
|
|
378
|
-
return fail("TYPE_MISMATCH", `${operation} result type ${String(expression.type)}; expected ${signature.result}.`, expression.location)
|
|
379
|
-
}
|
|
380
|
-
return signature.result
|
|
721
|
+
for (const parameter of declaration.parameters) {
|
|
722
|
+
const type = validateValueTypeReference(parameter.type, parameter.location, fail, undefined, records, typeParameters)
|
|
723
|
+
|
|
724
|
+
declareBinding(parameter.name, {knownValue: undefined, mutable: false, type}, roleLocation(parameter, "name"), scope, fail)
|
|
381
725
|
}
|
|
382
726
|
|
|
383
|
-
const
|
|
727
|
+
const returnType = validateReturnTypeReference(declaration.returnType, declaration.location, fail, records, typeParameters)
|
|
728
|
+
const returns = validateBlock(declaration.body, scope, returnType, functions, records, errors, callEffects, fail,
|
|
729
|
+
normalizeOperations, {owner: declaration, owners: loopOwners})
|
|
384
730
|
|
|
385
|
-
|
|
731
|
+
if (!isVoidType(returnType) && returns.normal) {
|
|
732
|
+
fail("MISSING_RETURN", `Function '${declaration.name}' does not return on every reachable path.`, declaration.location)
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Validates one ordered block and reports whether every path returns.
|
|
738
|
+
* @param {import("./types.js").Block} block - Semantic block.
|
|
739
|
+
* @param {Scope} scope - Scope belonging to this block.
|
|
740
|
+
* @param {import("./types.js").SemanticFunctionReturnType | undefined} returnType - Function return type, absent for entry points.
|
|
741
|
+
* @param {Map<string, import("./types.js").FunctionDeclaration>} functions - Function signatures.
|
|
742
|
+
* @param {RecordRegistry} records - Record declarations by identity.
|
|
743
|
+
* @param {ErrorRegistry} errors - Error declarations by identity.
|
|
744
|
+
* @param {Map<string, Set<string>>} callEffects - Internally inferred unchecked-error effects.
|
|
745
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
746
|
+
* @param {boolean} normalizeOperations - Whether to replace transient frontend operation intent.
|
|
747
|
+
* @param {LoopFlowContext} loopContext - Active loop and owner identities.
|
|
748
|
+
* @returns {{normal: boolean, raises: Set<string>}} Reachable normal continuation and escaping error identities.
|
|
749
|
+
*/
|
|
750
|
+
function validateBlock(block, scope, returnType, functions, records, errors, callEffects, fail, normalizeOperations, loopContext) {
|
|
751
|
+
let normal = true
|
|
752
|
+
const raises = new Set()
|
|
753
|
+
|
|
754
|
+
for (const statement of block.statements) {
|
|
755
|
+
if (!normal) fail("UNREACHABLE_STATEMENT", "Statement is unreachable.", statement.location)
|
|
756
|
+
|
|
757
|
+
if (statement.kind == "LocalDeclaration") {
|
|
758
|
+
const declaredType = validateValueTypeReference(statement.type, statement.location, fail, undefined, records, scope.typeParameters)
|
|
759
|
+
statement.initializer = normalizeOwnedMove(statement.initializer, declaredType, normalizeOperations, fail)
|
|
760
|
+
const initializerType = inferValueExpressionType(
|
|
761
|
+
statement.initializer, scope, functions, records, fail, normalizeOperations, "an initializer", declaredType
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
scope.pending.delete(statement.name)
|
|
765
|
+
if (!sameType(initializerType, declaredType)) {
|
|
766
|
+
if (initializerType.kind == "OptionalType" && declaredType.kind != "OptionalType") {
|
|
767
|
+
fail("FAILURE_ABSENCE_CONFLATION", "Optional absence cannot be coerced into a value or typed failure.", statement.initializer.location)
|
|
768
|
+
}
|
|
769
|
+
fail("TYPE_MISMATCH", `Initializer type ${typeDescription(initializerType)}; expected ${typeDescription(declaredType)}.`, statement.initializer.location)
|
|
770
|
+
}
|
|
771
|
+
declareBinding(statement.name, {
|
|
772
|
+
knownValue: knownValueForExpression(statement.initializer, scope),
|
|
773
|
+
mutable: statement.mutable,
|
|
774
|
+
type: declaredType
|
|
775
|
+
}, roleLocation(statement, "name"), scope, fail)
|
|
776
|
+
addExpressionEffects(raises, statement.initializer, callEffects)
|
|
777
|
+
continue
|
|
778
|
+
}
|
|
779
|
+
if (statement.kind == "AssignmentStatement") {
|
|
780
|
+
const binding = resolveBinding(statement.target.name, statement.target.location, scope, fail)
|
|
781
|
+
|
|
782
|
+
if (binding.type.kind == "ErrorType") {
|
|
783
|
+
fail("IMMUTABLE_ASSIGNMENT", `Cannot assign to immutable catch binding '${statement.target.name}'.`, statement.target.location)
|
|
784
|
+
}
|
|
785
|
+
if (binding.type.kind == "OwnedResourceType" || binding.type.kind == "OwnedReferenceType") {
|
|
786
|
+
fail("RESOURCE_ALIAS", "Owned resources cannot be reassigned or aliased.", statement.location)
|
|
787
|
+
}
|
|
788
|
+
const expressionType = inferValueExpressionType(
|
|
789
|
+
statement.expression, scope, functions, records, fail, normalizeOperations, "an assignment", binding.type
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
if (!binding.mutable) {
|
|
793
|
+
fail("IMMUTABLE_ASSIGNMENT", `Cannot assign to immutable binding '${statement.target.name}'.`, statement.target.location)
|
|
794
|
+
}
|
|
795
|
+
if (!sameType(expressionType, binding.type)) {
|
|
796
|
+
fail("TYPE_MISMATCH", `Assignment type ${typeDescription(expressionType)}; expected ${typeDescription(binding.type)}.`, statement.expression.location)
|
|
797
|
+
}
|
|
798
|
+
binding.knownValue = knownValueForExpression(statement.expression, scope)
|
|
799
|
+
scope.presenceProofs.delete(binding)
|
|
800
|
+
addExpressionEffects(raises, statement.expression, callEffects)
|
|
801
|
+
continue
|
|
802
|
+
}
|
|
803
|
+
if (statement.kind == "PrivateFieldWriteStatement") {
|
|
804
|
+
const declaration = scope.currentClass
|
|
805
|
+
|
|
806
|
+
if (!declaration || !statement.receiver || statement.receiver.kind != "ReceiverExpression" ||
|
|
807
|
+
statement.receiver.classId != declaration.id) {
|
|
808
|
+
fail("ILLEGAL_PRIVATE_ACCESS", "Private field receiver must be the declaring class receiver.", statement.location)
|
|
809
|
+
}
|
|
810
|
+
const field = declaration.fields.find((candidate) => candidate.id == statement.field)
|
|
811
|
+
|
|
812
|
+
if (!field) fail("ILLEGAL_PRIVATE_ACCESS", "Private field identity is not owned by the declaring class.", statement.location)
|
|
813
|
+
statement.expression = normalizeOwnedMove(statement.expression, field.type, normalizeOperations, fail)
|
|
814
|
+
const expressionType = inferValueExpressionType(
|
|
815
|
+
statement.expression, scope, functions, records, fail, normalizeOperations, "a private field assignment", field.type
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
if (!sameType(expressionType, field.type)) {
|
|
819
|
+
fail("TYPE_MISMATCH", `Private field assignment type ${typeDescription(expressionType)}; expected ${typeDescription(field.type)}.`,
|
|
820
|
+
statement.expression.location)
|
|
821
|
+
}
|
|
822
|
+
addExpressionEffects(raises, statement.expression, callEffects)
|
|
823
|
+
continue
|
|
824
|
+
}
|
|
825
|
+
if (statement.kind == "PrintStatement") {
|
|
826
|
+
const printedType = inferValueExpressionType(statement.expression, scope, functions, records, fail, normalizeOperations, "a print value")
|
|
827
|
+
|
|
828
|
+
if (printedType.kind == "OwnedResourceType" || printedType.kind == "OwnedReferenceType") {
|
|
829
|
+
fail("BORROW_ESCAPE", "Owned values cannot escape through printing or serialization.", statement.expression.location)
|
|
830
|
+
}
|
|
831
|
+
if (printedType.kind != "TypeReference") {
|
|
832
|
+
fail("TYPE_MISMATCH", "Collections and records cannot be printed directly.", statement.expression.location)
|
|
833
|
+
}
|
|
834
|
+
addExpressionEffects(raises, statement.expression, callEffects)
|
|
835
|
+
continue
|
|
836
|
+
}
|
|
837
|
+
if (statement.kind == "ExpressionStatement") {
|
|
838
|
+
if (!statement.expression || !["CallExpression", "EffectCallExpression", "MethodCallExpression"].includes(statement.expression.kind)) {
|
|
839
|
+
fail("TYPE_MISMATCH", "Expression statement must contain a direct or receiver call.", statement.location)
|
|
840
|
+
}
|
|
841
|
+
const expressionType = inferExpressionType(statement.expression, scope, functions, records, fail, normalizeOperations)
|
|
842
|
+
|
|
843
|
+
if (expressionType != "void") {
|
|
844
|
+
fail("TYPE_MISMATCH", "Expression statements may contain only void calls.", statement.expression.location)
|
|
845
|
+
}
|
|
846
|
+
addExpressionEffects(raises, statement.expression, callEffects)
|
|
847
|
+
continue
|
|
848
|
+
}
|
|
849
|
+
if (statement.kind == "BreakStatement" || statement.kind == "ContinueStatement") {
|
|
850
|
+
const control = statement.kind == "BreakStatement" ? "break" : "continue"
|
|
851
|
+
|
|
852
|
+
if (!loopContext.nearest) {
|
|
853
|
+
if (typeof statement.targetLoopId == "string" && loopContext.owners.has(statement.targetLoopId) &&
|
|
854
|
+
loopContext.owners.get(statement.targetLoopId) !== loopContext.owner) {
|
|
855
|
+
fail("CROSS_FUNCTION_LOOP_TARGET", `${control[0].toUpperCase()}${control.slice(1)} target crosses a function boundary.`, statement.location)
|
|
856
|
+
}
|
|
857
|
+
fail(`ILLEGAL_${control.toUpperCase()}_CONTEXT`, `${control[0].toUpperCase()}${control.slice(1)} statement outside a loop.`, statement.location)
|
|
858
|
+
}
|
|
859
|
+
if (typeof statement.targetLoopId != "string" || !loopContext.owners.has(statement.targetLoopId)) {
|
|
860
|
+
fail("INVALID_LOOP_TARGET", `${control[0].toUpperCase()}${control.slice(1)} has an invalid loop target identity.`, statement.location)
|
|
861
|
+
}
|
|
862
|
+
if (loopContext.owners.get(statement.targetLoopId) !== loopContext.owner) {
|
|
863
|
+
fail("CROSS_FUNCTION_LOOP_TARGET", `${control[0].toUpperCase()}${control.slice(1)} target crosses a function boundary.`, statement.location)
|
|
864
|
+
}
|
|
865
|
+
if (statement.targetLoopId != loopContext.nearest.id) {
|
|
866
|
+
fail("INVALID_LOOP_TARGET", `${control[0].toUpperCase()}${control.slice(1)} must target the nearest enclosing loop.`, statement.location)
|
|
867
|
+
}
|
|
868
|
+
normal = false
|
|
869
|
+
continue
|
|
870
|
+
}
|
|
871
|
+
if (statement.kind == "ReturnStatement") {
|
|
872
|
+
if (!returnType) fail("ILLEGAL_RETURN_CONTEXT", "Return statement outside a function.", statement.location)
|
|
873
|
+
if (isVoidType(returnType)) {
|
|
874
|
+
if (statement.expression) {
|
|
875
|
+
fail("VOID_RETURN_VALUE", "Void function cannot return a value.", statement.expression.location)
|
|
876
|
+
}
|
|
877
|
+
} else {
|
|
878
|
+
if (!statement.expression) {
|
|
879
|
+
fail("MISSING_RETURN_VALUE", `Function return requires a ${typeDescription(returnType)} value.`, statement.location)
|
|
880
|
+
}
|
|
881
|
+
statement.expression = normalizeOwnedMove(statement.expression, returnType, normalizeOperations, fail)
|
|
882
|
+
const actualType = inferValueExpressionType(
|
|
883
|
+
statement.expression,
|
|
884
|
+
scope,
|
|
885
|
+
functions,
|
|
886
|
+
records,
|
|
887
|
+
fail,
|
|
888
|
+
normalizeOperations,
|
|
889
|
+
"a returned value",
|
|
890
|
+
returnType
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
if (!sameType(actualType, returnType)) {
|
|
894
|
+
fail("TYPE_MISMATCH", `Return type ${typeDescription(actualType)}; expected ${typeDescription(returnType)}.`, statement.expression.location)
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
if (statement.expression) addExpressionEffects(raises, statement.expression, callEffects)
|
|
898
|
+
normal = false
|
|
899
|
+
continue
|
|
900
|
+
}
|
|
901
|
+
if (statement.kind == "IfStatement") {
|
|
902
|
+
const conditionType = inferValueExpressionType(statement.condition, scope, functions, records, fail, normalizeOperations, "a condition")
|
|
903
|
+
|
|
904
|
+
if (!isScalarType(conditionType, "boolean")) {
|
|
905
|
+
fail("NON_BOOLEAN_CONDITION", `If condition type ${typeDescription(conditionType)}; expected boolean.`, statement.condition.location)
|
|
906
|
+
}
|
|
907
|
+
const visibleBindings = bindingsVisibleFrom(scope)
|
|
908
|
+
const initialKnownValues = visibleBindings.map((binding) => binding.knownValue)
|
|
909
|
+
const consequentScope = createScope(scope, statement.consequent.statements)
|
|
910
|
+
const alternateScope = createScope(scope, statement.alternate?.statements ?? [])
|
|
911
|
+
applyPresenceNarrowing(statement.condition, scope, consequentScope, true)
|
|
912
|
+
applyPresenceNarrowing(statement.condition, scope, alternateScope, false)
|
|
913
|
+
const consequentReturns = validateBlock(statement.consequent, consequentScope, returnType, functions, records, errors, callEffects, fail, normalizeOperations, loopContext)
|
|
914
|
+
const consequentKnownValues = visibleBindings.map((binding) => binding.knownValue)
|
|
915
|
+
|
|
916
|
+
visibleBindings.forEach((binding, index) => {
|
|
917
|
+
binding.knownValue = initialKnownValues[index]
|
|
918
|
+
})
|
|
919
|
+
let alternateReturns = {normal: true, raises: new Set()}
|
|
920
|
+
|
|
921
|
+
if (statement.alternate) {
|
|
922
|
+
alternateReturns = validateBlock(statement.alternate, alternateScope, returnType, functions, records, errors, callEffects, fail, normalizeOperations, loopContext)
|
|
923
|
+
}
|
|
924
|
+
const alternateKnownValues = visibleBindings.map((binding) => binding.knownValue)
|
|
925
|
+
|
|
926
|
+
visibleBindings.forEach((binding, index) => {
|
|
927
|
+
if (!consequentReturns.normal && alternateReturns.normal) binding.knownValue = alternateKnownValues[index]
|
|
928
|
+
else if (consequentReturns.normal && !alternateReturns.normal) binding.knownValue = consequentKnownValues[index]
|
|
929
|
+
else if (consequentReturns.normal && alternateReturns.normal && consequentKnownValues[index] === alternateKnownValues[index]) {
|
|
930
|
+
binding.knownValue = consequentKnownValues[index]
|
|
931
|
+
} else binding.knownValue = undefined
|
|
932
|
+
})
|
|
933
|
+
const continuingProofs = !consequentReturns.normal && alternateReturns.normal
|
|
934
|
+
? alternateScope.presenceProofs
|
|
935
|
+
: consequentReturns.normal && !alternateReturns.normal
|
|
936
|
+
? consequentScope.presenceProofs
|
|
937
|
+
: consequentReturns.normal && alternateReturns.normal
|
|
938
|
+
? new Set([...consequentScope.presenceProofs].filter((binding) => alternateScope.presenceProofs.has(binding)))
|
|
939
|
+
: new Set()
|
|
940
|
+
const visibleSet = new Set(visibleBindings)
|
|
941
|
+
|
|
942
|
+
scope.presenceProofs.clear()
|
|
943
|
+
for (const binding of continuingProofs) {
|
|
944
|
+
if (visibleSet.has(binding)) scope.presenceProofs.add(binding)
|
|
945
|
+
}
|
|
946
|
+
addExpressionEffects(raises, statement.condition, callEffects)
|
|
947
|
+
addAll(raises, consequentReturns.raises)
|
|
948
|
+
addAll(raises, alternateReturns.raises)
|
|
949
|
+
normal = consequentReturns.normal || alternateReturns.normal
|
|
950
|
+
continue
|
|
951
|
+
}
|
|
952
|
+
if (statement.kind == "WhileStatement") {
|
|
953
|
+
const assignedOuterBindings = outerMutableBindingsAssignedBy(statement.body, scope)
|
|
954
|
+
|
|
955
|
+
for (const binding of assignedOuterBindings) {
|
|
956
|
+
binding.knownValue = undefined
|
|
957
|
+
scope.presenceProofs.delete(binding)
|
|
958
|
+
}
|
|
959
|
+
const conditionType = inferValueExpressionType(
|
|
960
|
+
statement.condition, scope, functions, records, fail, normalizeOperations, "a condition-controlled loop condition"
|
|
961
|
+
)
|
|
962
|
+
|
|
963
|
+
if (!isScalarType(conditionType, "boolean")) {
|
|
964
|
+
fail("NON_BOOLEAN_CONDITION", `While condition type ${typeDescription(conditionType)}; expected boolean.`, statement.condition.location)
|
|
965
|
+
}
|
|
966
|
+
const loopScope = createScope(scope, statement.body.statements)
|
|
967
|
+
|
|
968
|
+
applyPresenceNarrowing(statement.condition, scope, loopScope, true)
|
|
969
|
+
const bodyFlow = validateBlock(statement.body, loopScope, returnType, functions, records, errors, callEffects, fail,
|
|
970
|
+
normalizeOperations, {nearest: statement, owner: loopContext.owner, owners: loopContext.owners})
|
|
971
|
+
|
|
972
|
+
addExpressionEffects(raises, statement.condition, callEffects)
|
|
973
|
+
addAll(raises, bodyFlow.raises)
|
|
974
|
+
for (const binding of assignedOuterBindings) {
|
|
975
|
+
binding.knownValue = undefined
|
|
976
|
+
scope.presenceProofs.delete(binding)
|
|
977
|
+
}
|
|
978
|
+
continue
|
|
979
|
+
}
|
|
980
|
+
if (statement.kind == "ForEachStatement") {
|
|
981
|
+
const listType = inferValueExpressionType(statement.list, scope, functions, records, fail, normalizeOperations, "an iteration collection")
|
|
982
|
+
|
|
983
|
+
if (listType.kind != "ListType") {
|
|
984
|
+
fail("TYPE_MISMATCH", `Iteration collection type ${typeDescription(listType)}; expected list.`, statement.list.location)
|
|
985
|
+
}
|
|
986
|
+
if (!statement.valueBinding || statement.valueBinding.kind != "ValueBinding") {
|
|
987
|
+
fail("TYPE_MISMATCH", "Iteration requires one typed value binding.", statement.location)
|
|
988
|
+
}
|
|
989
|
+
if (statement.valueBinding.mutable !== false) {
|
|
990
|
+
fail("TYPE_MISMATCH", "Iteration binding must be immutable.", statement.valueBinding.location ?? statement.location)
|
|
991
|
+
}
|
|
992
|
+
const bindingType = validateValueTypeReference(statement.valueBinding.type, statement.valueBinding.location, fail, undefined, records, scope.typeParameters)
|
|
993
|
+
|
|
994
|
+
if (!sameType(bindingType, listType.elementType)) {
|
|
995
|
+
fail("TYPE_MISMATCH", `Iteration binding type ${typeDescription(bindingType)}; expected ${typeDescription(listType.elementType)}.`,
|
|
996
|
+
typeLocation(statement.valueBinding.type, statement.valueBinding.location))
|
|
997
|
+
}
|
|
998
|
+
const assignedOuterBindings = outerMutableBindingsAssignedBy(statement.body, scope)
|
|
999
|
+
|
|
1000
|
+
for (const binding of assignedOuterBindings) {
|
|
1001
|
+
binding.knownValue = undefined
|
|
1002
|
+
scope.presenceProofs.delete(binding)
|
|
1003
|
+
}
|
|
1004
|
+
const loopScope = createScope(scope, statement.body.statements)
|
|
1005
|
+
|
|
1006
|
+
declareBinding(statement.valueBinding.name, {
|
|
1007
|
+
knownValue: undefined,
|
|
1008
|
+
mutable: false,
|
|
1009
|
+
type: bindingType
|
|
1010
|
+
}, roleLocation(statement.valueBinding, "name"), loopScope, fail)
|
|
1011
|
+
const bodyFlow = validateBlock(statement.body, loopScope, returnType, functions, records, errors, callEffects, fail,
|
|
1012
|
+
normalizeOperations, {nearest: statement, owner: loopContext.owner, owners: loopContext.owners})
|
|
1013
|
+
addExpressionEffects(raises, statement.list, callEffects)
|
|
1014
|
+
addAll(raises, bodyFlow.raises)
|
|
1015
|
+
for (const binding of assignedOuterBindings) {
|
|
1016
|
+
binding.knownValue = undefined
|
|
1017
|
+
scope.presenceProofs.delete(binding)
|
|
1018
|
+
}
|
|
1019
|
+
continue
|
|
1020
|
+
}
|
|
1021
|
+
if (statement.kind == "ForEachMapStatement") {
|
|
1022
|
+
const mapType = inferValueExpressionType(statement.map, scope, functions, records, fail, normalizeOperations,
|
|
1023
|
+
"a map iteration collection")
|
|
1024
|
+
|
|
1025
|
+
if (mapType.kind != "OrderedMapType") {
|
|
1026
|
+
fail("TYPE_MISMATCH", `Map iteration collection type ${typeDescription(mapType)}; expected ordered map.`, statement.map.location)
|
|
1027
|
+
}
|
|
1028
|
+
if (!statement.keyBinding || statement.keyBinding.kind != "ValueBinding" ||
|
|
1029
|
+
!statement.valueBinding || statement.valueBinding.kind != "ValueBinding") {
|
|
1030
|
+
fail("TYPE_MISMATCH", "Map iteration requires exact key and value bindings.", statement.location)
|
|
1031
|
+
}
|
|
1032
|
+
if (statement.keyBinding.mutable !== false || statement.valueBinding.mutable !== false) {
|
|
1033
|
+
fail("TYPE_MISMATCH", "Map iteration bindings must be immutable.",
|
|
1034
|
+
statement.keyBinding.mutable !== false ? statement.keyBinding.location : statement.valueBinding.location)
|
|
1035
|
+
}
|
|
1036
|
+
const keyType = validateValueTypeReference(statement.keyBinding.type, statement.keyBinding.location, fail,
|
|
1037
|
+
undefined, records, scope.typeParameters)
|
|
1038
|
+
const valueType = validateValueTypeReference(statement.valueBinding.type, statement.valueBinding.location, fail,
|
|
1039
|
+
undefined, records, scope.typeParameters)
|
|
1040
|
+
|
|
1041
|
+
if (!sameType(keyType, mapType.keyType)) {
|
|
1042
|
+
fail("TYPE_MISMATCH", `Map iteration key binding type ${typeDescription(keyType)}; expected ${typeDescription(mapType.keyType)}.`,
|
|
1043
|
+
typeLocation(statement.keyBinding.type, statement.keyBinding.location))
|
|
1044
|
+
}
|
|
1045
|
+
if (!sameType(valueType, mapType.valueType)) {
|
|
1046
|
+
fail("TYPE_MISMATCH", `Map iteration value binding type ${typeDescription(valueType)}; expected ${typeDescription(mapType.valueType)}.`,
|
|
1047
|
+
typeLocation(statement.valueBinding.type, statement.valueBinding.location))
|
|
1048
|
+
}
|
|
1049
|
+
const assignedOuterBindings = outerMutableBindingsAssignedBy(statement.body, scope)
|
|
1050
|
+
|
|
1051
|
+
for (const binding of assignedOuterBindings) {
|
|
1052
|
+
binding.knownValue = undefined
|
|
1053
|
+
scope.presenceProofs.delete(binding)
|
|
1054
|
+
}
|
|
1055
|
+
const loopScope = createScope(scope, statement.body.statements)
|
|
1056
|
+
|
|
1057
|
+
declareBinding(statement.keyBinding.name, {knownValue: undefined, mutable: false, type: keyType},
|
|
1058
|
+
roleLocation(statement.keyBinding, "name"), loopScope, fail)
|
|
1059
|
+
declareBinding(statement.valueBinding.name, {knownValue: undefined, mutable: false, type: valueType},
|
|
1060
|
+
roleLocation(statement.valueBinding, "name"), loopScope, fail)
|
|
1061
|
+
const bodyFlow = validateBlock(statement.body, loopScope, returnType, functions, records, errors, callEffects,
|
|
1062
|
+
fail, normalizeOperations, {nearest: statement, owner: loopContext.owner, owners: loopContext.owners})
|
|
1063
|
+
|
|
1064
|
+
addExpressionEffects(raises, statement.map, callEffects)
|
|
1065
|
+
addAll(raises, bodyFlow.raises)
|
|
1066
|
+
for (const binding of assignedOuterBindings) {
|
|
1067
|
+
binding.knownValue = undefined
|
|
1068
|
+
scope.presenceProofs.delete(binding)
|
|
1069
|
+
}
|
|
1070
|
+
continue
|
|
1071
|
+
}
|
|
1072
|
+
if (statement.kind == "RaiseStatement") {
|
|
1073
|
+
if (!statement.error || statement.error.kind != "ErrorConstruction") {
|
|
1074
|
+
fail("NON_ERROR_RAISE", "Raise requires one constructed semantic error.", statement.location)
|
|
1075
|
+
}
|
|
1076
|
+
const raisedType = validateErrorType(statement.error.error, statement.error.location, errors, fail)
|
|
1077
|
+
const messageType = inferValueExpressionType(
|
|
1078
|
+
statement.error.message, scope, functions, records, fail, normalizeOperations, "an error message", scalarType("string")
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
if (!isScalarType(messageType, "string")) {
|
|
1082
|
+
fail("NON_ERROR_RAISE", `Error message type ${typeDescription(messageType)}; expected string.`, statement.error.message.location)
|
|
1083
|
+
}
|
|
1084
|
+
addExpressionEffects(raises, statement.error.message, callEffects)
|
|
1085
|
+
raises.add(raisedType.declarationId)
|
|
1086
|
+
normal = false
|
|
1087
|
+
continue
|
|
1088
|
+
}
|
|
1089
|
+
if (statement.kind == "TryStatement") {
|
|
1090
|
+
const caughtType = validateErrorType(statement.catchType, statement.location, errors, fail)
|
|
1091
|
+
|
|
1092
|
+
if (!statement.catchBinding || statement.catchBinding.kind != "CatchBinding" || statement.catchBinding.mutable !== false) {
|
|
1093
|
+
fail("INVALID_ERROR_HANDLER", "Catch requires one immutable typed binding.", statement.catchBinding?.location ?? statement.location)
|
|
1094
|
+
}
|
|
1095
|
+
const bindingType = validateErrorType(statement.catchBinding.type, statement.catchBinding.location, errors, fail)
|
|
1096
|
+
|
|
1097
|
+
if (bindingType.declarationId != caughtType.declarationId) {
|
|
1098
|
+
fail("INVALID_ERROR_HANDLER", "Catch binding type must exactly match its handler type.", statement.catchBinding.location)
|
|
1099
|
+
}
|
|
1100
|
+
const bodyAssignedOuterBindings = outerMutableBindingsAssignedBy(statement.body, scope)
|
|
1101
|
+
const assignedOuterBindings = new Set(bodyAssignedOuterBindings)
|
|
1102
|
+
|
|
1103
|
+
outerMutableBindingsAssignedBy(statement.catchBody, scope, assignedOuterBindings)
|
|
1104
|
+
const bodyScope = createScope(scope, statement.body.statements)
|
|
1105
|
+
const bodyFlow = validateBlock(statement.body, bodyScope, returnType, functions, records, errors, callEffects, fail, normalizeOperations, loopContext)
|
|
1106
|
+
const catchScope = createScope(scope, statement.catchBody.statements)
|
|
1107
|
+
|
|
1108
|
+
for (const binding of bodyAssignedOuterBindings) {
|
|
1109
|
+
binding.knownValue = undefined
|
|
1110
|
+
catchScope.presenceProofs.delete(binding)
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
declareBinding(statement.catchBinding.name, {
|
|
1114
|
+
knownValue: undefined,
|
|
1115
|
+
mutable: false,
|
|
1116
|
+
type: bindingType
|
|
1117
|
+
}, roleLocation(statement.catchBinding, "name"), catchScope, fail)
|
|
1118
|
+
const catchFlow = validateBlock(statement.catchBody, catchScope, returnType, functions, records, errors, callEffects, fail, normalizeOperations, loopContext)
|
|
1119
|
+
|
|
1120
|
+
if (!bodyFlow.raises.has(caughtType.declarationId)) {
|
|
1121
|
+
fail("UNREACHABLE_HANDLER", `Handler for '${errors.get(caughtType.declarationId)?.name}' cannot be reached.`, typeLocation(statement.catchType, statement.location))
|
|
1122
|
+
}
|
|
1123
|
+
bodyFlow.raises.delete(caughtType.declarationId)
|
|
1124
|
+
addAll(raises, bodyFlow.raises)
|
|
1125
|
+
addAll(raises, catchFlow.raises)
|
|
1126
|
+
normal = bodyFlow.normal || catchFlow.normal
|
|
1127
|
+
for (const binding of assignedOuterBindings) {
|
|
1128
|
+
binding.knownValue = undefined
|
|
1129
|
+
scope.presenceProofs.delete(binding)
|
|
1130
|
+
}
|
|
1131
|
+
continue
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const unexpected = /** @type {{kind: string, location: import("./types.js").SourceLocation}} */ (statement)
|
|
1135
|
+
|
|
1136
|
+
fail("UNSUPPORTED_STATEMENT", unexpected.kind, unexpected.location)
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
return {normal, raises}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/**
|
|
1143
|
+
* Validates one exact nominal error type.
|
|
1144
|
+
* @param {unknown} type - Candidate type.
|
|
1145
|
+
* @param {import("./types.js").SourceLocation} location - Owning location.
|
|
1146
|
+
* @param {ErrorRegistry} errors - Visible error declarations.
|
|
1147
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
1148
|
+
* @returns {import("./types.js").ErrorType} Validated type.
|
|
1149
|
+
*/
|
|
1150
|
+
function validateErrorType(type, location, errors, fail) {
|
|
1151
|
+
if (!type || typeof type != "object" || Array.isArray(type)) {
|
|
1152
|
+
return fail("UNKNOWN_ERROR", "Handler or raise is missing an exact error type.", location)
|
|
1153
|
+
}
|
|
1154
|
+
const candidate = /** @type {import("./types.js").ErrorType} */ (type)
|
|
1155
|
+
|
|
1156
|
+
if (candidate.kind != "ErrorType" ||
|
|
1157
|
+
Object.keys(candidate).filter((key) => key != "sourceProvenance").sort().join(",") != "declarationId,kind" ||
|
|
1158
|
+
typeof candidate.declarationId != "string" || !errors.has(candidate.declarationId)) {
|
|
1159
|
+
fail("UNKNOWN_ERROR", `Unknown error declaration '${String(candidate.declarationId)}'.`, typeLocation(candidate, location))
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
return candidate
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* Adds every source value to one set.
|
|
1167
|
+
* @param {Set<string>} target - Destination identities.
|
|
1168
|
+
* @param {Set<string>} source - Source identities.
|
|
1169
|
+
* @returns {void}
|
|
1170
|
+
*/
|
|
1171
|
+
function addAll(target, source) {
|
|
1172
|
+
for (const value of source) target.add(value)
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* Adds unchecked errors raised while evaluating one expression.
|
|
1177
|
+
* @param {Set<string>} target - Destination identities.
|
|
1178
|
+
* @param {unknown} expression - Candidate semantic expression.
|
|
1179
|
+
* @param {Map<string, Set<string>>} callEffects - Inferred call effects.
|
|
1180
|
+
* @param {Map<string, Set<string>>} [methodEffects] - Inferred effects by resolved method identity.
|
|
1181
|
+
* @returns {void}
|
|
1182
|
+
*/
|
|
1183
|
+
function addExpressionEffects(target, expression, callEffects, methodEffects = new Map()) {
|
|
1184
|
+
const pending = [expression]
|
|
1185
|
+
const seen = new Set()
|
|
1186
|
+
|
|
1187
|
+
while (pending.length > 0) {
|
|
1188
|
+
const value = pending.pop()
|
|
1189
|
+
|
|
1190
|
+
if (!value || typeof value != "object" || seen.has(value)) continue
|
|
1191
|
+
seen.add(value)
|
|
1192
|
+
if (Reflect.get(value, "kind") == "CallExpression") addAll(target, callEffects.get(Reflect.get(value, "callee")) ?? new Set())
|
|
1193
|
+
if (Reflect.get(value, "kind") == "MethodCallExpression" || Reflect.get(value, "kind") == "ReferenceConstruction") {
|
|
1194
|
+
const failures = Reflect.get(value, "failureIds")
|
|
1195
|
+
if (Array.isArray(failures)) for (const failure of failures) if (typeof failure == "string") target.add(failure)
|
|
1196
|
+
if (Reflect.get(value, "kind") == "MethodCallExpression") {
|
|
1197
|
+
addAll(target, methodEffects.get(Reflect.get(value, "method")) ?? new Set())
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
if (Reflect.get(value, "kind") == "EffectCallExpression") {
|
|
1201
|
+
const resolution = Reflect.get(value, "resolution")
|
|
1202
|
+
const failures = resolution && typeof resolution == "object" ? Reflect.get(resolution, "failureIds") : undefined
|
|
1203
|
+
|
|
1204
|
+
if (Array.isArray(failures)) for (const failure of failures) if (typeof failure == "string") target.add(failure)
|
|
1205
|
+
}
|
|
1206
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1207
|
+
if (["location", "sourceProvenance", "resolution"].includes(key)) continue
|
|
1208
|
+
if (Array.isArray(child)) pending.push(...child)
|
|
1209
|
+
else if (child && typeof child == "object") pending.push(child)
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/**
|
|
1215
|
+
* Derives unchecked-error propagation over the resolved finite function graph.
|
|
1216
|
+
* This is validator state, never a checked effect in the public function signature.
|
|
1217
|
+
* @param {Map<string, import("./types.js").FunctionDeclaration>} functions - Resolved functions.
|
|
1218
|
+
* @param {Map<string, Set<string>>} [seedEffects] - Dependency effects keyed by local import name.
|
|
1219
|
+
* @param {Set<string>} [localFunctionNames] - Functions whose bodies belong to the current module.
|
|
1220
|
+
* @returns {Map<string, Set<string>>} Escaping error identities by source-visible function name.
|
|
1221
|
+
*/
|
|
1222
|
+
function inferCallEffects(functions, seedEffects = new Map(), localFunctionNames = new Set(functions.keys())) {
|
|
1223
|
+
const effects = new Map([...functions.keys()].map((name) => [name, new Set(seedEffects.get(name) ?? [])]))
|
|
1224
|
+
let changed = true
|
|
1225
|
+
|
|
1226
|
+
while (changed) {
|
|
1227
|
+
changed = false
|
|
1228
|
+
for (const name of localFunctionNames) {
|
|
1229
|
+
const declaration = functions.get(name)
|
|
1230
|
+
|
|
1231
|
+
if (!declaration) continue
|
|
1232
|
+
const next = syntacticBlockEffects(declaration.body, effects)
|
|
1233
|
+
const current = /** @type {Set<string>} */ (effects.get(name))
|
|
1234
|
+
|
|
1235
|
+
for (const error of next) {
|
|
1236
|
+
if (!current.has(error)) {
|
|
1237
|
+
current.add(error)
|
|
1238
|
+
changed = true
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
return effects
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
/**
|
|
1248
|
+
* Computes escaping identities from one block for the call-effect fixed point.
|
|
1249
|
+
* @param {unknown} block - Candidate block.
|
|
1250
|
+
* @param {Map<string, Set<string>>} callEffects - Current fixed-point state.
|
|
1251
|
+
* @param {Map<string, Set<string>>} [methodEffects] - Current method fixed-point state.
|
|
1252
|
+
* @returns {Set<string>} Escaping identities.
|
|
1253
|
+
*/
|
|
1254
|
+
function syntacticBlockEffects(block, callEffects, methodEffects = new Map()) {
|
|
1255
|
+
const result = new Set()
|
|
1256
|
+
const statements = Reflect.get(/** @type {object} */ (block ?? {}), "statements")
|
|
1257
|
+
|
|
1258
|
+
if (!Array.isArray(statements)) return result
|
|
1259
|
+
for (const statement of statements) {
|
|
1260
|
+
if (!statement || typeof statement != "object") continue
|
|
1261
|
+
const kind = Reflect.get(statement, "kind")
|
|
1262
|
+
|
|
1263
|
+
if (kind == "RaiseStatement") {
|
|
1264
|
+
const construction = Reflect.get(statement, "error")
|
|
1265
|
+
const type = construction && typeof construction == "object" ? Reflect.get(construction, "error") : undefined
|
|
1266
|
+
const declarationId = type && typeof type == "object" ? Reflect.get(type, "declarationId") : undefined
|
|
1267
|
+
|
|
1268
|
+
if (typeof declarationId == "string") result.add(declarationId)
|
|
1269
|
+
if (construction && typeof construction == "object") {
|
|
1270
|
+
addExpressionEffects(result, Reflect.get(construction, "message"), callEffects, methodEffects)
|
|
1271
|
+
}
|
|
1272
|
+
continue
|
|
1273
|
+
}
|
|
1274
|
+
if (kind == "TryStatement") {
|
|
1275
|
+
const bodyEffects = syntacticBlockEffects(Reflect.get(statement, "body"), callEffects, methodEffects)
|
|
1276
|
+
const catchType = Reflect.get(statement, "catchType")
|
|
1277
|
+
const caughtId = catchType && typeof catchType == "object" ? Reflect.get(catchType, "declarationId") : undefined
|
|
1278
|
+
|
|
1279
|
+
if (typeof caughtId == "string" && bodyEffects.delete(caughtId)) {
|
|
1280
|
+
addAll(bodyEffects, syntacticBlockEffects(Reflect.get(statement, "catchBody"), callEffects, methodEffects))
|
|
1281
|
+
}
|
|
1282
|
+
addAll(result, bodyEffects)
|
|
1283
|
+
continue
|
|
1284
|
+
}
|
|
1285
|
+
if (kind == "IfStatement") {
|
|
1286
|
+
addExpressionEffects(result, Reflect.get(statement, "condition"), callEffects, methodEffects)
|
|
1287
|
+
addAll(result, syntacticBlockEffects(Reflect.get(statement, "consequent"), callEffects, methodEffects))
|
|
1288
|
+
addAll(result, syntacticBlockEffects(Reflect.get(statement, "alternate"), callEffects, methodEffects))
|
|
1289
|
+
continue
|
|
1290
|
+
}
|
|
1291
|
+
if (kind == "ForEachStatement" || kind == "ForEachMapStatement") {
|
|
1292
|
+
addExpressionEffects(result, Reflect.get(statement, kind == "ForEachStatement" ? "list" : "map"), callEffects,
|
|
1293
|
+
methodEffects)
|
|
1294
|
+
addAll(result, syntacticBlockEffects(Reflect.get(statement, "body"), callEffects, methodEffects))
|
|
1295
|
+
continue
|
|
1296
|
+
}
|
|
1297
|
+
if (kind == "WhileStatement") {
|
|
1298
|
+
addExpressionEffects(result, Reflect.get(statement, "condition"), callEffects, methodEffects)
|
|
1299
|
+
addAll(result, syntacticBlockEffects(Reflect.get(statement, "body"), callEffects, methodEffects))
|
|
1300
|
+
continue
|
|
1301
|
+
}
|
|
1302
|
+
for (const key of ["expression", "initializer"]) {
|
|
1303
|
+
addExpressionEffects(result, Reflect.get(statement, key), callEffects, methodEffects)
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
return result
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
/**
|
|
1311
|
+
* Derives the escaping user-defined error sets for local functions and resolved class methods.
|
|
1312
|
+
* The fixed point is computed over every unchecked error; capability failures are projected out
|
|
1313
|
+
* because call sites already carry their capability-failure boundary through resolution metadata.
|
|
1314
|
+
* @param {import("./types.js").SemanticModule} module - Resolved module.
|
|
1315
|
+
* @param {Map<string, Set<string>>} [seedEffects] - Dependency effects keyed by local import name.
|
|
1316
|
+
* @returns {{functions: Map<string, Set<string>>, methods: Map<string, Set<string>>}} Escaping user-defined error identities by function name and method identity.
|
|
1317
|
+
*/
|
|
1318
|
+
export function inferEscapingErrorEffects(module, seedEffects = new Map()) {
|
|
1319
|
+
const functions = new Map(module.functions.map((declaration) => [declaration.name, declaration]))
|
|
1320
|
+
const functionEffects = new Map([...functions.keys()].map((name) => [name, new Set(seedEffects.get(name) ?? [])]))
|
|
1321
|
+
const classes = module.classes ?? []
|
|
1322
|
+
const methodEffects = new Map()
|
|
1323
|
+
const capabilityFailureIds = new Set((module.capabilities ?? []).flatMap(({failures}) => failures).map(({id}) => id))
|
|
1324
|
+
|
|
1325
|
+
for (const declaration of classes) for (const method of declaration.methods) methodEffects.set(method.id, new Set())
|
|
1326
|
+
let changed = true
|
|
1327
|
+
|
|
1328
|
+
while (changed) {
|
|
1329
|
+
changed = false
|
|
1330
|
+
for (const declaration of module.functions) {
|
|
1331
|
+
const current = /** @type {Set<string>} */ (functionEffects.get(declaration.name))
|
|
1332
|
+
for (const error of syntacticBlockEffects(declaration.body, functionEffects, methodEffects)) {
|
|
1333
|
+
if (!current.has(error)) { current.add(error); changed = true }
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
for (const declaration of classes) {
|
|
1337
|
+
for (const method of declaration.methods) {
|
|
1338
|
+
const current = /** @type {Set<string>} */ (methodEffects.get(method.id))
|
|
1339
|
+
for (const error of syntacticBlockEffects(method.body, functionEffects, methodEffects)) {
|
|
1340
|
+
if (!current.has(error)) { current.add(error); changed = true }
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
for (const effects of [functionEffects, methodEffects]) {
|
|
1347
|
+
for (const errors of effects.values()) {
|
|
1348
|
+
for (const error of [...errors]) if (capabilityFailureIds.has(error)) errors.delete(error)
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
return {functions: functionEffects, methods: methodEffects}
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
/**
|
|
1356
|
+
* Creates a lexical scope with declarations marked pending until visited.
|
|
1357
|
+
* @param {Scope | undefined} parent - Enclosing scope.
|
|
1358
|
+
* @param {{kind: string, name?: string}[]} statements - Scope statements.
|
|
1359
|
+
* @param {Set<string>} [reservedNames] - Names unavailable to root bindings.
|
|
1360
|
+
* @param {Set<string>} [typeParameters] - Declaration-scoped type parameters.
|
|
1361
|
+
* @param {import("./types.js").ClassDeclaration} [currentClass] - Declaring class for receiver-private access.
|
|
1362
|
+
* @returns {Scope} Scope.
|
|
1363
|
+
*/
|
|
1364
|
+
function createScope(parent, statements, reservedNames = new Set(), typeParameters = parent?.typeParameters ?? new Set(),
|
|
1365
|
+
currentClass = parent?.currentClass) {
|
|
1366
|
+
const pending = new Set(statements.filter((statement) => statement.kind == "LocalDeclaration")
|
|
1367
|
+
.map((statement) => /** @type {{name: string}} */ (statement).name))
|
|
1368
|
+
|
|
1369
|
+
return {
|
|
1370
|
+
bindings: new Map(),
|
|
1371
|
+
currentClass,
|
|
1372
|
+
parent,
|
|
1373
|
+
pending,
|
|
1374
|
+
presenceProofs: new Set(parent?.presenceProofs ?? []),
|
|
1375
|
+
typeParameters,
|
|
1376
|
+
usedNames: parent?.usedNames ?? new Set(reservedNames)
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
/**
|
|
1381
|
+
* Applies a simple-identifier presence proof to exactly the branch where the test is true.
|
|
1382
|
+
* @param {import("./types.js").Expression} condition - Validated branch condition.
|
|
1383
|
+
* @param {Scope} sourceScope - Scope containing the tested binding.
|
|
1384
|
+
* @param {Scope} branchScope - Branch-local proof scope.
|
|
1385
|
+
* @param {boolean} branchWhenTrue - Whether this is the condition's true branch.
|
|
1386
|
+
* @returns {void}
|
|
1387
|
+
*/
|
|
1388
|
+
function applyPresenceNarrowing(condition, sourceScope, branchScope, branchWhenTrue) {
|
|
1389
|
+
let test = condition
|
|
1390
|
+
let presentWhenTrue = true
|
|
1391
|
+
|
|
1392
|
+
if (condition.kind == "UnaryExpression" && condition.operation == "BooleanNot" &&
|
|
1393
|
+
condition.operand.kind == "OptionalIsPresent") {
|
|
1394
|
+
test = condition.operand
|
|
1395
|
+
presentWhenTrue = false
|
|
1396
|
+
}
|
|
1397
|
+
if (test.kind != "OptionalIsPresent") return
|
|
1398
|
+
const binding = findBinding(test.operand.name, sourceScope)
|
|
1399
|
+
|
|
1400
|
+
if (!binding) return
|
|
1401
|
+
if (branchWhenTrue == presentWhenTrue) branchScope.presenceProofs.add(binding)
|
|
1402
|
+
else branchScope.presenceProofs.delete(binding)
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
/**
|
|
1406
|
+
* Adds one binding while rejecting duplicates and shadowing.
|
|
1407
|
+
* @param {string} name - Binding name.
|
|
1408
|
+
* @param {Binding} binding - Binding metadata.
|
|
1409
|
+
* @param {import("./types.js").SourceLocation} location - Declaration location.
|
|
1410
|
+
* @param {Scope} scope - Current scope.
|
|
1411
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
1412
|
+
* @returns {void}
|
|
1413
|
+
*/
|
|
1414
|
+
function declareBinding(name, binding, location, scope, fail) {
|
|
1415
|
+
if (scope.usedNames.has(name)) fail("DUPLICATE_BINDING", `Duplicate or shadowed binding '${name}'.`, location)
|
|
1416
|
+
|
|
1417
|
+
scope.bindings.set(name, binding)
|
|
1418
|
+
scope.usedNames.add(name)
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
/**
|
|
1422
|
+
* Resolves one identifier and distinguishes use-before-declaration.
|
|
1423
|
+
* @param {string} name - Binding name.
|
|
1424
|
+
* @param {import("./types.js").SourceLocation} location - Reference location.
|
|
1425
|
+
* @param {Scope} scope - Current scope.
|
|
1426
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
1427
|
+
* @returns {Binding} Binding.
|
|
1428
|
+
*/
|
|
1429
|
+
function resolveBinding(name, location, scope, fail) {
|
|
1430
|
+
for (let current = /** @type {Scope | undefined} */ (scope); current; current = current.parent) {
|
|
1431
|
+
const binding = current.bindings.get(name)
|
|
1432
|
+
|
|
1433
|
+
if (binding) return binding
|
|
1434
|
+
if (current.pending.has(name)) {
|
|
1435
|
+
return fail("USE_BEFORE_DECLARATION", `Binding '${name}' is used before its declaration.`, location)
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
return fail("UNRESOLVED_BINDING", `Unknown binding '${name}'.`, location)
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
/**
|
|
1443
|
+
* Validates one semantic value type reference.
|
|
1444
|
+
* @param {unknown} type - Candidate semantic type reference.
|
|
1445
|
+
* @param {import("./types.js").SourceLocation} location - Owning source location.
|
|
1446
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
1447
|
+
* @param {Set<object>} [seen] - Active recursive type path.
|
|
1448
|
+
* @param {RecordRegistry} [records] - Record declarations by identity.
|
|
1449
|
+
* @param {Set<string>} [typeParameters] - Type-variable identities in declaration scope.
|
|
1450
|
+
* @returns {import("./types.js").SemanticValueType} Validated value type.
|
|
1451
|
+
*/
|
|
1452
|
+
function validateValueTypeReference(type, location, fail, seen, records = new Map(), typeParameters = new Set()) {
|
|
1453
|
+
const candidate = validateTypeReference(type, location, fail, seen, records, typeParameters)
|
|
1454
|
+
|
|
1455
|
+
if (isVoidType(candidate)) return fail("VOID_AS_VALUE", "Void is valid only as a function return type.", typeLocation(type, location))
|
|
1456
|
+
|
|
1457
|
+
return candidate
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* Validates one function return type reference.
|
|
1462
|
+
* @param {unknown} type - Candidate semantic return type reference.
|
|
1463
|
+
* @param {import("./types.js").SourceLocation} location - Owning source location.
|
|
1464
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
1465
|
+
* @param {RecordRegistry} [records] - Record declarations by identity.
|
|
1466
|
+
* @param {Set<string>} [typeParameters] - Type-variable identities in declaration scope.
|
|
1467
|
+
* @returns {import("./types.js").SemanticFunctionReturnType} Validated return type.
|
|
1468
|
+
*/
|
|
1469
|
+
function validateReturnTypeReference(type, location, fail, records = new Map(), typeParameters = new Set()) {
|
|
1470
|
+
return validateTypeReference(type, location, fail, new Set(), records, typeParameters)
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
/**
|
|
1474
|
+
* Validates one recursive semantic type-reference shape.
|
|
1475
|
+
* @param {unknown} type - Candidate semantic type reference.
|
|
1476
|
+
* @param {import("./types.js").SourceLocation} location - Owning source location.
|
|
1477
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
1478
|
+
* @param {Set<object>} [seen] - Active recursive type path.
|
|
1479
|
+
* @param {RecordRegistry} [records] - Record declarations by identity.
|
|
1480
|
+
* @param {Set<string>} [typeParameters] - Type-variable identities in declaration scope.
|
|
1481
|
+
* @returns {import("./types.js").SemanticFunctionReturnType} Validated type.
|
|
1482
|
+
*/
|
|
1483
|
+
function validateTypeReference(type, location, fail, seen = new Set(), records = new Map(), typeParameters = new Set()) {
|
|
1484
|
+
if (!type || typeof type != "object" || Array.isArray(type)) {
|
|
1485
|
+
return fail("TYPE_MISMATCH", "Unsupported semantic type.", location)
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
const candidate = /** @type {import("./types.js").SemanticFunctionReturnType} */ (type)
|
|
1489
|
+
|
|
1490
|
+
if (["ListType", "MapType", "OptionalType", "OrderedMapType", "RecordType"].includes(candidate.kind) &&
|
|
1491
|
+
containsOwnedType(candidate)) {
|
|
1492
|
+
fail("RESOURCE_ALIAS", "Owned resources and references cannot be nested in value or generic types.", typeLocation(candidate, location))
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
if (candidate.kind == "TypeReference") {
|
|
1496
|
+
if (!isScalarTypeName(candidate.name) && candidate.name != "void") {
|
|
1497
|
+
fail("TYPE_MISMATCH", "Unsupported scalar or void return type.", typeLocation(candidate, location))
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
return candidate
|
|
1501
|
+
}
|
|
1502
|
+
if (candidate.kind == "TypeVariableReference") {
|
|
1503
|
+
if (Object.keys(candidate).filter((key) => key != "sourceProvenance").sort().join(",") != "kind,location,parameterId" ||
|
|
1504
|
+
typeof candidate.parameterId != "string" || !typeParameters.has(candidate.parameterId)) {
|
|
1505
|
+
fail("FREE_TYPE_VARIABLE", `Free or unknown type variable '${String(candidate.parameterId)}'.`, typeLocation(candidate, location))
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
return candidate
|
|
1509
|
+
}
|
|
1510
|
+
if (candidate.kind == "RecordType") {
|
|
1511
|
+
const keys = Object.keys(candidate).filter((key) => key != "sourceProvenance").sort().join(",")
|
|
1512
|
+
const declaration = records.get(candidate.declarationId)
|
|
1513
|
+
|
|
1514
|
+
if (!(["arguments,declarationId,kind", "declarationId,kind"].includes(keys)) ||
|
|
1515
|
+
typeof candidate.declarationId != "string" || !declaration) {
|
|
1516
|
+
fail("UNKNOWN_RECORD", `Unknown record declaration '${String(candidate.declarationId)}'.`, typeLocation(candidate, location))
|
|
1517
|
+
}
|
|
1518
|
+
const expectedArity = declaration.typeParameters?.length ?? 0
|
|
1519
|
+
|
|
1520
|
+
if (expectedArity > 0 && candidate.arguments === undefined) {
|
|
1521
|
+
fail("RAW_GENERIC_APPLICATION", `Generic record '${declaration.name}' requires ${expectedArity} type arguments.`, typeLocation(candidate, location))
|
|
1522
|
+
}
|
|
1523
|
+
if (expectedArity == 0 && candidate.arguments !== undefined) {
|
|
1524
|
+
fail("GENERIC_ARITY_MISMATCH", `Non-generic record '${declaration.name}' does not accept type arguments.`, typeLocation(candidate, location))
|
|
1525
|
+
}
|
|
1526
|
+
const candidateArguments = candidate.arguments
|
|
1527
|
+
|
|
1528
|
+
if (candidateArguments !== undefined && (!Array.isArray(candidateArguments) ||
|
|
1529
|
+
candidateArguments.some((_argument, index) => !Object.hasOwn(candidateArguments, index)))) {
|
|
1530
|
+
fail("TYPE_MISMATCH", "Generic record arguments must be a dense ordered array.", typeLocation(candidate, location))
|
|
1531
|
+
}
|
|
1532
|
+
const arguments_ = candidate.arguments ?? []
|
|
1533
|
+
|
|
1534
|
+
if (arguments_.length != expectedArity) {
|
|
1535
|
+
fail("GENERIC_ARITY_MISMATCH", `Record '${declaration.name}' has ${arguments_.length} type arguments; expected ${expectedArity}.`, typeLocation(candidate, location))
|
|
1536
|
+
}
|
|
1537
|
+
if (seen.has(candidate)) {
|
|
1538
|
+
return fail("TYPE_MISMATCH", "Recursive generic applications cannot contain cycles.", typeLocation(candidate, location))
|
|
1539
|
+
}
|
|
1540
|
+
seen.add(candidate)
|
|
1541
|
+
for (const argument of arguments_) {
|
|
1542
|
+
validateValueTypeReference(argument, typeLocation(argument, location), fail, seen, records, typeParameters)
|
|
1543
|
+
}
|
|
1544
|
+
seen.delete(candidate)
|
|
1545
|
+
|
|
1546
|
+
return candidate
|
|
1547
|
+
}
|
|
1548
|
+
if (candidate.kind == "ReferenceType") {
|
|
1549
|
+
const classes = referenceClassesFor(records)
|
|
1550
|
+
|
|
1551
|
+
if (Object.keys(candidate).filter((key) => key != "sourceProvenance").sort().join(",") != "declarationId,kind" ||
|
|
1552
|
+
typeof candidate.declarationId != "string" || !classes.has(candidate.declarationId)) {
|
|
1553
|
+
fail("UNKNOWN_CLASS", `Unknown reference class declaration '${String(candidate.declarationId)}'.`, typeLocation(candidate, location))
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
return candidate
|
|
1557
|
+
}
|
|
1558
|
+
if (candidate.kind == "OwnedResourceType") {
|
|
1559
|
+
if (Object.keys(candidate).filter((key) => key != "sourceProvenance").sort().join(",") != "kind,resourceId" ||
|
|
1560
|
+
typeof candidate.resourceId != "string" || !resourceIdsByRecordRegistry.get(records)?.has(candidate.resourceId)) {
|
|
1561
|
+
fail("RESOURCE_TYPE_MISMATCH", `Unknown owned resource '${String(candidate.resourceId)}'.`, typeLocation(candidate, location))
|
|
1562
|
+
}
|
|
1563
|
+
return candidate
|
|
1564
|
+
}
|
|
1565
|
+
if (candidate.kind == "OwnedReferenceType") {
|
|
1566
|
+
const classes = referenceClassesFor(records)
|
|
1567
|
+
|
|
1568
|
+
if (Object.keys(candidate).filter((key) => key != "sourceProvenance").sort().join(",") != "declarationId,kind" ||
|
|
1569
|
+
typeof candidate.declarationId != "string" || classes.get(candidate.declarationId)?.ownership?.kind != "ownedResource") {
|
|
1570
|
+
fail("RESOURCE_TYPE_MISMATCH", `Unknown owned reference '${String(candidate.declarationId)}'.`, typeLocation(candidate, location))
|
|
1571
|
+
}
|
|
1572
|
+
return candidate
|
|
1573
|
+
}
|
|
1574
|
+
if (candidate.kind == "ListType") {
|
|
1575
|
+
if (seen.has(candidate)) return fail("TYPE_MISMATCH", "Recursive collection types cannot contain cycles.", typeLocation(candidate, location))
|
|
1576
|
+
seen.add(candidate)
|
|
1577
|
+
const elementType = validateValueTypeReference(candidate.elementType, typeLocation(candidate, location, "elementType"), fail, seen, records, typeParameters)
|
|
1578
|
+
|
|
1579
|
+
seen.delete(candidate)
|
|
1580
|
+
|
|
1581
|
+
if (candidate.elementType !== elementType) candidate.elementType = elementType
|
|
1582
|
+
|
|
1583
|
+
return candidate
|
|
1584
|
+
}
|
|
1585
|
+
if (candidate.kind == "MapType" || candidate.kind == "OrderedMapType") {
|
|
1586
|
+
const expectedFields = candidate.kind == "OrderedMapType" ? "keyType,kind,order,valueType" : "keyType,kind,valueType"
|
|
1587
|
+
|
|
1588
|
+
if (Object.keys(candidate).filter((key) => key != "sourceProvenance").sort().join(",") != expectedFields) {
|
|
1589
|
+
return fail("TYPE_MISMATCH", `Malformed ${candidate.kind == "OrderedMapType" ? "ordered map" : "map"} type.`,
|
|
1590
|
+
typeLocation(candidate, location))
|
|
1591
|
+
}
|
|
1592
|
+
if (candidate.kind == "OrderedMapType" && candidate.order != "insertion") {
|
|
1593
|
+
return fail("TYPE_MISMATCH", "Ordered map type requires insertion order.", typeLocation(candidate, location))
|
|
1594
|
+
}
|
|
1595
|
+
if (seen.has(candidate)) return fail("TYPE_MISMATCH", "Recursive collection types cannot contain cycles.", typeLocation(candidate, location))
|
|
1596
|
+
seen.add(candidate)
|
|
1597
|
+
const keyType = validateValueTypeReference(candidate.keyType, typeLocation(candidate, location, "keyType"), fail, seen, records, typeParameters)
|
|
1598
|
+
|
|
1599
|
+
if (!isScalarType(keyType, "string")) {
|
|
1600
|
+
fail("TYPE_MISMATCH", `Map key type ${typeDescription(keyType)}; expected string.`, typeLocation(candidate.keyType, location))
|
|
1601
|
+
}
|
|
1602
|
+
const valueType = validateValueTypeReference(candidate.valueType, typeLocation(candidate, location, "valueType"), fail, seen, records, typeParameters)
|
|
1603
|
+
|
|
1604
|
+
seen.delete(candidate)
|
|
1605
|
+
|
|
1606
|
+
if (candidate.keyType !== keyType) candidate.keyType = /** @type {import("./types.js").TypeReference} */ (keyType)
|
|
1607
|
+
if (candidate.valueType !== valueType) candidate.valueType = valueType
|
|
1608
|
+
|
|
1609
|
+
return candidate
|
|
1610
|
+
}
|
|
1611
|
+
if (candidate.kind == "OptionalType") {
|
|
1612
|
+
if (Object.keys(candidate).filter((key) => key != "sourceProvenance").sort().join(",") != "kind,valueType") {
|
|
1613
|
+
return fail("TYPE_MISMATCH", "Malformed optional type.", typeLocation(candidate, location))
|
|
1614
|
+
}
|
|
1615
|
+
if (seen.has(candidate)) return fail("TYPE_MISMATCH", "Recursive optional types cannot contain cycles.", typeLocation(candidate, location))
|
|
1616
|
+
seen.add(candidate)
|
|
1617
|
+
const valueType = validateValueTypeReference(candidate.valueType, typeLocation(candidate, location, "valueType"), fail, seen, records, typeParameters)
|
|
1618
|
+
|
|
1619
|
+
seen.delete(candidate)
|
|
1620
|
+
if (valueType.kind == "OptionalType") {
|
|
1621
|
+
fail("INVALID_OPTIONAL_CONSTITUENT", "Optional values cannot directly contain another optional.", typeLocation(candidate, location, "valueType"))
|
|
1622
|
+
}
|
|
1623
|
+
if (candidate.valueType !== valueType) candidate.valueType = valueType
|
|
1624
|
+
|
|
1625
|
+
return candidate
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
return fail("TYPE_MISMATCH", "Unsupported semantic type.", typeLocation(candidate, location))
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* Retrieves the reference-class registry attached to the record validation context.
|
|
1633
|
+
* @param {RecordRegistry} records - Record validation registry.
|
|
1634
|
+
* @returns {ClassRegistry} Reference classes.
|
|
1635
|
+
*/
|
|
1636
|
+
function referenceClassesFor(records) {
|
|
1637
|
+
return referenceClassesByRecordRegistry.get(records) ?? new Map()
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
/**
|
|
1641
|
+
* Infers a value expression and rejects the non-value result of a void call.
|
|
1642
|
+
* @param {import("./types.js").Expression} expression - Semantic expression.
|
|
1643
|
+
* @param {Scope} scope - Visible lexical scope.
|
|
1644
|
+
* @param {Map<string, import("./types.js").FunctionDeclaration>} functions - Module function signatures.
|
|
1645
|
+
* @param {RecordRegistry} records - Record declarations by identity.
|
|
1646
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
1647
|
+
* @param {boolean} normalizeOperations - Whether to normalize frontend intent and call resolution.
|
|
1648
|
+
* @param {string} context - Value context for diagnostics.
|
|
1649
|
+
* @param {import("./types.js").SemanticValueType} [expectedType] - Contextual collection type.
|
|
1650
|
+
* @param {boolean} [inferCollectionElements] - Whether a generic-call evidence pass may infer nonempty collection literals.
|
|
1651
|
+
* @returns {import("./types.js").SemanticValueType} Expression value type.
|
|
1652
|
+
*/
|
|
1653
|
+
function inferValueExpressionType(expression, scope, functions, records, fail, normalizeOperations, context, expectedType,
|
|
1654
|
+
inferCollectionElements = false) {
|
|
1655
|
+
const allowOwnedBorrow = context == "an effect argument" || context == "a method receiver"
|
|
1656
|
+
const type = inferExpressionType(expression, scope, functions, records, fail, normalizeOperations, expectedType,
|
|
1657
|
+
inferCollectionElements, allowOwnedBorrow)
|
|
1658
|
+
|
|
1659
|
+
if (type == "void") return fail("VOID_AS_VALUE", `Void call cannot be used as ${context}.`, expression.location)
|
|
1660
|
+
if (type.kind == "ErrorType") return fail("TYPE_MISMATCH", `Error value cannot be used as ${context}.`, expression.location)
|
|
1661
|
+
|
|
1662
|
+
return type
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
/**
|
|
1666
|
+
* Infers and validates one expression.
|
|
1667
|
+
* @param {import("./types.js").Expression} expression - Semantic expression.
|
|
1668
|
+
* @param {Scope} scope - Visible lexical scope.
|
|
1669
|
+
* @param {Map<string, import("./types.js").FunctionDeclaration>} functions - Module function signatures.
|
|
1670
|
+
* @param {RecordRegistry} records - Record declarations by identity.
|
|
1671
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
1672
|
+
* @param {boolean} normalizeOperations - Whether to replace transient frontend operation intent.
|
|
1673
|
+
* @param {import("./types.js").SemanticValueType} [expectedType] - Contextual collection type.
|
|
1674
|
+
* @param {boolean} [inferCollectionElements] - Whether a generic-call evidence pass may infer nonempty collection literals.
|
|
1675
|
+
* @param {boolean} [allowOwnedBorrow] - Whether this exact call position admits an immediate borrow.
|
|
1676
|
+
* @returns {import("./types.js").SemanticBindingType | "void"} Expression type.
|
|
1677
|
+
*/
|
|
1678
|
+
function inferExpressionType(expression, scope, functions, records, fail, normalizeOperations, expectedType,
|
|
1679
|
+
inferCollectionElements = false, allowOwnedBorrow = false) {
|
|
1680
|
+
if (expression.kind == "IdentifierExpression") return resolveBinding(expression.name, expression.location, scope, fail).type
|
|
1681
|
+
|
|
1682
|
+
if (expression.kind == "ReceiverExpression") {
|
|
1683
|
+
if (!scope.currentClass || expression.classId != scope.currentClass.id) {
|
|
1684
|
+
return fail("ILLEGAL_PRIVATE_ACCESS", "Receiver expression is valid only inside its declaring class.", expression.location)
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
return {declarationId: /** @type {string} */ (scope.currentClass.id), kind: "ReferenceType"}
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
if (expression.kind == "PrivateFieldRead") {
|
|
1691
|
+
const declaration = scope.currentClass
|
|
1692
|
+
|
|
1693
|
+
if (!declaration || !expression.receiver || expression.receiver.kind != "ReceiverExpression" ||
|
|
1694
|
+
expression.receiver.classId != declaration.id) {
|
|
1695
|
+
return fail("ILLEGAL_PRIVATE_ACCESS", "Private field receiver must be the declaring class receiver.", expression.location)
|
|
1696
|
+
}
|
|
1697
|
+
const field = declaration.fields.find((candidate) => candidate.id == expression.field)
|
|
1698
|
+
|
|
1699
|
+
if (!field) return fail("ILLEGAL_PRIVATE_ACCESS", "Private field identity is not owned by the declaring class.", expression.location)
|
|
1700
|
+
|
|
1701
|
+
return field.type
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
if (expression.kind == "OwnedBorrowExpression") {
|
|
1705
|
+
if (!allowOwnedBorrow) {
|
|
1706
|
+
return fail("BORROW_ESCAPE", "BORROW_ESCAPE: Owned borrows are valid only as immediate capability arguments or method receivers.",
|
|
1707
|
+
expression.location)
|
|
1708
|
+
}
|
|
1709
|
+
if (expression.mode != "shared" && expression.mode != "exclusive") {
|
|
1710
|
+
return fail("INVALID_RESOURCE_BORROW", "INVALID_RESOURCE_BORROW: Owned borrow mode must be shared or exclusive.",
|
|
1711
|
+
expression.location)
|
|
1712
|
+
}
|
|
1713
|
+
const borrowed = inferValueExpressionType(expression.expression, scope, functions, records, fail, normalizeOperations, "an owned borrow")
|
|
1714
|
+
|
|
1715
|
+
if (borrowed.kind != "OwnedResourceType" && borrowed.kind != "OwnedReferenceType") {
|
|
1716
|
+
return fail("INVALID_RESOURCE_BORROW", "Borrowed expression must be an owned resource or owned reference.", expression.location)
|
|
1717
|
+
}
|
|
1718
|
+
if (!sameType(borrowed, expression.type)) {
|
|
1719
|
+
fail("RESOURCE_TYPE_MISMATCH", "Borrow metadata does not match its resource place.", expression.location)
|
|
1720
|
+
}
|
|
1721
|
+
return borrowed
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
if (expression.kind == "OwnedMoveExpression") {
|
|
1725
|
+
const moved = inferValueExpressionType(expression.expression, scope, functions, records, fail, normalizeOperations, "an owned move")
|
|
1726
|
+
|
|
1727
|
+
if (moved.kind != "OwnedResourceType" && moved.kind != "OwnedReferenceType") {
|
|
1728
|
+
return fail("INVALID_RESOURCE_TRANSFER", "Moved expression must be an owned resource or owned reference.", expression.location)
|
|
1729
|
+
}
|
|
1730
|
+
if (!sameType(moved, expression.type)) {
|
|
1731
|
+
fail("RESOURCE_TYPE_MISMATCH", "Move metadata does not match its resource place.", expression.location)
|
|
1732
|
+
}
|
|
1733
|
+
return moved
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
if (expression.kind == "ErrorMessageRead") {
|
|
1737
|
+
if (!expression.receiver || expression.receiver.kind != "IdentifierExpression") {
|
|
1738
|
+
return fail("INVALID_MEMBER_RECEIVER", "Error message receiver must be one catch binding.", expression.location)
|
|
1739
|
+
}
|
|
1740
|
+
const receiver = resolveBinding(expression.receiver.name, expression.receiver.location, scope, fail)
|
|
1741
|
+
|
|
1742
|
+
if (receiver.type.kind != "ErrorType") {
|
|
1743
|
+
return fail("INVALID_MEMBER_RECEIVER", "Message member requires a typed error catch binding.", expression.receiver.location)
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
return scalarType("string")
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
if (expression.kind == "IntegerLiteral") {
|
|
1750
|
+
if (!Number.isSafeInteger(expression.value)) fail("TYPE_MISMATCH", "Non-safe integer literal.", expression.location)
|
|
1751
|
+
return scalarType("integer")
|
|
1752
|
+
}
|
|
1753
|
+
if (expression.kind == "BooleanLiteral") {
|
|
1754
|
+
if (typeof Reflect.get(expression, "value") != "boolean") {
|
|
1755
|
+
fail("TYPE_MISMATCH", "Invalid boolean literal value.", expression.location)
|
|
1756
|
+
}
|
|
1757
|
+
return scalarType("boolean")
|
|
1758
|
+
}
|
|
1759
|
+
if (expression.kind == "StringLiteral") {
|
|
1760
|
+
const value = Reflect.get(expression, "value")
|
|
1761
|
+
|
|
1762
|
+
if (typeof value != "string" || !hasOnlyUnicodeScalars(value)) {
|
|
1763
|
+
fail("TYPE_MISMATCH", "Invalid Unicode string literal.", expression.location)
|
|
1764
|
+
}
|
|
1765
|
+
return scalarType("string")
|
|
1766
|
+
}
|
|
1767
|
+
if (expression.kind == "OptionalNone") {
|
|
1768
|
+
if (!expectedType || expectedType.kind != "OptionalType") {
|
|
1769
|
+
return fail("MISSING_TYPE", "Optional absence requires an explicit optional type.", expression.location)
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
return expectedType
|
|
1773
|
+
}
|
|
1774
|
+
if (expression.kind == "OptionalSome") {
|
|
1775
|
+
if (!expectedType || expectedType.kind != "OptionalType") {
|
|
1776
|
+
const valueType = inferValueExpressionType(
|
|
1777
|
+
expression.value, scope, functions, records, fail, normalizeOperations, "an optional present value"
|
|
1778
|
+
)
|
|
1779
|
+
|
|
1780
|
+
if (valueType.kind == "OptionalType") {
|
|
1781
|
+
return fail("INVALID_OPTIONAL_CONSTITUENT", "Optional values cannot directly contain another optional.", expression.value.location)
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
return {kind: "OptionalType", valueType}
|
|
1785
|
+
}
|
|
1786
|
+
const valueType = inferValueExpressionType(
|
|
1787
|
+
expression.value, scope, functions, records, fail, normalizeOperations, "an optional present value", expectedType.valueType
|
|
1788
|
+
)
|
|
1789
|
+
|
|
1790
|
+
if (!sameType(valueType, expectedType.valueType)) {
|
|
1791
|
+
fail("TYPE_MISMATCH", `Optional present type ${typeDescription(valueType)}; expected ${typeDescription(expectedType.valueType)}.`, expression.value.location)
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
return expectedType
|
|
1795
|
+
}
|
|
1796
|
+
if (expression.kind == "OptionalIsPresent") {
|
|
1797
|
+
if (!expression.operand || expression.operand.kind != "IdentifierExpression") {
|
|
1798
|
+
return fail("TYPE_MISMATCH", "Optional presence test requires one simple identifier.", expression.location)
|
|
1799
|
+
}
|
|
1800
|
+
const operandType = resolveBinding(expression.operand.name, expression.operand.location, scope, fail).type
|
|
1801
|
+
|
|
1802
|
+
if (operandType.kind != "OptionalType") {
|
|
1803
|
+
return fail("TYPE_MISMATCH", `Presence test received ${typeDescription(operandType)}; expected optional.`, expression.operand.location)
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
return scalarType("boolean")
|
|
1807
|
+
}
|
|
1808
|
+
if (expression.kind == "OptionalUnwrap") {
|
|
1809
|
+
if (!expression.operand || expression.operand.kind != "IdentifierExpression") {
|
|
1810
|
+
return fail("TYPE_MISMATCH", "Optional unwrap requires one simple identifier.", expression.location)
|
|
1811
|
+
}
|
|
1812
|
+
const binding = resolveBinding(expression.operand.name, expression.operand.location, scope, fail)
|
|
1813
|
+
|
|
1814
|
+
if (binding.type.kind != "OptionalType") {
|
|
1815
|
+
return fail("TYPE_MISMATCH", `Optional unwrap received ${typeDescription(binding.type)}; expected optional.`, expression.operand.location)
|
|
1816
|
+
}
|
|
1817
|
+
if (!scope.presenceProofs.has(binding)) {
|
|
1818
|
+
return fail("UNCHECKED_OPTIONAL_UNWRAP", `Optional binding '${expression.operand.name}' is not proven present.`, expression.location)
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
return binding.type.valueType
|
|
1822
|
+
}
|
|
1823
|
+
if (expression.kind == "ListLiteral") {
|
|
1824
|
+
if (!Array.isArray(expression.elements)) {
|
|
1825
|
+
return fail("TYPE_MISMATCH", "List literal elements must be a dense source-ordered array.", expression.location)
|
|
1826
|
+
}
|
|
1827
|
+
if ((!expectedType || expectedType.kind != "ListType") &&
|
|
1828
|
+
(!inferCollectionElements || expression.elements.length == 0)) {
|
|
1829
|
+
return fail("MISSING_TYPE", "List literal requires an explicit recursive list type.", expression.location)
|
|
1830
|
+
}
|
|
1831
|
+
/** @type {import("./types.js").SemanticValueType | undefined} */
|
|
1832
|
+
let inferredElementType
|
|
1833
|
+
for (let index = 0; index < expression.elements.length; index++) {
|
|
1834
|
+
if (!Object.hasOwn(expression.elements, index)) {
|
|
1835
|
+
fail("TYPE_MISMATCH", "Sparse list literals are outside the implemented subset.", expression.location)
|
|
1836
|
+
}
|
|
1837
|
+
const element = expression.elements[index]
|
|
1838
|
+
const actual = inferValueExpressionType(
|
|
1839
|
+
element, scope, functions, records, fail, normalizeOperations, "a list element",
|
|
1840
|
+
expectedType?.kind == "ListType" ? expectedType.elementType : inferredElementType, inferCollectionElements
|
|
1841
|
+
)
|
|
1842
|
+
|
|
1843
|
+
inferredElementType ??= actual
|
|
1844
|
+
const requiredElementType = expectedType?.kind == "ListType" ? expectedType.elementType : inferredElementType
|
|
1845
|
+
|
|
1846
|
+
if (!sameType(actual, requiredElementType)) {
|
|
1847
|
+
fail("TYPE_MISMATCH", `List element type ${typeDescription(actual)}; expected ${typeDescription(requiredElementType)}.`, element.location)
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
return expectedType?.kind == "ListType"
|
|
1852
|
+
? expectedType
|
|
1853
|
+
: {elementType: /** @type {import("./types.js").SemanticValueType} */ (inferredElementType), kind: "ListType"}
|
|
1854
|
+
}
|
|
1855
|
+
if (expression.kind == "MapLiteral") {
|
|
1856
|
+
if (expectedType && expectedType.kind != "MapType") {
|
|
1857
|
+
return fail("TYPE_MISMATCH", `Map literal context ${typeDescription(expectedType)}; expected map.`, expression.location)
|
|
1858
|
+
}
|
|
1859
|
+
if (!expectedType && !inferCollectionElements) {
|
|
1860
|
+
return fail("MISSING_TYPE", "Map literal requires an explicit recursive map type.", expression.location)
|
|
1861
|
+
}
|
|
1862
|
+
if (expectedType?.kind == "MapType" && !isScalarType(expectedType.keyType, "string")) {
|
|
1863
|
+
return fail("TYPE_MISMATCH", "Map key type must be string.", typeLocation(expectedType.keyType, expression.location))
|
|
1864
|
+
}
|
|
1865
|
+
const entries = validateMapLiteralEntries(expression, scope, functions, records, fail, normalizeOperations,
|
|
1866
|
+
inferCollectionElements)
|
|
1867
|
+
|
|
1868
|
+
if (!expectedType && entries.length == 0) {
|
|
1869
|
+
return fail("MISSING_TYPE", "Empty map literal requires an explicit recursive map type.", expression.location)
|
|
1870
|
+
}
|
|
1871
|
+
/** @type {import("./types.js").SemanticValueType | undefined} */
|
|
1872
|
+
let inferredValueType
|
|
1873
|
+
|
|
1874
|
+
for (const entry of entries) {
|
|
1875
|
+
const actual = inferValueExpressionType(
|
|
1876
|
+
entry.value, scope, functions, records, fail, normalizeOperations, "a map value",
|
|
1877
|
+
expectedType?.kind == "MapType" ? expectedType.valueType : inferredValueType, inferCollectionElements
|
|
1878
|
+
)
|
|
1879
|
+
inferredValueType ??= actual
|
|
1880
|
+
const requiredValueType = expectedType?.kind == "MapType" ? expectedType.valueType : inferredValueType
|
|
1881
|
+
|
|
1882
|
+
if (!sameType(actual, requiredValueType)) {
|
|
1883
|
+
fail("TYPE_MISMATCH", `Map value type ${typeDescription(actual)}; expected ${typeDescription(requiredValueType)}.`, entry.value.location)
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
return expectedType?.kind == "MapType" ? expectedType : {
|
|
1888
|
+
keyType: scalarType("string"),
|
|
1889
|
+
kind: "MapType",
|
|
1890
|
+
valueType: /** @type {import("./types.js").SemanticValueType} */ (inferredValueType)
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
if (expression.kind == "OrderedMapLiteral") {
|
|
1894
|
+
if (expectedType && expectedType.kind != "OrderedMapType") {
|
|
1895
|
+
return fail("TYPE_MISMATCH", `Ordered-map literal context ${typeDescription(expectedType)}; expected ordered map.`, expression.location)
|
|
1896
|
+
}
|
|
1897
|
+
if (!expectedType && !inferCollectionElements) {
|
|
1898
|
+
return fail("MISSING_TYPE", "Ordered-map literal requires an explicit recursive ordered-map type.", expression.location)
|
|
1899
|
+
}
|
|
1900
|
+
if (expectedType?.kind == "OrderedMapType" && !isScalarType(expectedType.keyType, "string")) {
|
|
1901
|
+
return fail("TYPE_MISMATCH", "Ordered-map key type must be string.", typeLocation(expectedType.keyType, expression.location))
|
|
1902
|
+
}
|
|
1903
|
+
const entries = validateMapLiteralEntries(expression, scope, functions, records, fail, normalizeOperations,
|
|
1904
|
+
inferCollectionElements)
|
|
1905
|
+
|
|
1906
|
+
if (!expectedType && entries.length == 0) {
|
|
1907
|
+
return fail("MISSING_TYPE", "Empty ordered-map literal requires an explicit recursive ordered-map type.", expression.location)
|
|
1908
|
+
}
|
|
1909
|
+
/** @type {import("./types.js").SemanticValueType | undefined} */
|
|
1910
|
+
let inferredValueType
|
|
1911
|
+
|
|
1912
|
+
for (const entry of entries) {
|
|
1913
|
+
const actual = inferValueExpressionType(
|
|
1914
|
+
entry.value, scope, functions, records, fail, normalizeOperations, "an ordered-map value",
|
|
1915
|
+
expectedType?.kind == "OrderedMapType" ? expectedType.valueType : inferredValueType, inferCollectionElements
|
|
1916
|
+
)
|
|
1917
|
+
inferredValueType ??= actual
|
|
1918
|
+
const requiredValueType = expectedType?.kind == "OrderedMapType" ? expectedType.valueType : inferredValueType
|
|
1919
|
+
|
|
1920
|
+
if (!sameType(actual, requiredValueType)) {
|
|
1921
|
+
fail("TYPE_MISMATCH", `Ordered-map value type ${typeDescription(actual)}; expected ${typeDescription(requiredValueType)}.`,
|
|
1922
|
+
entry.value.location)
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
return expectedType?.kind == "OrderedMapType" ? expectedType : {
|
|
1927
|
+
keyType: scalarType("string"),
|
|
1928
|
+
kind: "OrderedMapType",
|
|
1929
|
+
order: "insertion",
|
|
1930
|
+
valueType: /** @type {import("./types.js").SemanticValueType} */ (inferredValueType)
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
if (expression.kind == "ListIndexExpression") {
|
|
1934
|
+
const collectionType = inferValueExpressionType(expression.collection, scope, functions, records, fail, normalizeOperations, "a list receiver")
|
|
1935
|
+
const indexType = inferValueExpressionType(expression.index, scope, functions, records, fail, normalizeOperations, "a list index", scalarType("integer"))
|
|
1936
|
+
|
|
1937
|
+
if (collectionType.kind != "ListType") {
|
|
1938
|
+
return fail("TYPE_MISMATCH", `List index receiver type ${typeDescription(collectionType)}; expected list.`, expression.collection.location)
|
|
1939
|
+
}
|
|
1940
|
+
if (!isScalarType(indexType, "integer")) {
|
|
1941
|
+
fail("TYPE_MISMATCH", `List index type ${typeDescription(indexType)}; expected integer.`, expression.index.location)
|
|
1942
|
+
}
|
|
1943
|
+
const knownCollection = knownValueForExpression(expression.collection, scope)
|
|
1944
|
+
const knownIndex = knownValueForExpression(expression.index, scope)
|
|
1945
|
+
|
|
1946
|
+
if (knownCollection?.kind == "ListLiteral" && knownIndex?.kind == "IntegerLiteral" &&
|
|
1947
|
+
(knownIndex.value < 0 || knownIndex.value >= knownCollection.elements.length)) {
|
|
1948
|
+
fail("INDEX_OUT_OF_BOUNDS", `List index ${knownIndex.value} is outside a literal list of size ${knownCollection.elements.length}.`, expression.location)
|
|
1949
|
+
}
|
|
1950
|
+
const proven = provenListElement(expression.collection, expression.index, scope)
|
|
1951
|
+
|
|
1952
|
+
validateAccessTotality(expression, proven !== undefined, normalizeOperations, fail)
|
|
1953
|
+
|
|
1954
|
+
return collectionType.elementType
|
|
1955
|
+
}
|
|
1956
|
+
if (expression.kind == "MapLookupExpression") {
|
|
1957
|
+
const collectionType = inferValueExpressionType(expression.collection, scope, functions, records, fail, normalizeOperations, "a map receiver")
|
|
1958
|
+
const keyType = inferValueExpressionType(expression.key, scope, functions, records, fail, normalizeOperations, "a map key", scalarType("string"))
|
|
1959
|
+
|
|
1960
|
+
if (collectionType.kind != "MapType" && collectionType.kind != "OrderedMapType") {
|
|
1961
|
+
return fail("TYPE_MISMATCH", `Map lookup receiver type ${typeDescription(collectionType)}; expected map.`, expression.collection.location)
|
|
1962
|
+
}
|
|
1963
|
+
if (!isScalarType(keyType, "string")) {
|
|
1964
|
+
fail("TYPE_MISMATCH", `Map lookup key type ${typeDescription(keyType)}; expected string.`, expression.key.location)
|
|
1965
|
+
}
|
|
1966
|
+
const knownCollection = knownValueForExpression(expression.collection, scope)
|
|
1967
|
+
const knownKey = knownValueForExpression(expression.key, scope)
|
|
1968
|
+
|
|
1969
|
+
if ((knownCollection?.kind == "MapLiteral" || knownCollection?.kind == "OrderedMapLiteral") &&
|
|
1970
|
+
knownKey?.kind == "StringLiteral" &&
|
|
1971
|
+
!knownCollection.entries.some((entry) => entry.key.value == knownKey.value)) {
|
|
1972
|
+
fail("MISSING_MAP_KEY", `Map key '${knownKey.value}' is absent from the literal initializer.`, expression.location)
|
|
1973
|
+
}
|
|
1974
|
+
const proven = provenMapValue(expression.collection, expression.key, scope)
|
|
1975
|
+
|
|
1976
|
+
validateAccessTotality(expression, proven !== undefined, normalizeOperations, fail)
|
|
1977
|
+
|
|
1978
|
+
return collectionType.valueType
|
|
1979
|
+
}
|
|
1980
|
+
if (expression.kind == "CollectionSizeExpression") {
|
|
1981
|
+
const collectionType = inferValueExpressionType(expression.collection, scope, functions, records, fail, normalizeOperations, "a collection-size receiver")
|
|
1982
|
+
|
|
1983
|
+
if (collectionType.kind != "ListType" && collectionType.kind != "MapType" && collectionType.kind != "OrderedMapType") {
|
|
1984
|
+
return fail("TYPE_MISMATCH", `Collection size receiver type ${typeDescription(collectionType)}; expected list or map.`, expression.collection.location)
|
|
1985
|
+
}
|
|
1986
|
+
const collectionKind = collectionType.kind == "ListType" ? "list" : "map"
|
|
1987
|
+
|
|
1988
|
+
if (normalizeOperations) {
|
|
1989
|
+
if (expression.collectionKind !== undefined && expression.collectionKind != collectionKind) {
|
|
1990
|
+
fail("TYPE_MISMATCH", "Collection size operation does not match its receiver type.", expression.location)
|
|
1991
|
+
}
|
|
1992
|
+
expression.collectionKind = collectionKind
|
|
1993
|
+
}
|
|
1994
|
+
else if (expression.collectionKind != collectionKind) {
|
|
1995
|
+
fail("TYPE_MISMATCH", "Collection size kind does not match its receiver type.", expression.location)
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
return scalarType("integer")
|
|
1999
|
+
}
|
|
2000
|
+
if (expression.kind == "EffectCallExpression") {
|
|
2001
|
+
if (!Array.isArray(expression.arguments) || expression.arguments.length != expression.resolution.parameterTypes.length) {
|
|
2002
|
+
return fail("TYPE_MISMATCH", "Effect call argument count does not match its authorized operation.", expression.location)
|
|
2003
|
+
}
|
|
2004
|
+
const resourceFlow = expression.resolution.resourceFlow
|
|
2005
|
+
|
|
2006
|
+
if (resourceFlow.kind == "borrow" || resourceFlow.kind == "close") {
|
|
2007
|
+
const borrow = expression.arguments[resourceFlow.parameterIndex]
|
|
2008
|
+
const expectedMode = resourceFlow.kind == "close" ? "exclusive" : "shared"
|
|
2009
|
+
|
|
2010
|
+
if (borrow?.kind != "OwnedBorrowExpression" || borrow.mode != expectedMode) {
|
|
2011
|
+
fail("INVALID_RESOURCE_BORROW", `INVALID_RESOURCE_BORROW: ${resourceFlow.kind} requires an immediate ${expectedMode} borrow.`,
|
|
2012
|
+
borrow?.location ?? expression.location)
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
for (let index = 0; index < expression.arguments.length; index += 1) {
|
|
2016
|
+
const expected = identityAsValueType(expression.resolution.parameterTypes[index], expression.arguments[index].location, fail)
|
|
2017
|
+
const actual = inferValueExpressionType(expression.arguments[index], scope, functions, records, fail, normalizeOperations,
|
|
2018
|
+
"an effect argument", expected)
|
|
2019
|
+
|
|
2020
|
+
if (!sameType(actual, expected)) {
|
|
2021
|
+
fail("RESOURCE_TYPE_MISMATCH", `Effect argument type ${typeDescription(actual)}; expected ${typeDescription(expected)}.`,
|
|
2022
|
+
expression.arguments[index].location)
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
if (expression.resolution.returnType == "void") return "void"
|
|
2026
|
+
return identityAsValueType(expression.resolution.returnType, expression.location, fail)
|
|
2027
|
+
}
|
|
2028
|
+
if (expression.kind == "ReferenceConstruction") {
|
|
2029
|
+
const referenceType = validateValueTypeReference(expression.reference, expression.location, fail, undefined, records,
|
|
2030
|
+
scope.typeParameters)
|
|
2031
|
+
|
|
2032
|
+
if (referenceType.kind != "ReferenceType" && referenceType.kind != "OwnedReferenceType") {
|
|
2033
|
+
return fail("TYPE_MISMATCH", "Reference construction requires a nominal reference class type.", expression.location)
|
|
2034
|
+
}
|
|
2035
|
+
const declaration = referenceClassesFor(records).get(referenceType.declarationId)
|
|
2036
|
+
|
|
2037
|
+
if (!declaration) return fail("UNKNOWN_CLASS", `Unknown reference class '${referenceType.declarationId}'.`, expression.location)
|
|
2038
|
+
if (!isDenseArray(expression.arguments)) {
|
|
2039
|
+
return fail("TYPE_MISMATCH", "Constructor arguments must be a dense ordered array.", expression.location)
|
|
2040
|
+
}
|
|
2041
|
+
if (expression.arguments.length != declaration.constructor.parameters.length) {
|
|
2042
|
+
return fail("CONSTRUCTOR_ARITY_MISMATCH",
|
|
2043
|
+
`Constructor for '${declaration.name}' has ${expression.arguments.length} arguments; expected ${declaration.constructor.parameters.length}.`,
|
|
2044
|
+
expression.location)
|
|
2045
|
+
}
|
|
2046
|
+
const parameterTypes = declaration.constructor.parameters.map((parameter) =>
|
|
2047
|
+
validateValueTypeReference(parameter.type, parameter.location, fail, undefined, records))
|
|
2048
|
+
|
|
2049
|
+
for (let index = 0; index < expression.arguments.length; index += 1) {
|
|
2050
|
+
expression.arguments[index] = normalizeOwnedMove(expression.arguments[index], parameterTypes[index], normalizeOperations, fail)
|
|
2051
|
+
const actual = inferValueExpressionType(expression.arguments[index], scope, functions, records, fail,
|
|
2052
|
+
normalizeOperations, "a constructor argument", parameterTypes[index])
|
|
2053
|
+
|
|
2054
|
+
if (!sameType(actual, parameterTypes[index])) {
|
|
2055
|
+
fail("TYPE_MISMATCH", `Constructor argument type ${typeDescription(actual)}; expected ${typeDescription(parameterTypes[index])}.`,
|
|
2056
|
+
expression.arguments[index].location)
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
const resolution = {
|
|
2060
|
+
declarationId: /** @type {string} */ (declaration.constructor.id),
|
|
2061
|
+
kind: /** @type {const} */ ("ResolvedConstructorSignature"),
|
|
2062
|
+
parameterTypes: parameterTypes.map((type) => /** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(type)))
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
if (normalizeOperations) expression.resolution = resolution
|
|
2066
|
+
else validateReferenceResolution(expression.resolution, resolution, expression.location, fail, "constructor")
|
|
2067
|
+
|
|
2068
|
+
return referenceType
|
|
2069
|
+
}
|
|
2070
|
+
if (expression.kind == "MethodCallExpression") {
|
|
2071
|
+
const receiverType = inferValueExpressionType(expression.receiver, scope, functions, records, fail,
|
|
2072
|
+
normalizeOperations, "a method receiver")
|
|
2073
|
+
|
|
2074
|
+
if (receiverType.kind != "ReferenceType" && receiverType.kind != "OwnedReferenceType") {
|
|
2075
|
+
return fail("INVALID_METHOD_RECEIVER", "Method call receiver must have a reference class type.", expression.receiver.location)
|
|
2076
|
+
}
|
|
2077
|
+
const declaration = referenceClassesFor(records).get(receiverType.declarationId)
|
|
2078
|
+
|
|
2079
|
+
if (!declaration) return fail("UNKNOWN_CLASS", `Unknown reference class '${receiverType.declarationId}'.`, expression.receiver.location)
|
|
2080
|
+
const method = declaration.methods.find((candidate) => candidate.id == expression.method) ??
|
|
2081
|
+
(normalizeOperations ? declaration.methods.find((candidate) => candidate.name == expression.method) : undefined)
|
|
2082
|
+
|
|
2083
|
+
if (!method) return fail("UNKNOWN_METHOD", `Class '${declaration.name}' has no method '${expression.method}'.`,
|
|
2084
|
+
roleLocation(expression, "member"))
|
|
2085
|
+
if (normalizeOperations) expression.method = /** @type {string} */ (method.id)
|
|
2086
|
+
if (!isDenseArray(expression.arguments)) return fail("TYPE_MISMATCH", "Method arguments must be a dense ordered array.", expression.location)
|
|
2087
|
+
if (expression.arguments.length != method.parameters.length) {
|
|
2088
|
+
return fail("METHOD_ARITY_MISMATCH",
|
|
2089
|
+
`Method '${method.name}' has ${expression.arguments.length} arguments; expected ${method.parameters.length}.`, expression.location)
|
|
2090
|
+
}
|
|
2091
|
+
const parameterTypes = method.parameters.map((parameter) =>
|
|
2092
|
+
validateValueTypeReference(parameter.type, parameter.location, fail, undefined, records))
|
|
2093
|
+
|
|
2094
|
+
for (let index = 0; index < expression.arguments.length; index += 1) {
|
|
2095
|
+
expression.arguments[index] = normalizeOwnedMove(expression.arguments[index], parameterTypes[index], normalizeOperations, fail)
|
|
2096
|
+
const actual = inferValueExpressionType(expression.arguments[index], scope, functions, records, fail,
|
|
2097
|
+
normalizeOperations, "a method argument", parameterTypes[index])
|
|
2098
|
+
|
|
2099
|
+
if (!sameType(actual, parameterTypes[index])) {
|
|
2100
|
+
fail("TYPE_MISMATCH", `Method argument type ${typeDescription(actual)}; expected ${typeDescription(parameterTypes[index])}.`,
|
|
2101
|
+
expression.arguments[index].location)
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
const returnType = validateReturnTypeReference(method.returnType, method.location, fail, records)
|
|
2105
|
+
const resourceFlow = declaration.ownership?.kind == "ownedResource"
|
|
2106
|
+
? ownedMethodResourceFlow(declaration, method, fail) : undefined
|
|
2107
|
+
const resolution = {
|
|
2108
|
+
declarationId: /** @type {string} */ (method.id),
|
|
2109
|
+
kind: /** @type {const} */ ("ResolvedMethodSignature"),
|
|
2110
|
+
parameterTypes: parameterTypes.map((type) => /** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(type))),
|
|
2111
|
+
returnType: typeIdentity(returnType),
|
|
2112
|
+
...(resourceFlow ? {resourceFlow} : {})
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
if (expression.receiver.kind == "OwnedBorrowExpression" && resourceFlow) {
|
|
2116
|
+
const expectedMode = resourceFlow.kind == "terminal" ? "exclusive" : "shared"
|
|
2117
|
+
|
|
2118
|
+
if (expression.receiver.mode != expectedMode) {
|
|
2119
|
+
fail("INVALID_RESOURCE_BORROW", `INVALID_RESOURCE_BORROW: Owned method requires an immediate ${expectedMode} receiver borrow.`,
|
|
2120
|
+
expression.receiver.location)
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
if (receiverType.kind == "OwnedReferenceType" && expression.receiver.kind != "OwnedBorrowExpression") {
|
|
2125
|
+
if (!normalizeOperations) fail("INVALID_RESOURCE_BORROW", "Owned method receiver requires an immediate borrow.", expression.receiver.location)
|
|
2126
|
+
if (expression.receiver.kind != "IdentifierExpression") {
|
|
2127
|
+
fail("BORROW_ESCAPE", "Owned method receiver borrow must name one immediate binding.", expression.receiver.location)
|
|
2128
|
+
}
|
|
2129
|
+
expression.receiver = {
|
|
2130
|
+
expression: expression.receiver,
|
|
2131
|
+
kind: "OwnedBorrowExpression",
|
|
2132
|
+
location: expression.receiver.location,
|
|
2133
|
+
mode: resourceFlow?.kind == "terminal" ? "exclusive" : "shared",
|
|
2134
|
+
sourceProvenance: expression.receiver.sourceProvenance,
|
|
2135
|
+
type: receiverType
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
if (normalizeOperations) expression.resolution = resolution
|
|
2140
|
+
else validateReferenceResolution(expression.resolution, resolution, expression.location, fail, "method")
|
|
2141
|
+
|
|
2142
|
+
return isVoidType(returnType) ? "void" : returnType
|
|
2143
|
+
}
|
|
2144
|
+
if (expression.kind == "RecordConstruction") {
|
|
2145
|
+
const recordType = validateValueTypeReference(expression.record, expression.location, fail, undefined, records, scope.typeParameters)
|
|
2146
|
+
|
|
2147
|
+
if (recordType.kind != "RecordType") {
|
|
2148
|
+
return fail("TYPE_MISMATCH", "Record construction requires a nominal record type.", expression.location)
|
|
2149
|
+
}
|
|
2150
|
+
const declaration = records.get(recordType.declarationId)
|
|
2151
|
+
|
|
2152
|
+
if (!declaration) return fail("UNKNOWN_RECORD", `Unknown record declaration '${recordType.declarationId}'.`, expression.location)
|
|
2153
|
+
if (!Array.isArray(expression.arguments) || expression.arguments.some((_argument, index) => !Object.hasOwn(expression.arguments, index))) {
|
|
2154
|
+
return fail("TYPE_MISMATCH", "Record construction arguments must be a dense ordered array.", expression.location)
|
|
2155
|
+
}
|
|
2156
|
+
if (expression.arguments.length != declaration.fields.length) {
|
|
2157
|
+
return fail(
|
|
2158
|
+
"RECORD_ARITY_MISMATCH",
|
|
2159
|
+
`Record '${declaration.name}' construction has ${expression.arguments.length} arguments; expected ${declaration.fields.length}.`,
|
|
2160
|
+
expression.location
|
|
2161
|
+
)
|
|
2162
|
+
}
|
|
2163
|
+
const substitutions = recordTypeSubstitutions(recordType, declaration)
|
|
2164
|
+
for (let index = 0; index < expression.arguments.length; index += 1) {
|
|
2165
|
+
const argument = expression.arguments[index]
|
|
2166
|
+
const field = declaration.fields[index]
|
|
2167
|
+
const fieldType = substituteValueType(field.type, substitutions)
|
|
2168
|
+
const actual = inferValueExpressionType(
|
|
2169
|
+
argument, scope, functions, records, fail, normalizeOperations, `record field '${field.name}'`, fieldType
|
|
2170
|
+
)
|
|
2171
|
+
|
|
2172
|
+
if (!sameType(actual, fieldType)) {
|
|
2173
|
+
fail("TYPE_MISMATCH", `Record field '${field.name}' argument type ${typeDescription(actual)}; expected ${typeDescription(fieldType)}.`, argument.location)
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
return recordType
|
|
2178
|
+
}
|
|
2179
|
+
if (expression.kind == "MemberRead") {
|
|
2180
|
+
const receiverType = inferValueExpressionType(expression.receiver, scope, functions, records, fail, normalizeOperations, "a member receiver")
|
|
2181
|
+
|
|
2182
|
+
if (receiverType.kind != "RecordType") {
|
|
2183
|
+
return fail("INVALID_MEMBER_RECEIVER", `Member receiver type ${typeDescription(receiverType)}; expected record.`, expression.receiver.location)
|
|
2184
|
+
}
|
|
2185
|
+
const declaration = records.get(receiverType.declarationId)
|
|
2186
|
+
|
|
2187
|
+
if (!declaration) return fail("UNKNOWN_RECORD", `Unknown record declaration '${receiverType.declarationId}'.`, expression.receiver.location)
|
|
2188
|
+
const field = declaration.fields.find((candidate) => candidate.id == expression.field) ??
|
|
2189
|
+
(normalizeOperations ? declaration.fields.find((candidate) => candidate.name == expression.field) : undefined)
|
|
2190
|
+
|
|
2191
|
+
if (!field) fail("UNKNOWN_FIELD", `Record '${declaration.name}' has no field '${expression.field}'.`, roleLocation(expression, "member"))
|
|
2192
|
+
if (normalizeOperations) expression.field = /** @type {string} */ (field.id)
|
|
2193
|
+
|
|
2194
|
+
return substituteValueType(field.type, recordTypeSubstitutions(receiverType, declaration))
|
|
2195
|
+
}
|
|
2196
|
+
if (expression.kind == "CallExpression") {
|
|
2197
|
+
const functionDeclaration = functions.get(expression.callee)
|
|
2198
|
+
|
|
2199
|
+
if (!functionDeclaration) {
|
|
2200
|
+
return fail("UNRESOLVED_BINDING", `Unknown function '${expression.callee}'.`, roleLocation(expression, "callee"))
|
|
2201
|
+
}
|
|
2202
|
+
if (expression.arguments.length != functionDeclaration.parameters.length) {
|
|
2203
|
+
return fail("TYPE_MISMATCH", `Call argument count for '${expression.callee}'.`, expression.location)
|
|
2204
|
+
}
|
|
2205
|
+
const declarationTypeParameters = declarationTypeParameterIds(functionDeclaration)
|
|
2206
|
+
/** @type {Map<string, import("./types.js").SemanticValueType>} */
|
|
2207
|
+
const substitutions = new Map()
|
|
2208
|
+
const parameterTypes = functionDeclaration.parameters.map((parameter) => validateValueTypeReference(
|
|
2209
|
+
parameter.type, parameter.location, fail, undefined, records, declarationTypeParameters
|
|
2210
|
+
))
|
|
2211
|
+
for (let index = 0; index < expression.arguments.length; index += 1) {
|
|
2212
|
+
expression.arguments[index] = normalizeOwnedMove(expression.arguments[index], parameterTypes[index], normalizeOperations, fail)
|
|
2213
|
+
}
|
|
2214
|
+
for (let index = 0; index < expression.arguments.length; index++) {
|
|
2215
|
+
const argument = expression.arguments[index]
|
|
2216
|
+
const expectedType = parameterTypes[index]
|
|
2217
|
+
const gathersGenericEvidence = typeContainsAnyVariable(expectedType)
|
|
2218
|
+
|
|
2219
|
+
if (gathersGenericEvidence) {
|
|
2220
|
+
inferGenericArgumentEvidence(expectedType, argument, substitutions, scope, functions, records, fail,
|
|
2221
|
+
normalizeOperations)
|
|
2222
|
+
continue
|
|
2223
|
+
}
|
|
2224
|
+
const actualType = inferValueExpressionType(
|
|
2225
|
+
argument, scope, functions, records, fail, normalizeOperations, "a call argument", expectedType
|
|
2226
|
+
)
|
|
2227
|
+
|
|
2228
|
+
if ((functionDeclaration.typeParameters?.length ?? 0) > 0) {
|
|
2229
|
+
inferTypeArguments(expectedType, actualType, substitutions, argument.location, fail)
|
|
2230
|
+
} else if (!sameType(actualType, expectedType)) {
|
|
2231
|
+
fail("TYPE_MISMATCH", `Call argument type ${typeDescription(actualType)}; expected ${typeDescription(expectedType)}.`, argument.location)
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
for (const parameter of functionDeclaration.typeParameters ?? []) {
|
|
2235
|
+
if (!substitutions.has(/** @type {string} */ (parameter.id))) {
|
|
2236
|
+
const evidenceFreeIndex = parameterTypes.findIndex((type) =>
|
|
2237
|
+
typeContainsVariable(type, /** @type {string} */ (parameter.id)))
|
|
2238
|
+
|
|
2239
|
+
if (evidenceFreeIndex >= 0) {
|
|
2240
|
+
fail("GENERIC_INFERENCE_FAILURE", "Cannot infer a type parameter from an evidence-free call argument.",
|
|
2241
|
+
expression.arguments[evidenceFreeIndex].location)
|
|
2242
|
+
}
|
|
2243
|
+
fail("GENERIC_INFERENCE_FAILURE", `Cannot infer type parameter '${parameter.name}' from call arguments.`, expression.location)
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
if ([...substitutions.values()].some((type) => containsOwnedType(type))) {
|
|
2247
|
+
fail("RESOURCE_ALIAS", "Owned resources and references cannot be inferred as generic arguments.", expression.location)
|
|
2248
|
+
}
|
|
2249
|
+
for (let index = 0; index < functionDeclaration.parameters.length; index += 1) {
|
|
2250
|
+
const expected = substituteValueType(functionDeclaration.parameters[index].type, substitutions)
|
|
2251
|
+
const actual = inferValueExpressionType(expression.arguments[index], scope, functions, records, fail,
|
|
2252
|
+
normalizeOperations, "a call argument", expected)
|
|
2253
|
+
|
|
2254
|
+
if (!sameType(actual, expected)) {
|
|
2255
|
+
fail("TYPE_MISMATCH", `Call argument type ${typeDescription(actual)}; expected ${typeDescription(expected)}.`, expression.arguments[index].location)
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
const resolution = signatureFor(functionDeclaration, records, fail, substitutions)
|
|
2259
|
+
|
|
2260
|
+
if (normalizeOperations) expression.resolution = resolution
|
|
2261
|
+
else validateResolution(expression.resolution, resolution, expression.location, fail)
|
|
2262
|
+
const returnType = substituteReturnType(validateReturnTypeReference(functionDeclaration.returnType,
|
|
2263
|
+
functionDeclaration.location, fail, records, declarationTypeParameters), substitutions)
|
|
2264
|
+
|
|
2265
|
+
return isVoidType(returnType) ? "void" : returnType
|
|
2266
|
+
}
|
|
2267
|
+
if (expression.kind == "UnaryExpression") {
|
|
2268
|
+
const operand = inferValueExpressionType(expression.operand, scope, functions, records, fail, normalizeOperations, "a unary operand")
|
|
2269
|
+
const operandType = scalarNameForOperation(operand, expression.operand.location, fail)
|
|
2270
|
+
const operation = normalizeOperations
|
|
2271
|
+
? normalizeUnaryOperation(adaptedOperationFor(expression), operandType, expression.operand.location, fail)
|
|
2272
|
+
: expression.operation
|
|
2273
|
+
|
|
2274
|
+
if (normalizeOperations) setNormalizedOperation(expression, operation, unaryOperationSignatures[operation].result)
|
|
2275
|
+
|
|
2276
|
+
const signature = unaryOperationSignatures[operation]
|
|
2277
|
+
|
|
2278
|
+
if (!signature) return fail("TYPE_MISMATCH", `Unknown unary operation ${String(operation)}.`, expression.location)
|
|
2279
|
+
if (operandType != signature.operand) {
|
|
2280
|
+
return fail("INVALID_OPERAND_TYPE", `${operation} requires ${signature.operand}; received ${operandType}.`, expression.operand.location)
|
|
2281
|
+
}
|
|
2282
|
+
if (expression.type != signature.result) {
|
|
2283
|
+
return fail("TYPE_MISMATCH", `${operation} result type ${String(expression.type)}; expected ${signature.result}.`, expression.location)
|
|
2284
|
+
}
|
|
2285
|
+
return scalarType(signature.result)
|
|
2286
|
+
}
|
|
2287
|
+
if (expression.kind == "BinaryExpression") {
|
|
2288
|
+
const left = inferValueExpressionType(expression.left, scope, functions, records, fail, normalizeOperations, "a binary operand")
|
|
2289
|
+
const right = inferValueExpressionType(expression.right, scope, functions, records, fail, normalizeOperations, "a binary operand")
|
|
2290
|
+
const leftType = scalarNameForOperation(left, expression.left.location, fail)
|
|
2291
|
+
const rightType = scalarNameForOperation(right, expression.right.location, fail)
|
|
2292
|
+
const operation = normalizeOperations
|
|
2293
|
+
? normalizeBinaryOperation(adaptedOperationFor(expression), leftType, rightType, expression.left.location, expression.right.location, expression.location, fail)
|
|
2294
|
+
: expression.operation
|
|
2295
|
+
|
|
2296
|
+
if (normalizeOperations) setNormalizedOperation(expression, operation, binaryOperationSignatures[operation].result)
|
|
2297
|
+
|
|
2298
|
+
const signature = binaryOperationSignatures[operation]
|
|
2299
|
+
|
|
2300
|
+
if (!signature) return fail("TYPE_MISMATCH", `Unknown binary operation ${String(operation)}.`, expression.location)
|
|
2301
|
+
if (leftType != signature.left) {
|
|
2302
|
+
return fail("INVALID_OPERAND_TYPE", `${operation} requires ${signature.left} left operand; received ${leftType}.`, expression.left.location)
|
|
2303
|
+
}
|
|
2304
|
+
if (rightType != signature.right) {
|
|
2305
|
+
return fail("INVALID_OPERAND_TYPE", `${operation} requires ${signature.right} right operand; received ${rightType}.`, expression.right.location)
|
|
2306
|
+
}
|
|
2307
|
+
if (expression.type != signature.result) {
|
|
2308
|
+
return fail("TYPE_MISMATCH", `${operation} result type ${String(expression.type)}; expected ${signature.result}.`, expression.location)
|
|
2309
|
+
}
|
|
2310
|
+
return scalarType(signature.result)
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
const unexpected = /** @type {{kind: string, location: import("./types.js").SourceLocation}} */ (expression)
|
|
2314
|
+
|
|
2315
|
+
return fail("TYPE_MISMATCH", unexpected.kind, unexpected.location)
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
/**
|
|
2319
|
+
* Validates map layout and fixed string keys independently of contextual value inference.
|
|
2320
|
+
* @param {import("./types.js").MapLiteral | import("./types.js").OrderedMapLiteral} expression - Candidate map literal.
|
|
2321
|
+
* @param {Scope} scope - Visible lexical scope.
|
|
2322
|
+
* @param {Map<string, import("./types.js").FunctionDeclaration>} functions - Module function signatures.
|
|
2323
|
+
* @param {RecordRegistry} records - Record declarations by identity.
|
|
2324
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
2325
|
+
* @param {boolean} normalizeOperations - Whether to normalize frontend intent.
|
|
2326
|
+
* @param {boolean} inferCollectionElements - Whether nested collection evidence may be inferred.
|
|
2327
|
+
* @returns {import("./types.js").MapEntry[]} Validated dense entries.
|
|
2328
|
+
*/
|
|
2329
|
+
function validateMapLiteralEntries(expression, scope, functions, records, fail, normalizeOperations,
|
|
2330
|
+
inferCollectionElements) {
|
|
2331
|
+
if (!Array.isArray(expression.entries)) {
|
|
2332
|
+
return fail("TYPE_MISMATCH", "Map literal entries must be a dense source-ordered array.", expression.location)
|
|
2333
|
+
}
|
|
2334
|
+
if (!isDenseArray(expression.entries)) {
|
|
2335
|
+
return fail("TYPE_MISMATCH", "Sparse map literals are outside the implemented subset.", expression.location)
|
|
2336
|
+
}
|
|
2337
|
+
const keys = new Set()
|
|
2338
|
+
|
|
2339
|
+
for (const entry of expression.entries) {
|
|
2340
|
+
if (!entry || entry.kind != "MapEntry" || !entry.key || entry.key.kind != "StringLiteral") {
|
|
2341
|
+
fail("TYPE_MISMATCH", "Map entries require literal string keys.", entry?.location ?? expression.location)
|
|
2342
|
+
}
|
|
2343
|
+
inferValueExpressionType(entry.key, scope, functions, records, fail, normalizeOperations, "a map key",
|
|
2344
|
+
scalarType("string"), inferCollectionElements)
|
|
2345
|
+
if (isNumericString(entry.key.value)) {
|
|
2346
|
+
fail("INVALID_MAP_KEY", "Map initializer keys must be nonnumeric strings.", entry.key.location)
|
|
2347
|
+
}
|
|
2348
|
+
if (keys.has(entry.key.value)) {
|
|
2349
|
+
fail("DUPLICATE_MAP_KEY", `Duplicate ${expression.kind == "OrderedMapLiteral" ? "ordered-map" : "map"} key '${entry.key.value}'.`,
|
|
2350
|
+
entry.key.location)
|
|
2351
|
+
}
|
|
2352
|
+
keys.add(entry.key.value)
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
return expression.entries
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
/**
|
|
2359
|
+
* Collects argument-only generic evidence while deferring recursively contextual literals.
|
|
2360
|
+
* The complete argument is validated against its closed substituted type after inference.
|
|
2361
|
+
* @param {import("./types.js").SemanticValueType} formal - Open declaration parameter type.
|
|
2362
|
+
* @param {import("./types.js").Expression} expression - Call argument or nested collection value.
|
|
2363
|
+
* @param {Map<string, import("./types.js").SemanticValueType>} substitutions - Inference state.
|
|
2364
|
+
* @param {Scope} scope - Visible lexical scope.
|
|
2365
|
+
* @param {Map<string, import("./types.js").FunctionDeclaration>} functions - Module function signatures.
|
|
2366
|
+
* @param {RecordRegistry} records - Record declarations by identity.
|
|
2367
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
2368
|
+
* @param {boolean} normalizeOperations - Whether to normalize frontend intent.
|
|
2369
|
+
* @returns {void}
|
|
2370
|
+
*/
|
|
2371
|
+
function inferGenericArgumentEvidence(formal, expression, substitutions, scope, functions, records, fail,
|
|
2372
|
+
normalizeOperations) {
|
|
2373
|
+
if (expression.kind == "OptionalNone") return
|
|
2374
|
+
if (formal.kind == "OptionalType" && expression.kind == "OptionalSome") {
|
|
2375
|
+
inferGenericArgumentEvidence(formal.valueType, expression.value, substitutions, scope, functions, records, fail,
|
|
2376
|
+
normalizeOperations)
|
|
2377
|
+
return
|
|
2378
|
+
}
|
|
2379
|
+
if (formal.kind == "ListType" && expression.kind == "ListLiteral") {
|
|
2380
|
+
if (!Array.isArray(expression.elements)) {
|
|
2381
|
+
fail("TYPE_MISMATCH", "List literal elements must be a dense source-ordered array.", expression.location)
|
|
2382
|
+
}
|
|
2383
|
+
if (!isDenseArray(expression.elements)) {
|
|
2384
|
+
fail("TYPE_MISMATCH", "Sparse list literals are outside the implemented subset.", expression.location)
|
|
2385
|
+
}
|
|
2386
|
+
for (const element of expression.elements) {
|
|
2387
|
+
inferGenericArgumentEvidence(formal.elementType, element, substitutions, scope, functions, records, fail,
|
|
2388
|
+
normalizeOperations)
|
|
2389
|
+
}
|
|
2390
|
+
return
|
|
2391
|
+
}
|
|
2392
|
+
if ((formal.kind == "MapType" && expression.kind == "MapLiteral") ||
|
|
2393
|
+
(formal.kind == "OrderedMapType" && expression.kind == "OrderedMapLiteral")) {
|
|
2394
|
+
const entries = validateMapLiteralEntries(expression, scope, functions, records, fail, normalizeOperations, true)
|
|
2395
|
+
|
|
2396
|
+
for (const entry of entries) {
|
|
2397
|
+
inferGenericArgumentEvidence(formal.valueType, entry.value, substitutions, scope, functions, records, fail,
|
|
2398
|
+
normalizeOperations)
|
|
2399
|
+
}
|
|
2400
|
+
return
|
|
2401
|
+
}
|
|
2402
|
+
const actual = inferValueExpressionType(
|
|
2403
|
+
expression, scope, functions, records, fail, normalizeOperations, "a call argument", undefined, true
|
|
2404
|
+
)
|
|
2405
|
+
|
|
2406
|
+
inferTypeArguments(formal, actual, substitutions, expression.location, fail)
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
/**
|
|
2410
|
+
* Unifies one declaration-owned formal type with one argument type.
|
|
2411
|
+
* @param {import("./types.js").SemanticValueType} formal - Generic signature type.
|
|
2412
|
+
* @param {import("./types.js").SemanticValueType} actual - Inferred argument type.
|
|
2413
|
+
* @param {Map<string, import("./types.js").SemanticValueType>} substitutions - Inference state.
|
|
2414
|
+
* @param {import("./types.js").SourceLocation} location - Argument location.
|
|
2415
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
2416
|
+
* @returns {void}
|
|
2417
|
+
*/
|
|
2418
|
+
function inferTypeArguments(formal, actual, substitutions, location, fail) {
|
|
2419
|
+
if (formal.kind == "TypeVariableReference") {
|
|
2420
|
+
const prior = substitutions.get(formal.parameterId)
|
|
2421
|
+
|
|
2422
|
+
if (prior) {
|
|
2423
|
+
if (!sameType(prior, actual)) {
|
|
2424
|
+
fail("GENERIC_INFERENCE_CONFLICT", `Conflicting inference for type parameter '${formal.parameterId}'.`, location)
|
|
2425
|
+
}
|
|
2426
|
+
return
|
|
2427
|
+
}
|
|
2428
|
+
if (!(actual.kind == "TypeVariableReference" && actual.parameterId == formal.parameterId) &&
|
|
2429
|
+
typeContainsVariable(actual, formal.parameterId)) {
|
|
2430
|
+
fail("GENERIC_INFERENCE_RECURSION", `Recursive inference for type parameter '${formal.parameterId}'.`, location)
|
|
2431
|
+
}
|
|
2432
|
+
substitutions.set(formal.parameterId, actual)
|
|
2433
|
+
return
|
|
2434
|
+
}
|
|
2435
|
+
if (formal.kind != actual.kind) {
|
|
2436
|
+
fail("TYPE_MISMATCH", `Call argument type ${typeDescription(actual)}; expected ${typeDescription(formal)}.`, location)
|
|
2437
|
+
}
|
|
2438
|
+
if (formal.kind == "TypeReference") {
|
|
2439
|
+
if (!sameType(formal, actual)) {
|
|
2440
|
+
fail("TYPE_MISMATCH", `Call argument type ${typeDescription(actual)}; expected ${typeDescription(formal)}.`, location)
|
|
2441
|
+
}
|
|
2442
|
+
return
|
|
2443
|
+
}
|
|
2444
|
+
if (formal.kind == "ReferenceType" && actual.kind == "ReferenceType") {
|
|
2445
|
+
if (formal.declarationId != actual.declarationId) {
|
|
2446
|
+
fail("TYPE_MISMATCH", `Call argument type ${typeDescription(actual)}; expected ${typeDescription(formal)}.`, location)
|
|
2447
|
+
}
|
|
2448
|
+
return
|
|
2449
|
+
}
|
|
2450
|
+
if (formal.kind == "RecordType" && actual.kind == "RecordType") {
|
|
2451
|
+
if (formal.declarationId != actual.declarationId || (formal.arguments?.length ?? 0) != (actual.arguments?.length ?? 0)) {
|
|
2452
|
+
fail("TYPE_MISMATCH", `Call argument type ${typeDescription(actual)}; expected ${typeDescription(formal)}.`, location)
|
|
2453
|
+
}
|
|
2454
|
+
for (let index = 0; index < (formal.arguments?.length ?? 0); index += 1) {
|
|
2455
|
+
inferTypeArguments(/** @type {import("./types.js").SemanticValueType} */ (formal.arguments?.[index]),
|
|
2456
|
+
/** @type {import("./types.js").SemanticValueType} */ (actual.arguments?.[index]), substitutions, location, fail)
|
|
2457
|
+
}
|
|
2458
|
+
return
|
|
2459
|
+
}
|
|
2460
|
+
if (formal.kind == "ListType" && actual.kind == "ListType") {
|
|
2461
|
+
inferTypeArguments(formal.elementType, actual.elementType, substitutions, location, fail)
|
|
2462
|
+
return
|
|
2463
|
+
}
|
|
2464
|
+
if (formal.kind == "OptionalType" && actual.kind == "OptionalType") {
|
|
2465
|
+
inferTypeArguments(formal.valueType, actual.valueType, substitutions, location, fail)
|
|
2466
|
+
return
|
|
2467
|
+
}
|
|
2468
|
+
if ((formal.kind == "MapType" && actual.kind == "MapType") ||
|
|
2469
|
+
(formal.kind == "OrderedMapType" && actual.kind == "OrderedMapType")) {
|
|
2470
|
+
inferTypeArguments(formal.keyType, actual.keyType, substitutions, location, fail)
|
|
2471
|
+
inferTypeArguments(formal.valueType, actual.valueType, substitutions, location, fail)
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
/**
|
|
2476
|
+
* Checks the occurs constraint for first-order inference.
|
|
2477
|
+
* @param {import("./types.js").SemanticValueType} type - Candidate inferred type.
|
|
2478
|
+
* @param {string} parameterId - Parameter being inferred.
|
|
2479
|
+
* @returns {boolean} Whether the type recursively contains the parameter.
|
|
2480
|
+
*/
|
|
2481
|
+
function typeContainsVariable(type, parameterId) {
|
|
2482
|
+
if (type.kind == "TypeVariableReference") return type.parameterId == parameterId
|
|
2483
|
+
if (type.kind == "RecordType") return type.arguments?.some((argument) => typeContainsVariable(argument, parameterId)) ?? false
|
|
2484
|
+
if (type.kind == "ListType") return typeContainsVariable(type.elementType, parameterId)
|
|
2485
|
+
if (type.kind == "MapType" || type.kind == "OrderedMapType") {
|
|
2486
|
+
return typeContainsVariable(type.keyType, parameterId) || typeContainsVariable(type.valueType, parameterId)
|
|
2487
|
+
}
|
|
2488
|
+
if (type.kind == "OptionalType") return typeContainsVariable(type.valueType, parameterId)
|
|
2489
|
+
|
|
2490
|
+
return false
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
/**
|
|
2494
|
+
* Applies substitutions to a value or void return type.
|
|
2495
|
+
* @param {import("./types.js").SemanticFunctionReturnType} type - Open return type.
|
|
2496
|
+
* @param {Map<string, import("./types.js").SemanticValueType>} substitutions - Closed substitutions.
|
|
2497
|
+
* @returns {import("./types.js").SemanticFunctionReturnType} Substituted return type.
|
|
2498
|
+
*/
|
|
2499
|
+
function substituteReturnType(type, substitutions) {
|
|
2500
|
+
return isVoidType(type) ? type : substituteValueType(type, substitutions)
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
/**
|
|
2504
|
+
* Builds the exact semantic signature bound to one declaration.
|
|
2505
|
+
* @param {import("./types.js").FunctionDeclaration} declaration - Resolved declaration.
|
|
2506
|
+
* @param {RecordRegistry} records - Record declarations by identity.
|
|
2507
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
2508
|
+
* @param {Map<string, import("./types.js").SemanticValueType>} [substitutions] - Inferred generic substitutions.
|
|
2509
|
+
* @returns {import("./types.js").ResolvedFunctionSignature} Detached signature binding.
|
|
2510
|
+
*/
|
|
2511
|
+
function signatureFor(declaration, records, fail, substitutions = new Map()) {
|
|
2512
|
+
const typeParameters = declarationTypeParameterIds(declaration)
|
|
2513
|
+
const declaredParameters = declaration.typeParameters ?? []
|
|
2514
|
+
const parameterTypes = declaration.parameters.map((parameter) =>
|
|
2515
|
+
substituteValueType(validateValueTypeReference(parameter.type, parameter.location, fail, undefined, records, typeParameters), substitutions))
|
|
2516
|
+
const returnType = substituteReturnType(
|
|
2517
|
+
validateReturnTypeReference(declaration.returnType, declaration.location, fail, records, typeParameters), substitutions)
|
|
2518
|
+
|
|
2519
|
+
return {
|
|
2520
|
+
declarationId: /** @type {string} */ (declaration.id),
|
|
2521
|
+
kind: "ResolvedFunctionSignature",
|
|
2522
|
+
parameterTypes: parameterTypes.map((type) => /** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(type))),
|
|
2523
|
+
returnType: typeIdentity(returnType),
|
|
2524
|
+
...(declaredParameters.length > 0 ? {
|
|
2525
|
+
typeArguments: declaredParameters.map((parameter) =>
|
|
2526
|
+
/** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(/** @type {import("./types.js").SemanticValueType} */ (
|
|
2527
|
+
substitutions.get(/** @type {string} */ (parameter.id))
|
|
2528
|
+
))))
|
|
2529
|
+
} : {})
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
/**
|
|
2534
|
+
* Validates an externally supplied call binding against the declaration collected for this module.
|
|
2535
|
+
* @param {unknown} actual - Candidate call resolution.
|
|
2536
|
+
* @param {import("./types.js").ResolvedFunctionSignature} expected - Exact declaration signature.
|
|
2537
|
+
* @param {import("./types.js").SourceLocation} location - Call location.
|
|
2538
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
2539
|
+
* @returns {void}
|
|
2540
|
+
*/
|
|
2541
|
+
function validateResolution(actual, expected, location, fail) {
|
|
2542
|
+
const expectedKeys = expected.typeArguments ? "declarationId,kind,parameterTypes,returnType,typeArguments" :
|
|
2543
|
+
"declarationId,kind,parameterTypes,returnType"
|
|
2544
|
+
|
|
2545
|
+
if (!actual || typeof actual != "object" || Array.isArray(actual) ||
|
|
2546
|
+
Object.keys(actual).sort().join(",") != expectedKeys) {
|
|
2547
|
+
fail("TYPE_MISMATCH", "Call is missing an exact resolved function signature.", location)
|
|
2548
|
+
}
|
|
2549
|
+
const candidate = /** @type {import("./types.js").ResolvedFunctionSignature} */ (actual)
|
|
2550
|
+
|
|
2551
|
+
if (candidate.kind != "ResolvedFunctionSignature" || typeof candidate.declarationId != "string" ||
|
|
2552
|
+
!Array.isArray(candidate.parameterTypes) || !validTypeIdentity(candidate.returnType, true) ||
|
|
2553
|
+
candidate.declarationId != expected.declarationId ||
|
|
2554
|
+
!sameTypeIdentity(candidate.returnType, expected.returnType) || candidate.parameterTypes.length != expected.parameterTypes.length) {
|
|
2555
|
+
fail("TYPE_MISMATCH", "Call resolution does not match its declaration signature.", location)
|
|
2556
|
+
}
|
|
2557
|
+
if (expected.typeArguments) {
|
|
2558
|
+
const candidateTypeArguments = candidate.typeArguments
|
|
2559
|
+
|
|
2560
|
+
if (!isDenseArray(candidateTypeArguments) || candidateTypeArguments.length != expected.typeArguments.length ||
|
|
2561
|
+
candidateTypeArguments.some((type, index) =>
|
|
2562
|
+
!validTypeIdentity(type, false) || !sameTypeIdentity(type, expected.typeArguments?.[index]))) {
|
|
2563
|
+
fail("TYPE_MISMATCH", "Call resolution does not match its inferred type arguments.", location)
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
for (let index = 0; index < candidate.parameterTypes.length; index++) {
|
|
2567
|
+
const type = candidate.parameterTypes[index]
|
|
2568
|
+
|
|
2569
|
+
if (!Object.hasOwn(candidate.parameterTypes, index) || !validTypeIdentity(type, false) ||
|
|
2570
|
+
!sameTypeIdentity(type, expected.parameterTypes[index])) {
|
|
2571
|
+
fail("TYPE_MISMATCH", "Call resolution does not match its declaration signature.", location)
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
/**
|
|
2577
|
+
* Validates exact caller-owned constructor or method resolution metadata.
|
|
2578
|
+
* @param {unknown} actual - Candidate resolution.
|
|
2579
|
+
* @param {import("./types.js").ResolvedConstructorSignature | import("./types.js").ResolvedMethodSignature} expected - Exact resolution.
|
|
2580
|
+
* @param {import("./types.js").SourceLocation} location - Expression location.
|
|
2581
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
2582
|
+
* @param {"constructor" | "method"} role - Resolution role.
|
|
2583
|
+
* @returns {void}
|
|
2584
|
+
*/
|
|
2585
|
+
function validateReferenceResolution(actual, expected, location, fail, role) {
|
|
2586
|
+
const expectedMethod = role == "method" ? /** @type {import("./types.js").ResolvedMethodSignature} */ (expected) : undefined
|
|
2587
|
+
const expectedKeys = role == "constructor" ? "declarationId,kind,parameterTypes" :
|
|
2588
|
+
expectedMethod?.resourceFlow ? "declarationId,kind,parameterTypes,resourceFlow,returnType" :
|
|
2589
|
+
"declarationId,kind,parameterTypes,returnType"
|
|
2590
|
+
|
|
2591
|
+
if (!actual || typeof actual != "object" || Array.isArray(actual) || Object.keys(actual).sort().join(",") != expectedKeys) {
|
|
2592
|
+
fail("TYPE_MISMATCH", `Reference ${role} call is missing an exact resolved ${role} signature.`, location)
|
|
2593
|
+
}
|
|
2594
|
+
const candidate = /** @type {Record<string, unknown>} */ (actual)
|
|
2595
|
+
|
|
2596
|
+
if (candidate.kind != expected.kind || candidate.declarationId != expected.declarationId ||
|
|
2597
|
+
!isDenseArray(candidate.parameterTypes) || candidate.parameterTypes.length != expected.parameterTypes.length ||
|
|
2598
|
+
candidate.parameterTypes.some((type, index) => !validTypeIdentity(type, false) ||
|
|
2599
|
+
!sameTypeIdentity(type, expected.parameterTypes[index])) ||
|
|
2600
|
+
role == "method" && (!validTypeIdentity(candidate.returnType, true) ||
|
|
2601
|
+
!sameTypeIdentity(candidate.returnType, /** @type {import("./types.js").ResolvedMethodSignature} */ (expected).returnType) ||
|
|
2602
|
+
JSON.stringify(candidate.resourceFlow) != JSON.stringify(expectedMethod?.resourceFlow))) {
|
|
2603
|
+
fail("TYPE_MISMATCH", `Resolved ${role} signature does not match its declaration.`, location)
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
/**
|
|
2608
|
+
* Makes a source identifier's ownership transfer explicit in normalized IR.
|
|
2609
|
+
* @param {import("./types.js").Expression} expression - Candidate transfer expression.
|
|
2610
|
+
* @param {import("./types.js").SemanticFunctionReturnType} expected - Destination type.
|
|
2611
|
+
* @param {boolean} normalize - Whether parser intent may be normalized.
|
|
2612
|
+
* @param {SemanticFail} fail - Located failure.
|
|
2613
|
+
* @returns {import("./types.js").Expression} Explicit expression.
|
|
2614
|
+
*/
|
|
2615
|
+
function normalizeOwnedMove(expression, expected, normalize, fail) {
|
|
2616
|
+
if (expected.kind != "OwnedResourceType" && expected.kind != "OwnedReferenceType") return expression
|
|
2617
|
+
if (expression.kind == "OwnedMoveExpression") return expression
|
|
2618
|
+
if (expression.kind == "PrivateFieldRead") {
|
|
2619
|
+
return fail("INVALID_RESOURCE_TRANSFER", "Private resource fields cannot be moved or exposed; transfer the owning reference.",
|
|
2620
|
+
expression.location)
|
|
2621
|
+
}
|
|
2622
|
+
if (expression.kind != "IdentifierExpression") return expression
|
|
2623
|
+
if (!normalize) return fail("RESOURCE_MOVE_REQUIRED", "Owned transfer requires an explicit move expression.", expression.location)
|
|
2624
|
+
|
|
2625
|
+
return {
|
|
2626
|
+
expression,
|
|
2627
|
+
kind: "OwnedMoveExpression",
|
|
2628
|
+
location: expression.location,
|
|
2629
|
+
sourceProvenance: expression.sourceProvenance,
|
|
2630
|
+
type: expected
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
/**
|
|
2635
|
+
* Classifies the deliberately bounded Task 034 receiver transition.
|
|
2636
|
+
* @param {import("./types.js").ClassDeclaration} declaration - Owned class.
|
|
2637
|
+
* @param {import("./types.js").MethodDeclaration} method - Candidate method.
|
|
2638
|
+
* @param {SemanticFail} fail - Located failure.
|
|
2639
|
+
* @returns {{kind: "preserve" | "terminal", terminalFailureId?: string}} Receiver transition.
|
|
2640
|
+
*/
|
|
2641
|
+
function ownedMethodResourceFlow(declaration, method, fail) {
|
|
2642
|
+
const effectStatements = method.body.statements.flatMap((statement) => {
|
|
2643
|
+
const expression = statement.kind == "ExpressionStatement" || statement.kind == "ReturnStatement" ? statement.expression :
|
|
2644
|
+
statement.kind == "LocalDeclaration" ? statement.initializer : undefined
|
|
2645
|
+
|
|
2646
|
+
return expression?.kind == "EffectCallExpression" ? [expression] : []
|
|
2647
|
+
})
|
|
2648
|
+
const allEffects = methodEffectExpressions(method.body)
|
|
2649
|
+
|
|
2650
|
+
if (allEffects.length == 0) return {kind: "preserve"}
|
|
2651
|
+
if (method.body.statements.length != 1 || effectStatements.length != 1 || allEffects.length != 1 ||
|
|
2652
|
+
allEffects[0] !== effectStatements[0]) {
|
|
2653
|
+
return fail("UNSUPPORTED_STATEMENT",
|
|
2654
|
+
`Owned method '${method.name}' must contain one direct resource operation or only pure statements.`, method.location)
|
|
2655
|
+
}
|
|
2656
|
+
const operation = effectStatements[0]
|
|
2657
|
+
const flow = operation.resolution.resourceFlow
|
|
2658
|
+
|
|
2659
|
+
if (flow.kind == "none" || flow.kind == "acquire") return {kind: "preserve"}
|
|
2660
|
+
const argument = operation.arguments[flow.parameterIndex]
|
|
2661
|
+
|
|
2662
|
+
const owned = declaration.ownership?.kind == "ownedResource" ? declaration.ownership : undefined
|
|
2663
|
+
const borrowedParameterName = argument?.kind == "OwnedBorrowExpression" && argument.expression.kind == "IdentifierExpression"
|
|
2664
|
+
? argument.expression.name : undefined
|
|
2665
|
+
const borrowedParameter = borrowedParameterName !== undefined &&
|
|
2666
|
+
method.parameters.some((parameter) => parameter.name == borrowedParameterName && containsOwnedType(parameter.type))
|
|
2667
|
+
|
|
2668
|
+
if (borrowedParameter) return {kind: "preserve"}
|
|
2669
|
+
|
|
2670
|
+
if (!argument || argument.kind != "OwnedBorrowExpression" || argument.expression.kind != "PrivateFieldRead" ||
|
|
2671
|
+
argument.expression.field != owned?.fieldId) {
|
|
2672
|
+
return fail("INVALID_RESOURCE_BORROW", `Owned method '${method.name}' must operate on its private resource field.`, operation.location)
|
|
2673
|
+
}
|
|
2674
|
+
return {kind: flow.kind == "close" ? "terminal" : "preserve", terminalFailureId: flow.terminalFailureId}
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
/**
|
|
2678
|
+
* Collects every host-effect site nested in one owned method body.
|
|
2679
|
+
* @param {import("./types.js").Block} block - Method body.
|
|
2680
|
+
* @returns {import("./types.js").Expression[]} Effectful expressions.
|
|
2681
|
+
*/
|
|
2682
|
+
function methodEffectExpressions(block) {
|
|
2683
|
+
/** @type {import("./types.js").Expression[]} */
|
|
2684
|
+
const effects = []
|
|
2685
|
+
const pending = [/** @type {unknown} */ (block)]
|
|
2686
|
+
const seen = new Set()
|
|
2687
|
+
|
|
2688
|
+
while (pending.length > 0) {
|
|
2689
|
+
const value = pending.pop()
|
|
2690
|
+
|
|
2691
|
+
if (!value || typeof value != "object" || seen.has(value)) continue
|
|
2692
|
+
seen.add(value)
|
|
2693
|
+
if (!Array.isArray(value) && (Reflect.get(value, "kind") == "EffectCallExpression" ||
|
|
2694
|
+
typeof Reflect.get(value, "effectSiteId") == "string")) {
|
|
2695
|
+
effects.push(/** @type {import("./types.js").Expression} */ (value))
|
|
2696
|
+
}
|
|
2697
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2698
|
+
if (["location", "resolution", "sourceProvenance"].includes(key)) continue
|
|
2699
|
+
if (Array.isArray(child)) pending.push(...child)
|
|
2700
|
+
else if (child && typeof child == "object") pending.push(child)
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
return effects
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
/**
|
|
2707
|
+
* Rehydrates a closed capability type identity for ordinary expression checks.
|
|
2708
|
+
* @param {import("./types.js").FunctionReturnTypeIdentity} identity - Closed identity.
|
|
2709
|
+
* @param {import("./types.js").SourceLocation} location - Diagnostic location.
|
|
2710
|
+
* @param {SemanticFail} fail - Located failure.
|
|
2711
|
+
* @returns {import("./types.js").SemanticValueType} Value type.
|
|
2712
|
+
*/
|
|
2713
|
+
function identityAsValueType(identity, location, fail) {
|
|
2714
|
+
if (typeof identity == "string") {
|
|
2715
|
+
if (!isScalarTypeName(identity)) return fail("TYPE_MISMATCH", "Void or invalid identity used as an effect value type.", location)
|
|
2716
|
+
return scalarType(identity)
|
|
2717
|
+
}
|
|
2718
|
+
if (!identity || typeof identity != "object" || Array.isArray(identity)) {
|
|
2719
|
+
return fail("TYPE_MISMATCH", "Malformed effect type identity.", location)
|
|
2720
|
+
}
|
|
2721
|
+
const candidate = /** @type {Exclude<import("./types.js").SemanticTypeIdentity, string>} */ (identity)
|
|
2722
|
+
|
|
2723
|
+
if (candidate.kind == "OwnedResourceType") return {kind: "OwnedResourceType", resourceId: candidate.resourceId}
|
|
2724
|
+
if (candidate.kind == "OwnedReferenceType") return {declarationId: candidate.declarationId, kind: "OwnedReferenceType"}
|
|
2725
|
+
if (candidate.kind == "OptionalType") return {kind: "OptionalType", valueType: identityAsValueType(candidate.valueType, location, fail)}
|
|
2726
|
+
return fail("TYPE_MISMATCH", "Unsupported effect type identity.", location)
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
/**
|
|
2730
|
+
* Converts a semantic type to its detached call-resolution identity.
|
|
2731
|
+
* @param {import("./types.js").SemanticFunctionReturnType} type - Validated semantic type.
|
|
2732
|
+
* @returns {import("./types.js").FunctionReturnTypeIdentity} Detached identity.
|
|
2733
|
+
*/
|
|
2734
|
+
function typeIdentity(type) {
|
|
2735
|
+
if (type.kind == "TypeReference") return type.name
|
|
2736
|
+
if (type.kind == "TypeVariableReference") return {kind: "TypeVariableReference", parameterId: type.parameterId}
|
|
2737
|
+
if (type.kind == "RecordType") return {
|
|
2738
|
+
declarationId: type.declarationId,
|
|
2739
|
+
kind: "RecordType",
|
|
2740
|
+
...(type.arguments ? {arguments: type.arguments.map((argument) =>
|
|
2741
|
+
/** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(argument)))} : {})
|
|
2742
|
+
}
|
|
2743
|
+
if (type.kind == "ReferenceType") return {declarationId: type.declarationId, kind: "ReferenceType"}
|
|
2744
|
+
if (type.kind == "OwnedResourceType") return {kind: "OwnedResourceType", resourceId: type.resourceId}
|
|
2745
|
+
if (type.kind == "OwnedReferenceType") return {declarationId: type.declarationId, kind: "OwnedReferenceType"}
|
|
2746
|
+
if (type.kind == "ListType") return {elementType: /** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(type.elementType)), kind: "ListType"}
|
|
2747
|
+
if (type.kind == "OptionalType") return {kind: "OptionalType", valueType: /** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(type.valueType))}
|
|
2748
|
+
if (type.kind == "OrderedMapType") return {
|
|
2749
|
+
keyType: /** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(type.keyType)),
|
|
2750
|
+
kind: "OrderedMapType",
|
|
2751
|
+
order: "insertion",
|
|
2752
|
+
valueType: /** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(type.valueType))
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
return {
|
|
2756
|
+
keyType: /** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(type.keyType)),
|
|
2757
|
+
kind: "MapType",
|
|
2758
|
+
valueType: /** @type {import("./types.js").SemanticTypeIdentity} */ (typeIdentity(type.valueType))
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
/**
|
|
2763
|
+
* Checks one externally supplied recursive type identity.
|
|
2764
|
+
* @param {unknown} type - Candidate identity.
|
|
2765
|
+
* @param {boolean} allowVoid - Whether void is permitted.
|
|
2766
|
+
* @param {Set<object>} [seen] - Active recursion path.
|
|
2767
|
+
* @returns {boolean} Whether the identity is closed and acyclic.
|
|
2768
|
+
*/
|
|
2769
|
+
function validTypeIdentity(type, allowVoid, seen = new Set()) {
|
|
2770
|
+
if (typeof type == "string") return isScalarTypeName(type) || allowVoid && type == "void"
|
|
2771
|
+
if (!type || typeof type != "object" || Array.isArray(type) || seen.has(type)) return false
|
|
2772
|
+
|
|
2773
|
+
seen.add(type)
|
|
2774
|
+
const candidate = /** @type {Record<string, unknown>} */ (type)
|
|
2775
|
+
let valid = false
|
|
2776
|
+
|
|
2777
|
+
if (candidate.kind == "ListType" && Object.keys(candidate).sort().join(",") == "elementType,kind") {
|
|
2778
|
+
valid = validTypeIdentity(candidate.elementType, false, seen)
|
|
2779
|
+
} else if (candidate.kind == "MapType" && Object.keys(candidate).sort().join(",") == "keyType,kind,valueType") {
|
|
2780
|
+
valid = candidate.keyType == "string" && validTypeIdentity(candidate.valueType, false, seen)
|
|
2781
|
+
} else if (candidate.kind == "OrderedMapType" &&
|
|
2782
|
+
Object.keys(candidate).sort().join(",") == "keyType,kind,order,valueType") {
|
|
2783
|
+
valid = candidate.keyType == "string" && candidate.order == "insertion" &&
|
|
2784
|
+
validTypeIdentity(candidate.valueType, false, seen)
|
|
2785
|
+
} else if (candidate.kind == "OptionalType" && Object.keys(candidate).sort().join(",") == "kind,valueType") {
|
|
2786
|
+
valid = validTypeIdentity(candidate.valueType, false, seen) &&
|
|
2787
|
+
!(candidate.valueType && typeof candidate.valueType == "object" && Reflect.get(candidate.valueType, "kind") == "OptionalType")
|
|
2788
|
+
} else if (candidate.kind == "TypeVariableReference" && Object.keys(candidate).sort().join(",") == "kind,parameterId") {
|
|
2789
|
+
valid = typeof candidate.parameterId == "string" && /^(?:[a-z][a-z0-9._-]*#)?(?:record|function):[0-9]+:type:[0-9]+$/u.test(candidate.parameterId)
|
|
2790
|
+
} else if (candidate.kind == "RecordType" &&
|
|
2791
|
+
["arguments,declarationId,kind", "declarationId,kind"].includes(Object.keys(candidate).sort().join(","))) {
|
|
2792
|
+
const candidateArguments = candidate.arguments
|
|
2793
|
+
|
|
2794
|
+
valid = typeof candidate.declarationId == "string" && /^(?:[a-z][a-z0-9._-]*#)?record:[0-9]+$/u.test(candidate.declarationId) &&
|
|
2795
|
+
(candidateArguments === undefined || isDenseArray(candidateArguments) &&
|
|
2796
|
+
candidateArguments.every((argument) => validTypeIdentity(argument, false, seen)))
|
|
2797
|
+
} else if (candidate.kind == "ReferenceType" && Object.keys(candidate).sort().join(",") == "declarationId,kind") {
|
|
2798
|
+
valid = typeof candidate.declarationId == "string" &&
|
|
2799
|
+
/^(?:[a-z][a-z0-9._-]*#)?class:[0-9]+$/u.test(candidate.declarationId)
|
|
2800
|
+
} else if (candidate.kind == "OwnedResourceType" && Object.keys(candidate).sort().join(",") == "kind,resourceId") {
|
|
2801
|
+
valid = typeof candidate.resourceId == "string" && /^(?:module:[0-9]+\/)?capability:[0-9]+\/resource:[0-9]+$/u
|
|
2802
|
+
.test(candidate.resourceId)
|
|
2803
|
+
} else if (candidate.kind == "OwnedReferenceType" && Object.keys(candidate).sort().join(",") == "declarationId,kind") {
|
|
2804
|
+
valid = typeof candidate.declarationId == "string" && /^(?:[a-z][a-z0-9._-]*#)?class:[0-9]+$/u.test(candidate.declarationId)
|
|
2805
|
+
}
|
|
2806
|
+
seen.delete(type)
|
|
2807
|
+
|
|
2808
|
+
return valid
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
/**
|
|
2812
|
+
* Checks array identity without relying on iteration methods that skip sparse slots.
|
|
2813
|
+
* @param {unknown} value - Candidate array.
|
|
2814
|
+
* @returns {value is unknown[]} Whether every indexed slot is present.
|
|
2815
|
+
*/
|
|
2816
|
+
function isDenseArray(value) {
|
|
2817
|
+
if (!Array.isArray(value)) return false
|
|
2818
|
+
|
|
2819
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
2820
|
+
if (!Object.hasOwn(value, index)) return false
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
return true
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
/**
|
|
2827
|
+
* Compares two closed type identities structurally.
|
|
2828
|
+
* @param {unknown} left - First identity.
|
|
2829
|
+
* @param {unknown} right - Second identity.
|
|
2830
|
+
* @returns {boolean} Whether the identities are equal.
|
|
2831
|
+
*/
|
|
2832
|
+
function sameTypeIdentity(left, right) {
|
|
2833
|
+
if (typeof left == "string" || typeof right == "string") return left === right
|
|
2834
|
+
if (!left || !right || typeof left != "object" || typeof right != "object") return false
|
|
2835
|
+
const leftType = /** @type {Record<string, unknown>} */ (left)
|
|
2836
|
+
const rightType = /** @type {Record<string, unknown>} */ (right)
|
|
2837
|
+
|
|
2838
|
+
if (leftType.kind != rightType.kind) return false
|
|
2839
|
+
if (leftType.kind == "ListType") return sameTypeIdentity(leftType.elementType, rightType.elementType)
|
|
2840
|
+
if (leftType.kind == "OptionalType") return sameTypeIdentity(leftType.valueType, rightType.valueType)
|
|
2841
|
+
if (leftType.kind == "TypeVariableReference") return leftType.parameterId == rightType.parameterId
|
|
2842
|
+
if (leftType.kind == "RecordType") {
|
|
2843
|
+
const leftArguments = Array.isArray(leftType.arguments) ? leftType.arguments : []
|
|
2844
|
+
const rightArguments = Array.isArray(rightType.arguments) ? rightType.arguments : []
|
|
2845
|
+
|
|
2846
|
+
return leftType.declarationId == rightType.declarationId && leftArguments.length == rightArguments.length &&
|
|
2847
|
+
leftArguments.every((argument, index) => sameTypeIdentity(argument, rightArguments[index]))
|
|
2848
|
+
}
|
|
2849
|
+
if (leftType.kind == "ReferenceType") return leftType.declarationId == rightType.declarationId
|
|
2850
|
+
if (leftType.kind == "OwnedResourceType") return leftType.resourceId == rightType.resourceId
|
|
2851
|
+
if (leftType.kind == "OwnedReferenceType") return leftType.declarationId == rightType.declarationId
|
|
2852
|
+
if (leftType.kind == "MapType" || leftType.kind == "OrderedMapType") {
|
|
2853
|
+
return sameTypeIdentity(leftType.keyType, rightType.keyType) && sameTypeIdentity(leftType.valueType, rightType.valueType)
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
return false
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
/**
|
|
2860
|
+
* Compares two validated semantic types structurally.
|
|
2861
|
+
* @param {import("./types.js").SemanticFunctionReturnType} left - First type.
|
|
2862
|
+
* @param {import("./types.js").SemanticFunctionReturnType} right - Second type.
|
|
2863
|
+
* @returns {boolean} Whether the types are equal.
|
|
2864
|
+
*/
|
|
2865
|
+
function sameType(left, right) {
|
|
2866
|
+
return sameTypeIdentity(typeIdentity(left), typeIdentity(right))
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
/**
|
|
2870
|
+
* Formats one validated semantic type for diagnostics.
|
|
2871
|
+
* @param {import("./types.js").SemanticFunctionReturnType | import("./types.js").ErrorType} type - Semantic type.
|
|
2872
|
+
* @returns {string} Stable recursive spelling.
|
|
2873
|
+
*/
|
|
2874
|
+
function typeDescription(type) {
|
|
2875
|
+
if (type.kind == "TypeReference") return type.name
|
|
2876
|
+
if (type.kind == "TypeVariableReference") return `type-variable<${type.parameterId}>`
|
|
2877
|
+
if (type.kind == "RecordType") return `record<${type.declarationId}${type.arguments?.length
|
|
2878
|
+
? `, ${type.arguments.map(typeDescription).join(", ")}` : ""}>`
|
|
2879
|
+
if (type.kind == "ReferenceType") return `reference<${type.declarationId}>`
|
|
2880
|
+
if (type.kind == "OwnedResourceType") return `owned-resource<${type.resourceId}>`
|
|
2881
|
+
if (type.kind == "OwnedReferenceType") return `owned-reference<${type.declarationId}>`
|
|
2882
|
+
if (type.kind == "ErrorType") return `error<${type.declarationId}>`
|
|
2883
|
+
if (type.kind == "ListType") return `list<${typeDescription(type.elementType)}>`
|
|
2884
|
+
if (type.kind == "OptionalType") return `optional<${typeDescription(type.valueType)}>`
|
|
2885
|
+
if (type.kind == "OrderedMapType") return `ordered-map<string, ${typeDescription(type.valueType)}, insertion>`
|
|
2886
|
+
|
|
2887
|
+
return `map<string, ${typeDescription(type.valueType)}>`
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
/**
|
|
2891
|
+
* Checks whether a type graph contains linear ownership.
|
|
2892
|
+
* @param {unknown} type - Candidate semantic type.
|
|
2893
|
+
* @param {Set<object>} [seen] - Visited type containers.
|
|
2894
|
+
* @returns {boolean} Whether ownership occurs at any depth.
|
|
2895
|
+
*/
|
|
2896
|
+
function containsOwnedType(type, seen = new Set()) {
|
|
2897
|
+
if (!type || typeof type != "object" || Array.isArray(type) || seen.has(type)) return false
|
|
2898
|
+
seen.add(type)
|
|
2899
|
+
const kind = Reflect.get(type, "kind")
|
|
2900
|
+
|
|
2901
|
+
if (kind == "OwnedResourceType" || kind == "OwnedReferenceType") return true
|
|
2902
|
+
if (kind == "ListType") return containsOwnedType(Reflect.get(type, "elementType"), seen)
|
|
2903
|
+
if (kind == "OptionalType") return containsOwnedType(Reflect.get(type, "valueType"), seen)
|
|
2904
|
+
if (kind == "MapType" || kind == "OrderedMapType") {
|
|
2905
|
+
return containsOwnedType(Reflect.get(type, "keyType"), seen) || containsOwnedType(Reflect.get(type, "valueType"), seen)
|
|
2906
|
+
}
|
|
2907
|
+
if (kind == "RecordType") {
|
|
2908
|
+
const arguments_ = Reflect.get(type, "arguments")
|
|
2909
|
+
|
|
2910
|
+
return Array.isArray(arguments_) && arguments_.some((argument) => containsOwnedType(argument, seen))
|
|
2911
|
+
}
|
|
2912
|
+
return false
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
/**
|
|
2916
|
+
* Checks one scalar semantic type.
|
|
2917
|
+
* @param {import("./types.js").SemanticFunctionReturnType} type - Semantic type.
|
|
2918
|
+
* @param {import("./types.js").SemanticTypeName} [name] - Optional required scalar.
|
|
2919
|
+
* @returns {type is import("./types.js").TypeReference} Whether it is the requested scalar.
|
|
2920
|
+
*/
|
|
2921
|
+
function isScalarType(type, name) {
|
|
2922
|
+
return type.kind == "TypeReference" && isScalarTypeName(type.name) && (name === undefined || type.name == name)
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
/**
|
|
2926
|
+
* Checks the sole non-value return type.
|
|
2927
|
+
* @param {import("./types.js").SemanticFunctionReturnType} type - Semantic type.
|
|
2928
|
+
* @returns {type is import("./types.js").FunctionReturnTypeReference} Whether it is void.
|
|
2929
|
+
*/
|
|
2930
|
+
function isVoidType(type) {
|
|
2931
|
+
return type.kind == "TypeReference" && type.name == "void"
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
/**
|
|
2935
|
+
* Requires a scalar type for the pre-collection operator contract.
|
|
2936
|
+
* @param {import("./types.js").SemanticValueType} type - Operand type.
|
|
2937
|
+
* @param {import("./types.js").SourceLocation} location - Operand location.
|
|
2938
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
2939
|
+
* @returns {import("./types.js").SemanticTypeName} Scalar name.
|
|
2940
|
+
*/
|
|
2941
|
+
function scalarNameForOperation(type, location, fail) {
|
|
2942
|
+
if (!isScalarType(type)) {
|
|
2943
|
+
return fail("INVALID_OPERAND_TYPE", `Scalar operation received ${typeDescription(type)}.`, location)
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
return type.name
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2949
|
+
/**
|
|
2950
|
+
* Enforces that a total collection access is proven or uses a source operation that fails on absence.
|
|
2951
|
+
* @param {import("./types.js").ListIndexExpression | import("./types.js").MapLookupExpression} expression - Access.
|
|
2952
|
+
* @param {boolean} proven - Whether static literal propagation proves presence.
|
|
2953
|
+
* @param {boolean} normalizeOperations - Whether this is frontend admission.
|
|
2954
|
+
* @param {SemanticFail} fail - Diagnostic callback.
|
|
2955
|
+
* @returns {void}
|
|
2956
|
+
*/
|
|
2957
|
+
function validateAccessTotality(expression, proven, normalizeOperations, fail) {
|
|
2958
|
+
if (proven) {
|
|
2959
|
+
if (normalizeOperations) expression.totality = "proven"
|
|
2960
|
+
else if (expression.totality != "proven" && expression.totality != "fail-on-absence") {
|
|
2961
|
+
fail("UNCHECKED_COLLECTION_ACCESS", "Collection access lacks a totality guarantee.", expression.location)
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
return
|
|
2965
|
+
}
|
|
2966
|
+
if (expression.totality != "fail-on-absence") {
|
|
2967
|
+
fail("UNCHECKED_COLLECTION_ACCESS", "Collection access is not statically proven and does not fail on absence.", expression.location)
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
/**
|
|
2972
|
+
* Resolves a binding without producing a diagnostic.
|
|
2973
|
+
* @param {string} name - Binding name.
|
|
2974
|
+
* @param {Scope} scope - Visible scope.
|
|
2975
|
+
* @returns {Binding | undefined} Binding when visible.
|
|
2976
|
+
*/
|
|
2977
|
+
function findBinding(name, scope) {
|
|
2978
|
+
for (let current = /** @type {Scope | undefined} */ (scope); current; current = current.parent) {
|
|
2979
|
+
const binding = current.bindings.get(name)
|
|
2980
|
+
|
|
2981
|
+
if (binding) return binding
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
return undefined
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
/**
|
|
2988
|
+
* Returns the existing bindings whose known values must be merged across a conditional.
|
|
2989
|
+
* @param {Scope} scope - Visible scope.
|
|
2990
|
+
* @returns {Binding[]} Nearest-to-farthest bindings.
|
|
2991
|
+
*/
|
|
2992
|
+
function bindingsVisibleFrom(scope) {
|
|
2993
|
+
const bindings = []
|
|
2994
|
+
|
|
2995
|
+
for (let current = /** @type {Scope | undefined} */ (scope); current; current = current.parent) {
|
|
2996
|
+
bindings.push(...current.bindings.values())
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
return bindings
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
/**
|
|
3003
|
+
* Finds visible mutable bindings that a loop body may assign on any nested path.
|
|
3004
|
+
* The set is computed after validating the list expression because collection evaluation precedes body effects.
|
|
3005
|
+
* @param {import("./types.js").Block} block - Candidate loop body.
|
|
3006
|
+
* @param {Scope} outerScope - Scope visible before entering the loop.
|
|
3007
|
+
* @param {Set<Binding>} [assigned] - Accumulated binding identities.
|
|
3008
|
+
* @returns {Set<Binding>} Exactly the visible mutable bindings targeted by nested assignments.
|
|
3009
|
+
*/
|
|
3010
|
+
function outerMutableBindingsAssignedBy(block, outerScope, assigned = new Set()) {
|
|
3011
|
+
for (const statement of block.statements) {
|
|
3012
|
+
if (statement.kind == "AssignmentStatement") {
|
|
3013
|
+
const binding = findBinding(statement.target.name, outerScope)
|
|
3014
|
+
|
|
3015
|
+
if (binding?.mutable) assigned.add(binding)
|
|
3016
|
+
} else if (statement.kind == "IfStatement") {
|
|
3017
|
+
outerMutableBindingsAssignedBy(statement.consequent, outerScope, assigned)
|
|
3018
|
+
if (statement.alternate) outerMutableBindingsAssignedBy(statement.alternate, outerScope, assigned)
|
|
3019
|
+
} else if (statement.kind == "ForEachStatement" || statement.kind == "ForEachMapStatement" || statement.kind == "WhileStatement") {
|
|
3020
|
+
outerMutableBindingsAssignedBy(statement.body, outerScope, assigned)
|
|
3021
|
+
} else if (statement.kind == "TryStatement") {
|
|
3022
|
+
outerMutableBindingsAssignedBy(statement.body, outerScope, assigned)
|
|
3023
|
+
outerMutableBindingsAssignedBy(statement.catchBody, outerScope, assigned)
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
return assigned
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
/**
|
|
3031
|
+
* Propagates only immutable values whose exact collection contents remain known.
|
|
3032
|
+
* @param {import("./types.js").Expression} expression - Expression.
|
|
3033
|
+
* @param {Scope} scope - Visible scope.
|
|
3034
|
+
* @returns {import("./types.js").Expression | undefined} Known value.
|
|
3035
|
+
*/
|
|
3036
|
+
function knownValueForExpression(expression, scope) {
|
|
3037
|
+
if (["IntegerLiteral", "BooleanLiteral", "StringLiteral", "ListLiteral", "MapLiteral", "OrderedMapLiteral"].includes(expression.kind)) return expression
|
|
3038
|
+
if (expression.kind == "IdentifierExpression") return findBinding(expression.name, scope)?.knownValue
|
|
3039
|
+
if (expression.kind == "ListIndexExpression") return provenListElement(expression.collection, expression.index, scope)
|
|
3040
|
+
if (expression.kind == "MapLookupExpression") return provenMapValue(expression.collection, expression.key, scope)
|
|
3041
|
+
|
|
3042
|
+
return undefined
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
/**
|
|
3046
|
+
* Finds the statically selected list element.
|
|
3047
|
+
* @param {import("./types.js").Expression} collection - List receiver.
|
|
3048
|
+
* @param {import("./types.js").Expression} index - Index expression.
|
|
3049
|
+
* @param {Scope} scope - Visible scope.
|
|
3050
|
+
* @returns {import("./types.js").Expression | undefined} Proven element.
|
|
3051
|
+
*/
|
|
3052
|
+
function provenListElement(collection, index, scope) {
|
|
3053
|
+
const knownCollection = knownValueForExpression(collection, scope)
|
|
3054
|
+
const knownIndex = knownValueForExpression(index, scope)
|
|
3055
|
+
|
|
3056
|
+
if (knownCollection?.kind != "ListLiteral" || knownIndex?.kind != "IntegerLiteral" ||
|
|
3057
|
+
knownIndex.value < 0 || knownIndex.value >= knownCollection.elements.length) return undefined
|
|
3058
|
+
|
|
3059
|
+
return knownCollection.elements[knownIndex.value]
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
/**
|
|
3063
|
+
* Finds the statically selected map value.
|
|
3064
|
+
* @param {import("./types.js").Expression} collection - Map receiver.
|
|
3065
|
+
* @param {import("./types.js").Expression} key - Key expression.
|
|
3066
|
+
* @param {Scope} scope - Visible scope.
|
|
3067
|
+
* @returns {import("./types.js").Expression | undefined} Proven value.
|
|
3068
|
+
*/
|
|
3069
|
+
function provenMapValue(collection, key, scope) {
|
|
3070
|
+
const knownCollection = knownValueForExpression(collection, scope)
|
|
3071
|
+
const knownKey = knownValueForExpression(key, scope)
|
|
3072
|
+
|
|
3073
|
+
if (knownCollection?.kind != "MapLiteral" && knownCollection?.kind != "OrderedMapLiteral" ||
|
|
3074
|
+
knownKey?.kind != "StringLiteral") return undefined
|
|
3075
|
+
|
|
3076
|
+
return knownCollection.entries.find((entry) => entry.key.value == knownKey.value)?.value
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
/**
|
|
3080
|
+
* Identifies numeric strings independently from any target's key coercions.
|
|
3081
|
+
* @param {string} value - Literal key.
|
|
3082
|
+
* @returns {boolean} Whether it is numeric.
|
|
3083
|
+
*/
|
|
3084
|
+
function isNumericString(value) {
|
|
3085
|
+
return /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/iu.test(value.trim())
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
/**
|
|
3089
|
+
* Selects an exact semantic token location when parser provenance is available.
|
|
3090
|
+
* @param {{location: import("./types.js").SourceLocation, sourceProvenance?: import("./types.js").SemanticNodeSourceProvenance}} node - Located semantic node.
|
|
3091
|
+
* @param {string} role - Token role.
|
|
3092
|
+
* @returns {import("./types.js").SourceLocation} Exact or owning location.
|
|
3093
|
+
*/
|
|
3094
|
+
function roleLocation(node, role) {
|
|
3095
|
+
return node.sourceProvenance?.ranges[role] ?? parserRangeFor(node, role) ?? node.location
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
/**
|
|
3099
|
+
* Selects the exact type token or the owning declaration location.
|
|
3100
|
+
* @param {unknown} type - Candidate type reference.
|
|
3101
|
+
* @param {import("./types.js").SourceLocation} ownerLocation - Owning declaration location.
|
|
3102
|
+
* @param {string} [role] - Recursive type role.
|
|
3103
|
+
* @returns {import("./types.js").SourceLocation} Exact type location when known.
|
|
3104
|
+
*/
|
|
3105
|
+
function typeLocation(type, ownerLocation, role = "type") {
|
|
3106
|
+
if (!type || typeof type != "object" || Array.isArray(type)) return ownerLocation
|
|
3107
|
+
const candidate = /** @type {import("./types.js").SemanticFunctionReturnType} */ (type)
|
|
3108
|
+
|
|
3109
|
+
return candidate.sourceProvenance?.ranges[role] ?? parserRangeFor(candidate, role) ??
|
|
3110
|
+
candidate.sourceProvenance?.ranges.type ?? parserRangeFor(candidate, "type") ?? ownerLocation
|
|
386
3111
|
}
|
|
387
3112
|
|
|
388
3113
|
/** @type {Readonly<Record<import("./types.js").SemanticUnaryOperation, {operand: import("./types.js").SemanticTypeName, result: import("./types.js").SemanticTypeName}>>} */
|