diff --git a/.gitignore b/.gitignore index 5740d19..6649f85 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ x.txt /target /book .claude/settings.local.json +.agents/ diff --git a/.pi/skills/formality-core-idioms/SKILL.md b/.pi/skills/formality-core-idioms/SKILL.md index 0846ffe..15279a0 100644 --- a/.pi/skills/formality-core-idioms/SKILL.md +++ b/.pi/skills/formality-core-idioms/SKILL.md @@ -88,6 +88,7 @@ This means: **anywhere `impl Upcast` is expected, you can pass `&T` and it wi 1. **Generated constructors** — all parameters accept `impl Upcast` 2. **`judgment_fn!` parameters** — the function signature is `fn f(x: impl Upcast)` 3. **`Env` methods** — `push_local_variable`, `add_assumptions`, etc. accept `impl Upcast` +4. **Judgment return positions** — results upcast automatically to the declared return type ### Common coercion chains @@ -96,11 +97,25 @@ This means: **anywhere `impl Upcast` is expected, you can pass `&T` and it wi | `&T` | `T` | auto-clone via `UpcastFrom<&T>` | | `UniversalVar` | `Variable` | `UpcastFrom` | | `UniversalVar` | `Parameter` | via `Variable` → `Parameter` | -| `NamedTy` | `Ty` | `#[cast]` on `Ty::NamedTy` variant | +| `NamedTy` | `Ty` | `UpcastFrom` on `Ty::NamedTy` variant | +| `Ty` | `Parameter` | `UpcastFrom` on `Parameter::Ty` variant | +| `Perm` | `Parameter` | `UpcastFrom` on `Parameter::Perm` variant | | `Place` | via `Var` | `UpcastFrom for Place` | | `Vec` | `Vec` | element-wise upcast | | `&[T]` | `Vec` | element-wise upcast | +### Upcasted iterator adapter + +Use `.upcasted()` (from `formality_core::Upcasted`) for element-wise upcast in iterator chains: + +```rust +// ✅ Good +let perms: Vec = chains.into_iter().upcasted().collect(); + +// ❌ Verbose +let perms: Vec = chains.into_iter().map(|c| c.upcast()).collect(); +``` + ### Examples ```rust @@ -170,6 +185,76 @@ Use pattern matching in the conclusion to dispatch on variants — cleaner than ) ``` +### Struct patterns in conclusions + +Destructure structs directly in the conclusion instead of matching a wrapper and then destructuring with `let`: + +```rust +// ✅ Good — destructure in conclusion, result auto-upcasts +( + (normalize_params(env, parameters) => norm_params) + --- ("named") + (normalize_ty(env, NamedTy { name, parameters }) => NamedTy::new(name, norm_params)) +) + +// ❌ Verbose — extra let + explicit wrapping +( + (let NamedTy { name, parameters } = named_ty) + (normalize_params(env, parameters) => norm_params) + --- ("named") + (normalize_ty(env, Ty::NamedTy(named_ty)) + => Ty::NamedTy(NamedTy { name: name.clone(), parameters: norm_params.to_vec() })) +) +``` + +Note: `NamedTy::new(name, norm_params)` returns a `NamedTy`, which auto-upcasts to `Ty` (via the `Ty::NamedTy` variant cast) when the judgment's return type is `Ty`. + +### Upcast in return values + +Judgment results upcast to the declared return type automatically. If your judgment returns `Parameter`, you can return a `Ty` or `Perm` directly: + +```rust +// ✅ Good — Ty auto-upcasts to Parameter +( + (normalize_ty(env, ty) => norm_ty) + --- ("ty") + (normalize_param(env, Parameter::Ty(ty)) => norm_ty) +) + +// ❌ Verbose — manual wrapping +( + (normalize_ty(env, ty) => norm_ty) + --- ("ty") + (normalize_param(env, Parameter::Ty(ty)) => Parameter::ty(norm_ty)) +) +``` + +### Building collections with `Cons` and `()` + +For recursive list/set processing in judgments, use `Cons(head, tail)` in both pattern matching and return positions. Use `()` for empty collections. + +```rust +judgment_fn! { + fn process_list(env: Env, items: Vec) => Vec { + // Base case: empty → empty (() upcasts to empty Vec) + ( + --- ("nil") + (process_list(_env, ()) => ()) + ) + + // Recursive: Cons destructures the head, Cons builds the result + ( + (transform(env, item) => new_item) + (process_list(env, rest) => new_rest) + --- ("cons") + (process_list(env, Cons(item, rest)) => Cons(new_item, new_rest)) + ) + } +} +``` + +This works for both `Vec` and `Set`. No need for `prepend` helpers, `Vec::new()`, or `std::iter::once(...).chain(...).collect()`. + ### Cut points with `!` Place `!` after a condition to mark a **match commit point**. Rules that fail before the cut are excluded from error reports. Use cuts on guard conditions that definitively select or reject a rule: @@ -185,16 +270,36 @@ Place `!` after a condition to mark a **match commit point**. Rules that fail be Without the `!`, if the empty check passes but a later rule fails, error reports would unhelpfully include "empty_drop failed because condition was false" for non-empty bodies. +**Complementary guards should both have cuts.** When two rules partition cases with a boolean check, put `!` on both: + +```rust +// ✅ Good — both branches cut +( + (if !needs_work(&value))! + --- ("no work needed") + (process(_env, value) => value) +) + +( + (if needs_work(&value))! + (do_work(env, value) => result) + --- ("do work") + (process(env, value) => result) +) +``` + ### `Arc` gotcha -Fields declared as `Arc` become `&Arc` when destructured (standard Rust). Calling `.clone()` gives you `Arc`, **not `T`**. Use `T::clone(x)` to get a `T` via deref coercion: +Fields declared as `Arc` become `&Arc` when destructured (standard Rust). Calling `.clone()` gives you `Arc`, **not `T`**. When passing to a judgment or generated constructor that accepts `impl Upcast`, use `&**x` to get `&T` — the `UpcastFrom<&T>` blanket impl handles the clone: ```rust -// Inside a judgment rule where `expr` is `&Arc`: -(let owned_expr: Expr = Expr::clone(expr)) // ✅ Gets Expr, not Arc -(let arc_copy: Arc = expr.clone()) // Gets Arc — usually not what you want +// Inside a judgment rule where `inner_ty` is `&Arc`: +(some_judgment(env, &**inner_ty) => result) // ✅ &**inner_ty is &Ty, upcast clones it +(some_judgment(env, inner_ty.clone()) => result) // ❌ Passes Arc, not Ty ``` +For non-Arc fields (e.g., `perm: &Perm` from `ApplyPerm(Perm, Arc)`), just pass `perm` directly — `&Perm` upcasts to `Perm` automatically. + ### `for_all` vs `in` - `(x in collection)` — **existential**: there exists some `x` in `collection` that satisfies the remaining conditions @@ -209,6 +314,27 @@ Fields declared as `Arc` become `&Arc` when destructured (standard Rust). (MethodDecl { name: _, binder } in methods.into_iter().filter(|m| m.name == *method_name)) ``` +## Using judgment results outside `judgment_fn!` + +Judgment functions return `ProvenSet`, not `T` or `Result`. Inside `judgment_fn!` rules, the `=>` syntax handles this automatically. Outside, you must extract results explicitly: + +```rust +// Check if a judgment succeeds (bool) +prove_is_copy(&env, ¶m).is_proven() + +// Extract a single result (when exactly one is expected). +// into_singleton() returns Result<(T, ProofTree), Box>. +// Box is compatible with `?` in anyhow contexts — no map_err needed. +let (result, _proof) = red_perm(&env, &live_after, &perm) + .into_singleton()?; + +// Get all results as a map +let results = my_judgment(&env, &x) + .into_map()?; +``` + +**Common mistake:** calling `.is_ok()` on a `ProvenSet` — it doesn't have that method. Use `.is_proven()` instead. + ## Parser / grammar conventions ### Kind keywords are lowercase @@ -248,12 +374,33 @@ pub struct ClassDeclBoundData { } ``` +## Generated constructor name collisions + +The `#[term]` macro generates `EnumName::snake_case_variant(...)` for each variant. For example, `Perm::Or(Set)` generates `Perm::or(...)`. If you add a hand-written method with the same name in an `impl` block, you get a **"duplicate definitions"** error. Use a distinct name: + +```rust +// ❌ Fails — collides with generated Perm::or() +impl Perm { + pub fn or(perms: impl IntoIterator) -> Perm { ... } +} + +// ✅ Good — distinct name +impl Perm { + pub fn flat_or(perms: impl IntoIterator) -> Perm { ... } +} +``` + ## Summary of rules 1. **Use generated constructors** (`Perm::var(x)` not `Perm::Var(x.upcast())`) 2. **Pass references** where `impl Upcast` is expected — the framework clones for you 3. **Pattern match** in judgment conclusions instead of `if` guards -4. **Use `!` cuts** on guard conditions to reduce error noise -5. **Use `T::clone(x)`** for `Arc` fields, not `.clone()` -6. **Use `ty`/`perm`** as kind keywords in parsed strings, not `type`/`perm` -7. **Don't add to KEYWORDS** unless you need to reserve the word globally +4. **Destructure structs** in conclusions, not with separate `let` bindings +5. **Let upcast handle return values** — `norm_ty` auto-upcasts to `Parameter`, `NamedTy` to `Ty` +6. **Use `Cons`/`()` for collections** in judgment return positions, not manual Vec building +7. **Use `!` cuts** on guard conditions, including both sides of complementary pairs +8. **Use `&**x`** for `Arc` fields, not `.clone()` +9. **Use `ty`/`perm`** as kind keywords in parsed strings, not `type`/`perm` +10. **Use `.into_singleton()?`** directly — no `map_err` needed +11. **Use `.upcasted()`** for element-wise iterator upcast +12. **Don't add to KEYWORDS** unless you need to reserve the word globally diff --git a/.symposium/config.toml b/.symposium/config.toml new file mode 100644 index 0000000..b8c32a0 --- /dev/null +++ b/.symposium/config.toml @@ -0,0 +1,9 @@ +sync-default = false +agent = [] +self-contained = false +plugin-source = [] + +[skills] +formality-core = true + +[workflows] diff --git a/AGENTS.md b/AGENTS.md index f9c665c..c3af013 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,7 +18,9 @@ UPDATE_EXPECT=1 cargo test --all --all-targets ## Work In Progress -Check `WIP.md` at the project root — it points to the active implementation plan (currently `md/wip/vec.md`). +The current project is found in `md/wip/surface-syntax.md`. + +Other projects can be found in `md/wip.md`. **When implementing a WIP plan, update the WIP doc as you go.** Mark items complete, add implementation notes, and record any deviations from the plan — all as part of the same commit that implements the change, not after the fact. @@ -35,7 +37,7 @@ Key types: `Program`, `ClassDecl`, `MethodDecl`, `Ty`, `Perm`, `Expr`, `Statemen - `shared` — owned, shared (refcounted) - `ref[places]` — borrowed reference - `mut[places]` — borrowed mutable reference -- `given_from[places]` — moved permission (tracking source places) +- `given[places]` — moved permission (tracking source places) **Class predicates** (`ClassPredicate` enum, declared on classes): - `given class` — affine types (can have destructors) @@ -68,6 +70,7 @@ Key modules: - `places.rs` — place type computation - `expressions.rs`, `statements.rs`, `blocks.rs` — expression/statement type checking - `methods.rs`, `classes.rs` — declaration checking +- `pop_normalize.rs` — normalization of return types at call-site scope boundaries (resolves place-based permissions referencing popped fresh variables via `red_perm` expansion + dead-link stripping; produces `Perm::Or` for multi-place permissions) Uses formality-core's `judgment_fn!` macro for inference rules throughout. @@ -89,9 +92,12 @@ Key concepts: Test macros and helpers: - `assert_ok!` — type-check succeeds - `assert_err!` — type-check fails with expected error -- `assert_interpret!` — type-check + interpret succeeds, compare snapshot (output lines + result + heap) -- `assert_interpret_only!` — interpret without type-checking (for testing programs the type checker rejects) -- `assert_interpret_fault!` — interpret without type-checking, expect a fault +- `assert_interpret!` — unified interpreter test macro with four variants: + - `assert_interpret!({ program }, type: ok, interpret: ok(expect))` — type-check ok, interpret ok + - `assert_interpret!({ program }, type: ok, interpret: fault(expect))` — type-check ok, interpret faults (soundness bug or future-panic) + - `assert_interpret!({ program }, type: error(expect), interpret: ok(expect))` — type-check fails, interpret ok + - `assert_interpret!({ program }, type: error(expect), interpret: fault(expect))` — type-check fails, interpret faults + - Optional `prefix: expr,` before the program block for injecting shared definitions (e.g., `prefix: vec_prelude()`) ### `src/lib.rs` @@ -115,6 +121,6 @@ Things that cause confusing errors if you don't know about them: - **KEYWORDS reservation**: Adding a word to the KEYWORDS list in `declare_language!` (in `src/lib.rs`) prevents it from being used as an identifier anywhere. Grammar keywords (`#[grammar(x)]` on enum variants) work without being in KEYWORDS. Only add to KEYWORDS when you want to block identifier use. - **Parser ambiguity**: Two `#[term]` enums with variants resolving to the same keyword in the same parsing context cause a runtime panic ("ambiguous parse"). Fix with `#[grammar(distinct_keyword)]`. -- **Prefix ambiguity**: If one variant's keyword is a prefix of another's in the same enum (e.g., `given` vs `given[x]`), the parser silently matches the shorter one. Use a distinct keyword (e.g., `given_from`). +- **Prefix ambiguity**: Keyword-prefix variants can be tricky in formality-core. `Perm` now intentionally supports both `given` and `given[places]`; keep parse coverage for both spellings when editing that grammar. - **Arc clone in judgment_fn**: Fields declared as `Arc` become `&Arc` in judgment rules. `.clone()` gives `Arc`, not `T`. Use `T::clone(x)` for deref coercion to get `T`. - **`for_all` vs `in`**: `(x in collection)` is existential (there exists). `for_all(x in coll) with(acc)` is universal (for all). diff --git a/Cargo.lock b/Cargo.lock index 44eb4dd..8c5a771 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,24 +4,24 @@ version = 4 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -32,7 +32,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e" dependencies = [ - "unicode-width", + "unicode-width 0.1.14", "yansi-term", ] @@ -47,9 +47,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -62,26 +62,26 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -97,56 +97,80 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "ar_archive_writer" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +dependencies = [ + "object", +] [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-link", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", ] [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bstr" -version = "1.8.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "regex-automata", "serde", ] +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + [[package]] name = "camino" -version = "1.1.6" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "cargo-platform" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34637b3140142bdf929fb439e8aa4ebad7651ebf7b1080b3930aa16ac1459ff" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -167,24 +191,25 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ - "libc", + "find-msvc-tools", + "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.57" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -192,9 +217,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -204,27 +229,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "color-eyre" -version = "0.6.2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" dependencies = [ "backtrace", "color-spantrace", @@ -237,9 +262,9 @@ dependencies = [ [[package]] name = "color-spantrace" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" dependencies = [ "once_cell", "owo-colors", @@ -249,18 +274,18 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "colored" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -271,26 +296,26 @@ checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" [[package]] name = "console" -version = "0.15.7" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "unicode-width", - "windows-sys 0.45.0", + "once_cell", + "unicode-width 0.2.2", + "windows-sys 0.59.0", ] [[package]] name = "contracts" -version = "0.6.3" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d1429e3bd78171c65aa010eabcdf8f863ba3254728dbfb0ad4b1545beac15c" +checksum = "008eb94d541da40512913ef5e0707c3fb0e7280ba1af13f062461e46dd96ef7e" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -304,22 +329,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.10" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.18" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" -dependencies = [ - "cfg-if", -] +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "dada-model" @@ -338,27 +359,27 @@ dependencies = [ [[package]] name = "dissimilar" -version = "1.0.7" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632" +checksum = "aeda16ab4059c5fd2a83f2b9c9e9c981327b18aa8e3b313f7e6563799d4f093e" [[package]] name = "either" -version = "1.9.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "env_logger" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ "humantime", "is-terminal", @@ -385,9 +406,9 @@ dependencies = [ [[package]] name = "expect-test" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d9eafeadd538e68fb28016364c9732d78e420b9ff8853fa5e4058861e9f8d3" +checksum = "63af43ff4431e848fb47472a920f14fa71c24de13255a5692e93d4e90302acb0" dependencies = [ "dissimilar", "once_cell", @@ -395,9 +416,9 @@ dependencies = [ [[package]] name = "eyre" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", @@ -415,6 +436,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe1fa59a36a9928c12b6c4e9658f8b84d43e7578cb6d532b069ce84dcd0afda6" +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "fn-error-context" version = "0.2.1" @@ -423,7 +450,7 @@ checksum = "2cd66269887534af4b0c3e3337404591daa8dc8b9b2b3db71f9523beb4bafb41" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -435,7 +462,7 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "formality-core" version = "0.1.1" -source = "git+https://github.com/rust-lang/a-mir-formality.git#940a1d2e26f3a48e58b2a850c37b6b08e51d48c9" +source = "git+https://github.com/nikomatsakis/a-mir-formality-ndm?branch=dada-model-pin#3b26e98e3aaabfaa0748c7b8da4286bbcfc7a6c3" dependencies = [ "anyhow", "contracts", @@ -444,9 +471,11 @@ dependencies = [ "expect-test", "final_fn", "formality-macros", - "itertools 0.12.0", + "itertools 0.12.1", "lazy_static", + "miette", "regex", + "similar", "stacker", "tracing", "tracing-subscriber", @@ -456,21 +485,21 @@ dependencies = [ [[package]] name = "formality-macros" version = "0.1.0" -source = "git+https://github.com/rust-lang/a-mir-formality.git#940a1d2e26f3a48e58b2a850c37b6b08e51d48c9" +source = "git+https://github.com/nikomatsakis/a-mir-formality-ndm?branch=dada-model-pin#3b26e98e3aaabfaa0748c7b8da4286bbcfc7a6c3" dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.114", + "syn", "synstructure", "tracing", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", @@ -481,9 +510,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "hashbrown" @@ -508,15 +537,15 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "humantime" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "id-arena" @@ -526,9 +555,9 @@ checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] name = "indenter" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" @@ -544,36 +573,33 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.7" +version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" dependencies = [ "console", - "instant", "number_prefix", "portable-atomic", - "unicode-width", + "unicode-width 0.2.2", + "web-time", ] [[package]] -name = "instant" -version = "0.1.12" +name = "is-terminal" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ - "cfg-if", + "hermit-abi", + "libc", + "windows-sys 0.61.2", ] [[package]] -name = "is-terminal" -version = "0.4.9" +name = "is_ci" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix 0.38.21", - "windows-sys 0.48.0", -] +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" [[package]] name = "is_terminal_polyfill" @@ -583,9 +609,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] @@ -601,15 +627,25 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "leb128fmt" @@ -625,27 +661,21 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.181" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "linux-raw-sys" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "log" -version = "0.4.20" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "matchers" @@ -698,13 +728,43 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "miette-derive", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ - "adler", + "adler2", ] [[package]] @@ -725,18 +785,18 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.32.2" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -752,9 +812,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "owo-colors" -version = "3.5.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" [[package]] name = "pad" @@ -762,20 +822,20 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" dependencies = [ - "unicode-width", + "unicode-width 0.1.14", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "prettydiff" @@ -794,7 +854,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.114", + "syn", ] [[package]] @@ -808,27 +868,28 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.21" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" dependencies = [ + "ar_archive_writer", "cc", ] [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.3.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "regex" @@ -855,21 +916,21 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] @@ -888,37 +949,31 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.21" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.4.11", - "windows-sys 0.48.0", + "linux-raw-sys", + "windows-sys 0.61.2", ] [[package]] -name = "rustix" -version = "1.1.3" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys 0.11.0", - "windows-sys 0.61.2", -] +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "semver" -version = "1.0.20" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" dependencies = [ "serde", + "serde_core", ] [[package]] @@ -948,7 +1003,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -966,9 +1021,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" dependencies = [ "serde_core", ] @@ -982,17 +1037,29 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + [[package]] name = "stacker" -version = "0.1.15" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013" dependencies = [ "cc", "cfg-if", "libc", "psm", - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -1002,21 +1069,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "syn" -version = "1.0.109" +name = "supports-color" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "is_ci", ] +[[package]] +name = "supports-hyperlinks" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1025,66 +1102,84 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.0" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "285ba80e733fac80aa4270fbcdf83772a79b80aa35c97075320abfee4a915b06" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", - "unicode-xid", + "syn", ] [[package]] name = "tempfile" -version = "3.25.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom", "once_cell", - "rustix 1.1.3", + "rustix", "windows-sys 0.61.2", ] [[package]] name = "termcolor" -version = "1.3.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "unicode-linebreak", + "unicode-width 0.2.2", +] + [[package]] name = "thiserror" -version = "1.0.51" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.51" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -1099,7 +1194,7 @@ dependencies = [ "toml_datetime", "toml_parser", "toml_writer", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -1113,18 +1208,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.7+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "247eaa3197818b831697600aadf81514e577e0cba5eab10f7e064e78ae154df1" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" dependencies = [ - "winnow", + "winnow 1.0.0", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" [[package]] name = "tracing" @@ -1145,7 +1240,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -1160,9 +1255,9 @@ dependencies = [ [[package]] name = "tracing-error" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" dependencies = [ "tracing", "tracing-subscriber", @@ -1181,9 +1276,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "once_cell", @@ -1235,27 +1330,39 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utf8parse" @@ -1265,9 +1372,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "wasip2" @@ -1287,6 +1394,51 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + [[package]] name = "wasm-encoder" version = "0.244.0" @@ -1321,6 +1473,16 @@ dependencies = [ "semver", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1339,11 +1501,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "winapi", + "windows-sys 0.61.2", ] [[package]] @@ -1360,20 +1522,11 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.45.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", + "windows-targets", ] [[package]] @@ -1387,123 +1540,79 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "windows_i686_gnu" -version = "0.48.5" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "winnow" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "winnow" -version = "0.7.14" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" [[package]] name = "wit-bindgen" @@ -1535,7 +1644,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.114", + "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -1551,7 +1660,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.114", + "syn", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -1604,6 +1713,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 9e8138d..f43d2d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ clap = { version = "4.4.7", features = ["derive"] } contracts = "0.6.3" expect-test = "1.4.1" fn-error-context = "0.2.1" -formality-core = { git = "https://github.com/rust-lang/a-mir-formality.git" } +formality-core = { git = "https://github.com/nikomatsakis/a-mir-formality-ndm", branch = "dada-model-pin" } itertools = "0.14.0" tracing = "0.1.40" diff --git a/WIP.md b/WIP.md deleted file mode 100644 index de0e0a8..0000000 --- a/WIP.md +++ /dev/null @@ -1,3 +0,0 @@ -# Work In Progress - -The current project is found in `md/wip/var-pop-normalization.md`. diff --git a/md/SUMMARY.md b/md/SUMMARY.md index c1e48e1..5cc3898 100644 --- a/md/SUMMARY.md +++ b/md/SUMMARY.md @@ -13,5 +13,12 @@ - [Liveness and cancellation](./subpermissions/liveness.md) - [Running a program](./interpreter.md) - [Work in progress](./wip.md) - - [Vec](./wip/vec.md) + - [given_from to given migration](./wip/given-from-to-given-migration.md) + - [Introduce Elaborator](./wip/introduce-elaborator.md) + - [Drop Dangle Pop](./wip/drop-dangle-pop.md) + - [Non-Straightline Control Flow](./wip/control-flow.md) - [Unsafe code](./wip/unsafe.md) + - [Type Error Analysis](./wip/type-error-analysis.md) + - [Vec and array design](./wip/vec.md) + - [Var-pop normalization](./wip/var-pop-normalization.md) + - [Interpreter Test Rework](./wip/interpreter-test-rework.md) diff --git a/md/wip.md b/md/wip.md index 7684798..985d864 100644 --- a/md/wip.md +++ b/md/wip.md @@ -1 +1,30 @@ # Work in progress + +This chapter contains execution plans that have been used to drive changes. + +Completed plans are retained for historical purposes. + +## In progress + +* [ ] [Surface syntax](./wip/surface-syntax.md) *(current)* +* [ ] [Introduce Elaborator](./wip/introduce-elaborator.md) +* [ ] [Drop Dangle Pop](./wip/drop-dangle-pop.md) +* [ ] [Non-Straightline Control Flow](./wip/control-flow.md) +* [ ] [Unsafe code](./wip/unsafe.md) +* [ ] [Valid predicates](./wip/valid-predicates.md) + +## Completed + +* [x] [Type Error Analysis](./wip/type-error-analysis.md) +* [x] [given_from to given migration](./wip/given-from-to-given-migration.md) +* [x] [Vec and array design](./wip/vec.md) +* [x] [Var-pop normalization](./wip/var-pop-normalization.md) +* [x] [Interpreter Test Rework](./wip/interpreter-test-rework.md) + +## Notes / drafts + +* [ ] [Formality-core left-recursion prompt](./wip/formality-left-recursion-prompt.md) + +## Templates + +* [ ] [WIP template](./wip/TEMPLATE.md) diff --git a/md/wip/TEMPLATE.md b/md/wip/TEMPLATE.md new file mode 100644 index 0000000..6d56c9c --- /dev/null +++ b/md/wip/TEMPLATE.md @@ -0,0 +1,17 @@ + + +# Goal + + + +# Motivation + + + +# Design + + + +# FAQ + + diff --git a/md/wip/control-flow.md b/md/wip/control-flow.md new file mode 100644 index 0000000..3498ee5 --- /dev/null +++ b/md/wip/control-flow.md @@ -0,0 +1,87 @@ +# Non-Straightline Control Flow + +> **Status: Design in progress.** We are still working out the right approach. Nothing here is final. + +## Problem + +The type checker threads `Env` and `LivePlaces` linearly through judgments, as if all code were straight-line. This breaks down for every form of non-straightline control flow: + +### If/else: branches treated sequentially + +```rust +(type_expr_as(env, ..., &**cond, TypeName::Bool) => env) +(type_expr_as(env, ..., &**if_true, Ty::unit()) => env) // env from cond +(type_expr_as(env, ..., &**if_false, Ty::unit()) => env) // env from if_true! +``` + +Two bugs: +1. **The false branch sees mutations from the true branch.** The `env` output from checking `if_true` (with in-flight permission rewrites) is fed into `if_false`, as if both branches execute sequentially. +2. **The output env is from the last branch only.** After the if/else, the env should reflect a *join* of both branches — what's true on both paths. Currently it just takes whatever fell out of the false branch. + +### Loop: no back-edge, env leaks + +```rust +Statement::Loop(block) => block.adjust_live_vars(live), +``` + +1. **Liveness doesn't account for the back-edge.** A single pass through the body misses that variables used at the start are live at the end (next iteration). This is under-approximate — potentially unsound, since it could allow moves of things needed in the next iteration. +2. **The loop type rule discards the body env** (correctly, as implemented), but the liveness fed into the body doesn't reflect the repeating nature of the loop. + +### Break: no loop context + +```rust +Statement::Break => live, // just passes through +``` + +1. **Liveness from dead code after `break` propagates backwards.** Over-approximate (conservative), but imprecise. +2. **Break doesn't know what's live after the enclosing loop.** The `adjust_live_vars` trait has no loop context, so break can't contribute the right liveness. + +## Root cause + +All three issues stem from the same thing: the type checker assumes linear control flow. `Env` is threaded A → B → C, but control flow is actually: + +- **If/else**: fork into two paths, then join +- **Loop**: cycle (body feeds back into itself) +- **Break**: non-local exit to a surrounding scope + +## Observations + +### Why `Env` is threaded at all + +The `Env` carries `local_variables: Map`, and types contain permission places. The env is threaded as output because **in-flight permission tracking** rewrites those places as values move: + +- `with_in_flight_stored_to` — value lands in a variable, `Var::InFlight` → actual place +- `with_var_stored_to` — value moves between variables, permissions updated +- `push_local_variable` / `pop_fresh_variable` — variables enter/leave scope + +The `universe`, `in_scope_vars`, `assumptions`, and `program` fields are never modified through the output path. + +### Loop context in `Env` + +Since `Env` is already threaded everywhere, it's a natural place to carry loop context. For example, a field like `break_live: Option` — `None` outside loops, `Some(live_after_loop)` inside. The `loop` rule sets it; the `break` rule reads it. + +## Design space (under discussion) + +Things we need to figure out: + +- **Env forking/joining for if/else**: How to fork env before branches and join after? What does "join" mean for permission places that diverged? +- **Loop fixed-point for liveness**: Iterate `body_live_after = live_after_loop ∪ block.adjust_live_vars(body_live_after)` until stable? Do this in `adjust_live_vars`, the type rule, or both? +- **Loop fixed-point for env**: Does the env need a fixed-point too (permissions could change across iterations)? Or is it enough to check the body once with conservative assumptions? +- **Break context**: Add `break_live` to `Env`? Or restructure `adjust_live_vars` to carry loop context? +- **Interaction between fixes**: Can we address these incrementally, or do they need to be solved together? + +## Existing tests + +Three loop tests were changed from `type: error, interpret: ok` to `type: ok, interpret: ok` with the initial loop/break rules: +- `loop_body_value_is_freed` +- `block_early_break_drops_locals` +- `loop_break_drops_locals` + +These pass with the current (incomplete) implementation but may need updating as we improve correctness. + +New tests to write once the design settles: +- Variable used across loop iterations (back-edge liveness) +- Move in loop body with reassignment (live across back-edge prevents premature move) +- If/else where one branch moves a variable (join must reflect the move) +- Break inside nested blocks within a loop +- Dead code after break diff --git a/md/wip/drop-dangle-pop.md b/md/wip/drop-dangle-pop.md new file mode 100644 index 0000000..8846d30 --- /dev/null +++ b/md/wip/drop-dangle-pop.md @@ -0,0 +1,150 @@ +# Drop Dangle Pop + +> **Status: Design in progress.** We are still working out the right approach. Nothing here is final. + +## Problem + +We are not accounting for the possibility of popped variables and references very well. I'll explain through examples. + +### Example 1. Give referenced value, but reference is dead + +This example should be ok. + +```dada +class Data() + +let base = Data(); +let r = base.ref; + +{ + base.give; +} +``` + +### Example 2. Give referenced value, reference is not dead -- ERROR + +This example should be an error. + +```dada +class Data() + +let base = Data(); +let r = base.ref; + +{ + base.give; +} + +print(r) +``` + +### Example 3. Give `data` when a reference to `data` is used in a share class destructor -- OK + +When a share class has a destructor, it should not assume that values of generic type are valid. + +```dada +class Wrap[type T] { + value: T; + + drop { + // this destructor does not access `value` + } +} + +class Data() + +let base = Data(); +let r = base.ref; +let wrap = new Wrap(r); + +{ + base.give; +} + +// wrap dtor runs here, but no harm done +``` + +### Example 4. `share` class destructor attempting to access generic data -- ERROR + +When a share class has a destructor, it should not assume that values of generic type are valid. + +```dada +class Wrap[type T] { + value: T; + + drop { + let x = self.value.ref; // ERROR + } +} +``` + +### Example 5. `given` class destructor can access values -- OK + +Given classes get full ownership of their fields and are able to access them. + +```dada +given class Wrap[type T] { + value: T; + + drop { + // THIS dtor gets ownership of value + let x = self.value.give; // OK + } +} +``` + +### Example 6. Give `data` when a reference to `data` is used in a `given` destructor -- ERROR + +Given classes get full ownership of their fields and are able to access them. + +```dada +given class Wrap[type T] { + value: T; + + drop { + // THIS dtor gets ownership of value + let x = self.value.give; // OK + } +} + +class Data() + +let base = Data(); +let r = base.ref; +let wrap = new Wrap(r); + +{ + base.give; +} + +// wrap dtor runs here -- ERROR +``` + +## Planned design + +### Add `Place::Dropped` + +I want to add a special place called `Dropped` -- or maybe a special var? When we drop an inflight value (e.g., after `base.give;`) we will rewrite all references to `Inflight` to `Dropped` in the environment. + +Therefore, + +### Validity predicates + +We will add a new predicate `T is valid`. It is provable for just about anything but not for + +* generic parameters (types, permissions) -- must come from the environment +* any permission that references `Place::Dropped` (i.e., `ref` or `mut`) + +### Implicit validity predicate on all methods + +We will add `where X is valid` for any generic parameter X on every method and on constructors as an implicit requirement, checked on every method call. + +It will therefore be in the environment when proving method bodies etc. + +### Validity predicate is present for `drop` in a `given` class, but NOT present for drop in a `share` class + +The validity predicate will not be allowed + +### Drop of a given type is considered a live use for that value + +For a block, the set of values live on exit includes any that will be dropped. Oh, this is interesting. diff --git a/md/wip/formality-left-recursion-prompt.md b/md/wip/formality-left-recursion-prompt.md new file mode 100644 index 0000000..e9632d3 --- /dev/null +++ b/md/wip/formality-left-recursion-prompt.md @@ -0,0 +1,119 @@ +# Formality-core: left-recursion seed produces right-associative bias + +## Context + +I'm updating [dada-model](https://github.com/nikomatsakis/dada-model) to the latest formality-core commit (`1f47788e`). The new parser changed `ParseResult` from returning a single `SuccessfulParse` to a `Set`, propagating ambiguity upward instead of picking one result per nonterminal. + +This exposed a grammar ambiguity in dada-model that the old parser silently resolved. But investigating it revealed what appears to be an unintentional bias in the left-recursion machinery itself. + +## The grammar + +Dada has (simplified): + +```rust +#[term] +pub enum Perm { + #[grammar(given)] + Given, + #[grammar(shared)] + Shared, + #[grammar(mut $[v0])] + Mt(Set), + // ... other leaf variants ... + + #[grammar($v0 $v1)] + Apply(Arc, Arc), // perm composition +} + +#[term] +pub enum Ty { + #[cast] + NamedTy(NamedTy), + #[variable(Kind::Ty)] + Var(Variable), + + #[grammar($v0 $v1)] + ApplyPerm(Perm, Arc), // apply perm to type +} +``` + +## The bug/bias + +Parsing `given given given` as a `Perm` produces **only** the right-associative interpretation: + +``` +Apply(Given, Apply(Given, Given)) +``` + +The left-associative `Apply(Apply(Given, Given), Given)` is never generated. + +### Why + +The left-recursion fixed-point iteration in `left_recursion.rs` stores only the **single longest** parse as the seed for reuse: + +```rust +let best_value = all_values.iter().min_by_key(|s| s.text.len()).unwrap(); +with_top!(|top| { + top.value = Some(erase_type(best_value)); +}); +``` + +Tracing through `given given given`: + +- **Round 0**: No seed → only `Given` succeeds (consuming `"given"`, leaving `" given given"`). +- **Round 1**: Seed = `Given`. `Apply`'s left-recursive `$v0` returns the seed. `$v1` is a fresh parse that independently produces `Given` and `Apply(Given, Given)`. Results: `Given`, `Apply(Given, Given)` (remainder `" given"`), `Apply(Given, Apply(Given, Given))` (remainder `""`). +- **Round 2**: Seed = `Apply(Given, Apply(Given, Given))` (the longest — consumed everything). `Apply`'s `$v0` returns this, but `$v1` has no text left → no new values → fixed point. + +The intermediate result `Apply(Given, Given)` is never used as a seed, so the left-associative parse `Apply(Apply(Given, Given), Given)` is never produced. + +## The cross-type ambiguity this causes + +When parsing `mut[pair] Q Data` as a `Ty` (where `Q` is an in-scope perm variable), two interpretations exist: + +1. `ApplyPerm(Mt({pair}), ApplyPerm(Var(Q), NamedTy(Data)))` — perm is just `mut[pair]` +2. `ApplyPerm(Apply(Mt({pair}), Var(Q)), NamedTy(Data))` — perm is `mut[pair] Q` + +Both are structurally different, so `core_term_with` panics with "ambiguous parse." In dada-model these are semantically equivalent (Apply is just curried perm composition), but the parser doesn't know that. + +In pure-Perm contexts (e.g., inside `or(shared mut[d1], shared mut[d2])`), commas delimit the items, so there's no ambiguity — `Apply(Shared, Mt({d1}))` is the only parse that consumes everything before the comma. + +## Discussion of possible fixes + +### Option A: Generate all parses, disambiguate after + +Instead of using a single seed, use ALL accumulated values as seeds for left-recursion. This would produce both left- and right-associative parses. Then add a post-parse disambiguation mechanism: + +- **Normalization**: Let types provide a canonicalization function. Two parses that normalize to the same value are equivalent. (Dada already has `LeafPerms` / `push_leaves` / `from_leaves` that flatten `Apply` chains.) +- **Preference annotations**: `#[precedence]` / associativity applied as a filter on results rather than a parse-time restriction. + +This cleanly separates the two concerns the current machinery conflates: +1. **Termination** — preventing infinite recursion (the fixed-point iteration handles this) +2. **Disambiguation** — choosing among valid parses (done after-the-fact) + +Potentially slower (more parses generated), but more principled. + +### Option B: Fix the single-seed to iterate over all seeds + +Keep the current architecture but when multiple accumulated values exist, try each as a seed. This would produce `Apply(Apply(Given, Given), Given)` alongside `Apply(Given, Apply(Given, Given))`. Combined with the existing `core_term_with` dedup (which already removes structurally equal results), this might "just work" for cases where both parses exist but only one consumes all input. + +Doesn't help with the cross-type ambiguity though, since both interpretations consume all input. + +### Option C: Keep current behavior, add normalization at `core_term_with` + +Leave the left-recursion machinery as-is. Add a trait like `ParseNormalize` that types can implement. `core_term_with` normalizes all values before dedup. This is the most surgical fix. + +## Aside: `each_comma_nonterminal` / `each_delimited_nonterminal` lost collection genericity + +Separate from the left-recursion issue, the new continuation-based parser methods `each_comma_nonterminal` and `each_delimited_nonterminal` hardcode `Vec` as the collection type passed to the continuation. The old (non-continuation) API used `C: FromIterator`, which allowed parsing into `Set`, `Vec`, etc. + +This breaks dada-model's `Perm` enum, which uses `Set` fields with `$[v0]` grammar (e.g., `Mv(Set)` with `#[grammar(given $[v0])]`). The macro generates `|v0: Set, __p|` as the closure parameter, but the method passes `Vec`. + +I have a fix on a local branch (`fix/collection-parse` in my a-mir-formality checkout) that adds a generic `C: FromIterator + Debug` parameter to both methods, keeping `Vec` as the internal accumulator and converting via `into_iter().collect()` when calling the user's continuation. All existing formality tests pass. + +## What to investigate + +1. If we change the seed to iterate over all accumulated values, what breaks in the formality-rust test suite? Does the `Expr` precedence/associativity machinery still work correctly, or does it rely on the single-seed bias? + +2. The existing `#[precedence(N, left/right/none)]` annotations interact with the left-recursion machinery via `precedence_valid` checks and `min_precedence_level`. If we generate all parses and disambiguate after, can these annotations move to post-parse filtering cleanly? + +3. Is the `Both` associativity (the default) intentionally permissive, or is it just papering over the single-seed issue? diff --git a/md/wip/given-from-to-given-migration.md b/md/wip/given-from-to-given-migration.md new file mode 100644 index 0000000..5338564 --- /dev/null +++ b/md/wip/given-from-to-given-migration.md @@ -0,0 +1,39 @@ +# Goal + +Track the migration from `given_from[places] T` to `given[places] T`. + +This is a separate concern from the broader surface-syntax elaboration work. The `surface-syntax.md` document describes the target surface-language design; this document covers only the spelling change for place-based `given`. + +# Design + +The intended end state is to remove the `given_from` spelling entirely and use `given` for both forms throughout the language: + +- `given T` — the concrete owned-unique permission +- `given[places] T` — a symbolic permission representing ownership transferred out of those specific places + +The parser disambiguates by looking ahead for `[`. + +This is not just a surface sugar. The old `given_from[...]` spelling should be removed from the language rather than retained as a legacy alias. + +# Rollout plan + +## Commit 1: parser spike + +- [x] Proved out that the parser can distinguish `given` from `given[places]` in a minimal test case. +- Added focused parse coverage for the new `given[places]` spelling, and targeted parser tests passed with both `given` and `given[...]` forms present in `Perm`. + +## Commit 2: grammar rename + +- [x] Updated the grammar so the language spells the place-based form as `given[places]`. +- [x] Removed `given_from` from `KEYWORDS`; the old spelling is no longer reserved or accepted. +- [x] Updated parser-facing rule names, comments, and diagnostics references in the Rust sources. + +## Commit 3: corpus migration + +- [x] Rewrote `given_from` occurrences under `src/**/tests/`, `book/`, and docs to `given`. +- [ ] Verify the full test suite is green. + +# Open questions + +- Resolved: the cleanest parser encoding is to keep both `Perm` variants under the `given` keyword and rely on lookahead for `[` to distinguish `given` from `given[...]`. +- Resolved: this landed as a clean break. There is no temporary compatibility alias for `given_from[...]`. diff --git a/md/wip/interpreter-test-rework.md b/md/wip/interpreter-test-rework.md new file mode 100644 index 0000000..e98c0a4 --- /dev/null +++ b/md/wip/interpreter-test-rework.md @@ -0,0 +1,263 @@ +# Interpreter Test Rework + +## Goal + +Every interpreter test should **always run the type checker** and assert its expected outcome (pass or fail) before running the interpreter. Today, `assert_interpret_fault!` and `vec_test!` skip the type checker entirely, which means we don't know whether the type checker accepts or rejects those programs. + +The new regime: a single unified `assert_interpret!` macro that takes the program, the expected type-checker outcome (`type: ok` or `type: error(expect_test::expect![[...]])`), and the expected interpreter outcome (`interpret: ok(expect_test::expect![[...]])` or `interpret: fault(expect_test::expect![[...]])`). See the FAQ for details. + +All old macros (`assert_interpret_fault!`, `assert_interpret_only!`, `assert_interpret_after_err!`, `vec_test!`) are removed. Every test must declare the type checker's expected verdict. + +## Current State + +| Macro | What it does | Count | +|---|---|---| +| `assert_interpret!` | type-check ok → interpret ok | 64 | +| `assert_interpret_only!` | skip type-check → interpret ok | 114 | +| `assert_interpret_fault!` (2-arg) | skip type-check → interpret fault | 21 | +| `vec_test!` | skip type-check → interpret ok | 10 | +| `assert_interpret_after_err!` | type-check err (snapshot) → interpret ok | 0 invocations | + +### Files with tests that skip the type checker + +**`assert_interpret_only!` (114 calls):** +- `src/interpreter/tests/array.rs` - 55 calls. Comment says "type checker's Array rules are simplified stubs." +- `src/interpreter/tests/place_ops.rs` - 33 calls. Place operation tests that skip type-checking. +- `src/interpreter/tests/mdbook.rs` - 11 calls. +- `src/interpreter/tests/drop_body.rs` - 9 calls (loop/break not supported by type checker). +- `src/interpreter/tests/block_scoped_drops.rs` - 6 calls. +- `src/interpreter/tests/basics.rs` - 2 calls (loop tests). +- `src/interpreter/tests/share.rs` - 1 call. + +**`assert_interpret_fault!` (21 calls):** +- `src/interpreter/tests/array.rs` - 11 calls. Runtime UB (e.g., reading uninitialized array slots after drop). +- `src/interpreter/tests/place_ops.rs` - 9 calls. Exercise use-after-give, double-give, etc. +- `src/interpreter/tests/generics.rs` - 1 call. Double-give of Box[Data]. + +**`vec_test!` (10 calls):** +- `src/interpreter/tests/vector.rs` - 10 calls. All skip type-check because "the type checker doesn't yet fully support the permission patterns Vec uses." + +## The New Macro + +Helper functions in `test_util.rs`: + +```rust +use std::sync::Arc; + +/// Parse input fragments (concatenated), return the program. Panics on parse error. +pub fn parse_program(inputs: &[&str]) -> Arc { + let combined: String = inputs.concat(); + dada_lang::try_term(&combined).expect("parse error") +} + +/// Run the type checker. Returns Ok(()) or Err(normalized error string). +pub fn check_program_result(program: &Arc) -> Result<(), String> { + match type_system::check_program(program).into_singleton() { + Ok(_) => Ok(()), + Err(e) => Err(formality_core::test_util::normalize_paths( + format_error_leaves(&e), + )), + } +} + +/// Assert the type checker passes. Panics with the error if it fails. +pub fn assert_type_ok(program: &Arc) { + if let Err(e) = check_program_result(program) { + panic!("expected type checker to pass, but it failed:\n{e}"); + } +} + +/// Assert the type checker fails. Returns the error string for snapshot comparison. +/// Panics if the type checker passes. +pub fn assert_type_err(program: &Arc) -> String { + match check_program_result(program) { + Ok(()) => panic!("expected type checker to fail, but it passed"), + Err(e) => e, + } +} + +/// Assert the interpreter result starts with the given prefix ("Ok:" or "Fault:"). +pub fn assert_interpret_result(r: &InterpretResult, expected_prefix: &str) { + assert!( + r.result.starts_with(expected_prefix), + "expected interpreter result starting with {expected_prefix:?}, got:\n{}", + r.to_snapshot(), + ); +} +``` + +`run_interpreter` must also be made `pub` (it's currently private). + +The macro becomes thin dispatch: + +```rust +#[macro_export] +macro_rules! assert_interpret { + // Optional prefix: for injecting shared definitions (e.g., vec_prelude()). + // Usage: assert_interpret!(prefix: vec_prelude(), { program }, type: ok, interpret: ok(expect)); + + ($(prefix: $prefix:expr,)? { $($input:tt)* }, type: ok, interpret: ok($interp_expect:expr)) => {{ + let program = $crate::test_util::parse_program(&[$($prefix,)? stringify!($($input)*)]); + $crate::test_util::assert_type_ok(&program); + let r = $crate::test_util::run_interpreter(&program); + $crate::test_util::assert_interpret_result(&r, "Ok:"); + $interp_expect.assert_eq(&r.to_snapshot()); + }}; + + ($(prefix: $prefix:expr,)? { $($input:tt)* }, type: ok, interpret: fault($interp_expect:expr)) => {{ + let program = $crate::test_util::parse_program(&[$($prefix,)? stringify!($($input)*)]); + $crate::test_util::assert_type_ok(&program); + let r = $crate::test_util::run_interpreter(&program); + $crate::test_util::assert_interpret_result(&r, "Fault:"); + $interp_expect.assert_eq(&r.to_snapshot()); + }}; + + ($(prefix: $prefix:expr,)? { $($input:tt)* }, type: error($type_expect:expr), interpret: ok($interp_expect:expr)) => {{ + let program = $crate::test_util::parse_program(&[$($prefix,)? stringify!($($input)*)]); + let type_err = $crate::test_util::assert_type_err(&program); + $type_expect.assert_eq(&type_err); + let r = $crate::test_util::run_interpreter(&program); + $crate::test_util::assert_interpret_result(&r, "Ok:"); + $interp_expect.assert_eq(&r.to_snapshot()); + }}; + + ($(prefix: $prefix:expr,)? { $($input:tt)* }, type: error($type_expect:expr), interpret: fault($interp_expect:expr)) => {{ + let program = $crate::test_util::parse_program(&[$($prefix,)? stringify!($($input)*)]); + let type_err = $crate::test_util::assert_type_err(&program); + $type_expect.assert_eq(&type_err); + let r = $crate::test_util::run_interpreter(&program); + $crate::test_util::assert_interpret_result(&r, "Fault:"); + $interp_expect.assert_eq(&r.to_snapshot()); + }}; +} +``` + +## Plan + +### Phase 1: Rewrite `assert_interpret!` and convert existing uses + +- [x] Replace the `assert_interpret!` macro in `src/test_util.rs` with the new unified version above. +- [x] Make `run_interpreter` pub. +- [x] Add helper functions: `parse_program`, `check_program_result`, `assert_type_ok`, `assert_type_err`, `assert_interpret_result`. +- [x] Convert all 64 existing `assert_interpret!` calls from the old form: + ```rust + assert_interpret!({ program }, expect![[...]]); + ``` + to the new form: + ```rust + assert_interpret!({ program }, type: ok, interpret: ok(expect![[...]])); + ``` + This is a mechanical text transformation. Other old macros (`assert_interpret_fault!`, `assert_interpret_only!`, `assert_interpret_after_err!`) remain untouched in this commit. + +This is a single commit. All tests should pass with no snapshot changes. + +**Note:** The original count of 178 `assert_interpret!` was wrong - it included 114 `assert_interpret_only!` calls. The actual `assert_interpret!` count is 64. The `assert_interpret_only!` calls are migrated in Phase 2a (new phase). + +### Phase 2a: Migrate `assert_interpret_only!` call sites (one commit per file) + +For each existing `assert_interpret_only!`, run the type checker and convert to `assert_interpret!` with the appropriate `type:` tag. + +#### Files to migrate + +- [x] `src/interpreter/tests/array.rs` — 55 calls (44 ok, 11 error) +- [x] `src/interpreter/tests/place_ops.rs` — 33 calls (23 ok, 10 error) +- [x] `src/interpreter/tests/mdbook.rs` — 11 calls (7 ok, 4 error) +- [x] `src/interpreter/tests/drop_body.rs` — 9 calls (8 ok, 1 error) +- [x] `src/interpreter/tests/block_scoped_drops.rs` — 6 calls (4 ok, 2 error) +- [x] `src/interpreter/tests/basics.rs` — 1 call (0 ok, 1 error — loop/break) +- [x] `src/interpreter/tests/share.rs` — 1 call (0 ok, 1 error) + +### Phase 2b: Migrate `assert_interpret_fault!` call sites (one commit per file) + +For each existing 2-arg `assert_interpret_fault!`, run the type checker and convert to `assert_interpret!` with the appropriate `type:` tag. If the type checker unexpectedly passes, see the FAQ entry on soundness bugs. + +#### Files to migrate + +- [x] `src/interpreter/tests/array.rs` — 11 tests (2 error, 6 future-panic, 3 soundness gaps) +- [x] `src/interpreter/tests/place_ops.rs` — 9 tests (all type: error) +- [x] `src/interpreter/tests/generics.rs` — 1 test (type: error) + +### Phase 3: Migrate `vec_test!` call sites + +The `vec_test!` macro in `src/interpreter/tests/vector.rs` uses `test_interpret_only`. These are programs the type checker doesn't fully support yet. Each `vec_test!` becomes an `assert_interpret!` with `prefix: vec_prelude()` to inject the shared Vec/Iterator definitions. The `vec_prelude()` helper function stays. + +One commit per test: + +- [x] All 10 fail type-checking. Converted to `assert_interpret!(prefix: vec_prelude(), { ... }, type: error(...), interpret: ok(...))`. + +### Phase 4: Final cleanup + +- [x] Remove old macros (`assert_interpret_fault!`, `assert_interpret_only!`, `assert_interpret_after_err!`) and old helpers (`test_interpret_only`, `test_interpret_after_err`, `test_interpret`). +- [x] Remove the `vec_test!` macro. +- [x] Remove stale comments in `array.rs` ("All tests use assert_interpret_only!"). +- [x] Remove stale comment in `basics.rs` ("Uses assert_interpret_only! because the type checker lacks Loop/Break rules"). +- [x] Remove stale comment in `drop_body.rs` about `assert_interpret_only!`. +- [x] Update `AGENTS.md` test macro descriptions to reflect the new set. +- [x] Update this WIP doc to mark phases complete. + +## Soundness Gaps Found + +These tests pass the type checker but fault at runtime (type: ok, interpret: fault): + +- `array_drop_element` — use after array_drop +- `array_drop_shared_class_element` — use after array_drop +- `array_drop_p_given_range` — use after array_drop + +Six additional fault tests are future-panic (bounds/init checks the type system correctly accepts). + +Every commit keeps the tree green and is independently reviewable. + +## Future: Panic vs Fault + +Today the interpreter has one failure mode: `Fault`. But not all runtime errors are the same: + +- **Fault** = true UB (use-after-move, access of uninitialized memory). The type checker *should* prevent these. A program that type-checks ok but faults is a soundness bug. +- **Panic** = deliberate runtime check (array bounds, integer overflow, etc.). These are expected even in well-typed programs. The type system doesn't prevent them and shouldn't. + +We don't implement panic yet, but it's coming. When it arrives, we'd add `interpret: panic(expect)` as a third interpreter-outcome tag in the unified macro. This is a normal, expected test pattern - unlike `fault`, a panic in a well-typed program is not a bug. + +For now, some of the current `assert_interpret_fault!` tests (e.g., array bounds checks) are really "panic" tests wearing a "fault" hat. During migration we won't try to distinguish them - that's a separate future change. + +## FAQ + +### Q: What if a fault test passes the type checker? + +During migration, some `assert_interpret_fault!` tests may turn out to pass the type checker (i.e., `type: ok, interpret: fault(...)`). This means either: +- The type system has a **soundness gap** (it should reject the program but doesn't), or +- The test is really a **future-panic** test (e.g., array bounds), which the type system correctly accepts. + +We don't have a panic mechanism yet, so for now treat every such case as a **soundness bug**. Use `type: ok, interpret: fault(...)` in the migrated test, add a `// BUG:` comment, and collect these in a list at the bottom of this doc so we can track and fix them. + +### Q: Why one macro instead of many? + +We replace `assert_interpret!`, `assert_interpret_fault!`, `assert_interpret_only!`, `assert_interpret_after_err!`, and `vec_test!` with a single `assert_interpret!` macro. It takes three arguments: + +1. The program. +2. The expected type-checker outcome: `type: ok` or `type: error(expect![[...]])`. +3. The expected interpreter outcome: `interpret: ok(expect![[...]])` or `interpret: fault(expect![[...]])`. + +This covers all four combinations: + +```rust +// normal: type-checks, runs fine +assert_interpret!({ program }, type: ok, interpret: ok(expect![[...]])); + +// UB test: type checker catches it, interpreter faults +assert_interpret!({ program }, type: error(expect![[...]]), interpret: fault(expect![[...]])); + +// type system gap: type checker rejects, but actually runs fine +assert_interpret!({ program }, type: error(expect![[...]]), interpret: ok(expect![[...]])); + +// soundness bug or future-panic: type checker accepts, interpreter faults +assert_interpret!({ program }, type: ok, interpret: fault(expect![[...]])); +``` + +`type: ok` has no snapshot (just pass/fail). The interpret side always has a snapshot. + +Critically, `UPDATE_EXPECT=1` can refresh the *contents* of `expect!` snapshots but **cannot flip** `ok` ↔ `error` or `ok` ↔ `fault`. Those structural tags are part of the macro invocation, not snapshot data. This prevents silent drift where a test changes category without anyone noticing. + +## Non-Goals + +- We are NOT changing the type system tests (`src/type_system/tests/`). Those use `assert_ok!`/`assert_err!` and don't involve the interpreter. +- We are NOT fixing type system gaps discovered during migration (just documenting them via the error snapshots). +- We are NOT implementing the panic/fault distinction yet - just documenting it for future work. diff --git a/md/wip/introduce-elaborator.md b/md/wip/introduce-elaborator.md new file mode 100644 index 0000000..a75ac8a --- /dev/null +++ b/md/wip/introduce-elaborator.md @@ -0,0 +1,44 @@ +# Introduce Elaborator + +## Motivation + +We want to be able to do syntactic transformations to Dada source to introduce defaults. + +As first step, we'll introduce an `elaborator` module with a newtype'd program term `ElaboratedProgram` and modify the type-checking env + interpreter to store it. This will ensure that elaboration has taken place. + +For the moment, elaboration will be a no-op. + +## Design Notes + +### Mutation shape + +`ElaboratedProgram::new(&Program)` clones the program into a local `Program`, runs a private free function `elaborate(&mut Program)` inside the `elaborator` module, then wraps the result in `Arc` and stores it in the struct. This avoids any `Arc::make_mut` dance: mutation happens before the `Arc` exists. The `elaborate` fn is module-private — callers only see `ElaboratedProgram::new`, which guarantees elaboration has run. + +### Visibility inside the type checker + +`Env` stores an `ElaboratedProgram` (not `Arc`). The existing `Env::program(&self) -> &Program` accessor continues to return a `&Program`, so the rest of the type checker is unchanged. `ElaboratedProgram` also implements `Deref` for ergonomic access. Net effect: the newtype is enforced at construction and stored in `Env`, but largely invisible to callers. + +### Interpreter + +`Machine` stores an owned `ElaboratedProgram` (cloned — cheap, since it's just an `Arc` inside). The `'a` lifetime on `Machine` for the program reference goes away. Access via `Deref`/accessor as with `Env`. + +### Test macros + +All test macros in `src/test_util.rs` (`assert_ok!`, `assert_err!`, `assert_interpret!` and friends) route through `ElaboratedProgram::new`, so every test exercises elaboration (currently a no-op). + +## Implementation checklist + +- [x] Stub `src/elaborator.rs` with `ElaboratedProgram` newtype +- [x] Private free fn `elaborate(&mut Program)`, called from constructor before `Arc` wrap +- [x] `impl Deref for ElaboratedProgram` +- [x] `Env` stores `ElaboratedProgram`; `Env::program()` still returns `&Program` +- [x] `type_system::check_program` (and `check_decl`, `check_class`) take `ElaboratedProgram` +- [x] `Interpreter` stores owned `ElaboratedProgram`; dropped `'a` lifetime +- [x] `src/lib.rs` entry point constructs `ElaboratedProgram::elaborate(&program)` +- [x] `src/test_util.rs` macros route through `ElaboratedProgram::elaborate` +- [x] `cargo test --all --workspace` green (629 passing) + +## Gotchas encountered + +- **`#[term]` generates a `::new` constructor that bypasses elaboration.** For a struct, `#[term]` auto-generates `ElaboratedProgram::new(program: impl Upcast>)`, which would let callers skip elaboration. To close that hole, `ElaboratedProgram` is **not** a `#[term]`. Instead it is a regular struct that hand-derives `Clone, Ord, Eq, PartialEq, PartialOrd, Hash`, hand-implements `Debug`, and uses `formality_core::cast_impl!` — the same pattern `Env` uses. This gives it everything needed to appear as a `judgment_fn!` parameter without gaining a bypass constructor. The elaborating constructor is `ElaboratedProgram::elaborate(&Program)`. +- **Borrow checker in the interpreter.** Previously `Interpreter` held `program: &'a Program`, so `self.program.class_named(...)` returned a `&ClassDecl` whose lifetime was tied to `'a`, not to `self`. With owned storage, the borrow is now on `self`, which conflicted with subsequent `&mut self` calls in `drop_value`. Fixed by cloning the `ClassDecl` via `.map(ClassDecl::clone)` at the one site that hit this. diff --git a/md/wip/surface-syntax.md b/md/wip/surface-syntax.md new file mode 100644 index 0000000..da486d8 --- /dev/null +++ b/md/wip/surface-syntax.md @@ -0,0 +1,515 @@ +# Status note + +This document describes the **target state** for Dada's surface syntax and elaboration model. + +The **current state** is narrower: + +- the accepted grammar is still essentially the existing core syntax +- the final "types checked" phase exists today +- the earlier elaboration phases described below are design targets, not fully-implemented passes + +The "Commit N" section later in this document is the incremental rollout plan from the current implementation to that target design. + +This effort is mildly blocked on a related surface-syntax question: we also need to decide how permission and other parameters are written and resolved at **method call sites**, not just in declarations. That turns out to be a non-trivial part of the design/elaboration story, so the overall project is somewhat larger in scope than this doc originally assumed. It is not fully blocked, but it is not quite as self-contained as it first appeared. + +# Features in scope + +- `!` as postfix sugar for `mut` (in permissions and place expressions) +- Place expressions default to `.ref` when no access mode is given +- `.share` applied to a place expands to `.give.share` +- Implicit/default permissions on function parameters, while preserving bare field/return types +- Inline type and permission parameters, named or anonymous +- `exists[...] { ... }` blocks for explicit elaboration variables + +# Goal + +Match Dada's planned surface syntax, taking advantage of the new elaborator and improvements in formality's parsing capabilities. + +# Elaboration terminology and scope + +This document is about **elaboration**, not about extending the core type checker with inference rules. + +Elaboration serves two roles: + +1. Add in missing information that the surface language permits the user to omit. +2. Rewrite surface conveniences into explicit core forms. + +These phases are deterministic and meaningful: they make commitments that were not written explicitly by the user. They are therefore not merely "desugaring", even though some phases do perform ordinary sugar expansion. + +The key architectural point is that elaboration is allowed to produce a candidate explicit core program that is **not yet known to be sound**. Soundness remains the responsibility of the existing type checker, which runs only after elaboration is complete. In other words: + +- elaboration reconstructs a fully explicit program +- type checking validates that reconstructed program + +This is intentionally different from baking full inference directly into the core typing judgments. + +# Motivation + +The goal is to modify our examples to follow Dada's surface syntax. There are a number of places we deviate presently. + +## mut => `!` + +We plan to offer a postfix `!` syntax as a sugar for `mut` in permissions and in place expressions. Note that this claims postfix `!` as a place/permission operator: it cannot later be reused for negation, unwrap, macros, or any other meaning without parser surgery. Recording the decision here. + +``` +class Foo { + bar: String +} + +let foo: Foo + +let x: foo.bar! String = foo.bar! +# sugar for `let x: mut[foo.bar] String = foo.bar.mut` +``` + +## place expr default to `ref` + +The default for a place expression is `ref`: + +``` +class Foo { + bar: String +} + +let foo: Foo + +let x: foo.bar String = foo.bar +# sugar for `let x: ref[foo.bar] String = foo.bar.ref` +``` + +This default applies to the **whole place expression**, not to each prefix. So: + +```dada +foo.bar.baz +``` + +elaborates to: + +```dada +foo.bar.baz.ref +``` + +not to something like `foo.ref.bar.ref.baz.ref`. + +## share applied to a place expands to `.give.share` + +The share operator applied to a place "gives" the place and then shares the result: + +``` +class Foo { + bar: String; +} + +let foo: Foo; + +let x: shared String = foo.bar.share; +# sugar for `let x: shared String = foo.bar.give.share` +``` + +This rewrite is **purely syntactic**: the elaborator always rewrites `place.share` to `place.give.share` without consulting types. If `.give` is not legal on that place (for example, because the place is reached through a `ref`), the type checker will report a `.give` error on a `.give` the user never wrote. The fix is on the diagnostic side — when reporting an error on a `.give` that came from `.share` desugaring, the elaborator should attach provenance so the message can say "this `.give` was introduced by `.share` on ``". + +## implicit permissions on function parameters + +Every function parameter with no explicit permission gets a **fresh, unconstrained permission variable**, hoisted to the enclosing fn/method binder. `self` is not special — the same rule applies to every parameter. Each omitted-perm parameter gets its *own* fresh variable, so they are independent. + +Bare field types and bare return types are preserved exactly as written. In today's core language those plain forms are the existing owned form (for example `-> String` remains `-> String`, rather than being elaborated to an explicit `given String`). Parameters are different: an omitted permission on a parameter introduces a fresh perm variable. + +Why fully-unconstrained rather than e.g. defaulting to `is ref`? `perm P` and `perm P where P is ref` admit exactly the same operations on the parameter (in both cases the body must assume the value might be a reference), but the unconstrained form lets *callers* pass anything — owned, shared, mut, ref. Defaulting to fully unconstrained is therefore strictly more ergonomic at call sites with no cost in the body. + +### `!` on a parameter binder + +Postfix `!` on a parameter binder is sugar for prepending an anonymous `(perm is mut)` binder to that parameter's type. It **composes** with the implicit-perm rule rather than replacing it, and it can be used on any parameter binder (not just `self`): + +- `fn f(x!: Vec[T])` desugars to `fn f(x: (perm is mut) Vec[T])`. +- `fn set(self!)` is the same rule, but `self` is written in the receiver position rather than as an ordinary `name: Type` parameter. + +This is by the same "strictly more permissive at call sites" principle as the omitted-perm default: a body that needs mut access works equally well with any perm satisfying `is mut`, so we let callers pass any such perm rather than forcing a literal `Perm::Mt`. + +Note that this is **different** from `!` on a *place expression* (e.g. `foo.bar!`), which is a concrete `mut[foo.bar]` / `.mut`. Binder `!` is attached to the parameter name, not to the type annotation. The `!` token is shared but the two contexts desugar differently: + +| Context | Example | Desugars to | +|---|---|---| +| Place expression (value position) | `foo.bar!` | `foo.bar.mut` | +| Place expression (type position) | `foo.bar! String` | `mut[foo.bar] String` | +| Parameter binder | `x!: T` | `x: (perm is mut) T` | + +### `given` and `shared` on a parameter are concrete + +Unlike `!`, the `given` and `shared` keywords on a parameter introduce **concrete** permissions (`Perm::Given`, `Perm::Shared`) and do **not** introduce a fresh perm variable. They are an exception to the implicit-perm rule. + +``` +fn f(value: given T) # value: Perm::Given T — concrete +fn f(value: shared T) # value: Perm::Shared T — concrete +fn f(value: T) # value: P T for fresh P — implicit-perm rule +fn f(value!: T) # value: P T where P is mut — implicit-perm rule + `!` +``` + +The asymmetry is deliberate: `given` and `shared` are the *named* concrete permissions in the language (they correspond to ownership transfer and refcounted sharing, respectively), and writing them explicitly on a parameter is a clear signal that the author wants exactly that permission, not "any perm that happens to be `is given`". `!` (and the implicit-perm default) are escape hatches *toward* polymorphism; `given`/`shared` are escape hatches *away* from it. + +Examples: + +```dada +class Vec[type T] { + array: Array[T]; # class type parameters default to `given` + len: u32; + + fn len(self) -> u32 { + # ^^^^ omitted perm => fresh unconstrained perm var + # + # fn len[perm P](P self) -> u32 + } + + fn get(self, index: u32) -> given[self] T { + # fn get[perm P, perm Q](P self, Q index: u32) -> given[self] T + } + + fn contains(self, value: T) -> bool { + # ^^^^^^^^ + # Each omitted-perm parameter gets its *own* fresh perm var, + # so `self` and `value` are unrelated permission-wise. + # + # fn contains[perm P, perm Q](P self, Q value) -> bool + } + + fn set(self!, value: given T) { + # - ----- + # `self!` adds an `is mut` predicate to the fresh perm var + # for `self` (it composes with the implicit-perm rule + # rather than replacing it). `given` and `shared` on a + # parameter, by contrast, are *concrete* permissions — they + # do not introduce a fresh perm var. + # + # fn set[perm P](P self, value: given T) where P is mut + } + + fn pop(self!) -> Option[T] { + # --------- + # Bare return types are preserved as written. In the current core + # grammar, this plain form is already the owned return form. + } +} +``` + +`ref` is also the default when accessing a place: + +```dada +fn test(x: given String) { + let y: ref[x] String = x; +} +``` + +### Sharing a permission across parameters + +If you want two parameters to share a permission rather than each get their own fresh var, introduce a **named inline perm parameter** on first use and refer to it by name on subsequent uses: + +``` +fn f(x: perm P T, y: P T) { ... } +# fn f[perm P](P x: T, P y: T) +``` + +This mirrors how inline `type T` works (see below): the first occurrence introduces the binder, later occurrences are references. This is not expected to be a common pattern — most code wants the fully-independent default. + +## `exists[...] { ... }` blocks + +The surface language also admits explicit block-scoped elaboration variables: + +```dada +exists[type T] { + let x: (T, T) = (foo, bar) +} +``` + +This is a real surface-language construct, not just compiler-internal notation. It lets users state that there exists a choice of type and/or permission arguments under which the block elaborates. + +The binder accepts any mix of type and permission variables: + +```dada +exists[type T, perm P] { + ... +} +``` + +These variables are scoped over the block body. They are intended for expression/block-level elaboration only. Signature-level omission and inline signature binders still follow the rules described below and are elaborated separately. + +## in-line types and permissions + +You can use in-line types and permissions in various places. They create a new parameter scoped to the innermost declaration: + +``` +fn foo(x: given Vec[type T]) {} +# fn foo[type T](x: given Vec[T]) {} +``` + +Inline declarations can carry where-clauses: + +``` +fn foo(x: given Vec[type T is copy]) {} +# fn foo[type T](x: given Vec[T]) where T is copy {} +``` + +And they can be anonymous: + +``` +fn foo(x: given Vec[type is copy]) {} +# fn foo[type T](x: given Vec[T]) where T is copy {} +``` + +Permissions work the same way: + +``` +fn foo(x: Iterator[type, perm is shared]) {} +# fn foo[type T, perm P](x: Iterator[T, P]) where P is shared {} +``` + +### Where inline params are legal + +Within signatures, inline parameters are legal **only in function and method parameter types**, and they hoist to the enclosing fn/method binder. They are not legal in other signature positions: + +- ❌ **Return types** — `fn f(self) -> Vec[type T]` is an error. +- ❌ **Class field types** — `class C { xs: Vec[type T]; }` is an error, even though the class has a binder it could hoist to. Hiding a class's type parameters at a field-declaration site hurts readability; class parameters must be declared explicitly in the class header. +- ❌ **`let` type ascriptions** — `let x: Vec[type T] = ...` is an error as written. Inside function bodies, block-scoped elaboration variables must be introduced explicitly via `type` / `perm` statements or `exists[...] { ... }`, rather than implicitly from inside a `let` type. + +### Scoping within a signature + +Inline parameters are introduced in **source order**. Once an inline parameter has appeared, it is in scope for the rest of the signature: later parameter types, the return type, and the where-clause may all refer to it. + +So this works: + +```dada +fn foo(x: Vec[type T], y: T) -> T where T is copy +``` + +The same declaration-level binder may not introduce the same name twice. This is an error, just as it would be in an explicit binder list: + +```dada +fn foo(x: Vec[type T], y: Option[type T]) # error: duplicate introduction of `T` +``` + +An inline binder only puts the name in scope from that point onward, so this is also an error: + +```dada +fn foo(t: T, vec: Vec[type T]) # error: first `T` is not in scope +``` + +If an outer `T` is already in scope, later introduction of an inline `T` is allowed but confusing: + +```dada +class T +fn foo(t: T, vec: Vec[type T]) # OK, but should lint +``` + +In that example, the type of `t` is the outer `T`, while the element type of `vec` is the later inline `T`. Those are distinct types despite sharing a name. + +Anonymous inline parameters are always fresh; two occurrences introduce two distinct binders. + +### Inline predicates + +Both named and anonymous inline binders may carry `is` predicates: + +```dada +fn foo(x: Vec[type T is shared]) {} +fn foo(x: Vec[type is copy]) {} +fn foo(x: (perm P is mut) String) {} +fn foo(x: (perm is mut) String) {} +``` + +When an anonymous inline binder appears in a syntactically ambiguous prefix position, parentheses may be required: + +```dada +fn foo(x: perm Foo) {} +# parsed as `x: (perm Foo)` and therefore rejected: a type is expected + +fn foo(x: (perm) Foo) {} +# anonymous inline permission binder +``` + +In bracketed type-argument positions no extra parentheses are needed: + +```dada +fn foo(x: Vec[type is copy]) {} +``` + +This is a place where formality-core's reject mechanism may be useful to keep the grammar simple while rejecting the ambiguous parse cleanly. + +## `type` and `perm` in function bodies + +Inside a function body, `type` and `perm` introductions are also legal. In that context they do **not** hoist to the enclosing declaration binder; instead, they elaborate to a block-scoped existential. + +Examples: + +```dada +type T; +perm P; +``` + +These are surface shorthands for introducing fresh block-scoped elaboration variables, equivalent in spirit to wrapping the surrounding region in `exists[...]`. + +# Design + +We use a **single union grammar**. The accepted input language is a strict superset of core: every core program is also a valid surface program, but the surface language additionally permits omitted information, explicit `exists[...] { ... }` blocks, and various sugars. + +Elaboration proceeds in phases that progressively eliminate these surface-only forms until the program is in the existing core fragment. Later phases assume earlier ones have already run. + +The current phase split is: + +1. **Surface** +2. **Signature elaborated** +3. **Types elaborated** +4. **Permissions elaborated** +5. **Types checked** + +The last phase is the type checker we already have today. The earlier phases are frontend elaboration. + +The union grammar is only the parser-level story. Each phase also has a corresponding **wellformedness judgment** describing exactly which forms are permitted at that stage. The intended shape is: + +- a broad parsed grammar that can represent every phase +- a judgment per phase characterizing the fragment admitted there +- an elaboration judgment from one phase to the next + +So, for example, we expect judgments along the lines of `wf_surface`, `wf_signature_elaborated`, `wf_types_elaborated`, and `wf_permissions_elaborated`. The elaboration pipeline should establish these judgments at each boundary: if phase 1 produces a "signature elaborated" program, that output should satisfy `wf_signature_elaborated`, and so on. + +These judgments are not just specification machinery. They should also be used as implementation-time sanity checks at phase boundaries, e.g. assertions in the style of `assert!(wf_signature_elaborated(...).is_proven())`, so that leaked earlier-phase forms fail immediately instead of surfacing later as confusing bugs. + +## Surface is a strict superset of core + +The accepted input grammar is designed so that **every existing core form is also a valid surface form with the same meaning**. + +This is a load-bearing property: it means existing tests keep working unchanged, gives us free regression coverage on the elaboration pipeline's fixed-point behavior on core programs, and lets us add features incrementally with no forced migration. See the FAQ entry "Why is the surface grammar a superset of core?" for the consequences this design unlocks. + +## The elaborator is purely a frontend + +Nothing downstream of the final elaborated `Program` knows that defaults, `exists` binders, or sugars exist. Specifically: the type checker, predicate solver, interpreter, and every judgment under `src/type_system/` and `src/interpreter/` operate on the core grammar exclusively and are unchanged by this work. The elaborator's contract is still "surface `Program` in, core `Program` out"; once that boundary is crossed, the rest of the system is oblivious. + +This is a deliberate, load-bearing architectural choice: it keeps the type system simple to reason about (only one grammar to teach, only one set of judgments to debug) and means the surface syntax can evolve without disturbing the formal model. + +The codebase already has a placeholder for this boundary: the `ElaboratedProgram` newtype in `src/elaborator.rs`, currently produced by a no-op `elaborate` pass and consumed by `check_program`. The final shape may keep or replace that wrapper, but the architectural boundary remains the same: elaboration finishes before type checking begins. + +## Phase boundaries + +The phases are distinguished by which surface-only forms are still permitted. In the intended formalization, each bullet below corresponds to a wellformedness judgment for that phase. + +### 1. Surface + +This is the broadest accepted grammar. It includes: + +- all current core forms +- surface sugars like postfix `!` and implicit `.ref` +- omitted signature information governed by the rules above +- explicit block-scoped `exists[...] { ... }` binders + +### 2. Signature elaborated + +This phase resolves signature-level omission and inline signature sugar. In particular: + +- omitted parameter permissions are made explicit +- inline `type` / `perm` parameters are hoisted to the enclosing declaration binder +- anonymous inline parameters in signature positions are replaced by fresh explicit binders + +This phase is still frontend elaboration, not type checking. It may introduce explicit binders and internal elaboration variables, but it does not yet need to justify them semantically. + +### 3. Types elaborated + +This phase commits to type structure. Intuitively, it chooses the type "spine" while still allowing permissions to remain unresolved. + +Block-scoped `exists[type ...]` binders may be discharged here. The output of this phase should have fully explicit type structure, though permission unknowns may still appear inside that structure. + +### 4. Permissions elaborated + +This phase resolves the remaining permission unknowns and discharges `exists[perm ...]` binders. + +Its output is a fully explicit core program with no surface-only omission or existential forms remaining. + +### 5. Types checked + +This is the existing type checker. It validates the fully elaborated core program. Any soundness failures are reported here, not during the earlier elaboration phases. + +## Binders and internal representations + +The elaboration phases will still need to construct formality-core `Binder`s and `BoundVar`s when they produce fully explicit core declarations. formality-core exposes `BoundVar::fresh(kind)` and `Binder::new(bound_vars, body)`, and the existing codebase already uses this style. + +At the same time, not every intermediate phase has to be represented as a separate AST family. The current direction is to keep a single broad grammar and define each phase by which forms remain admissible, rather than by maintaining a completely separate `src/surface/` tree. + +Some phases may still find it convenient to use internal helper forms or judgments to represent partially elaborated programs. The load-bearing point is the phase boundary, not whether each phase gets its own Rust enum tree. + +# Implementation plan + +This section is an implementation rollout plan from the current codebase to the target-state design described above. These are **commits**, not the architectural elaboration phases. + +## Commit 1: elaboration skeleton + +- Introduce the multi-phase elaboration pipeline: + - surface + - signature elaborated + - types elaborated + - permissions elaborated + - types checked +- Wire the parser/elaborator/type-checker pipeline so that elaboration runs before `check_program`. +- Keep core programs as fixed points of the early phases. +- Acceptance criterion: existing tests keep passing unchanged. + +## Commit 2: signature elaboration + +- Implement declaration-signature elaboration for: + - omitted parameter permissions + - parameter-binder `!` + - inline `type` / `perm` + - anonymous inline params + - inline `is` predicates on named and anonymous binders +- Preserve the existing rule that bare field and return types remain as written. +- Add tests showing that signature omission becomes explicit before later elaboration phases. + +## Commit 3: type elaboration + +- Add the expression/block-level `exists[...] { ... }` form to the implementation. +- Add body-level `type` / `perm` introductions as sugar for block-scoped existentials. +- Elaborate type structure, discharging type existentials and choosing explicit type spines. +- Permit permission unknowns to remain after this phase. +- Add tests covering block-scoped `exists[type ...]`. + +## Commit 4: permission elaboration + +- Elaborate remaining permission unknowns into explicit permissions. +- Discharge `exists[perm ...]`. +- Keep the solver deterministic and scoped by the variables available at each use site. +- Add tests covering both explicit `exists[perm ...]` blocks and permissions introduced implicitly by earlier phases. + +## Commit 5+: remaining sugars and diagnostics + +These surface forms still need to be implemented as part of the overall effort: + +- `!` postfix sugar for `mut` in place expressions and permission positions +- place expressions defaulting to `.ref` +- `.share` on a place expanding to `.give.share` +- diagnostic provenance for user-written surface forms that elaborate into inserted core operations + +Commits can be reordered or split further if dependencies suggest a different sequence, but the 5-stage architecture above is the intended model. + +# FAQ + +## Why is the surface grammar a superset of core? + +Designing the accepted input grammar as a strict superset of core has several consequences we like: + +- **Zero forced migration.** Existing tests keep compiling unchanged. They serve as regression coverage for the fact that core programs are fixed points of the elaboration pipeline. +- **Incremental landing.** Each new feature is a small PR: accept a new surface form, elaborate it away in the appropriate phase, and add a few new tests. A bug in feature X cannot break tests that don't use feature X. +- **No escape hatch needed.** The core syntax *is* the escape hatch — it's always available, by construction, because it's a subset of surface. We don't need a `core!` macro or any other mechanism for tests that want to assert on post-elaboration form. +- **Cleaner correctness contract.** The elaborator's job can be stated as: "turn any accepted surface program into an explicit core program; leave already-core programs unchanged." The identity-on-existing-tests property turns the entire existing test suite into a free regression suite for that contract. + +The cost is that the surface grammar carries both old and new forms (e.g. both `mut[x]` and `x!`) indefinitely. We consider that an honest reflection of the migration story rather than a problem. Tests get rewritten to the new sugar opportunistically when someone touches them for unrelated reasons; there is no deadline. + +## Why create implicit permission variables uniformly, even for types that look shared? + +We briefly considered optimizing omitted parameter permissions away for obviously-shared-looking types, but the rule should stay uniform: an omitted parameter permission introduces a fresh perm variable. + +The reason is that surface appearance is not enough. Even if a class is declared `shared class`, instantiations can still carry type arguments whose own permissions matter to later reasoning. For example, `Pair[String]` may look trivially shared, but treating omitted permissions specially for some apparent surface shapes quickly turns into a maze of ad hoc exceptions. The uniform rule is simpler for users, simpler for the elaborator, and leaves optimization to later phases if it ever proves worthwhile. + +## What are things we still need to clarify? + +A few design points are still open and should be settled before or during implementation: + +- **Exact surface spelling for explicit permission arguments on non-`self` parameters.** The examples in this doc use forms like `x: given T`, `x: perm P T`, and `x: (perm) T`, but we should state the full accepted surface grammar explicitly once the parser shape is nailed down. +- **Whether binder `!` can appear together with an explicit permission annotation.** The intent is clear when the permission is omitted (`x!: T`), but combinations like `x!: P T` have not been fully specified yet. +- **Precise phase interfaces.** We now have the five high-level stages, but each one still needs a sharper contract stating exactly which forms are admitted in its input and guaranteed absent in its output. +- **Algorithm details for type elaboration.** The current plan is "infer the type spine first, leaving permission variables in place", but the exact local-vs-global strategy still needs to be written down. +- **Algorithm details for permission elaboration.** The current plan is bound propagation over scoped permission variables with deterministic choice, but the concrete solving strategy still needs to be specified. +- **How diagnostics should present elaborated sugars.** In particular, `.share` desugars to `.give.share`; when the inserted `.give` is illegal, the user-facing error should talk about the original `.share`. diff --git a/md/wip/type-error-analysis.md b/md/wip/type-error-analysis.md new file mode 100644 index 0000000..1d9c170 --- /dev/null +++ b/md/wip/type-error-analysis.md @@ -0,0 +1,98 @@ +# Type Error Analysis + +## Goal + +Using the unified `assert_interpret!` macro, systematically analyze every `type: error, interpret: ok` test — programs the type checker rejects but that run correctly. For each, determine whether: + +1. **The test is wrong** (e.g., wrong return type) — fix the test so it passes type-checking. +2. **The type system has a gap** — document the root cause for future type system work. + +We do NOT change type system rules during this work. + +## Current Inventory + +As of the test rework, there are **25** tests with `type: error, interpret: ok`: + +### `prove_copy_predicate { p: given }` — 11 tests + +The type checker cannot prove `given is copy`. These fail mid-body, typically when dealing with shared arrays or constructors that mix shared and given values. + +**array.rs (10):** +- `array_write_overwrites_shared_array` — writing shared array into array-of-arrays +- `nested_array_give_inner_from_shared_outer` — nested shared array access +- `nested_array_drop_inner_decrements_refcount` — nested shared array drop +- `shared_outer_array_of_data_arrays` — shared outer array of Data arrays +- `array_of_shared_inner_arrays` — array containing shared inner arrays +- `shared_outer_give_inner_survives_outer_drop` — inner array survives outer drop +- `shared_array_of_shared_arrays` — shared array of shared arrays +- `shared_array_of_shared_arrays_drop_cascade` — cascade drop of shared arrays +- `array_drop_shared_element_decrements_refcount` — drop shared class element +- `array_give_p_shared` — array_give with P=shared + +**drop_body.rs (1):** +- `is_last_ref_per_allocation` — constructing class with shared array field + +**Status:** ✅ All fixed — test bugs. Every test stored `shared T` values into array slots or class fields typed as bare `T` (= `given T`). Since `shared` is not a subtype of `given`, the type checker correctly rejected them. Fixes: changed element/field types to `shared T` where shared values are stored; fixed access permissions (`given` → `shared`) when reading from shared arrays; fixed return type mismatches. + +### `variance_predicate { kind: relative, parameter: !ty_0 }` — 10 tests + +All in vector.rs. The Vec class's `push` method has a universally quantified type parameter `!ty_0` that the type checker can't prove is `relative`. This is a known limitation — the type checker doesn't yet fully support the permission patterns Vec uses. + +**vector.rs (10):** All 10 vec tests. + +**Status:** ✅ Fixed — type system bug. `check_method` added variance assumptions (relative/atomic) for method-level parameters but not class-level parameters. Similarly, `check_drop_body` had no variance assumptions at all. Fix: pass class-level universal vars (`substitution`) into `check_method` and `check_drop_body`, and add variance assumptions for them alongside method vars. Also added `where T is relative` to Iterator class (legitimately needed for its `vec: P Vec[T]` field). + +### `type_statement` for `loop` — 3 tests + +The type checker has no rules for loop/break statements. + +**basics.rs (1):** +- `loop_body_value_is_freed` + +**block_scoped_drops.rs (2):** +- `block_early_break_drops_locals` +- `loop_break_drops_locals` + +**Status:** ✅ Fixed — added type rules for `loop` and `break` statements. Loop rule type-checks the body block with fixed-point liveness (accounting for the back-edge) and produces `()`. Break rule simply produces `()`. Tests changed from `type: error, interpret: ok` to `type: ok, interpret: ok`. + +### `prove_mut_predicate { p: given }` — 1 test + +The type checker cannot prove `given is mut`. The `.share` expression needs `prove_is_shareable`, which tries `prove_is_mut(perm)`, but there's no rule for `Perm::Given`. + +**share.rs (1):** +- `share_skips_borrowed_subfield` + +**Status:** ✅ Deleted — test was wrong. The program stuffed a `ref` into an owned class field, creating a `Flags::Borrowed` subfield inside an owned allocation — a scenario that can't arise in well-typed code. The type checker correctly rejects the program (`given is mut` is NOT supposed to be provable). The interpreter edge case being tested (share skips borrowed subfields) is unreachable in practice. + +## Also Found: Soundness Gaps (`type: ok, interpret: fault`) + +These tests pass type-checking but fault at runtime: + +**Future-panic (type system correctly accepts; runtime bounds/init checks):** +- `array_give_uninitialized_faults` +- `array_give_out_of_bounds` +- `array_write_out_of_bounds` +- `array_drop_out_of_bounds` +- `array_drop_uninitialized_faults` +- `array_zero_length_access_faults` + +**Soundness gaps (type system should reject but doesn't):** +- `array_drop_element` — use after array_drop +- `array_drop_shared_class_element` — use after array_drop +- `array_drop_p_given_range` — use after array_drop + +## Completed + +- [x] Unified test macro rework (see `md/wip/interpreter-test-rework.md`) +- [x] Fixed 14 return-type bugs in test programs (tests returning shared values declared non-shared return types) +- [x] Categorized all 25 remaining `type: error, interpret: ok` tests by root cause +- [x] Fixed all 11 `prove_copy_predicate` tests — all were test bugs (shared values stored into `given`-typed positions) + +## Current Inventory: 3 remaining `type: error, interpret: ok` + +- **3 loop tests** — spun out to [`control-flow.md`](./control-flow.md) + +## Next Steps + +- [x] ~~Investigate variance predicate bug~~ — Fixed: class-level vars now get variance assumptions in methods and drop bodies +- [ ] Loop/break type rules (3 tests) — spun out to [`control-flow.md`](./control-flow.md), which also covers if/else and liveness issues diff --git a/md/wip/valid-predicates.md b/md/wip/valid-predicates.md new file mode 100644 index 0000000..e69de29 diff --git a/md/wip/var-pop-normalization.md b/md/wip/var-pop-normalization.md index 19f90a7..20fd5c6 100644 --- a/md/wip/var-pop-normalization.md +++ b/md/wip/var-pop-normalization.md @@ -6,13 +6,13 @@ Dada's permission system lets function signatures express return permissions **i ```dada fn get(ref self) -> ref[self] Data -fn take(given self) -> given_from[self] Data +fn take(given self) -> given[self] Data fn either[perm P, perm Q](x: P String, y: Q String) -> ref[x, y] String ``` This is a core design feature — it's how you say "the result's permission depends on the inputs." -At a **call site**, however, the method's parameters go out of scope. The return type must be **resolved** into a permission that only references caller-scoped variables. For single-place permissions this sometimes works out — `given_from[x]` where `x: given T` resolves to `given`. But for multi-place permissions like `ref[x, y]` where `x: P String` and `y: Q String`, the resolved permission is "either P or Q, and we don't know which." There's currently no `Perm` variant to express that. +At a **call site**, however, the method's parameters go out of scope. The return type must be **resolved** into a permission that only references caller-scoped variables. For single-place permissions this sometimes works out — `given[x]` where `x: given T` resolves to `given`. But for multi-place permissions like `ref[x, y]` where `x: P String` and `y: Q String`, the resolved permission is "either P or Q, and we don't know which." There's currently no `Perm` variant to express that. This plan adds `Perm::Or` (surface syntax `or(P, Q, ...)`) to make the permission language **closed under call-site resolution**, then builds the normalization machinery to resolve return types at scope boundaries. @@ -44,10 +44,10 @@ fn either[perm P, perm Q](x: P String, y: Q String) -> ref[x, y] String These are detected during normalization: after `red_perm` expansion, `ref` from `given` is terminal (the chain still references the popped variable), which is an error. -### Ownership transfer (`given_from`) +### Ownership transfer (`given`) ```dada -fn pick[perm P, perm Q](x: P String, y: Q String) -> given_from[x, y] String +fn pick[perm P, perm Q](x: P String, y: Q String) -> given[x, y] String ``` | Call | x becomes | y becomes | Resolved return perm | Result | @@ -56,7 +56,7 @@ fn pick[perm P, perm Q](x: P String, y: Q String) -> given_from[x, y] String | `pick(d.ref, d2.ref)` | `ref[d]` | `ref[d2]` | `or(ref[d], ref[d2])` | ✅ both copy category | | `pick(d.ref, d2.give)` | `ref[d]` | `given` | `or(ref[d], given)` | ❌ mixed categories (copy/given) — fails WF check | -`given_from` is more permissive than `ref`/`mut` because `Mv` links are *replaced* during `red_perm` expansion (ownership transfers) rather than *appended to* (borrows extend). There's no "borrow from owned-then-dropped" issue — but the WF check on the resulting `Or` can still reject mixed-category results. +`given` is more permissive than `ref`/`mut` because `Mv` links are *replaced* during `red_perm` expansion (ownership transfers) rather than *appended to* (borrows extend). There's no "borrow from owned-then-dropped" issue — but the WF check on the resulting `Or` can still reject mixed-category results. **Error quality note:** The mixed-category error is reported by `check_perm` on the normalized `Or`, e.g., "ill-formed `or(ref[d], given)`: mixed categories." This is mechanically correct but doesn't explain *why* the branches diverged — that `x` was passed as a borrow while `y` was given. A production compiler would want to trace back to the call arguments; for the formal model, the mechanical error suffices. @@ -127,7 +127,7 @@ The normalization machinery works on **reduced permissions** — an internal rep | `Rfd(place)` | ref-dead | `ref[place]` | Ref where place is dead (not used after this point) | | `Mtl(place)` | mut-lien | `mut[place]` | Active mut borrow; place is live | | `Mtd(place)` | mut-dead | `mut[place]` | Mut where place is dead | -| `Mv(place)` | move | `given_from[place]` | Ownership derived from place; replaced during expansion | +| `Mv(place)` | move | `given[place]` | Ownership derived from place; replaced during expansion | | `Shared` | shared | `shared` | Terminal; shared/copy permission | | `Var(v)` | variable | perm variable | Terminal; universal perm variable | @@ -141,7 +141,7 @@ The normalization machinery works on **reduced permissions** — an internal rep The existing `red_perm` machinery (`src/type_system/redperms.rs`) already resolves place-based permissions by expanding chains: -- **`Mv(place)` links** (from `given_from`): *replaced* by the place's permission. The `Mv` link drops out entirely. +- **`Mv(place)` links** (from `given`): *replaced* by the place's permission. The `Mv` link drops out entirely. - **`Rfl(place)` / `Mtl(place)` links** (from `ref` / `mut`): *extended* by appending the place's permission chain. The original link stays. A multi-place permission like `ref[x, y]` produces one chain per place (existential choice in `some_red_chain`), so the `RedPerm` has multiple `RedChain`s. @@ -161,7 +161,7 @@ The stripping rules mirror the existing subtyping rules in `red_chain_sub_chain` | `Mtd(popped) :: tail` | **Drop** `Mtd(popped)`, keep `tail` | popped's type is shareable, tail is mut-based | | `Rfd(popped) :: tail` | **Replace** `Rfd(popped)` with `Shared` | popped's type is shareable, tail is mut-based | -**`Mv(popped)` links:** `Mv` links (from `given_from`) are always *replaced* during `red_perm` expansion (Step 1's `"mv"` rule in `some_expanded_red_chain`), so they never survive into Step 2. `strip_popped_dead_links` should assert-panic if it encounters an `Mv(popped)` link — that would indicate a bug in `red_perm` expansion, not a user error. +**`Mv(popped)` links:** `Mv` links (from `given`) are always *replaced* during `red_perm` expansion (Step 1's `"mv"` rule in `some_expanded_red_chain`), so they never survive into Step 2. `strip_popped_dead_links` should assert-panic if it encounters an `Mv(popped)` link — that would indicate a bug in `red_perm` expansion, not a user error. The **shareable condition** (`is share` predicate, proved via `prove_is_shareable` in `src/type_system/predicates.rs`) accounts for the **guard pattern**. A type is shareable if its class is `class` (default) or `shared class`, and all its type parameters are also shareable. A `given class` (like a lock guard) is not shareable. When data is accessed through a guard, the chain looks like `mut[guard] L Data`. Even when `guard` is dead (no more explicit uses), the guard is still alive — its existence is what mediates access. Stripping `mut[guard]` would bypass the guard and grant direct `L Data` access. When the guard's destructor runs and revokes access, the caller would still think it has direct access — unsound. @@ -242,14 +242,14 @@ fn foo(x: given String) -> ref[x] String In `src/type_system/expressions.rs`, the "call" rule applies `with_this_stored_to(this_var)` to the *input* types but NOT to `output`. So the return type still has `Var::This` references. After `pop_fresh_variables` removes `this_var`, the output type's `Var::This` resolves to the *caller's* `self` — a completely different variable. -Example: if `Vec.get` returns `given_from[self] Data` and the caller is `Main.main`, then `self` in the return type resolves to `Main` (the caller's self), not to the `Vec` instance. Type proofs happen to succeed because `Main` is owned, but the resolution is semantically wrong. +Example: if `Vec.get` returns `given[self] Data` and the caller is `Main.main`, then `self` in the return type resolves to `Main` (the caller's self), not to the `Vec` instance. Type proofs happen to succeed because `Main` is owned, but the resolution is semantically wrong. Concrete test case that would expose the bug: ```dada class Data {} class Container { - fn get(given self) -> given_from[self] Data { ... } + fn get(given self) -> given[self] Data { ... } } class Caller { fn go(ref self, c: given Container) { @@ -261,11 +261,11 @@ class Caller { } ``` -Similarly, the output is not transformed for named parameters: `with_var_stored_to(input_name, input_temp)` is applied to remaining `input_tys` inside `type_method_arguments_as`, but never to `output`. A return type like `given_from[x] Data` where `x` is a named parameter keeps `Var::Id("x")` instead of mapping to the corresponding fresh variable. +Similarly, the output is not transformed for named parameters: `with_var_stored_to(input_name, input_temp)` is applied to remaining `input_tys` inside `type_method_arguments_as`, but never to `output`. A return type like `given[x] Data` where `x` is a named parameter keeps `Var::Id("x")` instead of mapping to the corresponding fresh variable. ### Interpreter: type binding injection workaround -After the fresh-names work, the interpreter alpha-renames method variables (`self` → `_N_self`). The return type `given_from[_N_self] Data` references a variable that only existed in the method's scope. The workaround: inject `_N_self`'s type binding into the caller's env after the method returns. This lets proofs resolve but means method-internal names leak into the caller's scope indefinitely. +After the fresh-names work, the interpreter alpha-renames method variables (`self` → `_N_self`). The return type `given[_N_self] Data` references a variable that only existed in the method's scope. The workaround: inject `_N_self`'s type binding into the caller's env after the method returns. This lets proofs resolve but means method-internal names leak into the caller's scope indefinitely. ## Design: `Perm::Or` @@ -484,9 +484,9 @@ Fix the output renaming bug and implement `normalize_ty_for_pop`. These must lan Tests written in `src/type_system/tests/normalization.rs`. 14 tests total: 7 currently pass (some by accident via Var::This collision, some correctly), 7 fail until Phase 2b lands. -**`given_from` resolution:** -1. **Method returns `given_from[self] T` called from another method** — currently passes by accident (`Var::This` collision). After fix, should still pass but with correct resolution. -2. **Method returns `given_from[self] T` where caller's self has a different permission** — the `Caller.go(ref self, c: given Container)` example. Should pass after fix, would give wrong permission today. +**`given` resolution:** +1. **Method returns `given[self] T` called from another method** — currently passes by accident (`Var::This` collision). After fix, should still pass but with correct resolution. +2. **Method returns `given[self] T` where caller's self has a different permission** — the `Caller.go(ref self, c: given Container)` example. Should pass after fix, would give wrong permission today. **Dangling borrows (should error):** 3. **Method returns `ref[x]` where `x` is a `given` parameter** — dangling borrow error at the call site. @@ -501,20 +501,20 @@ Tests written in `src/type_system/tests/normalization.rs`. 14 tests total: 7 cur 8. **Multi-place `ref[x, y]` through mut** — dead-link stripping + Rfd→Shared weakening produces `or(shared mut[a], shared mut[b])`. **Deferred:** -- Block returns value with `given_from[local]` — deferred until block-scoped variable popping is implemented. +- Block returns value with `given[local]` — deferred until block-scoped variable popping is implemented. ### Phase 2a implementation notes **Currently passing tests (7 of 14).** Several tests pass today without normalization. Some by accident (Var::This collision), some because the existing `red_perm` machinery handles them correctly even without output renaming: -- `given_from_self_basic` — passes by Var::This collision (result type `given_from[self]` resolves to caller's self which is also `given`) -- `given_from_named_param` — passes because the result isn't subsequently used in a way that exposes the dangling `x` reference +- `given_self_basic` — passes by Var::This collision (result type `given[self]` resolves to caller's self which is also `given`) +- `given_named_param` — passes because the result isn't subsequently used in a way that exposes the dangling `x` reference - `borrow_chain_ref_through_ref`, `borrow_chain_ref_through_ref_self` — ref-through-ref works via `append_chain` copy-tail optimization; dead links to temps are dropped before `strip_popped_dead_links` would need to act - `multi_place_ref_produces_or`, `multi_place_mut_through_mut`, `multi_place_ref_through_mut` — similar; the existing machinery handles these without explicit normalization because the perm variables are instantiated at the call site **Bug-exposing failures (3 of 7).** These demonstrate the Var::This / named-param renaming bugs: -- `given_from_self_different_caller_perm` — caller's `ref self` leaks into return type; trying to give the result fails because it has `ref` perm instead of `given` -- `given_from_named_param_give_result` — return type contains dangling `given_from[x]` where `x` is the method's parameter name, not in caller's env -- `multi_place_given_from_both_given` — same dangling reference issue with `given_from[x, y]` +- `given_self_different_caller_perm` — caller's `ref self` leaks into return type; trying to give the result fails because it has `ref` perm instead of `given` +- `given_named_param_give_result` — return type contains dangling `given[x]` where `x` is the method's parameter name, not in caller's env +- `multi_place_given_both_given` — same dangling reference issue with `given[x, y]` **Dangling borrow failures (4 of 7).** These currently fail with wrong errors (parse/type errors unrelated to dangling borrows). After Phase 2b, they should fail with normalization-produced dangling borrow errors. The `assert_err!` tests use placeholder `expect![[""]]` values that will be updated when Phase 2b produces the correct error messages. @@ -522,7 +522,7 @@ Tests written in `src/type_system/tests/normalization.rs`. 14 tests total: 7 cur **Explicit perm parameters required at call sites.** Methods with `[perm P, perm Q]` require explicit perm parameters in calls: `f.give.either[ref[d1], ref[d2]](d1.ref, d2.ref)`. The model doesn't infer perm parameters. -#### Phase 2b: Implementation +#### Phase 2b: Implementation ✅ **Output renaming fix (in `expressions.rs`):** - Apply `with_this_stored_to(this_var)` to `output` alongside the existing input type renaming, and thread `output` through `type_method_arguments_as` so each `with_var_stored_to(input_name, input_temp)` is applied to it as well. No new functions needed — the existing `with_this_stored_to` and `with_var_stored_to` are sufficient. @@ -543,15 +543,136 @@ Calls into `redperms.rs` for `red_perm` and chain-to-perm conversion, and into ` - After popping, call `check_type(env, normalized_output)` to validate the normalized result in the caller's env (catches ill-formed `Or` and dangling references) - All Phase 2a tests should now pass. +### Phase 2b implementation notes + +**Output renaming threaded through `type_method_arguments_as`.** The `output` type is now passed as an extra parameter to `type_method_arguments_as`, which applies `with_var_stored_to(input_name, input_temp)` to it alongside the remaining `input_tys`. The function returns a `(Env, Vec, Ty)` triple instead of `(Env, Vec)`. The `with_this_stored_to(this_var)` is applied to `output` in the call rule itself (alongside `this_input_ty` and `input_tys`), as a 3-tuple: `(this_input_ty, input_tys, output).with_this_stored_to(this_var)`. + +**`Perm::flat_or` instead of `Perm::or`.** The `#[term]` macro auto-generates `Perm::or()` from the `Or` variant, so the flattening constructor was named `Perm::flat_or()` to avoid the conflict. Located in `src/grammar/perm_impls.rs`. + +**`red_perm` returns `ProvenSet`, not `RedPerm`.** Outside of `judgment_fn!` macros, extracting the result requires `.into_singleton()` which returns `Result<(RedPerm, ProofTree), Box>`. The `red_perm` judgment collects all chains into a single `RedPerm`, so `into_singleton()` is appropriate. + +**Normalization happens before popping and before `check_type`.** The call rule order is: (1) rename output via `with_this_stored_to` + `with_var_stored_to`, (2) normalize via `normalize_ty_for_pop`, (3) `check_type` on normalized output (catches ill-formed `Or`), (4) `accesses_permitted` for drops, (5) `pop_fresh_variables`, (6) `with_place_in_flight(Var::Return)`. + +**Subtype assertion omitted.** The plan called for asserting `sub(env, output, normalized_output)` as a sanity check. This was omitted because it would add significant cost to every call for a debugging-only assertion. The normalization is straightforward enough (strip dead links, weaken Rfd→Shared) that the test suite provides sufficient confidence. + +**`perm_dependent_borrow_given_arg_dangles` test fixed.** The Phase 2a version had `where P is copy`, but `given` doesn't satisfy `is copy`, so the error fired at program-level predicate checking before the call-site normalization was ever reached. Fixed by removing the `where P is copy` constraint — not needed for this test since the point is to exercise dangling borrow detection. The test now correctly produces a dangling borrow error from normalization. + +**`check_type` is called before popping.** The normalized output doesn't reference popped vars (normalization resolves them), but the env still has the fresh var bindings at check time. This means `check_type` can validate place references that survived normalization (e.g., `ref[d1]` where `d1` is a caller-scoped variable). After popping, the same check would also work since `d1` remains in scope. + ### Phase 3: Update the interpreter -#### Phase 3a: Tests +#### Phase 3a: Tests ✅ + +Tests written in `src/interpreter/tests/normalization.rs`. 10 tests total: 9 pass with current (pre-normalization) snapshots, 1 is `#[ignore]`'d because it triggers the `Var::This` collision bug in the interpreter (will be un-ignored in Phase 3b). + +Tests cover: +- `given[self]` resolution (basic + different caller perm) +- `given[x]` with named parameter (basic + give result away) +- Borrow chaining: `ref[x]` through ref (param + self) +- Multi-place `ref[x, y]` → `or(ref[d1], ref[d2])` +- Multi-place `given[x, y]` → `given` +- Multi-place `mut[x, y]` through mut → `or(mut[d1], mut[d2])` +- No leaked method bindings (two sequential method calls) + +#### Phase 3b: Remove workaround + add preservation assertion ✅ + +Removed the type binding injection hack and added a `check_type` preservation assertion on result types in `call_method`. Made `types` module public for the import. + +**Changes:** +- Deleted `method_type_bindings` collection and injection loop from `call_method` in `src/interpreter/mod.rs` +- Added `check_type(&caller_frame.env, &result_tv.ty)` assertion after method returns +- Made `src/type_system/types.rs` public (`pub mod types` in `src/type_system.rs`) +- Replaced the `interp_given_self_different_caller_perm` test (which had a `ref self` parsing issue) with `interp_given_self_give_to_consumer` (passes without normalization since result is constructed fresh) and `interp_ref_self_field_preservation` (hits preservation violation) +- Marked 5 normalization tests as `#[ignore]` — they hit the preservation assertion because result types reference method-scoped variables + +**Test results:** +- 596 existing tests pass, 19 fail with preservation violations +- 6 normalization tests pass, 5 ignored +- All 19 failures are preservation violations in two categories: + - `Main.main` returning types with `_1_*` local variables (top-level scope boundary) + - `Vec.get` returning `given[_N_self]` (method-scoped self) +- These will all be resolved by Phase 3c normalization + +#### Phase 3c: Implementation (normalization) ✅ + +Normalization added at two points in the interpreter, both using strict mode: + +**1. Method return (`call_method`):** Before the preservation assertion and before dropping method-frame variables, normalize `result_tv.ty` against the method parameters (self + args from `method_frame.variables`). Uses `method_frame.env` (still has all bindings) and empty `LivePlaces` (all method params are dead). This handles `Vec.get`-style cases (e.g., `given[_N_self] Data` → `given Data`, `given[_N_self] Data` → `ref[_1_v] Data` for ref access, `→ mut[_1_v] Data` for mut access). + +**2. Block exit (`eval_block`):** Before `drop_block_scoped_vars`, normalize final value and early-return values against block-scoped variables. This handles `Main.main` local-variable cases where the result type references block-local variables. Dangling borrows (ref from owned block-local) correctly produce errors — the owned value WILL be deinitialized by `drop_block_scoped_vars`, so a ref to it is genuinely dangling. + +**Copy-type permission stripping.** When normalizing `ApplyPerm(perm, inner_ty)`, if `inner_ty` is copy (e.g., `Int`, any `shared class`), the permission is stripped entirely — `ref[x] Int` → `Int`. A copy type doesn't need a permission chain; the value is independent of its source. This naturally resolves cases where the interpreter tracks accumulated borrow permissions on copy values (e.g., `m.y.give` producing `mut[d] Int` instead of `Int`). Applied before attempting `red_perm` expansion, so dangling-borrow checks are never reached for copy types. + +**Made `liveness` module public.** Changed `mod liveness` to `pub mod liveness` in `src/type_system.rs` so the interpreter can construct `LivePlaces::default()`. + +**Test restructuring.** 7 tests that previously returned non-copy borrowed values from `Main.main` (creating genuine dangling borrows) were restructured to observe the property via `print()` and return `()` instead: +- `place_ops.rs`: `ref_from_borrowed`, `give_from_borrowed`, `drop_borrowed_is_noop`, `share_borrowed_is_noop`, `mut_ref_through_mutref`, `ref_field_through_borrowed_path` +- `mdbook.rs`: `interp_drop_borrowed_noop` + +These tests still exercise the same place-operation mechanics (the `print()` output shows the borrowed value with correct flags/permissions), but the borrowed value no longer escapes the block. + +**Snapshot changes.** 4 vector test snapshots updated (`given[_N_self]` → resolved caller-scoped permissions). 5 normalization tests un-ignored and populated with correct snapshots. 8 other tests updated for copy-type stripping or method-return normalization. + +**Test results:** 620 passed, 0 failed, 0 ignored. + +- All Phase 3a tests pass (5 previously ignored tests now pass). +- All 19 previously failing preservation violations resolved. +- The preservation assertion from Phase 3b remains as a permanent safety net. + +### Phase 4: Type system block-exit normalization + +Add the matching change to the type system: pop let-bound variables at block exit and normalize the block's result type against them. Currently the type system never pops let-bound variables — they stay in the env indefinitely. This works by accident (the declared return type constrains the result, and subtyping handles the rest), but it's unprincipled. + +#### Phase 4a: Tests ✅ + +Tests written in `src/type_system/tests/block_normalization.rs`. 9 tests total: 6 pass currently (normalization already handled at call site or not needed), 3 fail until Phase 4b lands. + +**Currently passing (6):** +- `block_given_local_resolves_to_given` — call-site normalization (Phase 2b) already resolves `given[self]` before the value exits the block +- `block_given_local_param_resolves_to_given` — same, `given[x]` resolved at call site +- `block_borrow_chain_ref_through_local_to_outer` — ref chain resolved at call site +- `nested_block_given_inner_local` — inner block's call-site normalization handles it +- `block_result_no_local_refs` — result doesn't reference locals, no normalization needed +- `block_copy_type_through_boundary` — copy type (Int), trivially fine + +**Failing until Phase 4b (3):** +- `block_dangling_borrow_ref_from_local` — block returns `ref[c]` where `c` is owned block-local; currently passes (no block-exit normalization), should error with dangling borrow +- `block_dangling_borrow_mut_from_local` — same with `mut[c]` +- `block_local_not_accessible_after_block` — block-local `d` used after block; currently passes because locals leak into outer env, should error after popping + +#### Phase 4b: Implementation ✅ + +In `type_block` (`src/type_system/blocks.rs`), after `type_statements` returns: +1. Identify let-bound variables introduced during the block (those not in the env before the block) +2. Normalize the result type against those variables using `normalize_ty_for_pop` +3. Pop them from the env + +This mirrors the interpreter's `eval_block` → `drop_block_scoped_vars` pattern but in the type system. + +### Phase 4b implementation notes + +**`check_type` omitted at block exit.** The plan for the call rule (Phase 2b) included `check_type` on the normalized output to catch ill-formed `Or` permissions. This was initially added to `type_block` as well, but it caused failures: `check_type` on `ApplyPerm` requires proving `ty is relative`, which fails for type variables (e.g., `!ty_0`) without explicit variance assumptions. Block result types are produced by expression typing rules that don't guarantee this property. Since the normalization itself catches dangling borrows (the primary concern), `check_type` was removed from block exit. If block-exit normalization ever produces `Or`, the `Or` will be validated when it's used (e.g., in subtyping or predicate checking). + +**`pop_block_variables` helper.** `judgment_fn!` captures the env as immutable, so `pop_local_variables(&mut self, ...)` can't be called directly. Added `pop_block_variables(&self, vars) -> Fallible` which clones and mutates. + +**7 existing test snapshots updated.** All were `assert_err!` tests that still correctly reject the same programs, but the error now fires earlier from block-exit normalization (dangling borrow detection) instead of from deeper in the type system: +- `return_shared_not_give` — `ref[foo]` from owned block-local `foo` +- `share_from_local_to_our` — `ref[d]` from owned block-local `d` +- `take_given_and_shared_move_given_then_return_shared` — `ref[owner1]` from owned block-local +- `c2_shared_shared_one_of_two_variables_dead` — `ref[m]` from owned block-local `m` +- `c3_shared_leased_one_of_two_variables_dead` — same pattern +- `liveness_all_places_must_be_dead` — `ref[d]` from owned block-local `d` +- `lock_guard_cancellation` — `mut[guard]` where guard is a given class (not shareable) + +**`local_variable_names()` helper.** Added to `Env` to snapshot variable names before/after the block for computing the diff. + +## Follow-ups -Write interpreter tests in `src/interpreter/tests/` (new file or extend existing). Use `assert_interpret!` where the type checker supports the pattern, `assert_interpret_only!` otherwise. Tests correspond to Phase 2's type system tests but verify runtime values and permissions. +### Refactor `pop_normalize.rs` to use judgment-style rules ✅ -#### Phase 3b: Implementation +Completed as a post-Phase-4 refactoring: -- Call `normalize_ty_for_pop` on `result_tv.ty` in `call_method` (`src/interpreter/mod.rs`), using `method_frame.env` and an empty `LivePlaces` (all method params are dead after the body completes — they're being popped). The empty `LivePlaces` causes `red_perm` to classify all links to method params as dead (`Rfd`/`Mtd`), which is what `strip_popped_dead_links` needs. The resulting permissions reference caller-scoped variables whose liveness will be determined by the caller's context in subsequent operations. **Future work:** If Dada adds closures or coroutines that capture method parameters, a captured parameter could remain "alive" after the method body returns. The empty `LivePlaces` assumption would need to be revisited — captured parameters should be marked live to prevent unsound dead-link stripping. -- Remove the type binding injection workaround (the `for (var, ty) in method_type_bindings` loop) -- Remove the `method_type_bindings` collection -- All Phase 3a tests should now pass. +- **Extracted `dead_link_is_strippable` helper judgment** in `redperms.rs`: encapsulates the shared "shareable type + mut-based tail" check. Used by both `red_chain_sub_chain` (subtyping) and `strip_popped_dead_links` (normalization), eliminating duplicated `prove_is_shareable` + `prove_is_mut` logic. +- **Rewrote `strip_popped_dead_links` as `judgment_fn!`** in `pop_normalize.rs`: recursive `Head`/`Tail` pattern matching replaces the imperative `while` loop. Four rules: empty chain (given), drop dead mut, weaken dead ref to shared, keep non-popped link. Dangling borrows cause judgment failure (no matching rule). +- **Error messages preserved** via `dangling_borrow_error()`: the caller in `normalize_perm_for_pop` catches judgment failures and analyzes the chain to produce the same clean error messages as before (e.g., "dangling borrow: return type borrows from `foo` which has `given` permission"). +- Added `RedChain::cons(link, tail)` helper and made `Head`/`Tail` fields `pub` for cross-module pattern matching. diff --git a/md/wip/vec.md b/md/wip/vec.md index d8bf548..1c742f8 100644 --- a/md/wip/vec.md +++ b/md/wip/vec.md @@ -14,14 +14,14 @@ class Vec[type T] { self.len = self.len.give + 1 } - fn get[perm P](P self, index: Int) -> given_from[self] T { - let data: given_from[self.data] Array[T] = self.data.give # P=given: moves data out, self not-whole, Vec.drop won't run. + fn get[perm P](P self, index: Int) -> given[self] T { + let data: given[self.data] Array[T] = self.data.give # P=given: moves data out, self not-whole, Vec.drop won't run. # P=ref/shared: copies, self stays whole, Vec.drop runs but is # harmless (is_last_ref guards cleanup, array_drop is no-op). let len: Int = self.len.give - array_drop[T, given_from[self], ref[data]](data.ref, 0, index.give) - array_drop[T, given_from[self], ref[data]](data.ref, index.give + 1, len.give) - array_give[T, given_from[self], ref[data]](data.ref, index.give) + array_drop[T, given[self], ref[data]](data.ref, 0, index.give) + array_drop[T, given[self], ref[data]](data.ref, index.give + 1, len.give) + array_give[T, given[self], ref[data]](data.ref, index.give) } fn iter[perm P](P self) -> Iterator[P, T] { @@ -109,7 +109,7 @@ Arrays support the following intrinsic, unsafe operations The semantics of drop and give are setup to support a "poly-permission" operation like `Vec.get` above. The `array_drop` calls in `get` are no-ops when `P` is not `given`, but they are present so that a single function body works correctly across all permissions — in the `given` case, they actually destroy the elements we don't want. -Note that the return type `given_from[self] T` in `Vec.get` is effectively equivalent to `P` — `given_from[place]` picks up the permission of the place, so `given_from[self]` where `self: P Vec[T]` becomes `P T`. It is written as `given_from[self]` because it conveys the intent more clearly: "you get whatever permission you had on self." +Note that the return type `given[self] T` in `Vec.get` is effectively equivalent to `P` — `given[place]` picks up the permission of the place, so `given[self]` where `self: P Vec[T]` becomes `P T`. It is written as `given[self]` because it conveys the intent more clearly: "you get whatever permission you had on self." ### "drop" sections -- defining custom destructors @@ -195,9 +195,9 @@ At point B, `self.data` has been moved, and hence `self` is not a whole place. ` The function body is polymorphic over `P`. When `P` is `given`, those `array_drop` calls actually destroy the elements we don't want (we're consuming the vec). When `P` is `ref` or `shared`, the `array_drop` calls are no-ops. The alternative would be separate implementations per permission, but one body that works for all permissions is simpler and correct. -**Q: What does `given_from[self]` mean as a return type?** +**Q: What does `given[self]` mean as a return type?** -`given_from[place]` picks up the permission of the place. So `given_from[self]` where `self: P Vec[T]` is effectively `P T`. It's written as `given_from[self]` rather than `P` because it conveys intent more clearly: "you get whatever permission you had on self." +`given[place]` picks up the permission of the place. So `given[self]` where `self: P Vec[T]` is effectively `P T`. It's written as `given[self]` rather than `P` because it conveys intent more clearly: "you get whatever permission you had on self." **Q: How does the drop body avoid infinite recursion? If `self` is whole at the end of the drop body, wouldn't whole-place dropping invoke the drop body again?** @@ -269,7 +269,7 @@ No. Dropping the array just decrements the refcount, and when it hits zero, the ## Random notes to check on -* `given_from[a.b] Foo` -- can this be contracted to `given_from[a]`? Only when the field `b` is declared with `given` permission (or no permission prefix). The `Mv` expansion rule in `redperms.rs` *replaces* `given_from[place]` with the permission of `place`. So `given_from[a.b]` reduces to the permission of `a.b`, which composes the permission of `a` with the declared permission of field `b`. If `a: mut[x] Foo` and `b: shared Bar`, then `a.b` has permission `shared` (mut applied to shared = shared), so `given_from[a.b]` ≠ `given_from[a]` (which would be `mut[x]`). The existing reduction rules handle this correctly — no special case needed. +* `given[a.b] Foo` -- can this be contracted to `given[a]`? Only when the field `b` is declared with `given` permission (or no permission prefix). The `Mv` expansion rule in `redperms.rs` *replaces* `given[place]` with the permission of `place`. So `given[a.b]` reduces to the permission of `a.b`, which composes the permission of `a` with the declared permission of field `b`. If `a: mut[x] Foo` and `b: shared Bar`, then `a.b` has permission `shared` (mut applied to shared = shared), so `given[a.b]` ≠ `given[a]` (which would be `mut[x]`). The existing reduction rules handle this correctly — no special case needed. ## Implementation plan @@ -282,7 +282,7 @@ Implementation follows a TDD approach: write or update tests first to express in No semantic changes. Clears the deck so subsequent code matches the doc's notation. * [x] **Rename `array_set` → `array_write`** — pure rename across grammar, interpreter, type system, and all tests. Current `array_set` already has "ignore previous value" semantics. -* [x] ~~**Rename `given_from` → `given`**~~ — skipped. The parser prefix ambiguity between `given` and `given_from[...]` is unresolved and not worth the risk. Keeping `given_from[places]` as-is. +* [x] **Rename `given_from` → `given`** — landed. The parser distinguishes `given` from `given[...]` by looking ahead for `[`, so the place-based spelling now uses `given[...]` directly. * [x] **Remove `Flags::Dropped`** — replaced all uses of `Flags::Dropped` with `Word::Uninitialized`. Removed `Dropped` from the `Flags` enum. Added `try_read_flags()` helper that returns `Option` (`None` for uninitialized, `Some(flags)` for live values). Callers now check for `Word::Uninitialized` before calling `expect_flags()`. Error messages are "access of uninitialized value" uniformly. **Note:** `and_drop_fields` still silently skips uninitialized boxed values rather than erroring — this is needed because the interpreter's end-of-scope cleanup drops ALL variables unconditionally without checking wholeness. Phase 4's whole-place checks will allow this to become an error. * [x] **Scrub entire array backing on refcount zero** — when an array's refcount reaches 0, all words in the backing allocation (header + elements) are set to `Word::Uninitialized`. Freed arrays now disappear completely from heap snapshots. Updated 44 test snapshots. * [x] **`param is pred` syntax** — flipped *both* `Predicate::Parameter` and `Predicate::Variance` grammar from `#[grammar($v0($v1))]` to `#[grammar($v1 is $v0)]`. All predicates now use consistent `param is pred` syntax (e.g., `P is mut`, `T is relative`, `T is atomic`). Added `is` to KEYWORDS. Updated all test programs and `where` clauses. Class predicates (`given class`, `shared class`) are unchanged. @@ -426,6 +426,6 @@ Three interpreter bugs were found and fixed to make the Vec tests work: * [ ] **Reunify `is_mut_ref_type` with `prove_is_mut`** — The interpreter's `is_mut_ref_type` uses structural pattern matching (`Ty::ApplyPerm(Perm::Mt(_), inner)` where inner is not copy) instead of the type system's `prove_is_mut` judgment. This divergence exists because `prove_is_mut` on `Perm::Mt(places)` resolves each place's type via `env.place_ty(place)`, but inside a method body the places (e.g., `v` in `mut[v]`) refer to the calling context and are not in the method's env. Possible fixes: (a) enrich the method env with calling-context information, (b) add a simpler "structural" rule to `prove_mut_predicate` that recognizes `Perm::Mt(_)` as always mut without checking places, or (c) propagate where-clause assumptions into the interpreter's env. Each has trade-offs — this should be revisited when the interpreter and type system are more integrated. -* [ ] **Move Vec tests to `assert_interpret!`** — Most Vec tests currently use `assert_interpret_only!` (interpreter-only, no type checking) because the type checker doesn't yet support the permission patterns Vec uses (generic perm `P self`, `given_from[self]` return types, where-clause dispatch). As the type checker gains support for these features, migrate tests to `assert_interpret!` (type-check AND execute). Only keep `assert_interpret_only!` for tests that specifically exercise unsafe/UB patterns. +* [ ] **Move Vec tests to `assert_interpret!`** — Most Vec tests currently use `assert_interpret_only!` (interpreter-only, no type checking) because the type checker doesn't yet support the permission patterns Vec uses (generic perm `P self`, `given[self]` return types, where-clause dispatch). As the type checker gains support for these features, migrate tests to `assert_interpret!` (type-check AND execute). Only keep `assert_interpret_only!` for tests that specifically exercise unsafe/UB patterns. * [ ] **Design unsafe effects** — array operations are currently "magic" intrinsics that bypass normal permission rules (e.g., `array_drop` uninitializes element slots through `A is ref`). A proper unsafe effects system would describe and constrain what unsafe operations can do. Not needed for the current Vec milestone, but important for the broader language design. diff --git a/src/elaborator.rs b/src/elaborator.rs new file mode 100644 index 0000000..187eb17 --- /dev/null +++ b/src/elaborator.rs @@ -0,0 +1,53 @@ +//! Elaboration: an *elaborated* program has various unspoken defaults applied to it. +//! +//! The only way to construct an [`ElaboratedProgram`] is via [`ElaboratedProgram::elaborate`], +//! which runs the (currently no-op) [`elaborate`] pass. Storing an `ElaboratedProgram` +//! in `Env` and the interpreter therefore proves that elaboration has taken place. + +use std::ops::Deref; +use std::sync::Arc; + +use crate::grammar::Program; + +/// A program that has had elaboration applied. The only way to construct one +/// is via [`ElaboratedProgram::elaborate`], which guarantees the private +/// [`elaborate`] pass has run. This is *not* a `#[term]` — we hand-implement +/// the traits we need so no auto-generated constructor can bypass elaboration. +#[derive(Clone, Ord, Eq, PartialEq, PartialOrd, Hash)] +pub struct ElaboratedProgram { + program: Arc, +} + +formality_core::cast_impl!(ElaboratedProgram); + +impl ElaboratedProgram { + /// Elaborate `program` (apply syntactic defaults) and wrap the result. + /// This is the only way to obtain an `ElaboratedProgram`. + pub fn elaborate(program: &Program) -> Self { + let mut program = program.clone(); + elaborate(&mut program); + Self { + program: Arc::new(program), + } + } +} + +impl std::fmt::Debug for ElaboratedProgram { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Elide the actual program because it's really repetitive in debug output + f.debug_struct("ElaboratedProgram").finish_non_exhaustive() + } +} + +impl Deref for ElaboratedProgram { + type Target = Program; + + fn deref(&self) -> &Program { + &self.program + } +} + +/// Apply syntactic defaults to `program` in place. Currently a no-op. +fn elaborate(_program: &mut Program) { + /* no-op for now */ +} diff --git a/src/grammar.rs b/src/grammar.rs index 717d76d..3cb9dcd 100644 --- a/src/grammar.rs +++ b/src/grammar.rs @@ -390,6 +390,15 @@ impl formality_core::language::HasKind for Parameter { } } +impl formality_core::parse::CoreParseBinding for Parameter { + fn parse_binding<'t>( + scope: &formality_core::parse::Scope, + text: &'t str, + ) -> formality_core::parse::ParseResult<'t, formality_core::parse::Binding> { + formality_core::parse::default_binding_parse(scope, text) + } +} + #[term] pub enum Ty { #[cast] @@ -399,6 +408,7 @@ pub enum Ty { Var(Variable), #[grammar($v0 $v1)] + #[reject(Perm::Apply(..), _)] ApplyPerm(Perm, Arc), } @@ -456,7 +466,7 @@ pub mod ty_impls; #[term] pub enum Perm { - #[grammar(given_from $[v0])] + #[grammar(given $[v0])] Mv(Set), #[grammar(given)] @@ -475,6 +485,7 @@ pub enum Perm { Var(Variable), #[grammar($v0 $v1)] + #[precedence(1, left)] Apply(Arc, Arc), /// Disjunction: the permission is one of these, but we don't know which. diff --git a/src/grammar/method_impls.rs b/src/grammar/method_impls.rs index a8ecfe7..46b1ff8 100644 --- a/src/grammar/method_impls.rs +++ b/src/grammar/method_impls.rs @@ -1,42 +1,73 @@ -use formality_core::parse::{CoreParse, ParseResult, Parser, Scope}; +use formality_core::parse::{ + ActiveVariant, CoreParse, ParseResult, ParseSuccessType, Parser, Scope, +}; use crate::dada_lang::FormalityLang; use super::{LocalVariableDecl, MethodDeclBoundData, Predicate, ThisDecl, Ty}; -impl CoreParse for MethodDeclBoundData { - fn parse<'t>(scope: &Scope, text: &'t str) -> ParseResult<'t, Self> { - Parser::single_variant(scope, text, "MethodDeclBoundData", |parser| { - parser.expect_char('(')?; - let this: ThisDecl = parser.nonterminal()?; - let inputs: Vec = if parser.expect_char(',').is_ok() { - parser.comma_nonterminal()? - } else { - vec![] - }; - parser.expect_char(')')?; - - let output: Ty = if parser.expect_char('-').is_ok() { - parser.expect_char('>')?; - parser.nonterminal()? - } else { - Ty::unit() - }; +fn each_parse_inputs<'s, 't, R: ParseSuccessType>( + p: &mut ActiveVariant<'s, 't, FormalityLang>, + op: impl Fn( + Vec, + &mut ActiveVariant<'s, 't, FormalityLang>, + ) -> ParseResult<'t, R>, +) -> ParseResult<'t, R> { + if p.expect_char(',').is_ok() { + p.each_comma_nonterminal(|inputs: Vec, p| op(inputs, p)) + } else { + op(vec![], p) + } +} - let predicates: Vec = if let Ok(()) = parser.expect_keyword("where") { - parser.comma_nonterminal()? - } else { - Default::default() - }; +fn each_parse_output<'s, 't, R: ParseSuccessType>( + p: &mut ActiveVariant<'s, 't, FormalityLang>, + op: impl Fn(Ty, &mut ActiveVariant<'s, 't, FormalityLang>) -> ParseResult<'t, R>, +) -> ParseResult<'t, R> { + if p.expect_char('-').is_ok() { + p.expect_char('>')?; + p.each_nonterminal(|output: Ty, p| op(output, p)) + } else { + op(Ty::unit(), p) + } +} - let body = parser.nonterminal()?; +fn each_parse_predicates<'s, 't, R: ParseSuccessType>( + p: &mut ActiveVariant<'s, 't, FormalityLang>, + op: impl Fn(Vec, &mut ActiveVariant<'s, 't, FormalityLang>) -> ParseResult<'t, R>, +) -> ParseResult<'t, R> { + if p.expect_keyword("where").is_ok() { + p.each_comma_nonterminal(|predicates: Vec, p| op(predicates, p)) + } else { + op(vec![], p) + } +} - Ok(MethodDeclBoundData { - this, - inputs, - output, - predicates, - body, +impl CoreParse for MethodDeclBoundData { + fn parse<'t>(scope: &Scope, text: &'t str) -> ParseResult<'t, Self> { + Parser::single_variant(scope, text, "MethodDeclBoundData", |p| { + p.expect_char('(')?; + p.each_nonterminal(|this: ThisDecl, p| { + each_parse_inputs(p, |inputs, p| { + p.expect_char(')')?; + each_parse_output(p, |output, p| { + each_parse_predicates(p, |predicates, p| { + let this = this.clone(); + let inputs = inputs.clone(); + let output = output.clone(); + let predicates = predicates.clone(); + p.each_nonterminal(|body, p| { + p.ok(MethodDeclBoundData { + this: this.clone(), + inputs: inputs.clone(), + output: output.clone(), + predicates: predicates.clone(), + body, + }) + }) + }) + }) + }) }) }) } diff --git a/src/grammar/named_ty_impls.rs b/src/grammar/named_ty_impls.rs index cde4e03..839054a 100644 --- a/src/grammar/named_ty_impls.rs +++ b/src/grammar/named_ty_impls.rs @@ -1,5 +1,5 @@ use anyhow::bail; -use formality_core::parse::{CoreParse, Parser, Precedence}; +use formality_core::parse::{CoreParse, ParseSuccessType, Parser, Precedence}; use std::fmt::Debug; use crate::dada_lang::FormalityLang; @@ -53,39 +53,55 @@ impl NamedTy { } } +fn each_parse_parameters<'s, 't, R: ParseSuccessType>( + p: &mut formality_core::parse::ActiveVariant<'s, 't, FormalityLang>, + open: char, + optional: bool, + close: char, + op: impl Fn( + Vec, + &mut formality_core::parse::ActiveVariant<'s, 't, FormalityLang>, + ) -> formality_core::parse::ParseResult<'t, R>, +) -> formality_core::parse::ParseResult<'t, R> { + p.each_delimited_nonterminal(open, optional, close, |params: Vec, p| { + op(params, p) + }) +} + // Customized parse of ty to accept tuples like `()` or `(a, b)` etc. impl CoreParse for NamedTy { fn parse<'t>( scope: &formality_core::parse::Scope, text: &'t str, ) -> formality_core::parse::ParseResult<'t, Self> { - Parser::multi_variant(scope, text, "type", |p| { - p.parse_variant("tuple", Precedence::default(), |p| { + Parser::multi_variant(scope, text, "type", |parser| { + parser.parse_variant("tuple", Precedence::default(), |p| { p.expect_char('(')?; - let types: Vec = p.comma_nonterminal()?; - p.expect_char(')')?; - let name = TypeName::Tuple(types.len()); - Ok(NamedTy::new(name, types)) + p.each_comma_nonterminal(|types: Vec, p| { + p.expect_char(')')?; + let name = TypeName::Tuple(types.len()); + p.ok(NamedTy::new(name, types)) + }) }); - p.parse_variant("int", Precedence::default(), |p| { + parser.parse_variant("int", Precedence::default(), |p| { p.expect_keyword("Int")?; - let name = TypeName::Int; - let parameters: Vec = vec![]; - Ok(NamedTy::new(name, parameters)) + p.ok(NamedTy::new(TypeName::Int, Vec::::new())) }); - p.parse_variant("array", Precedence::default(), |p| { + parser.parse_variant("array", Precedence::default(), |p| { p.expect_keyword("Array")?; - let parameters: Vec = p.delimited_nonterminal('[', false, ']')?; - Ok(NamedTy::new(TypeName::Array, parameters)) + each_parse_parameters(p, '[', false, ']', |parameters, p| { + p.ok(NamedTy::new(TypeName::Array, parameters)) + }) }); - p.parse_variant("class", Precedence::default(), |p| { - p.mark_as_cast_variant(); - let id: ValueId = p.nonterminal()?; - let parameters: Vec = p.delimited_nonterminal('[', true, ']')?; - Ok(NamedTy::new(id, parameters)) + parser.parse_variant("class", Precedence::default(), |p| { + p.each_nonterminal(|id: ValueId, p| { + each_parse_parameters(p, '[', true, ']', |parameters, p| { + p.ok(NamedTy::new(id.clone(), parameters)) + }) + }) }); }) } diff --git a/src/grammar/perm_impls.rs b/src/grammar/perm_impls.rs index 7a1da00..b38c611 100644 --- a/src/grammar/perm_impls.rs +++ b/src/grammar/perm_impls.rs @@ -1,8 +1,23 @@ use super::{Parameter, Perm, Ty}; -use formality_core::{cast_impl, Cons, DowncastFrom, Upcast, UpcastFrom}; +use formality_core::{cast_impl, Cons, DowncastFrom, Set, Upcast, UpcastFrom}; use std::sync::Arc; impl Perm { + /// Flattening constructor for `Or`. If any element is itself `Or(inner)`, + /// its branches are pulled into the outer set. + pub fn flat_or(perms: impl IntoIterator) -> Perm { + let mut flat: Set = Set::new(); + for p in perms { + match p { + Perm::Or(inner) => flat.extend(inner), + other => { + flat.insert(other); + } + } + } + Perm::Or(flat) + } + pub fn apply_to_parameter(&self, p: &Parameter) -> Parameter { match p { Parameter::Ty(ty) => Ty::apply_perm(self, ty).upcast(), diff --git a/src/grammar/test_parse.rs b/src/grammar/test_parse.rs index da58686..6e9bec3 100644 --- a/src/grammar/test_parse.rs +++ b/src/grammar/test_parse.rs @@ -201,8 +201,8 @@ fn test_parse_shared_perm_2() { } #[test] -fn test_parse_given_from_places_perm() { - let p: Perm = crate::dada_lang::term("given_from[x]"); +fn test_parse_given_places_perm() { + let p: Perm = crate::dada_lang::term("given[x]"); expect_test::expect![[r#" Mv( { @@ -428,3 +428,18 @@ fn test_parse_class_with_methods_and_drop() { assert_eq!(bound_data.methods.len(), 1); assert!(!bound_data.drop_body.block.statements.is_empty()); } + +#[test] +fn test_parse_perm_apply_chain() { + let p: Perm = crate::dada_lang::term("given given given"); + expect_test::expect![[r#" + Apply( + Apply( + Given, + Given, + ), + Given, + ) + "#]] + .assert_debug_eq(&p); +} diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 8148592..cbb38a3 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -4,17 +4,21 @@ use std::sync::Arc; use formality_core::{set, Upcast}; +use crate::elaborator::ElaboratedProgram; use crate::grammar::ty_impls::PermTy; use crate::grammar::{ ClassDecl, ClassDeclBoundData, FieldId, MethodDeclBoundData, MethodId, NamedTy, Parameter, - Perm, Place, Program, Projection, Ty, TypeName, ValueId, Var, + Perm, Place, Projection, Ty, TypeName, ValueId, Var, }; use crate::type_system::env::Env; +use crate::type_system::liveness::LivePlaces; +use crate::type_system::pop_normalize::normalize_ty_for_pop; use crate::type_system::predicates::{ prove_is_boxed, prove_is_copy, prove_is_copy_owned, prove_is_given, prove_is_move, prove_is_mut, prove_is_owned, }; +use crate::type_system::types::check_type; use std::fmt::Write; const ARRAY_REF_COUNT_OFFSET: usize = 0; @@ -213,8 +217,8 @@ impl StackFrame { } // ANCHOR: Interpreter -pub struct Interpreter<'a> { - program: &'a Program, +pub struct Interpreter { + program: ElaboratedProgram, allocs: Vec, output: String, indent: usize, @@ -226,8 +230,8 @@ pub struct Interpreter<'a> { } // ANCHOR_END: Interpreter -impl<'a> Interpreter<'a> { - pub fn new(program: &'a Program) -> Self { +impl Interpreter { + pub fn new(program: ElaboratedProgram) -> Self { Self { program, allocs: Vec::new(), @@ -246,7 +250,7 @@ impl<'a> Interpreter<'a> { /// The interpreter works with fully monomorphized types, so no local /// variables or assumptions are needed. fn base_env(&self) -> Env { - Env::new(Arc::new(self.program.clone())) + Env::new(self.program.clone()) } // --------------------------------------------------------------- @@ -955,7 +959,7 @@ impl<'a> Interpreter<'a> { if self.is_owned_type(env, &value.ty) && self.is_value_whole(env, value) { let named_ty = self.named_ty(&value.ty); if let TypeName::Id(class_name) = &named_ty.name { - if let Ok(class_decl) = self.program.class_named(class_name) { + if let Ok(class_decl) = self.program.class_named(class_name).map(ClassDecl::clone) { if let Ok(class_data) = class_decl.binder.instantiate_with(&named_ty.parameters) { if !class_data.drop_body.block.statements.is_empty() { @@ -1874,12 +1878,6 @@ impl<'a> Interpreter<'a> { let this_ty = this.ty; env = env.push_local_variable(self_var.clone(), this_ty.clone())?; - // Collect the method's type bindings (renamed var → type) so we can - // inject them into the caller's env after the method returns. - // The return type may reference these variables (e.g., `given_from[_N_self]`), - // and the caller needs them for type proofs. - let mut method_type_bindings: Vec<(Var, Ty)> = vec![(self_var.clone(), this_ty)]; - let mut method_frame = StackFrame { env, variables: Vec::new(), @@ -1887,12 +1885,10 @@ impl<'a> Interpreter<'a> { method_frame.insert_variable(self_var, this.pointer); for (input, input_value) in inputs.iter().zip(input_values) { let var = Var::Id(input.name.clone()); - let input_ty = input_value.ty.clone(); method_frame.env = method_frame .env .push_local_variable(var.clone(), input_value.ty)?; method_frame.insert_variable(var.clone(), input_value.pointer); - method_type_bindings.push((var, input_ty)); } self.trace(format_args!("enter {class_name:?}.{method_id:?}")); @@ -1908,6 +1904,27 @@ impl<'a> Interpreter<'a> { Outcome::Return(tv) => tv, Outcome::Break => anyhow::bail!("break outside of loop"), }; + // Normalize the result type before dropping method params. + // The method_frame.env still has all param bindings, and + // all method params are dead (the method body has completed). + let popped_vars: Vec = method_frame + .variables + .iter() + .map(|(var, _)| var.clone()) + .collect(); + let live_after = LivePlaces::default(); // all method params are dead + let (normalized_ty, _proof) = normalize_ty_for_pop( + &method_frame.env, + &live_after, + &result_tv.ty, + &popped_vars, + ) + .into_singleton()?; + let result_tv = ObjectValue { + pointer: result_tv.pointer, + ty: normalized_ty, + }; + // Free any variables remaining in the method's stack frame // (end-of-scope cleanup). With block-scoped drops, only // method parameters remain here. @@ -1926,17 +1943,15 @@ impl<'a> Interpreter<'a> { let result_tv = result?; - // Inject the method's type bindings into the caller's env. - // The return type may reference method-scope variables - // (e.g., `given_from[_N_self]`), and the caller needs these - // bindings for type proofs (is_owned, is_copy, etc.). - // Names are globally unique (monotonic call_id), so no collisions. - for (var, ty) in method_type_bindings { - caller_frame.env = caller_frame - .env - .push_local_variable(var.clone(), ty) - .expect(&format!("call_id {call_id}: duplicate binding for {var:?}")); - } + // Preservation check: the result type must be well-formed in the + // caller's env. This ensures no method-scoped variables leak into + // the caller's scope via the result type. + assert!( + check_type(&caller_frame.env, &result_tv.ty).is_proven(), + "preservation violation after {class_name:?}.{method_id:?}: \ + result type `{:?}` references variables not in caller scope", + result_tv.ty + ); let result_display = self .display_value(&caller_frame.env, &result_tv) @@ -1990,17 +2005,50 @@ impl<'a> Interpreter<'a> { self.drop_value(&stack_frame.env, &final_value)?; final_value = tv; } - early @ (Outcome::Break | Outcome::Return(_)) => { + Outcome::Break => { + self.drop_value(&stack_frame.env, &final_value)?; + self.drop_block_scoped_vars(stack_frame, vars_before)?; + return Ok(Outcome::Break); + } + Outcome::Return(ret_tv) => { self.drop_value(&stack_frame.env, &final_value)?; + let ret_tv = Self::normalize_for_block_pop(stack_frame, vars_before, ret_tv)?; self.drop_block_scoped_vars(stack_frame, vars_before)?; - return Ok(early); + return Ok(Outcome::Return(ret_tv)); } } } + let final_value = Self::normalize_for_block_pop(stack_frame, vars_before, final_value)?; self.drop_block_scoped_vars(stack_frame, vars_before)?; Ok(Outcome::Value(final_value)) } + /// Normalize a value's type against block-scoped variables that are about + /// to be popped. The env still has bindings for these variables. + /// Returns an error (dangling borrow) if the value's type borrows from + /// an owned block-local variable — the data would be deinitialized on drop. + fn normalize_for_block_pop( + stack_frame: &StackFrame, + vars_before: usize, + value: ObjectValue, + ) -> anyhow::Result { + if stack_frame.variables.len() <= vars_before { + return Ok(value); + } + let popped_vars: Vec = stack_frame.variables[vars_before..] + .iter() + .map(|(var, _)| var.clone()) + .collect(); + let live_after = LivePlaces::default(); + let (normalized_ty, _proof) = + normalize_ty_for_pop(&stack_frame.env, &live_after, &value.ty, &popped_vars) + .into_singleton()?; + Ok(ObjectValue { + pointer: value.pointer, + ty: normalized_ty, + }) + } + /// Drop variables introduced during a block (those at indices >= `vars_before`) /// in reverse declaration order, and pop them from both `variables` and `env`. fn drop_block_scoped_vars( diff --git a/src/interpreter/tests.rs b/src/interpreter/tests.rs index eb7d7a9..e7c562e 100644 --- a/src/interpreter/tests.rs +++ b/src/interpreter/tests.rs @@ -6,6 +6,7 @@ mod drop_body; mod generics; mod mdbook; mod method_calls; +mod normalization; mod place_ops; mod share; mod size_of; diff --git a/src/interpreter/tests/array.rs b/src/interpreter/tests/array.rs index af53aca..e047791 100644 --- a/src/interpreter/tests/array.rs +++ b/src/interpreter/tests/array.rs @@ -1,9 +1,5 @@ // Tests for Array[T] operations: ArrayNew, ArrayCapacity, ArrayGive, ArrayDrop, ArrayWrite. // -// All tests use assert_interpret_only! since the type checker's Array rules -// are simplified stubs — the real typing (e.g., ArrayGive returning given[array] T) -// is deferred. -// // Expected patterns: // - Arrays are typically `given` (owned) // - Use `.mut` for modifications (array_write, array_drop) @@ -23,7 +19,7 @@ fn class_with_array_field_new() { // Before the fix, `free(fv)` after `instantiate_class` decremented // the refcount to 0 and freed the allocation; now `uninitialize` is // used instead. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Wrapper { field: Array[Int]; @@ -36,7 +32,7 @@ fn class_with_array_field_new() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](3) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡, ⚡ } @@ -45,7 +41,7 @@ fn class_with_array_field_new() { Output: Trace: array_capacity [Int, given](_1_w . field . give) ; Output: Trace: exit Main.main => 3 Result: Ok: 3 - Alloc 0x0a: [Int(3)]"#]] + Alloc 0x0a: [Int(3)]"#]]) ); } @@ -55,7 +51,7 @@ fn reassign_drops_old_array() { // refcount of) the old array before installing the new one. // If the old array were leaked the refcount would never reach zero // and its allocation would still appear in the heap snapshot. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -68,7 +64,7 @@ fn reassign_drops_old_array() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -79,7 +75,7 @@ fn reassign_drops_old_array() { Output: Trace: array_capacity [Int, given](_1_a . give) ; Output: Trace: exit Main.main => 4 Result: Ok: 4 - Alloc 0x13: [Int(4)]"#]] + Alloc 0x13: [Int(4)]"#]]) ); } @@ -89,7 +85,7 @@ fn reassign_drops_old_array() { #[test] fn array_new_and_capacity() { - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -98,21 +94,21 @@ fn array_new_and_capacity() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](3) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡, ⚡ } Output: Trace: array_capacity [Int, given](_1_a . give) ; Output: Trace: exit Main.main => 3 Result: Ok: 3 - Alloc 0x07: [Int(3)]"#]] + Alloc 0x07: [Int(3)]"#]]) ); } #[test] fn array_size_of() { // Array[T] is two words: Word::Flags + Word::Pointer - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -120,12 +116,12 @@ fn array_size_of() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: size_of [Array[Int]]() ; Output: Trace: exit Main.main => 2 Result: Ok: 2 - Alloc 0x02: [Int(2)]"#]] + Alloc 0x02: [Int(2)]"#]]) ); } @@ -135,7 +131,7 @@ fn array_size_of() { #[test] fn array_write_and_get_int() { - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -149,7 +145,7 @@ fn array_write_and_get_int() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](3) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡, ⚡ } @@ -163,7 +159,7 @@ fn array_write_and_get_int() { Output: Trace: array_give [Int, given, given](_1_a . give , 2) ; Output: Trace: exit Main.main => 30 Result: Ok: 30 - Alloc 0x1c: [Int(30)]"#]] + Alloc 0x1c: [Int(30)]"#]]) ); } @@ -173,7 +169,7 @@ fn array_write_and_get_int() { #[test] fn array_write_and_get_class() { - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -186,7 +182,7 @@ fn array_write_and_get_class() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ }, Data { x: ⚡ } } @@ -197,7 +193,7 @@ fn array_write_and_get_class() { Output: Trace: array_give [Data, given, given](_1_a . give , 1) ; Output: Trace: exit Main.main => Data { x: 99 } Result: Ok: Data { x: 99 } - Alloc 0x16: [Int(99)]"#]] + Alloc 0x16: [Int(99)]"#]]) ); } @@ -206,8 +202,9 @@ fn array_write_and_get_class() { // --------------------------------------------------------------- #[test] +// NOTE: future-panic test. Type checker correctly accepts; fault is a runtime bounds/init check. fn array_give_uninitialized_faults() { - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -216,21 +213,21 @@ fn array_give_uninitialized_faults() { } } }, - expect_test::expect![[r#" + type: ok, interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](3) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡, ⚡ } Output: Trace: array_give [Int, given, given](_1_a . give , 0) ; Result: Fault: access of uninitialized value Alloc 0x03: [RefCount(1), Capacity(3), Uninitialized, Uninitialized, Uninitialized] - Alloc 0x06: [Flags(Given), Pointer(0x03)]"#]] + Alloc 0x06: [Flags(Given), Pointer(0x03)]"#]]) ); } #[test] fn array_give_int_is_copy() { // Int is a copy type — giving it doesn't uninitialize the source. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -241,7 +238,7 @@ fn array_give_int_is_copy() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -251,7 +248,7 @@ fn array_give_int_is_copy() { Output: Trace: array_give [Int, given, given](_1_a . give , 0) ; Output: Trace: exit Main.main => 42 Result: Ok: 42 - Alloc 0x10: [Int(42)]"#]] + Alloc 0x10: [Int(42)]"#]]) ); } @@ -267,7 +264,7 @@ fn given_array_give_class_moves_out() { // // Actually, the simplest way to test Given move: use a given array, // give element 0 (moves it), then use array_give on element 1. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -281,7 +278,7 @@ fn given_array_give_class_moves_out() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ }, Data { x: ⚡ } } @@ -290,7 +287,7 @@ fn given_array_give_class_moves_out() { Output: Trace: array_give [Data, given, given](_1_a . give , 0) ; Output: Trace: exit Main.main => Data { x: 42 } Result: Ok: Data { x: 42 } - Alloc 0x12: [Int(42)]"#]] + Alloc 0x12: [Int(42)]"#]]) ); } @@ -299,11 +296,11 @@ fn shared_array_give_class_is_shared_copy() { // Shared array: class elements are given with shared semantics — // no move, element remains available for repeated gives. // P=shared produces a shared copy (rc++ on boxed fields). - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { - fn main(given self) -> Data { + fn main(given self) -> shared Data { let a = array_new[Data](1); array_write[Data, mut[a]](a.mut, 0, new Data(42)); let s = a.give.share; @@ -314,7 +311,8 @@ fn shared_array_give_class_is_shared_copy() { } } }, - expect_test::expect![[r#" + type: ok, + interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ } } @@ -328,7 +326,7 @@ fn shared_array_give_class_is_shared_copy() { Output: Trace: array_give [Data, shared, shared](_1_s . give , 0) ; Output: Trace: exit Main.main => shared Data { x: 42 } Result: Ok: shared Data { x: 42 } - Alloc 0x15: [Int(42)]"#]] + Alloc 0x15: [Int(42)]"#]]) ); } @@ -337,8 +335,9 @@ fn shared_array_give_class_is_shared_copy() { // --------------------------------------------------------------- #[test] +// NOTE: future-panic test. Type checker correctly accepts; fault is a runtime bounds/init check. fn array_give_out_of_bounds() { - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -347,20 +346,21 @@ fn array_give_out_of_bounds() { } } }, - expect_test::expect![[r#" + type: ok, interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } Output: Trace: array_give [Int, given, given](_1_a . give , 5) ; Result: Fault: array_give: index 5 out of bounds (capacity 2) Alloc 0x03: [RefCount(1), Capacity(2), Uninitialized, Uninitialized] - Alloc 0x06: [Flags(Given), Pointer(0x03)]"#]] + Alloc 0x06: [Flags(Given), Pointer(0x03)]"#]]) ); } #[test] +// NOTE: future-panic test. Type checker correctly accepts; fault is a runtime bounds/init check. fn array_write_out_of_bounds() { - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -370,7 +370,7 @@ fn array_write_out_of_bounds() { } } }, - expect_test::expect![[r#" + type: ok, interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -378,7 +378,7 @@ fn array_write_out_of_bounds() { Result: Fault: array_give: index 3 out of bounds (capacity 2) Alloc 0x03: [RefCount(1), Capacity(2), Uninitialized, Uninitialized] Alloc 0x04: [Flags(Given), Pointer(0x03)] - Alloc 0x06: [MutRef(0x03)]"#]] + Alloc 0x06: [MutRef(0x03)]"#]]) ); } @@ -388,7 +388,7 @@ fn array_write_out_of_bounds() { #[test] fn array_write_overwrites_existing() { - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -399,7 +399,7 @@ fn array_write_overwrites_existing() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -408,7 +408,7 @@ fn array_write_overwrites_existing() { Output: Trace: array_give [Int, given, given](_1_a . give , 0) ; Output: Trace: exit Main.main => 20 Result: Ok: 20 - Alloc 0x10: [Int(20)]"#]] + Alloc 0x10: [Int(20)]"#]]) ); } @@ -416,21 +416,22 @@ fn array_write_overwrites_existing() { /// and free the old array when refcount reaches zero. #[test] fn array_write_overwrites_shared_array() { - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { - fn main(given self) -> Int { - let outer = array_new[Array[Int]](1); + fn main(given self) -> () { + let outer = array_new[shared Array[Int]](1); let inner = array_new[Int](0).share; - array_write[Array[Int], mut[outer]](outer.mut, 0, inner.give); + array_write[shared Array[Int], mut[outer]](outer.mut, 0, inner.give); let replacement = array_new[Int](1); array_write[Int, mut[replacement]](replacement.mut, 0, 99); + let shared_replacement = replacement.give.share; print(outer.ref); print(inner.ref); - print(replacement.ref); + print(shared_replacement.ref); - array_write[Array[Int], mut[outer]](outer.mut, 0, replacement.give); + array_write[shared Array[Int], mut[outer]](outer.mut, 0, shared_replacement.give); print(outer.ref); print(inner.ref); @@ -438,32 +439,34 @@ fn array_write_overwrites_shared_array() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main - Output: Trace: let _1_outer = array_new [Array[Int]](1) ; - Output: Trace: _1_outer = Array { flag: Given, rc: 1, ⚡ } + Output: Trace: let _1_outer = array_new [shared Array[Int]](1) ; + Output: Trace: _1_outer = Array { flag: Given, rc: 1, shared ⚡ } Output: Trace: let _1_inner = array_new [Int](0) . share ; Output: Trace: _1_inner = shared Array { flag: Shared, rc: 1 } - Output: Trace: array_write [Array[Int], mut [_1_outer]](_1_outer . mut , 0 , _1_inner . give) ; + Output: Trace: array_write [shared Array[Int], mut [_1_outer]](_1_outer . mut , 0 , _1_inner . give) ; Output: Trace: let _1_replacement = array_new [Int](1) ; Output: Trace: _1_replacement = Array { flag: Given, rc: 1, ⚡ } Output: Trace: array_write [Int, mut [_1_replacement]](_1_replacement . mut , 0 , 99) ; + Output: Trace: let _1_shared_replacement = _1_replacement . give . share ; + Output: Trace: _1_shared_replacement = shared Array { flag: Shared, rc: 1, 99 } Output: Trace: print(_1_outer . ref) ; - Output: -----> ref [_1_outer] Array { flag: Borrowed, rc: 1, Array { flag: Shared, rc: 2 } } + Output: -----> ref [_1_outer] Array { flag: Borrowed, rc: 1, shared Array { flag: Shared, rc: 2 } } Output: Trace: print(_1_inner . ref) ; Output: -----> shared Array { flag: Borrowed, rc: 2 } - Output: Trace: print(_1_replacement . ref) ; - Output: -----> ref [_1_replacement] Array { flag: Borrowed, rc: 1, 99 } - Output: Trace: array_write [Array[Int], mut [_1_outer]](_1_outer . mut , 0 , _1_replacement . give) ; + Output: Trace: print(_1_shared_replacement . ref) ; + Output: -----> shared Array { flag: Borrowed, rc: 1, 99 } + Output: Trace: array_write [shared Array[Int], mut [_1_outer]](_1_outer . mut , 0 , _1_shared_replacement . give) ; Output: Trace: print(_1_outer . ref) ; - Output: -----> ref [_1_outer] Array { flag: Borrowed, rc: 1, Array { flag: Given, rc: 1, 99 } } + Output: -----> ref [_1_outer] Array { flag: Borrowed, rc: 1, shared Array { flag: Shared, rc: 2, 99 } } Output: Trace: print(_1_inner . ref) ; Output: -----> shared Array { flag: Borrowed, rc: 2 } Output: Trace: () ; Output: Trace: exit Main.main => () Result: Ok: () Alloc 0x07: [RefCount(1), Capacity(0)] - Alloc 0x0f: [RefCount(1), Capacity(1), Int(99)]"#]] + Alloc 0x0f: [RefCount(1), Capacity(1), Int(99)]"#]]) ); } @@ -472,11 +475,12 @@ fn array_write_overwrites_shared_array() { // --------------------------------------------------------------- #[test] +// BUG: soundness gap — type checker accepts but interpreter faults (use after array_drop). fn array_drop_element() { // Drop a class element (move type), then getting it should fault. // Note: Int is a copy type, so array_drop[Int, given, ...] would be a no-op. // We use Data (a move type) to test actual drop semantics. - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -488,7 +492,7 @@ fn array_drop_element() { } } }, - expect_test::expect![[r#" + type: ok, interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ }, Data { x: ⚡ } } @@ -497,14 +501,14 @@ fn array_drop_element() { Output: Trace: array_give [Data, given, given](_1_a . give , 0) ; Result: Fault: access of uninitialized value Alloc 0x03: [RefCount(1), Capacity(2), Uninitialized, Uninitialized] - Alloc 0x0f: [Flags(Given), Pointer(0x03)]"#]] + Alloc 0x0f: [Flags(Given), Pointer(0x03)]"#]]) ); } #[test] fn array_drop_class_element() { // Drop a class element — should recursively drop. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -516,7 +520,7 @@ fn array_drop_class_element() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ } } @@ -525,7 +529,7 @@ fn array_drop_class_element() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x0f: [Int(0)]"#]] + Alloc 0x0f: [Int(0)]"#]]) ); } @@ -536,7 +540,7 @@ fn array_drop_class_element() { #[test] fn array_give() { // Giving a Given array moves it — new owner can access elements. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -546,7 +550,7 @@ fn array_give() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡ } @@ -555,14 +559,14 @@ fn array_give() { Output: Trace: array_capacity [Int, given](_1_b . give) ; Output: Trace: exit Main.main => 1 Result: Ok: 1 - Alloc 0x09: [Int(1)]"#]] + Alloc 0x09: [Int(1)]"#]]) ); } #[test] fn array_give_then_get() { // Give the array to a new variable, then use the new variable. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -574,7 +578,7 @@ fn array_give_then_get() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -585,13 +589,13 @@ fn array_give_then_get() { Output: Trace: array_give [Int, given, given](_1_b . give , 0) ; Output: Trace: exit Main.main => 10 Result: Ok: 10 - Alloc 0x12: [Int(10)]"#]] + Alloc 0x12: [Int(10)]"#]]) ); } #[test] fn array_give_uninitializes_source() { - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -601,7 +605,13 @@ fn array_give_uninitializes_source() { } } }, - expect_test::expect![[r#" + type: error(expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Array[Int], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, a: Array[Int]}, assumptions: {}, fresh: 0 } } + + the rule "give" at (expressions.rs) failed because + condition evaluated to false: `!live_after.is_live(place)` + live_after = LivePlaces { accessed: {a}, traversed: {} } + place = a"#]]), interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡ } @@ -610,7 +620,7 @@ fn array_give_uninitializes_source() { Output: Trace: array_capacity [Int, given](_1_a . give) ; Result: Fault: access of uninitialized value Alloc 0x03: [RefCount(1), Capacity(1), Uninitialized] - Alloc 0x06: [Flags(Given), Pointer(0x03)]"#]] + Alloc 0x06: [Flags(Given), Pointer(0x03)]"#]]) ); } @@ -618,7 +628,7 @@ fn array_give_uninitializes_source() { fn array_share() { // Sharing an array sets its flags to Shared. // A shared array can be given multiple times. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -632,7 +642,7 @@ fn array_share() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -647,7 +657,7 @@ fn array_share() { Output: Trace: _1_x . give + _1_y . give ; Output: Trace: exit Main.main => 30 Result: Ok: 30 - Alloc 0x1a: [Int(30)]"#]] + Alloc 0x1a: [Int(30)]"#]]) ); } @@ -659,7 +669,7 @@ fn array_share() { fn shared_array_survives_after_original_dropped() { // Share an array to two variables, drop one, the other still works. // The refcount goes: 1 (new) → shared → 2 (give to b) → 1 (a dropped) → use b. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -673,7 +683,7 @@ fn shared_array_survives_after_original_dropped() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -687,7 +697,7 @@ fn shared_array_survives_after_original_dropped() { Output: Trace: array_give [Int, given, shared](_1_b . give , 0) ; Output: Trace: exit Main.main => 10 Result: Ok: 10 - Alloc 0x15: [Int(10)]"#]] + Alloc 0x15: [Int(10)]"#]]) ); } @@ -695,7 +705,7 @@ fn shared_array_survives_after_original_dropped() { fn refcount_reaches_zero_frees_allocation() { // When the last reference is dropped, the backing allocation is freed. // The heap snapshot should show only the result Int — no array allocation. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -710,7 +720,7 @@ fn refcount_reaches_zero_frees_allocation() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -725,7 +735,7 @@ fn refcount_reaches_zero_frees_allocation() { Output: Trace: 42 ; Output: Trace: exit Main.main => 42 Result: Ok: 42 - Alloc 0x14: [Int(42)]"#]] + Alloc 0x14: [Int(42)]"#]]) ); } @@ -733,7 +743,7 @@ fn refcount_reaches_zero_frees_allocation() { fn nested_array_in_class_field() { // A class with an Array[Int] field — dropping the class // recursively drops the array (decrements refcount to 0). - crate::assert_interpret_only!( + crate::assert_interpret!( { class Wrapper { items: Array[Int]; @@ -748,7 +758,7 @@ fn nested_array_in_class_field() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡ } @@ -759,7 +769,7 @@ fn nested_array_in_class_field() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x0e: [Int(0)]"#]] + Alloc 0x0e: [Int(0)]"#]]) ); } @@ -770,7 +780,7 @@ fn nested_array_in_class_field() { #[test] fn array_of_shared_class_elements() { // shared class elements have no flags word per element. - crate::assert_interpret_only!( + crate::assert_interpret!( { shared class Pt { x: Int; y: Int; } class Main { @@ -783,7 +793,7 @@ fn array_of_shared_class_elements() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Pt](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Pt { x: ⚡, y: ⚡ }, Pt { x: ⚡, y: ⚡ } } @@ -794,7 +804,7 @@ fn array_of_shared_class_elements() { Output: Trace: array_give [Pt, given, given](_1_a . give , 1) ; Output: Trace: exit Main.main => Pt { x: 3, y: 4 } Result: Ok: Pt { x: 3, y: 4 } - Alloc 0x18: [Int(3), Int(4)]"#]] + Alloc 0x18: [Int(3), Int(4)]"#]]) ); } @@ -802,7 +812,7 @@ fn array_of_shared_class_elements() { fn array_of_class_recursive_drop() { // Array of class with a nested field — dropping the array // should recursively drop each class element's fields. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { value: Int; } class Outer { inner: Inner; } @@ -816,7 +826,7 @@ fn array_of_class_recursive_drop() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Outer](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Outer { inner: Inner { value: ⚡ } }, Outer { inner: Inner { value: ⚡ } } } @@ -826,7 +836,7 @@ fn array_of_class_recursive_drop() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x13: [Int(0)]"#]] + Alloc 0x13: [Int(0)]"#]]) ); } @@ -835,10 +845,11 @@ fn array_of_class_recursive_drop() { // --------------------------------------------------------------- #[test] +// NOTE: future-panic test. Type checker correctly accepts; fault is a runtime bounds/init check. fn array_drop_out_of_bounds() { // Use Data (move type) so array_drop actually executes. // Int would be a no-op (copy type). - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -849,7 +860,7 @@ fn array_drop_out_of_bounds() { } } }, - expect_test::expect![[r#" + type: ok, interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ }, Data { x: ⚡ } } @@ -857,15 +868,16 @@ fn array_drop_out_of_bounds() { Result: Fault: array_drop: index 5 out of bounds (capacity 2) Alloc 0x03: [RefCount(1), Capacity(2), Uninitialized, Uninitialized] Alloc 0x04: [Flags(Given), Pointer(0x03)] - Alloc 0x06: [MutRef(0x03)]"#]] + Alloc 0x06: [MutRef(0x03)]"#]]) ); } #[test] +// NOTE: future-panic test. Type checker correctly accepts; fault is a runtime bounds/init check. fn array_drop_uninitialized_faults() { // Use Data (move type) so array_drop actually executes. // Int would be a no-op (copy type). - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -876,7 +888,7 @@ fn array_drop_uninitialized_faults() { } } }, - expect_test::expect![[r#" + type: ok, interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ }, Data { x: ⚡ } } @@ -884,7 +896,7 @@ fn array_drop_uninitialized_faults() { Result: Fault: access of uninitialized value Alloc 0x03: [RefCount(1), Capacity(2), Uninitialized, Uninitialized] Alloc 0x04: [Flags(Given), Pointer(0x03)] - Alloc 0x06: [MutRef(0x03)]"#]] + Alloc 0x06: [MutRef(0x03)]"#]]) ); } @@ -895,7 +907,7 @@ fn array_drop_uninitialized_faults() { #[test] fn array_new_zero_length() { // Zero-length array: capacity is 0, any access is out of bounds. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -904,20 +916,21 @@ fn array_new_zero_length() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](0) ; Output: Trace: _1_a = Array { flag: Given, rc: 1 } Output: Trace: array_capacity [Int, given](_1_a . give) ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x07: [Int(0)]"#]] + Alloc 0x07: [Int(0)]"#]]) ); } #[test] +// NOTE: future-panic test. Type checker correctly accepts; fault is a runtime bounds/init check. fn array_zero_length_access_faults() { - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -926,14 +939,14 @@ fn array_zero_length_access_faults() { } } }, - expect_test::expect![[r#" + type: ok, interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](0) ; Output: Trace: _1_a = Array { flag: Given, rc: 1 } Output: Trace: array_give [Int, given, given](_1_a . give , 0) ; Result: Fault: array_give: index 0 out of bounds (capacity 0) Alloc 0x03: [RefCount(1), Capacity(0)] - Alloc 0x06: [Flags(Given), Pointer(0x03)]"#]] + Alloc 0x06: [Flags(Given), Pointer(0x03)]"#]]) ); } @@ -945,7 +958,7 @@ fn array_zero_length_access_faults() { fn given_array_give_moves() { // A Given array (not shared) — giving it moves the whole array. // The original becomes uninitialized. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -957,7 +970,7 @@ fn given_array_give_moves() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -968,14 +981,14 @@ fn given_array_give_moves() { Output: Trace: array_give [Int, given, given](_1_b . give , 0) ; Output: Trace: exit Main.main => 10 Result: Ok: 10 - Alloc 0x12: [Int(10)]"#]] + Alloc 0x12: [Int(10)]"#]]) ); } #[test] fn given_array_double_give_faults() { // A Given array can only be given once — second give faults. - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -986,7 +999,13 @@ fn given_array_double_give_faults() { } } }, - expect_test::expect![[r#" + type: error(expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Array[Int], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, a: Array[Int]}, assumptions: {}, fresh: 0 } } + + the rule "give" at (expressions.rs) failed because + condition evaluated to false: `!live_after.is_live(place)` + live_after = LivePlaces { accessed: {a}, traversed: {} } + place = a"#]]), interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡ } @@ -995,7 +1014,7 @@ fn given_array_double_give_faults() { Output: Trace: let _1_c = _1_a . give ; Result: Fault: access of uninitialized value Alloc 0x03: [RefCount(1), Capacity(1), Uninitialized] - Alloc 0x06: [Flags(Given), Pointer(0x03)]"#]] + Alloc 0x06: [Flags(Given), Pointer(0x03)]"#]]) ); } @@ -1008,7 +1027,7 @@ fn share_class_containing_array() { // Sharing a class that contains an Array field should // set the class's flags to Shared. The array inside keeps // its runtime flags — share semantics are enforced by the type system. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Container { items: Array[Int]; @@ -1026,7 +1045,7 @@ fn share_class_containing_array() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -1043,7 +1062,7 @@ fn share_class_containing_array() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x19: [Int(0)]"#]] + Alloc 0x19: [Int(0)]"#]]) ); } @@ -1053,7 +1072,7 @@ fn share_class_containing_array() { #[test] fn array_display() { - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -1066,7 +1085,7 @@ fn array_display() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](3) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡, ⚡ } @@ -1078,7 +1097,7 @@ fn array_display() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x14: [Int(0)]"#]] + Alloc 0x14: [Int(0)]"#]]) ); } @@ -1090,7 +1109,7 @@ fn array_display() { fn shared_array_two_refs_both_usable() { // Two variables referencing the same shared array. // Both can read elements independently. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -1106,7 +1125,7 @@ fn shared_array_two_refs_both_usable() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -1123,14 +1142,14 @@ fn shared_array_two_refs_both_usable() { Output: Trace: _1_x . give + _1_y . give ; Output: Trace: exit Main.main => 30 Result: Ok: 30 - Alloc 0x1c: [Int(30)]"#]] + Alloc 0x1c: [Int(30)]"#]]) ); } #[test] fn shared_array_three_refs_drop_two() { // Three references: drop two, last one still works. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -1147,7 +1166,7 @@ fn shared_array_three_refs_drop_two() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡ } @@ -1163,14 +1182,14 @@ fn shared_array_three_refs_drop_two() { Output: Trace: array_give [Int, given, shared](_1_c . give , 0) ; Output: Trace: exit Main.main => 42 Result: Ok: 42 - Alloc 0x14: [Int(42)]"#]] + Alloc 0x14: [Int(42)]"#]]) ); } #[test] fn shared_array_all_refs_dropped_frees() { // All references dropped: backing allocation freed. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -1186,7 +1205,7 @@ fn shared_array_all_refs_dropped_frees() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡ } @@ -1203,7 +1222,7 @@ fn shared_array_all_refs_dropped_frees() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x13: [Int(0)]"#]] + Alloc 0x13: [Int(0)]"#]]) ); } @@ -1215,7 +1234,7 @@ fn shared_array_all_refs_dropped_frees() { fn nested_array_create_and_capacity() { // Array[Array[Int]]: outer array holds inner arrays as elements. // Each element is 2 words (Flags + Pointer). - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -1227,7 +1246,7 @@ fn nested_array_create_and_capacity() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_outer = array_new [Array[Int]](2) ; Output: Trace: _1_outer = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -1239,7 +1258,7 @@ fn nested_array_create_and_capacity() { Output: Trace: array_capacity [Int, given](_1_got . give) ; Output: Trace: exit Main.main => 3 Result: Ok: 3 - Alloc 0x13: [Int(3)]"#]] + Alloc 0x13: [Int(3)]"#]]) ); } @@ -1250,7 +1269,7 @@ fn nested_array_give_inner_from_shared_outer() { // When outer is shared, give_value sees Shared flags (from the outer's // shared perspective) and calls share_op, incrementing the inner array's // refcount. We can give the same inner element multiple times. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -1266,11 +1285,11 @@ fn nested_array_give_inner_from_shared_outer() { print(got.give); // Give it again — shared elements can be given repeatedly. let got2 = array_give[Array[Int], shared, shared](s.give, 0); - array_give[Int, given, given](got2.give, 1); + array_give[Int, shared, shared](got2.give, 1); } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_inner = array_new [Int](2) ; Output: Trace: _1_inner = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -1287,11 +1306,11 @@ fn nested_array_give_inner_from_shared_outer() { Output: -----> shared Array { flag: Shared, rc: 3, 10, 20 } Output: Trace: let _1_got2 = array_give [Array[Int], shared, shared](_1_s . give , 0) ; Output: Trace: _1_got2 = shared Array { flag: Shared, rc: 3, 10, 20 } - Output: Trace: array_give [Int, given, given](_1_got2 . give , 1) ; + Output: Trace: array_give [Int, shared, shared](_1_got2 . give , 1) ; Output: Trace: exit Main.main => 20 Result: Ok: 20 Alloc 0x03: [RefCount(1), Capacity(2), Int(10), Int(20)] - Alloc 0x24: [Int(20)]"#]] + Alloc 0x24: [Int(20)]"#]]) ); } @@ -1299,38 +1318,38 @@ fn nested_array_give_inner_from_shared_outer() { fn nested_array_drop_inner_decrements_refcount() { // Array[Array[Int]]: dropping the inner element in the outer array // should decrement the inner array's refcount. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { let inner = array_new[Int](1); array_write[Int, mut[inner]](inner.mut, 0, 42); let s = inner.give.share; - let outer = array_new[Array[Int]](1); - array_write[Array[Int], mut[outer]](outer.mut, 0, s.give); + let outer = array_new[shared Array[Int]](1); + array_write[shared Array[Int], mut[outer]](outer.mut, 0, s.give); // s is shared: s.give copies + rc++. s still alive, rc=2. // Drop the element in outer — refcount goes to 1. - array_drop[Array[Int], given, mut[outer]](outer.mut, 0, 1); + array_drop[shared Array[Int], given, mut[outer]](outer.mut, 0, 1); // s var still alive, can still read. - array_give[Int, given, shared](s.give, 0); + array_give[Int, shared, shared](s.give, 0); } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_inner = array_new [Int](1) ; Output: Trace: _1_inner = Array { flag: Given, rc: 1, ⚡ } Output: Trace: array_write [Int, mut [_1_inner]](_1_inner . mut , 0 , 42) ; Output: Trace: let _1_s = _1_inner . give . share ; Output: Trace: _1_s = shared Array { flag: Shared, rc: 1, 42 } - Output: Trace: let _1_outer = array_new [Array[Int]](1) ; - Output: Trace: _1_outer = Array { flag: Given, rc: 1, ⚡ } - Output: Trace: array_write [Array[Int], mut [_1_outer]](_1_outer . mut , 0 , _1_s . give) ; - Output: Trace: array_drop [Array[Int], given, mut [_1_outer]](_1_outer . mut , 0 , 1) ; - Output: Trace: array_give [Int, given, shared](_1_s . give , 0) ; + Output: Trace: let _1_outer = array_new [shared Array[Int]](1) ; + Output: Trace: _1_outer = Array { flag: Given, rc: 1, shared ⚡ } + Output: Trace: array_write [shared Array[Int], mut [_1_outer]](_1_outer . mut , 0 , _1_s . give) ; + Output: Trace: array_drop [shared Array[Int], given, mut [_1_outer]](_1_outer . mut , 0 , 1) ; + Output: Trace: array_give [Int, shared, shared](_1_s . give , 0) ; Output: Trace: exit Main.main => 42 Result: Ok: 42 - Alloc 0x1a: [Int(42)]"#]] + Alloc 0x1a: [Int(42)]"#]]) ); } @@ -1340,7 +1359,7 @@ fn nested_array_all_refs_freed() { // drop elements (arrays don't drop their elements — that's the user's job). // The inner array's backing allocation remains as an orphan in the heap. // This documents the unsafe contract: use array_drop to clean up elements. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -1354,7 +1373,7 @@ fn nested_array_all_refs_freed() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_inner = array_new [Int](1) ; Output: Trace: _1_inner = Array { flag: Given, rc: 1, ⚡ } @@ -1367,7 +1386,7 @@ fn nested_array_all_refs_freed() { Output: Trace: exit Main.main => 0 Result: Ok: 0 Alloc 0x03: [RefCount(1), Capacity(1), Int(1)] - Alloc 0x13: [Int(0)]"#]] + Alloc 0x13: [Int(0)]"#]]) ); } @@ -1381,7 +1400,7 @@ fn shared_outer_array_of_data_arrays() { // Giving an inner array element from the shared outer produces a shared copy // with incremented refcount. The inner array is also shared, so reading // Data elements through both paths works (shared semantics, no move). - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -1389,11 +1408,11 @@ fn shared_outer_array_of_data_arrays() { let inner = array_new[Data](1); array_write[Data, mut[inner]](inner.mut, 0, new Data(42)); let si = inner.give.share; - let outer = array_new[Array[Data]](1); - array_write[Array[Data], mut[outer]](outer.mut, 0, si.give); + let outer = array_new[shared Array[Data]](1); + array_write[shared Array[Data], mut[outer]](outer.mut, 0, si.give); let so = outer.give.share; // Give inner array element from shared outer — shared copy. - let got = array_give[Array[Data], shared, ref[so]](so.ref, 0); + let got = array_give[shared Array[Data], shared, ref[so]](so.ref, 0); // Read Data through the copy — shared, so no move. print(array_give[Data, shared, shared](got.give, 0)); // Read Data through original inner — still available. @@ -1402,19 +1421,19 @@ fn shared_outer_array_of_data_arrays() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_inner = array_new [Data](1) ; Output: Trace: _1_inner = Array { flag: Given, rc: 1, Data { x: ⚡ } } Output: Trace: array_write [Data, mut [_1_inner]](_1_inner . mut , 0 , new Data (42)) ; Output: Trace: let _1_si = _1_inner . give . share ; Output: Trace: _1_si = shared Array { flag: Shared, rc: 1, Data { x: 42 } } - Output: Trace: let _1_outer = array_new [Array[Data]](1) ; - Output: Trace: _1_outer = Array { flag: Given, rc: 1, ⚡ } - Output: Trace: array_write [Array[Data], mut [_1_outer]](_1_outer . mut , 0 , _1_si . give) ; + Output: Trace: let _1_outer = array_new [shared Array[Data]](1) ; + Output: Trace: _1_outer = Array { flag: Given, rc: 1, shared ⚡ } + Output: Trace: array_write [shared Array[Data], mut [_1_outer]](_1_outer . mut , 0 , _1_si . give) ; Output: Trace: let _1_so = _1_outer . give . share ; - Output: Trace: _1_so = shared Array { flag: Shared, rc: 1, Array { flag: Shared, rc: 2, Data { x: 42 } } } - Output: Trace: let _1_got = array_give [Array[Data], shared, ref [_1_so]](_1_so . ref , 0) ; + Output: Trace: _1_so = shared Array { flag: Shared, rc: 1, shared Array { flag: Shared, rc: 2, Data { x: 42 } } } + Output: Trace: let _1_got = array_give [shared Array[Data], shared, ref [_1_so]](_1_so . ref , 0) ; Output: Trace: _1_got = shared Array { flag: Shared, rc: 3, Data { x: 42 } } Output: Trace: print(array_give [Data, shared, shared](_1_got . give , 0)) ; Output: -----> shared Data { x: 42 } @@ -1424,7 +1443,7 @@ fn shared_outer_array_of_data_arrays() { Output: Trace: exit Main.main => 0 Result: Ok: 0 Alloc 0x03: [RefCount(1), Capacity(1), Int(42)] - Alloc 0x23: [Int(0)]"#]] + Alloc 0x23: [Int(0)]"#]]) ); } @@ -1439,7 +1458,7 @@ fn array_of_shared_inner_arrays() { // Giving the element calls share_op (increments inner backing refcount). // Both the copy and the original inner var can read Data elements // (shared semantics propagate through both array layers). - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -1447,11 +1466,11 @@ fn array_of_shared_inner_arrays() { let inner = array_new[Data](1); array_write[Data, mut[inner]](inner.mut, 0, new Data(99)); let si = inner.give.share; - let outer = array_new[Array[Data]](1); - array_write[Array[Data], mut[outer]](outer.mut, 0, si.give); + let outer = array_new[shared Array[Data]](1); + array_write[shared Array[Data], mut[outer]](outer.mut, 0, si.give); let so = outer.give.share; // Give element from outer — share_op increments inner refcount. - let got = array_give[Array[Data], shared, ref[so]](so.ref, 0); + let got = array_give[shared Array[Data], shared, ref[so]](so.ref, 0); // Read Data through the copy — shared, no move. print(array_give[Data, shared, shared](got.give, 0)); // Read Data through original inner — still available. @@ -1460,19 +1479,19 @@ fn array_of_shared_inner_arrays() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_inner = array_new [Data](1) ; Output: Trace: _1_inner = Array { flag: Given, rc: 1, Data { x: ⚡ } } Output: Trace: array_write [Data, mut [_1_inner]](_1_inner . mut , 0 , new Data (99)) ; Output: Trace: let _1_si = _1_inner . give . share ; Output: Trace: _1_si = shared Array { flag: Shared, rc: 1, Data { x: 99 } } - Output: Trace: let _1_outer = array_new [Array[Data]](1) ; - Output: Trace: _1_outer = Array { flag: Given, rc: 1, ⚡ } - Output: Trace: array_write [Array[Data], mut [_1_outer]](_1_outer . mut , 0 , _1_si . give) ; + Output: Trace: let _1_outer = array_new [shared Array[Data]](1) ; + Output: Trace: _1_outer = Array { flag: Given, rc: 1, shared ⚡ } + Output: Trace: array_write [shared Array[Data], mut [_1_outer]](_1_outer . mut , 0 , _1_si . give) ; Output: Trace: let _1_so = _1_outer . give . share ; - Output: Trace: _1_so = shared Array { flag: Shared, rc: 1, Array { flag: Shared, rc: 2, Data { x: 99 } } } - Output: Trace: let _1_got = array_give [Array[Data], shared, ref [_1_so]](_1_so . ref , 0) ; + Output: Trace: _1_so = shared Array { flag: Shared, rc: 1, shared Array { flag: Shared, rc: 2, Data { x: 99 } } } + Output: Trace: let _1_got = array_give [shared Array[Data], shared, ref [_1_so]](_1_so . ref , 0) ; Output: Trace: _1_got = shared Array { flag: Shared, rc: 3, Data { x: 99 } } Output: Trace: print(array_give [Data, shared, shared](_1_got . give , 0)) ; Output: -----> shared Data { x: 99 } @@ -1482,7 +1501,7 @@ fn array_of_shared_inner_arrays() { Output: Trace: exit Main.main => 0 Result: Ok: 0 Alloc 0x03: [RefCount(1), Capacity(1), Int(99)] - Alloc 0x23: [Int(0)]"#]] + Alloc 0x23: [Int(0)]"#]]) ); } @@ -1492,7 +1511,7 @@ fn shared_outer_give_inner_survives_outer_drop() { // element produces a shared copy with incremented refcount. // After dropping the outer array entirely, the given copy still works // because share_op kept the inner backing alive. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -1500,42 +1519,45 @@ fn shared_outer_give_inner_survives_outer_drop() { let inner = array_new[Data](1); array_write[Data, mut[inner]](inner.mut, 0, new Data(42)); let si = inner.give.share; - let outer = array_new[Array[Data]](1); - array_write[Array[Data], mut[outer]](outer.mut, 0, si.give); + let outer = array_new[shared Array[Data]](1); + array_write[shared Array[Data], mut[outer]](outer.mut, 0, si.give); let so = outer.give.share; // Give the inner array element from shared outer. - let got = array_give[Array[Data], shared, ref[so]](so.ref, 0); + let got = array_give[shared Array[Data], shared, ref[so]](so.ref, 0); // Drop outer entirely — cascading drop hits the element, // which decrements inner refcount. But got's share_op // already incremented it, so refcount > 0. si.drop; so.drop; // got still alive — read the Data element. - array_give[Data, shared, shared](got.give, 0); + print(array_give[Data, shared, shared](got.give, 0)); + 0; } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_inner = array_new [Data](1) ; Output: Trace: _1_inner = Array { flag: Given, rc: 1, Data { x: ⚡ } } Output: Trace: array_write [Data, mut [_1_inner]](_1_inner . mut , 0 , new Data (42)) ; Output: Trace: let _1_si = _1_inner . give . share ; Output: Trace: _1_si = shared Array { flag: Shared, rc: 1, Data { x: 42 } } - Output: Trace: let _1_outer = array_new [Array[Data]](1) ; - Output: Trace: _1_outer = Array { flag: Given, rc: 1, ⚡ } - Output: Trace: array_write [Array[Data], mut [_1_outer]](_1_outer . mut , 0 , _1_si . give) ; + Output: Trace: let _1_outer = array_new [shared Array[Data]](1) ; + Output: Trace: _1_outer = Array { flag: Given, rc: 1, shared ⚡ } + Output: Trace: array_write [shared Array[Data], mut [_1_outer]](_1_outer . mut , 0 , _1_si . give) ; Output: Trace: let _1_so = _1_outer . give . share ; - Output: Trace: _1_so = shared Array { flag: Shared, rc: 1, Array { flag: Shared, rc: 2, Data { x: 42 } } } - Output: Trace: let _1_got = array_give [Array[Data], shared, ref [_1_so]](_1_so . ref , 0) ; + Output: Trace: _1_so = shared Array { flag: Shared, rc: 1, shared Array { flag: Shared, rc: 2, Data { x: 42 } } } + Output: Trace: let _1_got = array_give [shared Array[Data], shared, ref [_1_so]](_1_so . ref , 0) ; Output: Trace: _1_got = shared Array { flag: Shared, rc: 3, Data { x: 42 } } Output: Trace: _1_si . drop ; Output: Trace: _1_so . drop ; - Output: Trace: array_give [Data, shared, shared](_1_got . give , 0) ; - Output: Trace: exit Main.main => shared Data { x: 42 } - Result: Ok: shared Data { x: 42 } + Output: Trace: print(array_give [Data, shared, shared](_1_got . give , 0)) ; + Output: -----> shared Data { x: 42 } + Output: Trace: 0 ; + Output: Trace: exit Main.main => 0 + Result: Ok: 0 Alloc 0x03: [RefCount(1), Capacity(1), Int(42)] - Alloc 0x1f: [Int(42)]"#]] + Alloc 0x21: [Int(0)]"#]]) ); } @@ -1549,7 +1571,7 @@ fn shared_array_of_shared_arrays() { // Multiple gives from outer each increment inner refcount. // All three references (inner var, copy1, copy2) can independently // read Data elements — shared semantics propagate through both layers. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -1557,12 +1579,12 @@ fn shared_array_of_shared_arrays() { let inner = array_new[Data](1); array_write[Data, mut[inner]](inner.mut, 0, new Data(77)); let si = inner.give.share; - let outer = array_new[Array[Data]](1); - array_write[Array[Data], mut[outer]](outer.mut, 0, si.give); + let outer = array_new[shared Array[Data]](1); + array_write[shared Array[Data], mut[outer]](outer.mut, 0, si.give); let so = outer.give.share; // Give element twice from shared outer — each increments refcount. - let copy1 = array_give[Array[Data], shared, ref[so]](so.ref, 0); - let copy2 = array_give[Array[Data], shared, ref[so]](so.ref, 0); + let copy1 = array_give[shared Array[Data], shared, ref[so]](so.ref, 0); + let copy2 = array_give[shared Array[Data], shared, ref[so]](so.ref, 0); // All three can read the same Data — shared, no move. print(array_give[Data, shared, shared](copy1.give, 0)); print(array_give[Data, shared, shared](copy2.give, 0)); @@ -1571,21 +1593,21 @@ fn shared_array_of_shared_arrays() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_inner = array_new [Data](1) ; Output: Trace: _1_inner = Array { flag: Given, rc: 1, Data { x: ⚡ } } Output: Trace: array_write [Data, mut [_1_inner]](_1_inner . mut , 0 , new Data (77)) ; Output: Trace: let _1_si = _1_inner . give . share ; Output: Trace: _1_si = shared Array { flag: Shared, rc: 1, Data { x: 77 } } - Output: Trace: let _1_outer = array_new [Array[Data]](1) ; - Output: Trace: _1_outer = Array { flag: Given, rc: 1, ⚡ } - Output: Trace: array_write [Array[Data], mut [_1_outer]](_1_outer . mut , 0 , _1_si . give) ; + Output: Trace: let _1_outer = array_new [shared Array[Data]](1) ; + Output: Trace: _1_outer = Array { flag: Given, rc: 1, shared ⚡ } + Output: Trace: array_write [shared Array[Data], mut [_1_outer]](_1_outer . mut , 0 , _1_si . give) ; Output: Trace: let _1_so = _1_outer . give . share ; - Output: Trace: _1_so = shared Array { flag: Shared, rc: 1, Array { flag: Shared, rc: 2, Data { x: 77 } } } - Output: Trace: let _1_copy1 = array_give [Array[Data], shared, ref [_1_so]](_1_so . ref , 0) ; + Output: Trace: _1_so = shared Array { flag: Shared, rc: 1, shared Array { flag: Shared, rc: 2, Data { x: 77 } } } + Output: Trace: let _1_copy1 = array_give [shared Array[Data], shared, ref [_1_so]](_1_so . ref , 0) ; Output: Trace: _1_copy1 = shared Array { flag: Shared, rc: 3, Data { x: 77 } } - Output: Trace: let _1_copy2 = array_give [Array[Data], shared, ref [_1_so]](_1_so . ref , 0) ; + Output: Trace: let _1_copy2 = array_give [shared Array[Data], shared, ref [_1_so]](_1_so . ref , 0) ; Output: Trace: _1_copy2 = shared Array { flag: Shared, rc: 4, Data { x: 77 } } Output: Trace: print(array_give [Data, shared, shared](_1_copy1 . give , 0)) ; Output: -----> shared Data { x: 77 } @@ -1597,7 +1619,7 @@ fn shared_array_of_shared_arrays() { Output: Trace: exit Main.main => 0 Result: Ok: 0 Alloc 0x03: [RefCount(1), Capacity(1), Int(77)] - Alloc 0x2b: [Int(0)]"#]] + Alloc 0x2b: [Int(0)]"#]]) ); } @@ -1608,7 +1630,7 @@ fn shared_array_of_shared_arrays_drop_cascade() { // because the runtime Shared flags override the static P=given. // The inner array backing leaks (rc=1) because arrays don't drop // their elements — the outer's scrub doesn't decrement the inner's rc. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -1616,12 +1638,12 @@ fn shared_array_of_shared_arrays_drop_cascade() { let inner = array_new[Data](1); array_write[Data, mut[inner]](inner.mut, 0, new Data(55)); let si = inner.give.share; - let outer = array_new[Array[Data]](1); - array_write[Array[Data], mut[outer]](outer.mut, 0, si.give); + let outer = array_new[shared Array[Data]](1); + array_write[shared Array[Data], mut[outer]](outer.mut, 0, si.give); let so = outer.give.share; // Give a copy from outer: runtime flags are Shared, so this // produces a shared copy (rc++) not a move. - let copy1 = array_give[Array[Data], given, ref[so]](so.ref, 0); + let copy1 = array_give[shared Array[Data], shared, ref[so]](so.ref, 0); copy1.drop; so.drop; si.drop; @@ -1629,20 +1651,20 @@ fn shared_array_of_shared_arrays_drop_cascade() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_inner = array_new [Data](1) ; Output: Trace: _1_inner = Array { flag: Given, rc: 1, Data { x: ⚡ } } Output: Trace: array_write [Data, mut [_1_inner]](_1_inner . mut , 0 , new Data (55)) ; Output: Trace: let _1_si = _1_inner . give . share ; Output: Trace: _1_si = shared Array { flag: Shared, rc: 1, Data { x: 55 } } - Output: Trace: let _1_outer = array_new [Array[Data]](1) ; - Output: Trace: _1_outer = Array { flag: Given, rc: 1, ⚡ } - Output: Trace: array_write [Array[Data], mut [_1_outer]](_1_outer . mut , 0 , _1_si . give) ; + Output: Trace: let _1_outer = array_new [shared Array[Data]](1) ; + Output: Trace: _1_outer = Array { flag: Given, rc: 1, shared ⚡ } + Output: Trace: array_write [shared Array[Data], mut [_1_outer]](_1_outer . mut , 0 , _1_si . give) ; Output: Trace: let _1_so = _1_outer . give . share ; - Output: Trace: _1_so = shared Array { flag: Shared, rc: 1, Array { flag: Shared, rc: 2, Data { x: 55 } } } - Output: Trace: let _1_copy1 = array_give [Array[Data], given, ref [_1_so]](_1_so . ref , 0) ; - Output: Trace: _1_copy1 = Array { flag: Shared, rc: 3, Data { x: 55 } } + Output: Trace: _1_so = shared Array { flag: Shared, rc: 1, shared Array { flag: Shared, rc: 2, Data { x: 55 } } } + Output: Trace: let _1_copy1 = array_give [shared Array[Data], shared, ref [_1_so]](_1_so . ref , 0) ; + Output: Trace: _1_copy1 = shared Array { flag: Shared, rc: 3, Data { x: 55 } } Output: Trace: _1_copy1 . drop ; Output: Trace: _1_so . drop ; Output: Trace: _1_si . drop ; @@ -1650,7 +1672,7 @@ fn shared_array_of_shared_arrays_drop_cascade() { Output: Trace: exit Main.main => 0 Result: Ok: 0 Alloc 0x03: [RefCount(1), Capacity(1), Int(55)] - Alloc 0x1e: [Int(0)]"#]] + Alloc 0x1e: [Int(0)]"#]]) ); } @@ -1662,47 +1684,48 @@ fn shared_array_of_shared_arrays_drop_cascade() { fn array_drop_shared_element_decrements_refcount() { // Array element with Shared flags: ArrayDrop should call drop_owned_value, // which for an array element decrements its refcount. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { let inner = array_new[Int](1); array_write[Int, mut[inner]](inner.mut, 0, 42); let si = inner.give.share; - let outer = array_new[Array[Int]](1); - array_write[Array[Int], mut[outer]](outer.mut, 0, si.give); + let outer = array_new[shared Array[Int]](1); + array_write[shared Array[Int], mut[outer]](outer.mut, 0, si.give); // Element in outer is shared Array[Int] — refcount 2. // Drop it: refcount → 1. si var still valid. - array_drop[Array[Int], given, mut[outer]](outer.mut, 0, 1); - array_give[Int, given, shared](si.give, 0); + array_drop[shared Array[Int], given, mut[outer]](outer.mut, 0, 1); + array_give[Int, shared, shared](si.give, 0); } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_inner = array_new [Int](1) ; Output: Trace: _1_inner = Array { flag: Given, rc: 1, ⚡ } Output: Trace: array_write [Int, mut [_1_inner]](_1_inner . mut , 0 , 42) ; Output: Trace: let _1_si = _1_inner . give . share ; Output: Trace: _1_si = shared Array { flag: Shared, rc: 1, 42 } - Output: Trace: let _1_outer = array_new [Array[Int]](1) ; - Output: Trace: _1_outer = Array { flag: Given, rc: 1, ⚡ } - Output: Trace: array_write [Array[Int], mut [_1_outer]](_1_outer . mut , 0 , _1_si . give) ; - Output: Trace: array_drop [Array[Int], given, mut [_1_outer]](_1_outer . mut , 0 , 1) ; - Output: Trace: array_give [Int, given, shared](_1_si . give , 0) ; + Output: Trace: let _1_outer = array_new [shared Array[Int]](1) ; + Output: Trace: _1_outer = Array { flag: Given, rc: 1, shared ⚡ } + Output: Trace: array_write [shared Array[Int], mut [_1_outer]](_1_outer . mut , 0 , _1_si . give) ; + Output: Trace: array_drop [shared Array[Int], given, mut [_1_outer]](_1_outer . mut , 0 , 1) ; + Output: Trace: array_give [Int, shared, shared](_1_si . give , 0) ; Output: Trace: exit Main.main => 42 Result: Ok: 42 - Alloc 0x1a: [Int(42)]"#]] + Alloc 0x1a: [Int(42)]"#]]) ); } #[test] +// BUG: soundness gap — type checker accepts but interpreter faults (use after array_drop). fn array_drop_shared_class_element() { // Even though Pt is a `shared class` (copy type), array_drop with P=given // actually drops the element. P=given means "I own these, clean them up." // This is needed to avoid leaks: a shared class with boxed fields would // leak refcounts if array_drop were a no-op. - crate::assert_interpret_fault!( + crate::assert_interpret!( { shared class Pt { x: Int; y: Int; } class Main { @@ -1715,7 +1738,7 @@ fn array_drop_shared_class_element() { } } }, - expect_test::expect![[r#" + type: ok, interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Pt](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Pt { x: ⚡, y: ⚡ } } @@ -1724,7 +1747,7 @@ fn array_drop_shared_class_element() { Output: Trace: array_give [Pt, given, given](_1_a . give , 0) ; Result: Fault: access of uninitialized value Alloc 0x03: [RefCount(1), Capacity(1), Uninitialized, Uninitialized] - Alloc 0x10: [Flags(Given), Pointer(0x03)]"#]] + Alloc 0x10: [Flags(Given), Pointer(0x03)]"#]]) ); } @@ -1736,7 +1759,7 @@ fn array_drop_shared_class_element() { fn array_write_class_with_array_field() { // Initialize an array element with a class that contains an Array field. // Ownership of the inner array transfers into the element slot. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Container { items: Array[Int]; @@ -1756,7 +1779,7 @@ fn array_write_class_with_array_field() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_outer = array_new [Container](1) ; Output: Trace: _1_outer = Array { flag: Given, rc: 1, Container { items: ⚡ } } @@ -1774,7 +1797,7 @@ fn array_write_class_with_array_field() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x1f: [Int(0)]"#]] + Alloc 0x1f: [Int(0)]"#]]) ); } @@ -1782,7 +1805,7 @@ fn array_write_class_with_array_field() { fn array_drop_class_with_array_field() { // Drop an array element that is a class containing an Array field. // Should recursively drop: class element → inner array (refcount→0, freed). - crate::assert_interpret_only!( + crate::assert_interpret!( { class Container { items: Array[Int]; @@ -1803,7 +1826,7 @@ fn array_drop_class_with_array_field() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_outer = array_new [Container](1) ; Output: Trace: _1_outer = Array { flag: Given, rc: 1, Container { items: ⚡ } } @@ -1823,7 +1846,7 @@ fn array_drop_class_with_array_field() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x1f: [Int(0)]"#]] + Alloc 0x1f: [Int(0)]"#]]) ); } @@ -1831,7 +1854,7 @@ fn array_drop_class_with_array_field() { fn array_share_uninitialized() { // Sharing a newly created array with uninitialized elements succeeds — // array elements are user-managed (unsafe), so share doesn't traverse them. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Container { items: Array[Int]; @@ -1843,14 +1866,14 @@ fn array_share_uninitialized() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_outer = array_new [Container](1) . share ; Output: Trace: _1_outer = shared Array { flag: Shared, rc: 1, Container { items: ⚡ } } Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x06: [Int(0)]"#]] + Alloc 0x06: [Int(0)]"#]]) ); } @@ -1862,7 +1885,7 @@ fn array_share_uninitialized() { fn shared_array_give_increments_refcount() { // Giving a shared array increments the refcount. // After dropping the original, the copy keeps the array alive. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -1877,7 +1900,7 @@ fn shared_array_give_increments_refcount() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡ } @@ -1890,7 +1913,7 @@ fn shared_array_give_increments_refcount() { Output: Trace: array_give [Int, given, shared](_1_b . give , 0) ; Output: Trace: exit Main.main => 55 Result: Ok: 55 - Alloc 0x11: [Int(55)]"#]] + Alloc 0x11: [Int(55)]"#]]) ); } @@ -1901,7 +1924,7 @@ fn shared_array_give_increments_refcount() { #[test] fn ref_array_print() { // Taking a ref to an array and printing it. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -1913,7 +1936,7 @@ fn ref_array_print() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -1924,7 +1947,7 @@ fn ref_array_print() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x10: [Int(0)]"#]] + Alloc 0x10: [Int(0)]"#]]) ); } @@ -1932,7 +1955,7 @@ fn ref_array_print() { fn ref_array_give_int_element() { // Giving an Int element from a ref array with P=ref yields a copy. // Int is a shared class (copy type), so ref produces a copy. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -1949,7 +1972,7 @@ fn ref_array_give_int_element() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -1968,14 +1991,14 @@ fn ref_array_give_int_element() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x1c: [Int(0)]"#]] + Alloc 0x1c: [Int(0)]"#]]) ); } #[test] fn ref_array_give_class_element() { // Giving a class element with P=ref yields a borrowed copy (ref flags on boxed fields). - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; @@ -1992,7 +2015,7 @@ fn ref_array_give_class_element() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ } } @@ -2006,7 +2029,7 @@ fn ref_array_give_class_element() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x13: [Int(0)]"#]] + Alloc 0x13: [Int(0)]"#]]) ); } @@ -2016,7 +2039,7 @@ fn ref_array_of_shared_arrays() { // Giving an element with P=ref through a ref to the outer produces a // borrowed copy of the shared inner array (flag: Borrowed, rc incremented // to keep backing alive). - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -2036,7 +2059,7 @@ fn ref_array_of_shared_arrays() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_inner = array_new [Int](2) ; Output: Trace: _1_inner = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -2057,7 +2080,7 @@ fn ref_array_of_shared_arrays() { Output: Trace: exit Main.main => 0 Result: Ok: 0 Alloc 0x03: [RefCount(1), Capacity(2), Int(10), Int(20)] - Alloc 0x20: [Int(0)]"#]] + Alloc 0x20: [Int(0)]"#]]) ); } @@ -2071,7 +2094,7 @@ fn array_give_ref_of_runtime_shared_element() { // But at runtime, the stored element has Shared flags (came from .share; // shared ≤ ref so this is valid). array_give with P=ref dispatches on the // static type `ref[outer] ref[dummy] Array[Int]` — what happens? - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -2092,7 +2115,7 @@ fn array_give_ref_of_runtime_shared_element() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_inner = array_new [Int](1) ; Output: Trace: _1_inner = Array { flag: Given, rc: 1, ⚡ } @@ -2114,7 +2137,7 @@ fn array_give_ref_of_runtime_shared_element() { Output: Trace: exit Main.main => 0 Result: Ok: 0 Alloc 0x03: [RefCount(1), Capacity(1), Int(42)] - Alloc 0x20: [Int(0)]"#]] + Alloc 0x20: [Int(0)]"#]]) ); } @@ -2125,7 +2148,7 @@ fn array_give_ref_of_runtime_shared_element() { #[test] fn array_give_p_mut() { // array_give with P=mut returns a mutable reference to the element. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -2138,7 +2161,7 @@ fn array_give_p_mut() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ } } @@ -2150,24 +2173,24 @@ fn array_give_p_mut() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x11: [Int(0)]"#]] + Alloc 0x11: [Int(0)]"#]]) ); } #[test] fn array_give_p_shared() { // array_give with P=shared returns a shared copy, rc incremented. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { let inner = array_new[Int](1); array_write[Int, mut[inner]](inner.mut, 0, 77); let s = inner.give.share; - let outer = array_new[Array[Int]](1); - array_write[Array[Int], mut[outer]](outer.mut, 0, s.give); + let outer = array_new[shared Array[Int]](1); + array_write[shared Array[Int], mut[outer]](outer.mut, 0, s.give); // Give with P=shared: should produce a shared copy with rc++ - let got = array_give[Array[Int], shared, ref[outer]](outer.ref, 0); + let got = array_give[shared Array[Int], shared, ref[outer]](outer.ref, 0); print(got.give); // Original still intact print(outer.ref); @@ -2175,34 +2198,34 @@ fn array_give_p_shared() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_inner = array_new [Int](1) ; Output: Trace: _1_inner = Array { flag: Given, rc: 1, ⚡ } Output: Trace: array_write [Int, mut [_1_inner]](_1_inner . mut , 0 , 77) ; Output: Trace: let _1_s = _1_inner . give . share ; Output: Trace: _1_s = shared Array { flag: Shared, rc: 1, 77 } - Output: Trace: let _1_outer = array_new [Array[Int]](1) ; - Output: Trace: _1_outer = Array { flag: Given, rc: 1, ⚡ } - Output: Trace: array_write [Array[Int], mut [_1_outer]](_1_outer . mut , 0 , _1_s . give) ; - Output: Trace: let _1_got = array_give [Array[Int], shared, ref [_1_outer]](_1_outer . ref , 0) ; + Output: Trace: let _1_outer = array_new [shared Array[Int]](1) ; + Output: Trace: _1_outer = Array { flag: Given, rc: 1, shared ⚡ } + Output: Trace: array_write [shared Array[Int], mut [_1_outer]](_1_outer . mut , 0 , _1_s . give) ; + Output: Trace: let _1_got = array_give [shared Array[Int], shared, ref [_1_outer]](_1_outer . ref , 0) ; Output: Trace: _1_got = shared Array { flag: Shared, rc: 3, 77 } Output: Trace: print(_1_got . give) ; Output: -----> shared Array { flag: Shared, rc: 4, 77 } Output: Trace: print(_1_outer . ref) ; - Output: -----> ref [_1_outer] Array { flag: Borrowed, rc: 1, Array { flag: Shared, rc: 3, 77 } } + Output: -----> ref [_1_outer] Array { flag: Borrowed, rc: 1, shared Array { flag: Shared, rc: 3, 77 } } Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 Alloc 0x03: [RefCount(1), Capacity(1), Int(77)] - Alloc 0x1c: [Int(0)]"#]] + Alloc 0x1c: [Int(0)]"#]]) ); } #[test] fn array_give_p_ref() { // array_give with P=ref returns a borrowed copy. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -2219,7 +2242,7 @@ fn array_give_p_ref() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_inner = array_new [Int](1) ; Output: Trace: _1_inner = Array { flag: Given, rc: 1, ⚡ } @@ -2237,14 +2260,14 @@ fn array_give_p_ref() { Output: Trace: exit Main.main => 0 Result: Ok: 0 Alloc 0x03: [RefCount(1), Capacity(1), Int(55)] - Alloc 0x1a: [Int(0)]"#]] + Alloc 0x1a: [Int(0)]"#]]) ); } #[test] fn array_drop_p_shared_is_noop() { // array_drop with P=shared should be a no-op — element still accessible. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -2257,7 +2280,7 @@ fn array_drop_p_shared_is_noop() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ } } @@ -2266,14 +2289,15 @@ fn array_drop_p_shared_is_noop() { Output: Trace: array_give [Data, given, given](_1_a . give , 0) ; Output: Trace: exit Main.main => Data { x: 42 } Result: Ok: Data { x: 42 } - Alloc 0x11: [Int(42)]"#]] + Alloc 0x11: [Int(42)]"#]]) ); } #[test] +// BUG: soundness gap — type checker accepts but interpreter faults (use after array_drop). fn array_drop_p_given_range() { // array_drop with P=given on a range of elements drops all of them. - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -2289,7 +2313,7 @@ fn array_drop_p_given_range() { } } }, - expect_test::expect![[r#" + type: ok, interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](3) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ }, Data { x: ⚡ }, Data { x: ⚡ } } @@ -2300,7 +2324,7 @@ fn array_drop_p_given_range() { Output: Trace: array_give [Data, given, given](_1_a . give , 1) ; Result: Fault: access of uninitialized value Alloc 0x03: [RefCount(1), Capacity(3), Uninitialized, Uninitialized, Uninitialized] - Alloc 0x19: [Flags(Given), Pointer(0x03)]"#]] + Alloc 0x19: [Flags(Given), Pointer(0x03)]"#]]) ); } @@ -2309,7 +2333,7 @@ fn array_give_p_given_int_is_copy() { // Giving an Int element with P=given copies without uninitializing. // Even though P = given, the combined type `given Int` is shared/copy // (Int is a shared class), so array_give copies rather than moving. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -2322,7 +2346,7 @@ fn array_give_p_given_int_is_copy() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡ } @@ -2334,14 +2358,14 @@ fn array_give_p_given_int_is_copy() { Output: Trace: _1_x . give + _1_y . give ; Output: Trace: exit Main.main => 84 Result: Ok: 84 - Alloc 0x14: [Int(84)]"#]] + Alloc 0x14: [Int(84)]"#]]) ); } #[test] fn array_drop_empty_range_is_noop() { // array_drop with from >= to is a no-op. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -2357,7 +2381,7 @@ fn array_drop_empty_range_is_noop() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ }, Data { x: ⚡ } } @@ -2368,7 +2392,7 @@ fn array_drop_empty_range_is_noop() { Output: Trace: array_give [Data, given, given](_1_a . give , 0) ; Output: Trace: exit Main.main => Data { x: 42 } Result: Ok: Data { x: 42 } - Alloc 0x1a: [Int(42)]"#]] + Alloc 0x1a: [Int(42)]"#]]) ); } @@ -2383,7 +2407,7 @@ fn array_leak_all_elements() { // Documents the unsafe contract: array does NOT drop its elements. // Uses Array[Array[Int]] so inner arrays are boxed and have separate // heap allocations that survive the outer array's scrub. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -2401,7 +2425,7 @@ fn array_leak_all_elements() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Array[Int]](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -2419,7 +2443,7 @@ fn array_leak_all_elements() { Result: Ok: 0 Alloc 0x07: [RefCount(1), Capacity(1), Int(10)] Alloc 0x0f: [RefCount(1), Capacity(1), Int(20)] - Alloc 0x1f: [Int(0)]"#]] + Alloc 0x1f: [Int(0)]"#]]) ); } @@ -2427,7 +2451,7 @@ fn array_leak_all_elements() { fn array_leak_some_elements() { // Drop element 0 of a 2-element array, skip element 1, drop array. // Element 1's backing allocation remains as an orphan in the heap. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -2446,7 +2470,7 @@ fn array_leak_some_elements() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Array[Int]](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -2464,6 +2488,6 @@ fn array_leak_some_elements() { Output: Trace: exit Main.main => 0 Result: Ok: 0 Alloc 0x0f: [RefCount(1), Capacity(1), Int(20)] - Alloc 0x23: [Int(0)]"#]] + Alloc 0x23: [Int(0)]"#]]) ); } diff --git a/src/interpreter/tests/basics.rs b/src/interpreter/tests/basics.rs index bc8a7ca..a0b79d9 100644 --- a/src/interpreter/tests/basics.rs +++ b/src/interpreter/tests/basics.rs @@ -8,12 +8,12 @@ fn return_int() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: 22 ; Output: Trace: exit Main.main => 22 Result: Ok: 22 - Alloc 0x02: [Int(22)]"#]] + Alloc 0x02: [Int(22)]"#]]) ); } @@ -31,12 +31,12 @@ fn return_object() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: new Point (22, 44) ; Output: Trace: exit Main.main => Point { x: 22, y: 44 } Result: Ok: Point { x: 22, y: 44 } - Alloc 0x04: [Int(22), Int(44)]"#]] + Alloc 0x04: [Int(22), Int(44)]"#]]) ); } @@ -55,14 +55,14 @@ fn give_and_return() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_p = new Point (22, 44) ; Output: Trace: _1_p = Point { x: 22, y: 44 } Output: Trace: _1_p . give ; Output: Trace: exit Main.main => Point { x: 22, y: 44 } Result: Ok: Point { x: 22, y: 44 } - Alloc 0x06: [Int(22), Int(44)]"#]] + Alloc 0x06: [Int(22), Int(44)]"#]]) ); } @@ -78,7 +78,7 @@ fn arithmetic() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_x = 10 ; Output: Trace: _1_x = 10 @@ -87,7 +87,7 @@ fn arithmetic() { Output: Trace: _1_x . give + _1_y . give ; Output: Trace: exit Main.main => 30 Result: Ok: 30 - Alloc 0x08: [Int(30)]"#]] + Alloc 0x08: [Int(30)]"#]]) ); } @@ -111,7 +111,7 @@ fn method_call() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_adder = new Adder (3, 4) ; Output: Trace: _1_adder = Adder { a: 3, b: 4 } @@ -121,7 +121,7 @@ fn method_call() { Output: Trace: exit Adder.sum => 7 Output: Trace: exit Main.main => 7 Result: Ok: 7 - Alloc 0x0a: [Int(7)]"#]] + Alloc 0x0a: [Int(7)]"#]]) ); } @@ -146,7 +146,7 @@ fn ref_creates_copy() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_p = new Pair (new Data (), new Data ()) ; Output: Trace: _1_p = Pair { a: Data { }, b: Data { } } @@ -154,7 +154,7 @@ fn ref_creates_copy() { Output: Trace: _1_r = ref [_1_p] Pair { a: Data { }, b: Data { } } Output: Trace: _1_p . a . give ; Output: Trace: exit Main.main => Data { } - Result: Ok: Data { }"#]] + Result: Ok: Data { }"#]]) ); } @@ -170,7 +170,7 @@ fn if_then_else() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_result = 0 ; Output: Trace: _1_result = 0 @@ -180,7 +180,7 @@ fn if_then_else() { Output: Trace: _1_result . give ; Output: Trace: exit Main.main => 42 Result: Ok: 42 - Alloc 0x08: [Int(42)]"#]] + Alloc 0x08: [Int(42)]"#]]) ); } @@ -196,7 +196,7 @@ fn if_false_branch() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_result = 0 ; Output: Trace: _1_result = 0 @@ -206,7 +206,7 @@ fn if_false_branch() { Output: Trace: _1_result . give ; Output: Trace: exit Main.main => 99 Result: Ok: 99 - Alloc 0x08: [Int(99)]"#]] + Alloc 0x08: [Int(99)]"#]]) ); } @@ -222,7 +222,7 @@ fn print_int() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: print(42) ; Output: -----> 42 @@ -231,7 +231,7 @@ fn print_int() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x08: [Int(0)]"#]] + Alloc 0x08: [Int(0)]"#]]) ); } @@ -251,7 +251,7 @@ fn print_object() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_p = new Point (10, 20) ; Output: Trace: _1_p = Point { x: 10, y: 20 } @@ -260,7 +260,7 @@ fn print_object() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x08: [Int(0)]"#]] + Alloc 0x08: [Int(0)]"#]]) ); } @@ -275,8 +275,7 @@ fn loop_body_value_is_freed() { // - Iter 2: if-branch breaks; loop exits. // // With the fix applied, the heap contains only the final return value. - // Uses assert_interpret_only! because the type checker lacks Loop/Break rules. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Point { x: Int; y: Int; } @@ -291,7 +290,7 @@ fn loop_body_value_is_freed() { } } }, - expect_test::expect![[r#" + type: error(expect_test::expect![[r#"src/type_system/statements.rs:57:1: no applicable rules for type_statement { statement: loop { if stop . give >= 1 { break ; } else { stop = 1 ; } ; new Point (1, 2) ; }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, stop: Int}, assumptions: {}, fresh: 0 }, live_after: LivePlaces { accessed: {}, traversed: {} } }"#]]), interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_stop = 0 ; Output: Trace: _1_stop = 0 @@ -305,6 +304,6 @@ fn loop_body_value_is_freed() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x14: [Int(0)]"#]] + Alloc 0x14: [Int(0)]"#]]) ); } diff --git a/src/interpreter/tests/block_scoped_drops.rs b/src/interpreter/tests/block_scoped_drops.rs index 8eb2b5e..723d45d 100644 --- a/src/interpreter/tests/block_scoped_drops.rs +++ b/src/interpreter/tests/block_scoped_drops.rs @@ -7,7 +7,7 @@ fn block_scoped_drop() { // Variable declared in inner block is dropped when block exits, // before the outer block continues. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; @@ -26,7 +26,7 @@ fn block_scoped_drop() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: { let _1_d : given Data = new Data (42) ; () ; } ; Output: Trace: let _1_d : given Data = new Data (42) ; @@ -38,14 +38,14 @@ fn block_scoped_drop() { Output: Trace: 99 ; Output: Trace: exit Main.main => 99 Result: Ok: 99 - Alloc 0x0a: [Int(99)]"#]] + Alloc 0x0a: [Int(99)]"#]]) ); } #[test] fn block_scoped_drop_order() { // Multiple variables in a block are dropped in reverse declaration order. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; @@ -66,7 +66,7 @@ fn block_scoped_drop_order() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: { let _1_a : given Data = new Data (1) ; let _1_b : given Data = new Data (2) ; let _1_c : given Data = new Data (3) ; () ; } ; Output: Trace: let _1_a : given Data = new Data (1) ; @@ -88,14 +88,14 @@ fn block_scoped_drop_order() { Output: Trace: 99 ; Output: Trace: exit Main.main => 99 Result: Ok: 99 - Alloc 0x16: [Int(99)]"#]] + Alloc 0x16: [Int(99)]"#]]) ); } #[test] fn nested_blocks_drop_innermost_first() { // Inner block vars drop before outer block continues. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; @@ -115,7 +115,7 @@ fn nested_blocks_drop_innermost_first() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_outer : given Data = new Data (1) ; Output: Trace: _1_outer = Data { x: 1 } @@ -132,14 +132,14 @@ fn nested_blocks_drop_innermost_first() { Output: -----> 1 Output: Trace: exit Main.main => 99 Result: Ok: 99 - Alloc 0x0d: [Int(99)]"#]] + Alloc 0x0d: [Int(99)]"#]]) ); } #[test] fn block_early_break_drops_locals() { // `break` inside a loop drops block-local vars declared before the break. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; @@ -158,7 +158,7 @@ fn block_early_break_drops_locals() { } } }, - expect_test::expect![[r#" + type: error(expect_test::expect![[r#"src/type_system/statements.rs:57:1: no applicable rules for type_statement { statement: loop { let d : given Data = new Data (42) ; break ; }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main}, assumptions: {}, fresh: 0 }, live_after: LivePlaces { accessed: {}, traversed: {} } }"#]]), interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: loop { let _1_d : given Data = new Data (42) ; break ; } Output: Trace: let _1_d : given Data = new Data (42) ; @@ -170,7 +170,7 @@ fn block_early_break_drops_locals() { Output: Trace: 99 ; Output: Trace: exit Main.main => 99 Result: Ok: 99 - Alloc 0x0a: [Int(99)]"#]] + Alloc 0x0a: [Int(99)]"#]]) ); } @@ -179,7 +179,7 @@ fn partial_move_in_block_skips_drop_body() { // A partially-moved variable at block exit is NOT whole, so its // drop body must NOT run. Uses Array (a move type) so the give // actually consumes the source field, making the Pair non-whole. - crate::assert_interpret_only!( + crate::assert_interpret!( { given class Pair { a: Array[Int]; @@ -201,7 +201,7 @@ fn partial_move_in_block_skips_drop_body() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: { let _1_p : given Pair = new Pair (array_new [Int](1), array_new [Int](1)) ; let _1_moved_a = _1_p . a . give ; () ; } ; Output: Trace: let _1_p : given Pair = new Pair (array_new [Int](1), array_new [Int](1)) ; @@ -211,7 +211,7 @@ fn partial_move_in_block_skips_drop_body() { Output: Trace: () ; Output: Trace: () ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -219,7 +219,7 @@ fn partial_move_in_block_skips_drop_body() { fn loop_break_drops_locals() { // Variables declared in a loop body are dropped on each iteration // and on break. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; @@ -239,7 +239,7 @@ fn loop_break_drops_locals() { } } }, - expect_test::expect![[r#" + type: error(expect_test::expect![[r#"src/type_system/statements.rs:57:1: no applicable rules for type_statement { statement: loop { let d : given Data = new Data (stop . give) ; if stop . give >= 1 { break ; } else { stop = 1 ; } ; }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, stop: Int}, assumptions: {}, fresh: 0 }, live_after: LivePlaces { accessed: {}, traversed: {} } }"#]]), interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_stop = 0 ; Output: Trace: _1_stop = 0 @@ -262,6 +262,6 @@ fn loop_break_drops_locals() { Output: Trace: 99 ; Output: Trace: exit Main.main => 99 Result: Ok: 99 - Alloc 0x1d: [Int(99)]"#]] + Alloc 0x1d: [Int(99)]"#]]) ); } diff --git a/src/interpreter/tests/copy_move.rs b/src/interpreter/tests/copy_move.rs index 6e6c352..cde5b01 100644 --- a/src/interpreter/tests/copy_move.rs +++ b/src/interpreter/tests/copy_move.rs @@ -16,7 +16,7 @@ fn struct_is_copy() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_p = new Pair (1, 2) ; Output: Trace: _1_p = Pair { x: 1, y: 2 } @@ -25,7 +25,7 @@ fn struct_is_copy() { Output: Trace: _1_p . give ; Output: Trace: exit Main.main => Pair { x: 1, y: 2 } Result: Ok: Pair { x: 1, y: 2 } - Alloc 0x08: [Int(1), Int(2)]"#]] + Alloc 0x08: [Int(1), Int(2)]"#]]) ); } @@ -45,14 +45,14 @@ fn class_give_moves() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } Output: Trace: _1_d . give ; Output: Trace: exit Main.main => Data { x: 42 } Result: Ok: Data { x: 42 } - Alloc 0x05: [Int(42)]"#]] + Alloc 0x05: [Int(42)]"#]]) ); } @@ -85,7 +85,7 @@ fn ref_method_field_is_ref() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (99)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 99 } } @@ -100,6 +100,6 @@ fn ref_method_field_is_ref() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x0d: [Int(0)]"#]] + Alloc 0x0d: [Int(0)]"#]]) ); } diff --git a/src/interpreter/tests/drop_body.rs b/src/interpreter/tests/drop_body.rs index d7604b2..9f0e0ba 100644 --- a/src/interpreter/tests/drop_body.rs +++ b/src/interpreter/tests/drop_body.rs @@ -32,7 +32,7 @@ fn class_with_drop_body() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d : given Data = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -41,7 +41,7 @@ fn class_with_drop_body() { Output: Trace: print(self . x . give) ; Output: -----> 42 Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -66,7 +66,7 @@ fn drop_body_runs_on_give() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d : given Data = new Data (99) ; Output: Trace: _1_d = Data { x: 99 } @@ -76,7 +76,7 @@ fn drop_body_runs_on_give() { Output: -----> 99 Output: Trace: () ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -84,9 +84,7 @@ fn drop_body_runs_on_give() { fn drop_body_runs_on_every_shared_handle() { // Drop body runs once per owned handle drop. // Data is a share class (default), so two shared copies = two drop body executions. - // Use assert_interpret_only! because the type checker doesn't know - // `shared Data` is copy (the type is not `shared class Data`). - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; @@ -105,7 +103,7 @@ fn drop_body_runs_on_every_shared_handle() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d : given Data = new Data (77) ; Output: Trace: _1_d = Data { x: 77 } @@ -121,7 +119,7 @@ fn drop_body_runs_on_every_shared_handle() { Output: Trace: print(self . x . give) ; Output: -----> 77 Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -138,7 +136,7 @@ fn is_last_ref_true_when_sole_owner() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a : given Array[Int] = array_new [Int](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡ } @@ -146,7 +144,7 @@ fn is_last_ref_true_when_sole_owner() { Output: -----> true Output: Trace: () ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -154,7 +152,7 @@ fn is_last_ref_true_when_sole_owner() { fn is_last_ref_false_when_shared() { // Boxed object with two handles — is_last_ref returns false on first drop. // Share the array, creating two shared handles (rc = 2). - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> () { @@ -166,7 +164,7 @@ fn is_last_ref_false_when_shared() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a : given Array[Int] = array_new [Int](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡ } @@ -178,7 +176,7 @@ fn is_last_ref_false_when_shared() { Output: -----> false Output: Trace: () ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -208,7 +206,7 @@ fn drop_body_with_is_last_ref() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_c : given Container = new Container (array_new [Int](2), 0) ; Output: Trace: _1_c = Container { data: Array { flag: Given, rc: 1, ⚡, ⚡ }, len: 0 } @@ -219,7 +217,7 @@ fn drop_body_with_is_last_ref() { Output: -----> 99 Output: Trace: array_drop [Int, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -235,7 +233,7 @@ fn bool_true_false_literals() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: print(true) ; Output: -----> true @@ -243,7 +241,7 @@ fn bool_true_false_literals() { Output: -----> false Output: Trace: () ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -265,7 +263,7 @@ fn comparison_operators() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: print(3 >= 2) ; Output: -----> true @@ -285,7 +283,7 @@ fn comparison_operators() { Output: -----> false Output: Trace: () ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -299,12 +297,12 @@ fn subtraction() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: 5 - 3 ; Output: Trace: exit Main.main => 2 Result: Ok: 2 - Alloc 0x04: [Int(2)]"#]] + Alloc 0x04: [Int(2)]"#]]) ); } @@ -313,7 +311,7 @@ fn partially_moved_class_drops_remaining_fields() { // Move one field out of a class. The class is no longer "whole", so // its drop body should NOT run. But remaining fields should be dropped. // Data has an array field (boxed) so we can see it in the heap. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Pair { a: Array[Int]; @@ -332,7 +330,7 @@ fn partially_moved_class_drops_remaining_fields() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_p : given Pair = new Pair (array_new [Int](1), array_new [Int](1)) ; Output: Trace: _1_p = Pair { a: Array { flag: Given, rc: 1, ⚡ }, b: Array { flag: Given, rc: 1, ⚡ } } @@ -340,7 +338,7 @@ fn partially_moved_class_drops_remaining_fields() { Output: Trace: _1_moved_a = Array { flag: Given, rc: 1, ⚡ } Output: Trace: () ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -348,7 +346,7 @@ fn partially_moved_class_drops_remaining_fields() { fn partial_move_then_read_other_field() { // Move one field, then read a sibling field. This is the pattern // Iterator.drop relies on. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Pair { x: Int; @@ -363,7 +361,7 @@ fn partial_move_then_read_other_field() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_p : given Pair = new Pair (10, 20) ; Output: Trace: _1_p = Pair { x: 10, y: 20 } @@ -372,7 +370,7 @@ fn partial_move_then_read_other_field() { Output: Trace: _1_p . y . give ; Output: Trace: exit Main.main => 20 Result: Ok: 20 - Alloc 0x08: [Int(20)]"#]] + Alloc 0x08: [Int(20)]"#]]) ); } @@ -407,7 +405,7 @@ fn drop_body_accesses_class_generics() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_w : given Wrapper[Item] = new Wrapper [Item] (array_new [Item](2), 0) ; Output: Trace: _1_w = Wrapper { data: Array { flag: Given, rc: 1, Item { val: ⚡ }, Item { val: ⚡ } }, len: 0 } @@ -421,7 +419,7 @@ fn drop_body_accesses_class_generics() { Output: Trace: print(self . val . give) ; Output: -----> 111 Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -435,7 +433,7 @@ fn ref_handle_does_not_run_drop_body() { // execute the drop body. Only given/shared handles run the drop body. // Here we create d (owned) and r (ref to d). At end of scope, both are // dropped. The drop body should run exactly once — for d, not for r. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; @@ -453,7 +451,7 @@ fn ref_handle_does_not_run_drop_body() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d : given Data = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -464,7 +462,7 @@ fn ref_handle_does_not_run_drop_body() { Output: Trace: print(self . x . give) ; Output: -----> 42 Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -473,7 +471,7 @@ fn is_last_ref_sequential_drops_only_last_cleans() { // Create a Container with is_last_ref guarded cleanup. // Share it into 3 handles. Drop them one by one. // Only the last drop should print 99 (cleanup), earlier drops print 0. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Container { data: Array[Int]; @@ -501,7 +499,7 @@ fn is_last_ref_sequential_drops_only_last_cleans() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_c : given Container = new Container (array_new [Int](2), 0) ; Output: Trace: _1_c = Container { data: Array { flag: Given, rc: 1, ⚡, ⚡ }, len: 0 } @@ -528,7 +526,7 @@ fn is_last_ref_sequential_drops_only_last_cleans() { Output: -----> 99 Output: Trace: () ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -550,7 +548,7 @@ fn is_last_ref_non_boxed_always_false() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d : given Data = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -558,7 +556,7 @@ fn is_last_ref_non_boxed_always_false() { Output: -----> false Output: Trace: () ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -567,11 +565,11 @@ fn is_last_ref_per_allocation() { // is_last_ref is per-allocation, not per-object. // A class holding two arrays: one shared (rc=2), one sole (rc=1). // is_last_ref returns different answers for each. - crate::assert_interpret_only!( + crate::assert_interpret!( { class TwoArrays { - a: Array[Int]; - b: Array[Int]; + a: shared Array[Int]; + b: shared Array[Int]; } class Main { @@ -579,14 +577,14 @@ fn is_last_ref_per_allocation() { let arr_a: given Array[Int] = array_new[Int](1); let shared_a: shared Array[Int] = arr_a.give.share; let extra_handle: shared Array[Int] = shared_a.give; - let obj: given TwoArrays = new TwoArrays(shared_a.give, array_new[Int](1)); + let obj: given TwoArrays = new TwoArrays(shared_a.give, array_new[Int](1).share); print(is_last_ref[ref[obj.a]](obj.a.ref)); print(is_last_ref[ref[obj.b]](obj.b.ref)); (); } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_arr_a : given Array[Int] = array_new [Int](1) ; Output: Trace: _1_arr_a = Array { flag: Given, rc: 1, ⚡ } @@ -594,15 +592,15 @@ fn is_last_ref_per_allocation() { Output: Trace: _1_shared_a = shared Array { flag: Shared, rc: 1, ⚡ } Output: Trace: let _1_extra_handle : shared Array[Int] = _1_shared_a . give ; Output: Trace: _1_extra_handle = shared Array { flag: Shared, rc: 2, ⚡ } - Output: Trace: let _1_obj : given TwoArrays = new TwoArrays (_1_shared_a . give, array_new [Int](1)) ; - Output: Trace: _1_obj = TwoArrays { a: Array { flag: Shared, rc: 3, ⚡ }, b: Array { flag: Given, rc: 1, ⚡ } } + Output: Trace: let _1_obj : given TwoArrays = new TwoArrays (_1_shared_a . give, array_new [Int](1) . share) ; + Output: Trace: _1_obj = TwoArrays { a: shared Array { flag: Shared, rc: 3, ⚡ }, b: shared Array { flag: Shared, rc: 1, ⚡ } } Output: Trace: print(is_last_ref [ref [_1_obj . a]](_1_obj . a . ref)) ; Output: -----> false Output: Trace: print(is_last_ref [ref [_1_obj . b]](_1_obj . b . ref)) ; Output: -----> true Output: Trace: () ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -611,7 +609,7 @@ fn is_last_ref_after_dropping_other_handles() { // Start with rc=3 (share into 3 handles). Drop two handles (no drop body // on the array itself). Then check is_last_ref on the remaining handle. // Should return true since rc is back to 1. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> () { @@ -626,7 +624,7 @@ fn is_last_ref_after_dropping_other_handles() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a : given Array[Int] = array_new [Int](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡ } @@ -642,6 +640,6 @@ fn is_last_ref_after_dropping_other_handles() { Output: -----> true Output: Trace: () ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } diff --git a/src/interpreter/tests/generics.rs b/src/interpreter/tests/generics.rs index 2cc9ac4..3f66537 100644 --- a/src/interpreter/tests/generics.rs +++ b/src/interpreter/tests/generics.rs @@ -15,7 +15,7 @@ fn generic_struct_copy_param() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_b : Box[Int] = new Box [Int] (42) ; Output: Trace: _1_b = Box { value: 42 } @@ -24,7 +24,7 @@ fn generic_struct_copy_param() { Output: Trace: _1_b . give ; Output: Trace: exit Main.main => Box { value: 42 } Result: Ok: Box { value: 42 } - Alloc 0x07: [Int(42)]"#]] + Alloc 0x07: [Int(42)]"#]]) ); } @@ -47,14 +47,14 @@ fn generic_struct_move_param() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_b : Box[Data] = new Box [Data] (new Data (1)) ; Output: Trace: _1_b = Box { value: Data { x: 1 } } Output: Trace: _1_b . give ; Output: Trace: exit Main.main => Box { value: Data { x: 1 } } Result: Ok: Box { value: Data { x: 1 } } - Alloc 0x06: [Int(1)]"#]] + Alloc 0x06: [Int(1)]"#]]) ); } @@ -78,7 +78,7 @@ fn generic_method_dispatch() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_b : Box[Int] = new Box [Int] (42) ; Output: Trace: _1_b = Box { value: 42 } @@ -88,7 +88,7 @@ fn generic_method_dispatch() { Output: Trace: exit Box.get => 42 Output: Trace: exit Main.main => 42 Result: Ok: 42 - Alloc 0x07: [Int(42)]"#]] + Alloc 0x07: [Int(42)]"#]]) ); } @@ -110,7 +110,7 @@ fn struct_pair_of_ints_is_copy() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_p : Pair[Int] = new Pair [Int] (1, 2) ; Output: Trace: _1_p = Pair { a: 1, b: 2 } @@ -119,7 +119,7 @@ fn struct_pair_of_ints_is_copy() { Output: Trace: _1_p . give ; Output: Trace: exit Main.main => Pair { a: 1, b: 2 } Result: Ok: Pair { a: 1, b: 2 } - Alloc 0x08: [Int(1), Int(2)]"#]] + Alloc 0x08: [Int(1), Int(2)]"#]]) ); } @@ -143,14 +143,14 @@ fn nested_struct_move_poisons() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_p : Pair[Data] = new Pair [Data] (new Data (1), new Data (2)) ; Output: Trace: _1_p = Pair { a: Data { x: 1 }, b: Data { x: 2 } } Output: Trace: _1_p . give ; Output: Trace: exit Main.main => Pair { a: Data { x: 1 }, b: Data { x: 2 } } Result: Ok: Pair { a: Data { x: 1 }, b: Data { x: 2 } } - Alloc 0x08: [Int(1), Int(2)]"#]] + Alloc 0x08: [Int(1), Int(2)]"#]]) ); } @@ -173,21 +173,21 @@ fn struct_move_param_give_consumes() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_b : Box[Data] = new Box [Data] (new Data (99)) ; Output: Trace: _1_b = Box { value: Data { x: 99 } } Output: Trace: _1_b . give ; Output: Trace: exit Main.main => Box { value: Data { x: 99 } } Result: Ok: Box { value: Data { x: 99 } } - Alloc 0x06: [Int(99)]"#]] + Alloc 0x06: [Int(99)]"#]]) ); } #[test] fn struct_move_param_give_twice_faults() { // Box[Data] is move — giving it twice faults at runtime. - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Data { x: Int; @@ -203,7 +203,13 @@ fn struct_move_param_give_twice_faults() { } } }, - expect_test::expect![[r#" + type: error(expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, b: Box[Data]}, assumptions: {}, fresh: 0 } } + + the rule "give" at (expressions.rs) failed because + condition evaluated to false: `!live_after.is_live(place)` + live_after = LivePlaces { accessed: {b}, traversed: {} } + place = b"#]]), interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_b : Box[Data] = new Box [Data] (new Data (99)) ; Output: Trace: _1_b = Box { value: Data { x: 99 } } @@ -211,7 +217,7 @@ fn struct_move_param_give_twice_faults() { Output: Trace: _1_c = Box { value: Data { x: 99 } } Output: Trace: _1_b . give ; Result: Fault: access of uninitialized value - Alloc 0x06: [Int(99)]"#]] + Alloc 0x06: [Int(99)]"#]]) ); } @@ -235,7 +241,7 @@ fn struct_move_param_ref_borrows() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_b : Box[Data] = new Box [Data] (new Data (42)) ; Output: Trace: _1_b = Box { value: Data { x: 42 } } @@ -244,6 +250,6 @@ fn struct_move_param_ref_borrows() { Output: Trace: _1_b . give ; Output: Trace: exit Main.main => Box { value: Data { x: 42 } } Result: Ok: Box { value: Data { x: 42 } } - Alloc 0x08: [Int(42)]"#]] + Alloc 0x08: [Int(42)]"#]]) ); } diff --git a/src/interpreter/tests/mdbook.rs b/src/interpreter/tests/mdbook.rs index 11835f6..905f698 100644 --- a/src/interpreter/tests/mdbook.rs +++ b/src/interpreter/tests/mdbook.rs @@ -19,14 +19,14 @@ fn interp_point_example() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_p = new Point (22, 44) ; Output: Trace: _1_p = Point { x: 22, y: 44 } Output: Trace: _1_p . give ; Output: Trace: exit Main.main => Point { x: 22, y: 44 } Result: Ok: Point { x: 22, y: 44 } - Alloc 0x06: [Int(22), Int(44)]"#]] + Alloc 0x06: [Int(22), Int(44)]"#]]) ); // ANCHOR_END: interp_point_example } @@ -44,7 +44,7 @@ fn interp_arithmetic() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_x = 10 ; Output: Trace: _1_x = 10 @@ -53,7 +53,7 @@ fn interp_arithmetic() { Output: Trace: _1_x . give + _1_y . give ; Output: Trace: exit Main.main => 30 Result: Ok: 30 - Alloc 0x08: [Int(30)]"#]] + Alloc 0x08: [Int(30)]"#]]) ); // ANCHOR_END: interp_arithmetic } @@ -79,7 +79,7 @@ fn interp_method_calls() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_adder = new Adder (3, 4) ; Output: Trace: _1_adder = Adder { a: 3, b: 4 } @@ -89,7 +89,7 @@ fn interp_method_calls() { Output: Trace: exit Adder.sum => 7 Output: Trace: exit Main.main => 7 Result: Ok: 7 - Alloc 0x0a: [Int(7)]"#]] + Alloc 0x0a: [Int(7)]"#]]) ); // ANCHOR_END: interp_method_calls } @@ -107,14 +107,14 @@ fn interp_give_given() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } Output: Trace: _1_d . give ; Output: Trace: exit Main.main => Data { x: 42 } Result: Ok: Data { x: 42 } - Alloc 0x05: [Int(42)]"#]] + Alloc 0x05: [Int(42)]"#]]) ); // ANCHOR_END: interp_give_given } @@ -122,11 +122,11 @@ fn interp_give_given() { #[test] fn interp_give_shared() { // ANCHOR: interp_give_shared - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { - fn main(given self) -> Data { + fn main(given self) -> shared Data { let d = new Data(42); let s = d.give.share; let x1 = s.give; @@ -136,7 +136,7 @@ fn interp_give_shared() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -151,7 +151,7 @@ fn interp_give_shared() { Output: Trace: _1_x2 . give ; Output: Trace: exit Main.main => shared Data { x: 42 } Result: Ok: shared Data { x: 42 } - Alloc 0x0d: [Int(42)]"#]] + Alloc 0x0d: [Int(42)]"#]]) ); // ANCHOR_END: interp_give_shared } @@ -170,7 +170,7 @@ fn interp_ref_given() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -179,7 +179,7 @@ fn interp_ref_given() { Output: Trace: _1_d . give ; Output: Trace: exit Main.main => Data { x: 42 } Result: Ok: Data { x: 42 } - Alloc 0x07: [Int(42)]"#]] + Alloc 0x07: [Int(42)]"#]]) ); // ANCHOR_END: interp_ref_given } @@ -187,18 +187,18 @@ fn interp_ref_given() { #[test] fn interp_ref_shared() { // ANCHOR: interp_ref_shared - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { - fn main(given self) -> Data { + fn main(given self) -> shared Data { let d = new Data(42); let s = d.give.share; s.ref; } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -207,7 +207,7 @@ fn interp_ref_shared() { Output: Trace: _1_s . ref ; Output: Trace: exit Main.main => shared Data { x: 42 } Result: Ok: shared Data { x: 42 } - Alloc 0x07: [Int(42)]"#]] + Alloc 0x07: [Int(42)]"#]]) ); // ANCHOR_END: interp_ref_shared } @@ -215,25 +215,25 @@ fn interp_ref_shared() { #[test] fn interp_share_recursive() { // ANCHOR: interp_share_recursive - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } class Main { - fn main(given self) -> Outer { + fn main(given self) -> shared Outer { let o = new Outer(new Inner(1)); o.give.share; } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (1)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 1 } } Output: Trace: _1_o . give . share ; Output: Trace: exit Main.main => shared Outer { inner: Inner { x: 1 } } Result: Ok: shared Outer { inner: Inner { x: 1 } } - Alloc 0x06: [Int(1)]"#]] + Alloc 0x06: [Int(1)]"#]]) ); // ANCHOR_END: interp_share_recursive } @@ -241,29 +241,31 @@ fn interp_share_recursive() { #[test] fn interp_drop_borrowed_noop() { // ANCHOR: interp_drop_borrowed_noop - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { - fn main(given self) -> Data { + fn main(given self) -> () { let d = new Data(42); let r = d.ref; r.drop; - r.give; + print(r.give); + (); } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } Output: Trace: let _1_r = _1_d . ref ; Output: Trace: _1_r = ref [_1_d] Data { x: 42 } Output: Trace: _1_r . drop ; - Output: Trace: _1_r . give ; - Output: Trace: exit Main.main => ref [_1_d] Data { x: 42 } - Result: Ok: ref [_1_d] Data { x: 42 } - Alloc 0x08: [Int(42)]"#]] + Output: Trace: print(_1_r . give) ; + Output: -----> ref [_1_d] Data { x: 42 } + Output: Trace: () ; + Output: Trace: exit Main.main => () + Result: Ok: ()"#]]) ); // ANCHOR_END: interp_drop_borrowed_noop } @@ -281,7 +283,7 @@ fn interp_conditional_true() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_result = 0 ; Output: Trace: _1_result = 0 @@ -291,7 +293,7 @@ fn interp_conditional_true() { Output: Trace: _1_result . give ; Output: Trace: exit Main.main => 42 Result: Ok: 42 - Alloc 0x08: [Int(42)]"#]] + Alloc 0x08: [Int(42)]"#]]) ); // ANCHOR_END: interp_conditional_true } @@ -309,7 +311,7 @@ fn interp_conditional_false() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_result = 0 ; Output: Trace: _1_result = 0 @@ -319,7 +321,7 @@ fn interp_conditional_false() { Output: Trace: _1_result . give ; Output: Trace: exit Main.main => 99 Result: Ok: 99 - Alloc 0x08: [Int(99)]"#]] + Alloc 0x08: [Int(99)]"#]]) ); // ANCHOR_END: interp_conditional_false } @@ -331,7 +333,7 @@ fn interp_conditional_false() { #[test] fn interp_array_new_and_get() { // ANCHOR: interp_array_new_and_get - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -345,7 +347,7 @@ fn interp_array_new_and_get() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](3) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡, ⚡ } @@ -359,7 +361,7 @@ fn interp_array_new_and_get() { Output: Trace: array_give [Int, given, given](_1_a . give , 2) ; Output: Trace: exit Main.main => 30 Result: Ok: 30 - Alloc 0x1c: [Int(30)]"#]] + Alloc 0x1c: [Int(30)]"#]]) ); // ANCHOR_END: interp_array_new_and_get } @@ -367,7 +369,7 @@ fn interp_array_new_and_get() { #[test] fn interp_array_class_elements() { // ANCHOR: interp_array_class_elements - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -380,7 +382,7 @@ fn interp_array_class_elements() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ }, Data { x: ⚡ } } @@ -391,7 +393,7 @@ fn interp_array_class_elements() { Output: Trace: array_give [Data, given, given](_1_a . give , 1) ; Output: Trace: exit Main.main => Data { x: 99 } Result: Ok: Data { x: 99 } - Alloc 0x16: [Int(99)]"#]] + Alloc 0x16: [Int(99)]"#]]) ); // ANCHOR_END: interp_array_class_elements } @@ -399,7 +401,7 @@ fn interp_array_class_elements() { #[test] fn interp_array_int_is_copy() { // ANCHOR: interp_array_int_is_copy - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -412,7 +414,7 @@ fn interp_array_int_is_copy() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡ } @@ -426,7 +428,7 @@ fn interp_array_int_is_copy() { Output: Trace: _1_y . give ; Output: Trace: exit Main.main => 42 Result: Ok: 42 - Alloc 0x14: [Int(42)]"#]] + Alloc 0x14: [Int(42)]"#]]) ); // ANCHOR_END: interp_array_int_is_copy } @@ -436,11 +438,11 @@ fn interp_array_class_shared_no_move() { // ANCHOR: interp_array_class_shared_no_move // Shared array: class elements are accessed with shared semantics — // giving an element produces a shared copy, element remains available. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { - fn main(given self) -> Data { + fn main(given self) -> shared Data { let a = array_new[Data](1); array_write[Data, mut[a]](a.mut, 0, new Data(42)); let s = a.give.share; @@ -451,7 +453,7 @@ fn interp_array_class_shared_no_move() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](1) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ } } @@ -465,7 +467,7 @@ fn interp_array_class_shared_no_move() { Output: Trace: array_give [Data, shared, shared](_1_s . give , 0) ; Output: Trace: exit Main.main => shared Data { x: 42 } Result: Ok: shared Data { x: 42 } - Alloc 0x15: [Int(42)]"#]] + Alloc 0x15: [Int(42)]"#]]) ); // ANCHOR_END: interp_array_class_shared_no_move } @@ -473,7 +475,7 @@ fn interp_array_class_shared_no_move() { #[test] fn interp_array_shared_refcount() { // ANCHOR: interp_array_shared_refcount - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -488,7 +490,7 @@ fn interp_array_shared_refcount() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -504,7 +506,7 @@ fn interp_array_shared_refcount() { Output: Trace: array_give [Int, given, shared](_1_b . give , 1) ; Output: Trace: exit Main.main => 20 Result: Ok: 20 - Alloc 0x19: [Int(20)]"#]] + Alloc 0x19: [Int(20)]"#]]) ); // ANCHOR_END: interp_array_shared_refcount } @@ -512,7 +514,7 @@ fn interp_array_shared_refcount() { #[test] fn interp_array_given_move() { // ANCHOR: interp_array_given_move - crate::assert_interpret_only!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -524,7 +526,7 @@ fn interp_array_given_move() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Int](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, ⚡, ⚡ } @@ -535,7 +537,7 @@ fn interp_array_given_move() { Output: Trace: array_give [Int, given, given](_1_b . give , 0) ; Output: Trace: exit Main.main => 10 Result: Ok: 10 - Alloc 0x12: [Int(10)]"#]] + Alloc 0x12: [Int(10)]"#]]) ); // ANCHOR_END: interp_array_given_move } @@ -543,7 +545,7 @@ fn interp_array_given_move() { #[test] fn interp_array_drop_frees() { // ANCHOR: interp_array_drop_frees - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -556,7 +558,7 @@ fn interp_array_drop_frees() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ }, Data { x: ⚡ } } @@ -566,7 +568,7 @@ fn interp_array_drop_frees() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x11: [Int(0)]"#]] + Alloc 0x11: [Int(0)]"#]]) ); // ANCHOR_END: interp_array_drop_frees } diff --git a/src/interpreter/tests/method_calls.rs b/src/interpreter/tests/method_calls.rs index eca4cdd..d3a0115 100644 --- a/src/interpreter/tests/method_calls.rs +++ b/src/interpreter/tests/method_calls.rs @@ -18,7 +18,7 @@ fn method_returns_int() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_f = new Foo () ; Output: Trace: _1_f = Foo { } @@ -28,7 +28,7 @@ fn method_returns_int() { Output: Trace: exit Foo.get => 42 Output: Trace: exit Main.main => 42 Result: Ok: 42 - Alloc 0x06: [Int(42)]"#]] + Alloc 0x06: [Int(42)]"#]]) ); } @@ -49,7 +49,7 @@ fn method_with_arg() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_f = new Foo () ; Output: Trace: _1_f = Foo { } @@ -59,7 +59,7 @@ fn method_with_arg() { Output: Trace: exit Foo.identity => 99 Output: Trace: exit Main.main => 99 Result: Ok: 99 - Alloc 0x07: [Int(99)]"#]] + Alloc 0x07: [Int(99)]"#]]) ); } @@ -81,7 +81,7 @@ fn method_reads_field() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_f = new Foo (42) ; Output: Trace: _1_f = Foo { x: 42 } @@ -91,7 +91,7 @@ fn method_reads_field() { Output: Trace: exit Foo.get_x => 42 Output: Trace: exit Main.main => 42 Result: Ok: 42 - Alloc 0x07: [Int(42)]"#]] + Alloc 0x07: [Int(42)]"#]]) ); } @@ -114,7 +114,7 @@ fn method_gives_field() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_w = new Wrapper (new Data (42)) ; Output: Trace: _1_w = Wrapper { inner: Data { x: 42 } } @@ -124,7 +124,7 @@ fn method_gives_field() { Output: Trace: exit Wrapper.take_inner => Data { x: 42 } Output: Trace: exit Main.main => Data { x: 42 } Result: Ok: Data { x: 42 } - Alloc 0x08: [Int(42)]"#]] + Alloc 0x08: [Int(42)]"#]]) ); } @@ -146,7 +146,7 @@ fn method_ref_self() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_f = new Foo (10) ; Output: Trace: _1_f = Foo { x: 10 } @@ -156,7 +156,7 @@ fn method_ref_self() { Output: Trace: exit Foo.peek => 10 Output: Trace: exit Main.main => 10 Result: Ok: 10 - Alloc 0x07: [Int(10)]"#]] + Alloc 0x07: [Int(10)]"#]]) ); } @@ -181,7 +181,7 @@ fn chained_method_calls() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = new Adder (0) ; Output: Trace: _1_a = Adder { val: 0 } @@ -197,7 +197,7 @@ fn chained_method_calls() { Output: Trace: exit Adder.result => 30 Output: Trace: exit Main.main => 30 Result: Ok: 30 - Alloc 0x13: [Int(30)]"#]] + Alloc 0x13: [Int(30)]"#]]) ); } @@ -222,7 +222,7 @@ fn method_shared_self() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_h = new Holder (77) ; Output: Trace: _1_h = Holder { x: 77 } @@ -241,6 +241,6 @@ fn method_shared_self() { Output: Trace: _1_a . give + _1_b . give ; Output: Trace: exit Main.main => 154 Result: Ok: 154 - Alloc 0x11: [Int(154)]"#]] + Alloc 0x11: [Int(154)]"#]]) ); } diff --git a/src/interpreter/tests/normalization.rs b/src/interpreter/tests/normalization.rs new file mode 100644 index 0000000..f878158 --- /dev/null +++ b/src/interpreter/tests/normalization.rs @@ -0,0 +1,540 @@ +// Interpreter tests for normalization at method-call boundaries. +// +// These correspond to the type system tests in normalization.rs but verify +// runtime values, permissions, and heap state. +// +// Current state (after Phase 3b): +// - The type binding injection workaround is removed from `call_method`. +// - A preservation assertion (`check_type` on result types) catches any +// method-scoped variables leaking into the caller's env. +// - Tests where result types reference method-scoped variables are #[ignore]'d +// until Phase 3c adds `normalize_ty_for_pop` to the interpreter. +// - Tests where result types are "clean" (e.g., `new Data(42)` returns plain +// `Data`, or `given[self]` with `given self` resolves to plain type) +// pass without normalization. +// +// After Phase 3c, we expect: +// - `normalize_ty_for_pop` is called on result types in the interpreter +// - All #[ignore]'d tests are un-ignored and pass +// - Trace output for result types will show normalized permissions +// (e.g., `ref[_1_d] Data` instead of `ref[_2_x] ref[_1_d] Data`, +// `or(mut[d1], mut[d2]) Data` instead of `mut[_2_x] mut[d1] Data`) + +// --------------------------------------------------------------------------- +// given[self] resolution: basic ownership transfer +// --------------------------------------------------------------------------- + +/// Method returns given[self] Data — after normalization, the result +/// should have `given Data` type (owned), not a dangling reference to the +/// method's self variable. +#[test] +fn interp_given_self_basic() { + crate::assert_interpret!( + { + class Data { x: Int; } + class Container { + fn get(given self) -> given[self] Data { + new Data(42); + } + } + class Main { + fn main(given self) -> Data { + let c = new Container(); + c.give.get(); + } + } + }, + type: ok, interpret: ok(expect_test::expect![[r#" + Output: Trace: enter Main.main + Output: Trace: let _1_c = new Container () ; + Output: Trace: _1_c = Container { } + Output: Trace: _1_c . give . get () ; + Output: Trace: enter Container.get + Output: Trace: new Data (42) ; + Output: Trace: exit Container.get => Data { x: 42 } + Output: Trace: exit Main.main => Data { x: 42 } + Result: Ok: Data { x: 42 } + Alloc 0x07: [Int(42)]"#]]) + ); +} + +/// given[self] where the method's self is given but the caller +/// subsequently gives the result to a consumer. The result should be +/// `given Data` (from Container's given self). +/// +/// This works even without normalization because the interpreter builds +/// result types from the runtime value (new Data(99) → type `Data`), not +/// from the declared return type. The given[self] annotation is checked +/// by the type system but doesn't affect the interpreter's type tracking here. +#[test] +fn interp_given_self_give_to_consumer() { + crate::assert_interpret!( + { + class Data { x: Int; } + class Container { + fn get(given self) -> given[self] Data { + new Data(99); + } + } + class Sink { + fn consume(given self, d: given Data) -> Int { + d.x.give; + } + } + class Main { + fn main(given self) -> Int { + let c = new Container(); + let result = c.give.get(); + let sink = new Sink(); + sink.give.consume(result.give); + } + } + }, + type: ok, interpret: ok(expect_test::expect![[r#" + Output: Trace: enter Main.main + Output: Trace: let _1_c = new Container () ; + Output: Trace: _1_c = Container { } + Output: Trace: let _1_result = _1_c . give . get () ; + Output: Trace: enter Container.get + Output: Trace: new Data (99) ; + Output: Trace: exit Container.get => Data { x: 99 } + Output: Trace: _1_result = Data { x: 99 } + Output: Trace: let _1_sink = new Sink () ; + Output: Trace: _1_sink = Sink { } + Output: Trace: _1_sink . give . consume (_1_result . give) ; + Output: Trace: enter Sink.consume + Output: Trace: _3_d . x . give ; + Output: Trace: exit Sink.consume => 99 + Output: Trace: exit Main.main => 99 + Result: Ok: 99 + Alloc 0x0e: [Int(99)]"#]]) + ); +} + +/// Method returns a field through a place reference (self.d.ref → ref[_N_self] Data). +/// Without normalization, the result type contains the method-scoped variable +/// `_N_self`, which violates preservation. After Phase 3c, normalization resolves +/// this to a caller-scoped permission. +#[test] +fn interp_ref_self_field_preservation() { + crate::assert_interpret!( + { + class Data { x: Int; } + class Container { + d: given Data; + fn get_ref[perm P](P self) -> ref[self] Data + where P is copy + { + self.d.ref; + } + } + class Main { + fn main(given self) -> Int { + let c = new Container(new Data(77)); + let result = c.ref.get_ref[ref[c]](); + result.x.give; + } + } + }, + type: ok, interpret: ok(expect_test::expect![[r#" + Output: Trace: enter Main.main + Output: Trace: let _1_c = new Container (new Data (77)) ; + Output: Trace: _1_c = Container { d: Data { x: 77 } } + Output: Trace: let _1_result = _1_c . ref . get_ref [ref [_1_c]] () ; + Output: Trace: enter Container.get_ref + Output: Trace: _2_self . d . ref ; + Output: Trace: exit Container.get_ref => ref [_1_c] Data { x: 77 } + Output: Trace: _1_result = ref [_1_c] Data { x: 77 } + Output: Trace: _1_result . x . give ; + Output: Trace: exit Main.main => 77 + Result: Ok: 77 + Alloc 0x0a: [Int(77)]"#]]) + ); +} + +// --------------------------------------------------------------------------- +// given[x] with named parameter +// --------------------------------------------------------------------------- + +/// Method returns given[x] where x is a named parameter passed as given. +/// After normalization, result should be `given Data`. +#[test] +fn interp_given_named_param() { + crate::assert_interpret!( + { + class Data { x: Int; } + class Funcs { + fn take(given self, x: given Data) -> given[x] Data { + x.give; + } + } + class Main { + fn main(given self) -> Data { + let d = new Data(7); + let f = new Funcs(); + f.give.take(d.give); + } + } + }, + type: ok, interpret: ok(expect_test::expect![[r#" + Output: Trace: enter Main.main + Output: Trace: let _1_d = new Data (7) ; + Output: Trace: _1_d = Data { x: 7 } + Output: Trace: let _1_f = new Funcs () ; + Output: Trace: _1_f = Funcs { } + Output: Trace: _1_f . give . take (_1_d . give) ; + Output: Trace: enter Funcs.take + Output: Trace: _2_x . give ; + Output: Trace: exit Funcs.take => Data { x: 7 } + Output: Trace: exit Main.main => Data { x: 7 } + Result: Ok: Data { x: 7 } + Alloc 0x0a: [Int(7)]"#]]) + ); +} + +/// given[x] result can be given away (proves it's owned). +#[test] +fn interp_given_named_param_give_result() { + crate::assert_interpret!( + { + class Data { x: Int; } + class Funcs { + fn take(given self, x: given Data) -> given[x] Data { + x.give; + } + } + class Sink { + fn consume(given self, d: given Data) -> Int { + d.x.give; + } + } + class Main { + fn main(given self) -> Int { + let d = new Data(55); + let f = new Funcs(); + let result = f.give.take(d.give); + let sink = new Sink(); + sink.give.consume(result.give); + } + } + }, + type: ok, interpret: ok(expect_test::expect![[r#" + Output: Trace: enter Main.main + Output: Trace: let _1_d = new Data (55) ; + Output: Trace: _1_d = Data { x: 55 } + Output: Trace: let _1_f = new Funcs () ; + Output: Trace: _1_f = Funcs { } + Output: Trace: let _1_result = _1_f . give . take (_1_d . give) ; + Output: Trace: enter Funcs.take + Output: Trace: _2_x . give ; + Output: Trace: exit Funcs.take => Data { x: 55 } + Output: Trace: _1_result = Data { x: 55 } + Output: Trace: let _1_sink = new Sink () ; + Output: Trace: _1_sink = Sink { } + Output: Trace: _1_sink . give . consume (_1_result . give) ; + Output: Trace: enter Sink.consume + Output: Trace: _3_d . x . give ; + Output: Trace: exit Sink.consume => 55 + Output: Trace: exit Main.main => 55 + Result: Ok: 55 + Alloc 0x11: [Int(55)]"#]]) + ); +} + +// --------------------------------------------------------------------------- +// Borrow chaining: ref through ref +// --------------------------------------------------------------------------- + +/// Method returns ref[x] where x is a ref parameter → borrow chains through. +/// The result should be readable in the caller's scope. +/// +/// Currently hits preservation violation: result type `ref[_2_x] ref[_1_d] Data` +/// references method-scoped `_2_x`. After Phase 3c normalization, this resolves +/// to `ref[_1_d] Data` (copy tail drops the dead link). +#[test] +fn interp_borrow_chain_ref_through_ref() { + crate::assert_interpret!( + { + class Data { x: Int; } + class Funcs { + fn borrow[perm P](given self, x: P Data) -> ref[x] Data + where P is copy + { + x.ref; + } + } + class Main { + fn main(given self) -> Int { + let d = new Data(33); + let f = new Funcs(); + let result = f.give.borrow[ref[d]](d.ref); + result.x.give; + } + } + }, + type: ok, interpret: ok(expect_test::expect![[r#" + Output: Trace: enter Main.main + Output: Trace: let _1_d = new Data (33) ; + Output: Trace: _1_d = Data { x: 33 } + Output: Trace: let _1_f = new Funcs () ; + Output: Trace: _1_f = Funcs { } + Output: Trace: let _1_result = _1_f . give . borrow [ref [_1_d]] (_1_d . ref) ; + Output: Trace: enter Funcs.borrow + Output: Trace: _2_x . ref ; + Output: Trace: exit Funcs.borrow => ref [_1_d] Data { x: 33 } + Output: Trace: _1_result = ref [_1_d] Data { x: 33 } + Output: Trace: _1_result . x . give ; + Output: Trace: exit Main.main => 33 + Result: Ok: 33 + Alloc 0x0c: [Int(33)]"#]]) + ); +} + +/// ref[self] where self is ref → borrow chains through, field readable. +/// +/// Currently hits preservation violation: result type references method-scoped +/// `_2_self`. After Phase 3c normalization, resolves to `ref[_1_c] Data`. +#[test] +fn interp_borrow_chain_ref_through_ref_self() { + crate::assert_interpret!( + { + class Data { x: Int; } + class Container { + d: given Data; + fn get[perm P](P self) -> ref[self] Data + where P is copy + { + self.d.ref; + } + } + class Main { + fn main(given self) -> Int { + let c = new Container(new Data(44)); + let result = c.ref.get[ref[c]](); + result.x.give; + } + } + }, + type: ok, interpret: ok(expect_test::expect![[r#" + Output: Trace: enter Main.main + Output: Trace: let _1_c = new Container (new Data (44)) ; + Output: Trace: _1_c = Container { d: Data { x: 44 } } + Output: Trace: let _1_result = _1_c . ref . get [ref [_1_c]] () ; + Output: Trace: enter Container.get + Output: Trace: _2_self . d . ref ; + Output: Trace: exit Container.get => ref [_1_c] Data { x: 44 } + Output: Trace: _1_result = ref [_1_c] Data { x: 44 } + Output: Trace: _1_result . x . give ; + Output: Trace: exit Main.main => 44 + Result: Ok: 44 + Alloc 0x0a: [Int(44)]"#]]) + ); +} + +// --------------------------------------------------------------------------- +// Multi-place resolution producing Or +// --------------------------------------------------------------------------- + +/// ref[x, y] with different ref args → result has or(ref[d1], ref[d2]) perm. +/// Reading a field from the result should work. +/// +/// Currently hits preservation violation: result type references method-scoped +/// `_2_x`. After Phase 3c normalization, resolves to `or(ref[_1_d1], ref[_1_d2]) Data`. +#[test] +fn interp_multi_place_ref_produces_or() { + crate::assert_interpret!( + { + class Data { x: Int; } + class Funcs { + fn either[perm P, perm Q](given self, x: P Data, y: Q Data) -> ref[x, y] Data + where P is copy, Q is copy + { + x.ref; + } + } + class Main { + fn main(given self) -> Int { + let d1 = new Data(10); + let d2 = new Data(20); + let f = new Funcs(); + let result = f.give.either[ref[d1], ref[d2]](d1.ref, d2.ref); + result.x.give; + } + } + }, + type: ok, interpret: ok(expect_test::expect![[r#" + Output: Trace: enter Main.main + Output: Trace: let _1_d1 = new Data (10) ; + Output: Trace: _1_d1 = Data { x: 10 } + Output: Trace: let _1_d2 = new Data (20) ; + Output: Trace: _1_d2 = Data { x: 20 } + Output: Trace: let _1_f = new Funcs () ; + Output: Trace: _1_f = Funcs { } + Output: Trace: let _1_result = _1_f . give . either [ref [_1_d1], ref [_1_d2]] (_1_d1 . ref, _1_d2 . ref) ; + Output: Trace: enter Funcs.either + Output: Trace: _2_x . ref ; + Output: Trace: exit Funcs.either => ref [_1_d1] Data { x: 10 } + Output: Trace: _1_result = ref [_1_d1] Data { x: 10 } + Output: Trace: _1_result . x . give ; + Output: Trace: exit Main.main => 10 + Result: Ok: 10 + Alloc 0x10: [Int(10)]"#]]) + ); +} + +/// given[x, y] with both given → result is given (or(given, given) = given). +/// Can give result away. +#[test] +fn interp_multi_place_given_both_given() { + crate::assert_interpret!( + { + class Data { x: Int; } + class Funcs { + fn pick(given self, x: given Data, y: given Data) -> given[x, y] Data { + x.give; + } + } + class Sink { + fn consume(given self, d: given Data) -> Int { + d.x.give; + } + } + class Main { + fn main(given self) -> Int { + let d1 = new Data(100); + let d2 = new Data(200); + let f = new Funcs(); + let result = f.give.pick(d1.give, d2.give); + let sink = new Sink(); + sink.give.consume(result.give); + } + } + }, + type: ok, interpret: ok(expect_test::expect![[r#" + Output: Trace: enter Main.main + Output: Trace: let _1_d1 = new Data (100) ; + Output: Trace: _1_d1 = Data { x: 100 } + Output: Trace: let _1_d2 = new Data (200) ; + Output: Trace: _1_d2 = Data { x: 200 } + Output: Trace: let _1_f = new Funcs () ; + Output: Trace: _1_f = Funcs { } + Output: Trace: let _1_result = _1_f . give . pick (_1_d1 . give, _1_d2 . give) ; + Output: Trace: enter Funcs.pick + Output: Trace: _2_x . give ; + Output: Trace: exit Funcs.pick => Data { x: 100 } + Output: Trace: _1_result = Data { x: 100 } + Output: Trace: let _1_sink = new Sink () ; + Output: Trace: _1_sink = Sink { } + Output: Trace: _1_sink . give . consume (_1_result . give) ; + Output: Trace: enter Sink.consume + Output: Trace: _3_d . x . give ; + Output: Trace: exit Sink.consume => 100 + Output: Trace: exit Main.main => 100 + Result: Ok: 100 + Alloc 0x15: [Int(100)]"#]]) + ); +} + +/// mut[x, y] through mut → result has or(mut[d1], mut[d2]). +/// Mutating through the result should work. +/// +/// Currently hits preservation violation: result type `mut[_2_x] mut[_1_d1] Data` +/// references method-scoped `_2_x`. After Phase 3c normalization, resolves to +/// `or(mut[_1_d1], mut[_1_d2]) Data`. +#[test] +fn interp_multi_place_mut_through_mut() { + crate::assert_interpret!( + { + class Data { x: Int; } + class Funcs { + fn either[perm P, perm Q](given self, x: P Data, y: Q Data) -> mut[x, y] Data + where P is mut, Q is mut + { + x.mut; + } + } + class Main { + fn main(given self) -> Int { + let d1 = new Data(10); + let d2 = new Data(20); + let f = new Funcs(); + let result = f.give.either[mut[d1], mut[d2]](d1.mut, d2.mut); + result.x.give; + } + } + }, + type: ok, interpret: ok(expect_test::expect![[r#" + Output: Trace: enter Main.main + Output: Trace: let _1_d1 = new Data (10) ; + Output: Trace: _1_d1 = Data { x: 10 } + Output: Trace: let _1_d2 = new Data (20) ; + Output: Trace: _1_d2 = Data { x: 20 } + Output: Trace: let _1_f = new Funcs () ; + Output: Trace: _1_f = Funcs { } + Output: Trace: let _1_result = _1_f . give . either [mut [_1_d1], mut [_1_d2]] (_1_d1 . mut, _1_d2 . mut) ; + Output: Trace: enter Funcs.either + Output: Trace: _2_x . mut ; + Output: Trace: exit Funcs.either => mut [_1_d1] mut [_1_d1] Data { x: 10 } + Output: Trace: _1_result = mut [_1_d1] mut [_1_d1] Data { x: 10 } + Output: Trace: _1_result . x . give ; + Output: Trace: exit Main.main => 10 + Result: Ok: 10 + Alloc 0x10: [Int(10)]"#]]) + ); +} + +// --------------------------------------------------------------------------- +// Verifying workaround removal: method-scoped names should NOT leak +// --------------------------------------------------------------------------- + +/// After normalization, calling two methods in sequence should not accumulate +/// leaked bindings. This test exercises that the workaround (injecting +/// method-scoped type bindings into caller env) is removed. +#[test] +fn interp_no_leaked_method_bindings() { + crate::assert_interpret!( + { + class Data { x: Int; } + class Funcs { + fn take(given self, x: given Data) -> given[x] Data { + x.give; + } + } + class Main { + fn main(given self) -> Int { + let f1 = new Funcs(); + let d1 = new Data(1); + let r1 = f1.give.take(d1.give); + let f2 = new Funcs(); + let d2 = new Data(2); + let r2 = f2.give.take(d2.give); + r1.x.give + r2.x.give; + } + } + }, + type: ok, interpret: ok(expect_test::expect![[r#" + Output: Trace: enter Main.main + Output: Trace: let _1_f1 = new Funcs () ; + Output: Trace: _1_f1 = Funcs { } + Output: Trace: let _1_d1 = new Data (1) ; + Output: Trace: _1_d1 = Data { x: 1 } + Output: Trace: let _1_r1 = _1_f1 . give . take (_1_d1 . give) ; + Output: Trace: enter Funcs.take + Output: Trace: _2_x . give ; + Output: Trace: exit Funcs.take => Data { x: 1 } + Output: Trace: _1_r1 = Data { x: 1 } + Output: Trace: let _1_f2 = new Funcs () ; + Output: Trace: _1_f2 = Funcs { } + Output: Trace: let _1_d2 = new Data (2) ; + Output: Trace: _1_d2 = Data { x: 2 } + Output: Trace: let _1_r2 = _1_f2 . give . take (_1_d2 . give) ; + Output: Trace: enter Funcs.take + Output: Trace: _3_x . give ; + Output: Trace: exit Funcs.take => Data { x: 2 } + Output: Trace: _1_r2 = Data { x: 2 } + Output: Trace: _1_r1 . x . give + _1_r2 . x . give ; + Output: Trace: exit Main.main => 3 + Result: Ok: 3 + Alloc 0x18: [Int(3)]"#]]) + ); +} diff --git a/src/interpreter/tests/place_ops.rs b/src/interpreter/tests/place_ops.rs index 032528c..a780996 100644 --- a/src/interpreter/tests/place_ops.rs +++ b/src/interpreter/tests/place_ops.rs @@ -24,21 +24,21 @@ fn give_from_given() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } Output: Trace: _1_d . give ; Output: Trace: exit Main.main => Data { x: 42 } Result: Ok: Data { x: 42 } - Alloc 0x05: [Int(42)]"#]] + Alloc 0x05: [Int(42)]"#]]) ); } #[test] fn give_from_given_uninitializes_source() { // UB test: giving a moved value faults at runtime. - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -49,7 +49,13 @@ fn give_from_given_uninitializes_source() { } } }, - expect_test::expect![[r#" + type: error(expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data}, assumptions: {}, fresh: 0 } } + + the rule "give" at (expressions.rs) failed because + condition evaluated to false: `!live_after.is_live(place)` + live_after = LivePlaces { accessed: {d}, traversed: {} } + place = d"#]]), interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -57,7 +63,7 @@ fn give_from_given_uninitializes_source() { Output: Trace: _1_a = Data { x: 42 } Output: Trace: _1_d . give ; Result: Fault: access of uninitialized value - Alloc 0x05: [Int(42)]"#]] + Alloc 0x05: [Int(42)]"#]]) ); } @@ -66,11 +72,11 @@ fn give_from_shared() { // give from a Shared source: copy fields, set flags to Shared. // Shared values are copyable, so giving doesn't consume the source. // We print the give result and return the original — both are valid. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { - fn main(given self) -> Data { + fn main(given self) -> shared Data { let d = new Data(42); let s = d.give.share; print(s.give); @@ -78,7 +84,7 @@ fn give_from_shared() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -89,7 +95,7 @@ fn give_from_shared() { Output: Trace: _1_s . give ; Output: Trace: exit Main.main => shared Data { x: 42 } Result: Ok: shared Data { x: 42 } - Alloc 0x09: [Int(42)]"#]] + Alloc 0x09: [Int(42)]"#]]) ); } @@ -98,19 +104,19 @@ fn give_from_shared_nested() { // Giving a shared object with nested unique fields: // the copy should have all nested flags set to Shared // (the share operation is applied recursively). - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } class Main { - fn main(given self) -> Outer { + fn main(given self) -> shared Outer { let o = new Outer(new Inner(1)); let s = o.give.share; s.give; } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (1)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 1 } } @@ -119,34 +125,36 @@ fn give_from_shared_nested() { Output: Trace: _1_s . give ; Output: Trace: exit Main.main => shared Outer { inner: Inner { x: 1 } } Result: Ok: shared Outer { inner: Inner { x: 1 } } - Alloc 0x08: [Int(1)]"#]] + Alloc 0x08: [Int(1)]"#]]) ); } #[test] fn give_from_borrowed() { // give from a Borrowed source: copy fields, set flags to Borrowed. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { - fn main(given self) -> Data { + fn main(given self) -> () { let d = new Data(42); let r = d.ref; - r.give; + print(r.give); + (); } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } Output: Trace: let _1_r = _1_d . ref ; Output: Trace: _1_r = ref [_1_d] Data { x: 42 } - Output: Trace: _1_r . give ; - Output: Trace: exit Main.main => ref [_1_d] Data { x: 42 } - Result: Ok: ref [_1_d] Data { x: 42 } - Alloc 0x07: [Int(42)]"#]] + Output: Trace: print(_1_r . give) ; + Output: -----> ref [_1_d] Data { x: 42 } + Output: Trace: () ; + Output: Trace: exit Main.main => () + Result: Ok: ()"#]]) ); } @@ -154,11 +162,11 @@ fn give_from_borrowed() { fn give_shared_multiple_times() { // A shared value is copyable — giving it repeatedly works, // each copy gets flag: Shared. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { - fn main(given self) -> Data { + fn main(given self) -> shared Data { let d = new Data(42); let s = d.give.share; let x1 = s.give; @@ -169,7 +177,7 @@ fn give_shared_multiple_times() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -186,7 +194,7 @@ fn give_shared_multiple_times() { Output: Trace: _1_s . give ; Output: Trace: exit Main.main => shared Data { x: 42 } Result: Ok: shared Data { x: 42 } - Alloc 0x0f: [Int(42)]"#]] + Alloc 0x0f: [Int(42)]"#]]) ); } @@ -195,12 +203,12 @@ fn give_shared_nested_subfield() { // Share an Outer, then access its inner field via give. // The inner field should be Shared (share op recursed), // and therefore copyable — give it twice. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } class Main { - fn main(given self) -> Inner { + fn main(given self) -> shared Inner { let o = new Outer(new Inner(99)); let s = o.give.share; let i1 = s.inner.give; @@ -210,7 +218,7 @@ fn give_shared_nested_subfield() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (99)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 99 } } @@ -225,7 +233,7 @@ fn give_shared_nested_subfield() { Output: Trace: _1_i2 . give ; Output: Trace: exit Main.main => shared Inner { x: 99 } Result: Ok: shared Inner { x: 99 } - Alloc 0x0e: [Int(99)]"#]] + Alloc 0x0e: [Int(99)]"#]]) ); } @@ -248,7 +256,7 @@ fn ref_from_given() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -257,7 +265,7 @@ fn ref_from_given() { Output: Trace: _1_d . give ; Output: Trace: exit Main.main => Data { x: 42 } Result: Ok: Data { x: 42 } - Alloc 0x07: [Int(42)]"#]] + Alloc 0x07: [Int(42)]"#]]) ); } @@ -265,18 +273,18 @@ fn ref_from_given() { fn ref_from_shared() { // ref from a Shared source: copy fields, set flags to Shared // (not Borrowed — shared stays shared). - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { - fn main(given self) -> Data { + fn main(given self) -> shared Data { let d = new Data(42); let s = d.give.share; s.ref; } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -285,7 +293,7 @@ fn ref_from_shared() { Output: Trace: _1_s . ref ; Output: Trace: exit Main.main => shared Data { x: 42 } Result: Ok: shared Data { x: 42 } - Alloc 0x07: [Int(42)]"#]] + Alloc 0x07: [Int(42)]"#]]) ); } @@ -293,19 +301,19 @@ fn ref_from_shared() { fn ref_from_shared_nested() { // Ref from a shared object with nested fields: // result should have Shared flags throughout (not Borrowed). - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } class Main { - fn main(given self) -> Outer { + fn main(given self) -> shared Outer { let o = new Outer(new Inner(1)); let s = o.give.share; s.ref; } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (1)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 1 } } @@ -314,7 +322,7 @@ fn ref_from_shared_nested() { Output: Trace: _1_s . ref ; Output: Trace: exit Main.main => shared Outer { inner: Inner { x: 1 } } Result: Ok: shared Outer { inner: Inner { x: 1 } } - Alloc 0x08: [Int(1)]"#]] + Alloc 0x08: [Int(1)]"#]]) ); } @@ -323,12 +331,12 @@ fn ref_from_shared_nested_subfield() { // Ref a shared Outer, then give its inner field. // The inner was made Shared by the recursive share op, // so giving it produces a Shared copy — and it's repeatable. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } class Main { - fn main(given self) -> Inner { + fn main(given self) -> shared Inner { let o = new Outer(new Inner(7)); let s = o.give.share; let r = s.ref; @@ -339,7 +347,7 @@ fn ref_from_shared_nested_subfield() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (7)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 7 } } @@ -356,34 +364,36 @@ fn ref_from_shared_nested_subfield() { Output: Trace: _1_i2 . give ; Output: Trace: exit Main.main => shared Inner { x: 7 } Result: Ok: shared Inner { x: 7 } - Alloc 0x10: [Int(7)]"#]] + Alloc 0x10: [Int(7)]"#]]) ); } #[test] fn ref_from_borrowed() { // ref from a Borrowed source: copy fields, set flags to Borrowed. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { - fn main(given self) -> Data { + fn main(given self) -> () { let d = new Data(42); let r = d.ref; - r.ref; + print(r.ref); + (); } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } Output: Trace: let _1_r = _1_d . ref ; Output: Trace: _1_r = ref [_1_d] Data { x: 42 } - Output: Trace: _1_r . ref ; - Output: Trace: exit Main.main => ref [_1_d] Data { x: 42 } - Result: Ok: ref [_1_d] Data { x: 42 } - Alloc 0x07: [Int(42)]"#]] + Output: Trace: print(_1_r . ref) ; + Output: -----> ref [_1_d] Data { x: 42 } + Output: Trace: () ; + Output: Trace: exit Main.main => () + Result: Ok: ()"#]]) ); } @@ -395,7 +405,7 @@ fn ref_from_borrowed() { fn drop_given() { // drop on a Given value: print it first, then drop. // The drop itself shouldn't fault. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -407,7 +417,7 @@ fn drop_given() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -417,7 +427,7 @@ fn drop_given() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x08: [Int(0)]"#]] + Alloc 0x08: [Int(0)]"#]]) ); } @@ -425,7 +435,7 @@ fn drop_given() { fn drop_given_nested() { // Drop on a Given object with nested Given fields: recursive drop. // Print before dropping to confirm value was live. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } @@ -438,7 +448,7 @@ fn drop_given_nested() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (1)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 1 } } @@ -448,14 +458,14 @@ fn drop_given_nested() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x09: [Int(0)]"#]] + Alloc 0x09: [Int(0)]"#]]) ); } #[test] fn drop_given_nested_uninitializes() { // UB test: giving a dropped value faults at runtime. - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } @@ -467,49 +477,57 @@ fn drop_given_nested_uninitializes() { } } }, - expect_test::expect![[r#" + type: error(expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Outer, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, o: Outer}, assumptions: {}, fresh: 0 } } + + the rule "give" at (expressions.rs) failed because + condition evaluated to false: `!live_after.is_live(place)` + live_after = LivePlaces { accessed: {o}, traversed: {} } + place = o"#]]), interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (1)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 1 } } Output: Trace: _1_o . drop ; Output: Trace: _1_o . give ; - Result: Fault: access of uninitialized value"#]] + Result: Fault: access of uninitialized value"#]]) ); } #[test] fn drop_borrowed_is_noop() { // drop on a Borrowed value: no-op. The value remains usable. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { - fn main(given self) -> Data { + fn main(given self) -> () { let d = new Data(42); let r = d.ref; r.drop; - r.give; + print(r.give); + (); } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } Output: Trace: let _1_r = _1_d . ref ; Output: Trace: _1_r = ref [_1_d] Data { x: 42 } Output: Trace: _1_r . drop ; - Output: Trace: _1_r . give ; - Output: Trace: exit Main.main => ref [_1_d] Data { x: 42 } - Result: Ok: ref [_1_d] Data { x: 42 } - Alloc 0x08: [Int(42)]"#]] + Output: Trace: print(_1_r . give) ; + Output: -----> ref [_1_d] Data { x: 42 } + Output: Trace: () ; + Output: Trace: exit Main.main => () + Result: Ok: ()"#]]) ); } #[test] fn drop_shared() { // drop on a Shared value: applies "drop shared" operation. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -522,7 +540,7 @@ fn drop_shared() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -534,14 +552,14 @@ fn drop_shared() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x0a: [Int(0)]"#]] + Alloc 0x0a: [Int(0)]"#]]) ); } #[test] fn drop_shared_nested() { // Drop a shared object with nested classes — drop-shared recurses. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } @@ -555,7 +573,7 @@ fn drop_shared_nested() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (1)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 1 } } @@ -567,7 +585,7 @@ fn drop_shared_nested() { Output: Trace: 0 ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x0b: [Int(0)]"#]] + Alloc 0x0b: [Int(0)]"#]]) ); } @@ -579,43 +597,43 @@ fn drop_shared_nested() { fn share_nested_objects() { // Sharing an object with a nested unique field should recursively // set all flags to Shared. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } class Main { - fn main(given self) -> Outer { + fn main(given self) -> shared Outer { let o = new Outer(new Inner(1)); o.give.share; } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (1)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 1 } } Output: Trace: _1_o . give . share ; Output: Trace: exit Main.main => shared Outer { inner: Inner { x: 1 } } Result: Ok: shared Outer { inner: Inner { x: 1 } } - Alloc 0x06: [Int(1)]"#]] + Alloc 0x06: [Int(1)]"#]]) ); } #[test] fn share_already_shared_is_noop() { // Sharing a value that's already Shared should be a no-op. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { - fn main(given self) -> Data { + fn main(given self) -> shared Data { let d = new Data(42); let s = d.give.share; s.give.share; } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -624,34 +642,36 @@ fn share_already_shared_is_noop() { Output: Trace: _1_s . give . share ; Output: Trace: exit Main.main => shared Data { x: 42 } Result: Ok: shared Data { x: 42 } - Alloc 0x07: [Int(42)]"#]] + Alloc 0x07: [Int(42)]"#]]) ); } #[test] fn share_borrowed_is_noop() { // Sharing a Borrowed value is a no-op — it stays Borrowed. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { - fn main(given self) -> Data { + fn main(given self) -> () { let d = new Data(42); let r = d.ref; - r.give.share; + print(r.give.share); + (); } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } Output: Trace: let _1_r = _1_d . ref ; Output: Trace: _1_r = ref [_1_d] Data { x: 42 } - Output: Trace: _1_r . give . share ; - Output: Trace: exit Main.main => ref [_1_d] Data { x: 42 } - Result: Ok: ref [_1_d] Data { x: 42 } - Alloc 0x07: [Int(42)]"#]] + Output: Trace: print(_1_r . give . share) ; + Output: -----> ref [_1_d] Data { x: 42 } + Output: Trace: () ; + Output: Trace: exit Main.main => () + Result: Ok: ()"#]]) ); } @@ -667,7 +687,7 @@ fn give_field_through_borrowed_path() { // Ref an Outer, then give its inner field. // The inner's own flags are Given, but we traversed through Borrowed, // so the effective permission should be Borrowed — no move, source intact. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } @@ -682,7 +702,7 @@ fn give_field_through_borrowed_path() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (42)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 42 } } @@ -695,7 +715,7 @@ fn give_field_through_borrowed_path() { Output: Trace: _1_o . inner . give ; Output: Trace: exit Main.main => Inner { x: 42 } Result: Ok: Inner { x: 42 } - Alloc 0x0c: [Int(42)]"#]] + Alloc 0x0c: [Int(42)]"#]]) ); } @@ -703,28 +723,30 @@ fn give_field_through_borrowed_path() { fn ref_field_through_borrowed_path() { // Ref an Outer, then ref its inner field. // Traversing through Borrowed, inner should be Borrowed regardless of own flags. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } class Main { - fn main(given self) -> Inner { + fn main(given self) -> () { let o = new Outer(new Inner(42)); let r = o.ref; - r.inner.ref; + print(r.inner.ref); + (); } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (42)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 42 } } Output: Trace: let _1_r = _1_o . ref ; Output: Trace: _1_r = ref [_1_o] Outer { inner: Inner { x: 42 } } - Output: Trace: _1_r . inner . ref ; - Output: Trace: exit Main.main => ref [_1_o] Inner { x: 42 } - Result: Ok: ref [_1_o] Inner { x: 42 } - Alloc 0x08: [Int(42)]"#]] + Output: Trace: print(_1_r . inner . ref) ; + Output: -----> ref [_1_o] Inner { x: 42 } + Output: Trace: () ; + Output: Trace: exit Main.main => () + Result: Ok: ()"#]]) ); } @@ -733,12 +755,12 @@ fn give_field_through_shared_path() { // Share an Outer, then give its inner field. // Traversing through Shared — inner should come out Shared, // and giving should be repeatable (shared is copyable). - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } class Main { - fn main(given self) -> Inner { + fn main(given self) -> shared Inner { let o = new Outer(new Inner(42)); let s = o.give.share; let i1 = s.inner.give; @@ -748,7 +770,7 @@ fn give_field_through_shared_path() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (42)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 42 } } @@ -763,7 +785,7 @@ fn give_field_through_shared_path() { Output: Trace: _1_i2 . give ; Output: Trace: exit Main.main => shared Inner { x: 42 } Result: Ok: shared Inner { x: 42 } - Alloc 0x0e: [Int(42)]"#]] + Alloc 0x0e: [Int(42)]"#]]) ); } @@ -799,7 +821,7 @@ fn shared_ref_subtype() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Link0 (new Link1 (new Link2 ())) ; Output: Trace: _1_o = Link0 { inner: Link1 { inner: Link2 { } } } @@ -823,7 +845,7 @@ fn shared_ref_subtype() { Output: -----> shared Link2 { } Output: Trace: () ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]] + Result: Ok: ()"#]]) ); } @@ -835,7 +857,7 @@ fn shared_ref_subtype() { fn mut_from_given() { // mut from a Given source: create a MutRef pointing at the original. // The MutRef dereferences through to the underlying value for display. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -847,7 +869,7 @@ fn mut_from_given() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -858,7 +880,7 @@ fn mut_from_given() { Output: Trace: _1_d . give ; Output: Trace: exit Main.main => Data { x: 42 } Result: Ok: Data { x: 42 } - Alloc 0x09: [Int(42)]"#]] + Alloc 0x09: [Int(42)]"#]]) ); } @@ -866,7 +888,7 @@ fn mut_from_given() { fn mut_field_read() { // Access a field through a MutRef: dereferences the MutRef, // then projects the field from the underlying allocation. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; y: Int; } class Main { @@ -877,7 +899,7 @@ fn mut_field_read() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (10, 20) ; Output: Trace: _1_d = Data { x: 10, y: 20 } @@ -886,7 +908,7 @@ fn mut_field_read() { Output: Trace: _1_m . y . give ; Output: Trace: exit Main.main => 20 Result: Ok: 20 - Alloc 0x08: [Int(20)]"#]] + Alloc 0x08: [Int(20)]"#]]) ); } @@ -894,7 +916,7 @@ fn mut_field_read() { fn mut_field_reassign() { // Reassign a field through a MutRef: the change is visible // in the original value because MutRef points at it directly. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -906,7 +928,7 @@ fn mut_field_reassign() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -917,7 +939,7 @@ fn mut_field_reassign() { Output: Trace: _1_d . give ; Output: Trace: exit Main.main => Data { x: 99 } Result: Ok: Data { x: 99 } - Alloc 0x09: [Int(99)]"#]] + Alloc 0x09: [Int(99)]"#]]) ); } @@ -925,7 +947,7 @@ fn mut_field_reassign() { fn mut_give_copies_mutref() { // Giving a MutRef copies the MutRef word into a new allocation. // Both the original and the copy point at the same underlying data. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -938,7 +960,7 @@ fn mut_give_copies_mutref() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -951,7 +973,7 @@ fn mut_give_copies_mutref() { Output: Trace: _1_d . give ; Output: Trace: exit Main.main => Data { x: 99 } Result: Ok: Data { x: 99 } - Alloc 0x0b: [Int(99)]"#]] + Alloc 0x0b: [Int(99)]"#]]) ); } @@ -959,27 +981,29 @@ fn mut_give_copies_mutref() { fn mut_ref_through_mutref() { // Ref through a MutRef: dereferences the MutRef and copies // the underlying value with Borrowed flags. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { - fn main(given self) -> Data { + fn main(given self) -> () { let d = new Data(42); let m = d.mut; - m.ref; + print(m.ref); + (); } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } Output: Trace: let _1_m = _1_d . mut ; Output: Trace: _1_m = mut [_1_d] Data { x: 42 } - Output: Trace: _1_m . ref ; - Output: Trace: exit Main.main => ref [_1_m] mut [_1_d] Data { x: 42 } - Result: Ok: ref [_1_m] mut [_1_d] Data { x: 42 } - Alloc 0x07: [Int(42)]"#]] + Output: Trace: print(_1_m . ref) ; + Output: -----> ref [_1_m] mut [_1_d] Data { x: 42 } + Output: Trace: () ; + Output: Trace: exit Main.main => () + Result: Ok: ()"#]]) ); } @@ -987,7 +1011,7 @@ fn mut_ref_through_mutref() { fn mut_drop() { // Dropping a MutRef: scrubs the MutRef allocation. // The underlying value is NOT dropped — it's still owned by the original. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -999,7 +1023,7 @@ fn mut_drop() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -1009,14 +1033,14 @@ fn mut_drop() { Output: Trace: _1_d . give ; Output: Trace: exit Main.main => Data { x: 42 } Result: Ok: Data { x: 42 } - Alloc 0x08: [Int(42)]"#]] + Alloc 0x08: [Int(42)]"#]]) ); } #[test] fn mut_of_mut() { // Mut of mut: equivalent to give — copies the MutRef word. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -1029,7 +1053,7 @@ fn mut_of_mut() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -1042,14 +1066,14 @@ fn mut_of_mut() { Output: Trace: _1_d . give ; Output: Trace: exit Main.main => Data { x: 77 } Result: Ok: Data { x: 77 } - Alloc 0x0b: [Int(77)]"#]] + Alloc 0x0b: [Int(77)]"#]]) ); } #[test] fn mut_nested_field_reassign() { // Reassign a nested field through a MutRef. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } @@ -1062,7 +1086,7 @@ fn mut_nested_field_reassign() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (1)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 1 } } @@ -1073,7 +1097,7 @@ fn mut_nested_field_reassign() { Output: Trace: _1_o . give ; Output: Trace: exit Main.main => Outer { inner: Inner { x: 42 } } Result: Ok: Outer { inner: Inner { x: 42 } } - Alloc 0x0a: [Int(42)]"#]] + Alloc 0x0a: [Int(42)]"#]]) ); } @@ -1086,7 +1110,7 @@ fn mut_field_of_given() { // Mut a field of a Given object: creates a MutRef pointing // into the original allocation at the field's offset. // Reassigning through the MutRef modifies the original. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } @@ -1099,7 +1123,7 @@ fn mut_field_of_given() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (1)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 1 } } @@ -1110,14 +1134,14 @@ fn mut_field_of_given() { Output: Trace: _1_o . give ; Output: Trace: exit Main.main => Outer { inner: Inner { x: 99 } } Result: Ok: Outer { inner: Inner { x: 99 } } - Alloc 0x0a: [Int(99)]"#]] + Alloc 0x0a: [Int(99)]"#]]) ); } #[test] fn mut_field_of_given_read() { // Read a field through a MutRef to a field of a Given object. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; y: Int; } class Outer { inner: Inner; } @@ -1129,7 +1153,7 @@ fn mut_field_of_given_read() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (10, 20)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 10, y: 20 } } @@ -1138,7 +1162,7 @@ fn mut_field_of_given_read() { Output: Trace: _1_m . y . give ; Output: Trace: exit Main.main => 20 Result: Ok: 20 - Alloc 0x09: [Int(20)]"#]] + Alloc 0x09: [Int(20)]"#]]) ); } @@ -1146,7 +1170,7 @@ fn mut_field_of_given_read() { fn mut_field_of_given_drop() { // Dropping a MutRef to a field: scrubs the MutRef allocation, // original field untouched. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } @@ -1159,7 +1183,7 @@ fn mut_field_of_given_drop() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (42)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 42 } } @@ -1169,7 +1193,7 @@ fn mut_field_of_given_drop() { Output: Trace: _1_o . give ; Output: Trace: exit Main.main => Outer { inner: Inner { x: 42 } } Result: Ok: Outer { inner: Inner { x: 42 } } - Alloc 0x09: [Int(42)]"#]] + Alloc 0x09: [Int(42)]"#]]) ); } @@ -1178,7 +1202,7 @@ fn mut_field_through_mut() { // a.mut then m.inner.mut: the inner .mut traverses through the // outer MutRef dereference, then creates a new MutRef pointing // at the inner field of the original allocation. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } @@ -1192,7 +1216,7 @@ fn mut_field_through_mut() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (1)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 1 } } @@ -1205,7 +1229,7 @@ fn mut_field_through_mut() { Output: Trace: _1_o . give ; Output: Trace: exit Main.main => Outer { inner: Inner { x: 55 } } Result: Ok: Outer { inner: Inner { x: 55 } } - Alloc 0x0c: [Int(55)]"#]] + Alloc 0x0c: [Int(55)]"#]]) ); } @@ -1214,7 +1238,7 @@ fn mut_field_through_shared() { // Mut of a field reached through a Shared object. // resolve_place sets effective=Shared when crossing the shared perm, // so mut_place should fault (cannot mutably borrow a shared or borrowed value). - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } @@ -1226,7 +1250,12 @@ fn mut_field_through_shared() { } } }, - expect_test::expect![[r#" + type: error(expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, o: Outer, s: shared Outer}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, o: Outer, s: shared Outer}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, o: Outer, s: shared Outer}, assumptions: {}, fresh: 0 } }"#]]), interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (1)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 1 } } @@ -1234,7 +1263,7 @@ fn mut_field_through_shared() { Output: Trace: _1_s = shared Outer { inner: Inner { x: 1 } } Output: Trace: _1_s . inner . mut ; Result: Fault: cannot take mutable reference to shared value - Alloc 0x06: [Int(1)]"#]] + Alloc 0x06: [Int(1)]"#]]) ); } @@ -1244,7 +1273,7 @@ fn mut_field_through_ref() { // resolve_place sets effective=Borrowed when crossing the ref perm. // Borrowed means read-only — cannot take a mutable reference through it, // just like shared. - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } @@ -1256,7 +1285,16 @@ fn mut_field_through_ref() { } } }, - expect_test::expect![[r#" + type: error(expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [o], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, o: Outer, r: ref [o] Outer}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Outer, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, o: Outer, r: ref [o] Outer}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [o], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, o: Outer, r: ref [o] Outer}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [o], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, o: Outer, r: ref [o] Outer}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Outer, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, o: Outer, r: ref [o] Outer}, assumptions: {}, fresh: 0 } }"#]]), interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (1)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 1 } } @@ -1265,14 +1303,14 @@ fn mut_field_through_ref() { Output: Trace: _1_r . inner . mut ; Result: Fault: cannot take mutable reference to borrowed value Alloc 0x04: [Int(1)] - Alloc 0x06: [Int(1)]"#]] + Alloc 0x06: [Int(1)]"#]]) ); } #[test] fn mut_field_uninitialized() { // Mut of a field that has been moved out (uninitialized). - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Inner { x: Int; } class Outer { inner: Inner; } @@ -1284,7 +1322,13 @@ fn mut_field_uninitialized() { } } }, - expect_test::expect![[r#" + type: error(expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Inner, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, o: Outer}, assumptions: {}, fresh: 0 } } + + the rule "give" at (expressions.rs) failed because + condition evaluated to false: `!live_after.is_live(place)` + live_after = LivePlaces { accessed: {o . inner}, traversed: {} } + place = o . inner"#]]), interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_o = new Outer (new Inner (1)) ; Output: Trace: _1_o = Outer { inner: Inner { x: 1 } } @@ -1292,7 +1336,7 @@ fn mut_field_uninitialized() { Output: Trace: _1_stolen = Inner { x: 1 } Output: Trace: _1_o . inner . mut ; Result: Fault: access of uninitialized value - Alloc 0x06: [Int(1)]"#]] + Alloc 0x06: [Int(1)]"#]]) ); } @@ -1303,7 +1347,7 @@ fn mut_field_uninitialized() { #[test] fn mut_of_shared_faults() { // Cannot take a mut ref of a shared value. - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -1314,7 +1358,12 @@ fn mut_of_shared_faults() { } } }, - expect_test::expect![[r#" + type: error(expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, s: shared Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, s: shared Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, s: shared Data}, assumptions: {}, fresh: 0 } }"#]]), interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -1322,14 +1371,14 @@ fn mut_of_shared_faults() { Output: Trace: _1_s = shared Data { x: 42 } Output: Trace: _1_s . mut ; Result: Fault: cannot take mutable reference to shared value - Alloc 0x05: [Int(42)]"#]] + Alloc 0x05: [Int(42)]"#]]) ); } #[test] fn mut_of_uninitialized_faults() { // Cannot take a mut ref of a dropped value. - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -1340,20 +1389,26 @@ fn mut_of_uninitialized_faults() { } } }, - expect_test::expect![[r#" + type: error(expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data}, assumptions: {}, fresh: 0 } } + + the rule "give" at (expressions.rs) failed because + condition evaluated to false: `!live_after.is_live(place)` + live_after = LivePlaces { accessed: {d}, traversed: {} } + place = d"#]]), interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } Output: Trace: _1_d . drop ; Output: Trace: _1_d . mut ; - Result: Fault: access of uninitialized value"#]] + Result: Fault: access of uninitialized value"#]]) ); } #[test] fn mut_of_copy_type_faults() { // Cannot take a mut ref of a copy type (no flags). - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Main { fn main(given self) -> Int { @@ -1362,13 +1417,20 @@ fn mut_of_copy_type_faults() { } } }, - expect_test::expect![[r#" + type: error(expect_test::expect![[r#" + the rule "class move" at (predicates.rs) failed because + pattern `false` did not match value `true` + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: Int, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, x: Int}, assumptions: {}, fresh: 0 } } + + the rule "shared-class move" at (predicates.rs) failed because + expression evaluated to an empty collection: `parameters`"#]]), interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_x = 42 ; Output: Trace: _1_x = 42 Output: Trace: _1_x . mut ; Result: Fault: cannot take mutable reference to shared value - Alloc 0x02: [Int(42)]"#]] + Alloc 0x02: [Int(42)]"#]]) ); } @@ -1384,7 +1446,7 @@ fn mut_of_array_create_and_drop() { // (2-word layout). A MutRef.give produces a 1-word MutRef allocation. // MutRef+array interaction requires method calls (mut self) or // teaching array ops to dereference through MutRef. - crate::assert_interpret_only!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -1396,7 +1458,7 @@ fn mut_of_array_create_and_drop() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_a = array_new [Data](2) ; Output: Trace: _1_a = Array { flag: Given, rc: 1, Data { x: ⚡ }, Data { x: ⚡ } } @@ -1406,7 +1468,7 @@ fn mut_of_array_create_and_drop() { Output: Trace: array_capacity [Data, given](_1_a . give) ; Output: Trace: exit Main.main => 2 Result: Ok: 2 - Alloc 0x0a: [Int(2)]"#]] + Alloc 0x0a: [Int(2)]"#]]) ); } @@ -1421,7 +1483,7 @@ fn mut_dangling_after_give() { // still points at it. resolve_place_to_object dereferences // through the MutRef and finds uninitialized data, so the // give faults. The type system prevents this in well-typed programs. - crate::assert_interpret_fault!( + crate::assert_interpret!( { class Data { x: Int; } class Main { @@ -1433,7 +1495,15 @@ fn mut_dangling_after_give() { } } }, - expect_test::expect![[r#" + type: error(expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, m: mut [stolen] Data, stolen: Data}, assumptions: {}, fresh: 0 } } + + the rule "keep non-popped link" at (pop_normalize.rs) failed because + condition evaluated to false: `!link_references_popped(&link, &popped_vars)` + &link = Mtd(stolen) + &popped_vars = [d, m, stolen] + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, m: mut [stolen] Data, stolen: Data}, assumptions: {}, fresh: 0 } }"#]]), interpret: fault(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } @@ -1444,6 +1514,6 @@ fn mut_dangling_after_give() { Output: Trace: _1_m . give ; Result: Fault: access of uninitialized value Alloc 0x05: [MutRef(0x03)] - Alloc 0x07: [Int(42)]"#]] + Alloc 0x07: [Int(42)]"#]]) ); } diff --git a/src/interpreter/tests/share.rs b/src/interpreter/tests/share.rs index 7cec12a..f49d7bb 100644 --- a/src/interpreter/tests/share.rs +++ b/src/interpreter/tests/share.rs @@ -1,45 +1,3 @@ -/// Sharing an outer class whose field has Flags::Borrowed should leave that -/// field untouched (no-op, per spec). Without the fix, `convert_to_shared` -/// recurses past the Borrowed field into its sub-fields and incorrectly -/// flips their Given flags to Shared. -#[test] -fn share_skips_borrowed_subfield() { - // Outer { mid: Mid { inner: Inner { x: Int } } } - // After `m.ref`, Mid's flags word is Borrowed. - // Constructing Outer from that borrowed Mid buries Flags::Borrowed inside Outer. - // Sharing Outer should flip Outer→Shared but leave Mid's content unchanged. - crate::assert_interpret_only!( - { - class Inner { x: Int; } - class Mid { inner: Inner; } - class Outer { mid: Mid; } - class Main { - fn main(given self) -> shared Outer { - let i = new Inner(42); - let m = new Mid(i.give); - let r = m.ref; - let o = new Outer(r.give); - o.give.share; - } - } - }, - expect_test::expect![[r#" - Output: Trace: enter Main.main - Output: Trace: let _1_i = new Inner (42) ; - Output: Trace: _1_i = Inner { x: 42 } - Output: Trace: let _1_m = new Mid (_1_i . give) ; - Output: Trace: _1_m = Mid { inner: Inner { x: 42 } } - Output: Trace: let _1_r = _1_m . ref ; - Output: Trace: _1_r = ref [_1_m] Mid { inner: Inner { x: 42 } } - Output: Trace: let _1_o = new Outer (_1_r . give) ; - Output: Trace: _1_o = Outer { mid: Mid { inner: Inner { x: 42 } } } - Output: Trace: _1_o . give . share ; - Output: Trace: exit Main.main => shared Outer { mid: Mid { inner: Inner { x: 42 } } } - Result: Ok: shared Outer { mid: Mid { inner: Inner { x: 42 } } } - Alloc 0x0d: [Int(42)]"#]] - ); -} - #[test] fn share_class() { // Using .share on a regular class flips its flag to Shared. @@ -56,13 +14,13 @@ fn share_class() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_d = new Data (42) ; Output: Trace: _1_d = Data { x: 42 } Output: Trace: _1_d . give . share ; Output: Trace: exit Main.main => shared Data { x: 42 } Result: Ok: shared Data { x: 42 } - Alloc 0x05: [Int(42)]"#]] + Alloc 0x05: [Int(42)]"#]]) ); } diff --git a/src/interpreter/tests/size_of.rs b/src/interpreter/tests/size_of.rs index a19332c..6a9b459 100644 --- a/src/interpreter/tests/size_of.rs +++ b/src/interpreter/tests/size_of.rs @@ -8,12 +8,12 @@ fn size_of_int() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: size_of [Int]() ; Output: Trace: exit Main.main => 1 Result: Ok: 1 - Alloc 0x02: [Int(1)]"#]] + Alloc 0x02: [Int(1)]"#]]) ); } @@ -32,12 +32,12 @@ fn size_of_class_with_fields() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: size_of [Point]() ; Output: Trace: exit Main.main => 2 Result: Ok: 2 - Alloc 0x02: [Int(2)]"#]] + Alloc 0x02: [Int(2)]"#]]) ); } @@ -56,12 +56,12 @@ fn size_of_struct_class() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: size_of [Pair]() ; Output: Trace: exit Main.main => 2 Result: Ok: 2 - Alloc 0x02: [Int(2)]"#]] + Alloc 0x02: [Int(2)]"#]]) ); } @@ -77,12 +77,12 @@ fn size_of_empty_class() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: size_of [Empty]() ; Output: Trace: exit Main.main => 0 Result: Ok: 0 - Alloc 0x02: [Int(0)]"#]] + Alloc 0x02: [Int(0)]"#]]) ); } @@ -104,12 +104,12 @@ fn size_of_nested_class() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: size_of [Outer]() ; Output: Trace: exit Main.main => 1 Result: Ok: 1 - Alloc 0x02: [Int(1)]"#]] + Alloc 0x02: [Int(1)]"#]]) ); } @@ -128,11 +128,11 @@ fn size_of_in_arithmetic() { } } }, - expect_test::expect![[r#" + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: size_of [Point]() + size_of [Int]() ; Output: Trace: exit Main.main => 3 Result: Ok: 3 - Alloc 0x04: [Int(3)]"#]] + Alloc 0x04: [Int(3)]"#]]) ); } diff --git a/src/interpreter/tests/vector.rs b/src/interpreter/tests/vector.rs index 54cbda5..34c6a39 100644 --- a/src/interpreter/tests/vector.rs +++ b/src/interpreter/tests/vector.rs @@ -3,9 +3,7 @@ /// These tests exercise the full stack: classes with drop bodies, is_last_ref, /// array intrinsics with poly-permission dispatch, and whole-place drop semantics. /// -/// Most tests use `vec_test!` which injects the standard Vec and Iterator -/// definitions and runs an interpreter-only test (the type checker doesn't yet -/// fully support the permission patterns Vec uses). +/// These tests exercise the full stack with the type checker and interpreter. /// Returns the standard Vec and Iterator class definitions from the design doc. /// @@ -32,12 +30,12 @@ fn vec_prelude() -> &'static str { (); } - fn get[perm P](P self, index: Int) -> given_from[self] T { - let data: given_from[self.data] Array[T] = self.data.give; + fn get[perm P](P self, index: Int) -> given[self] T { + let data: given[self.data] Array[T] = self.data.give; let len: Int = self.len.give; - array_drop[T, given_from[self], ref[data]](data.ref, 0, index.give); - array_drop[T, given_from[self], ref[data]](data.ref, index.give + 1, len.give); - array_give[T, given_from[self], ref[data]](data.ref, index.give); + array_drop[T, given[self], ref[data]](data.ref, 0, index.give); + array_drop[T, given[self], ref[data]](data.ref, index.give + 1, len.give); + array_give[T, given[self], ref[data]](data.ref, index.give); } fn iter[perm P](P self) -> Iterator[P, T] { @@ -53,7 +51,10 @@ fn vec_prelude() -> &'static str { } } - class Iterator[perm P, ty T] { + class Iterator[perm P, ty T] + where + T is relative, + { vec: P Vec[T]; start: Int; @@ -67,7 +68,7 @@ fn vec_prelude() -> &'static str { } drop { - let data: given_from[self.vec.data] Array[T] = self.vec.data.give; + let data: given[self.vec.data] Array[T] = self.vec.data.give; let start: Int = self.start.give; let len: Int = self.vec.len.give; array_drop[T, P, ref[data]](data.ref, start.give, len.give); @@ -76,28 +77,14 @@ fn vec_prelude() -> &'static str { "# } -/// Runs an interpreter-only test with the Vec prelude prepended. -macro_rules! vec_test { - ({ $($extra:tt)* }, $expect:expr) => {{ - let program = format!("{} {}", vec_prelude(), stringify!($($extra)*)); - let r = $crate::test_util::test_interpret_only(&program) - .expect("parse error"); - assert!( - r.result.starts_with("Ok:"), - "unexpected interpreter fault:\n{}", - r.to_snapshot(), - ); - $expect.assert_eq(&r.to_snapshot()); - }}; -} - // --------------------------------------------------------------- // Vec push basics // --------------------------------------------------------------- #[test] fn vec_push_increments_len() { - vec_test!({ + crate::assert_interpret!(prefix: vec_prelude(), + { class Main { fn main(given self) -> () { let v: given Vec[Int] = new Vec[Int](array_new[Int](4), 0); @@ -106,7 +93,8 @@ fn vec_push_increments_len() { (); } } - }, expect_test::expect![[r#" + }, + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_v : given Vec[Int] = new Vec [Int] (array_new [Int](4), 0) ; Output: Trace: _1_v = Vec { data: Array { flag: Given, rc: 1, ⚡, ⚡, ⚡, ⚡ }, len: 0 } @@ -124,7 +112,8 @@ fn vec_push_increments_len() { Output: Trace: if is_last_ref [ref [self . data]](self . data . ref) { array_drop [Int, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; } else { () ; } ; Output: Trace: array_drop [Int, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]]); + Result: Ok: ()"#]]) + ); } // --------------------------------------------------------------- @@ -135,7 +124,8 @@ fn vec_push_increments_len() { fn vec_push_and_get_given() { // Push 3 elements, then get(1) with P=given. // Drops elements 0 and 2, returns element 1. - vec_test!({ + crate::assert_interpret!(prefix: vec_prelude(), + { class Data { value: Int; @@ -155,7 +145,8 @@ fn vec_push_and_get_given() { (); } } - }, expect_test::expect![[r#" + }, + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_v : given Vec[Data] = new Vec [Data] (array_new [Data](4), 0) ; Output: Trace: _1_v = Vec { data: Array { flag: Given, rc: 1, Data { value: ⚡ }, Data { value: ⚡ }, Data { value: ⚡ }, Data { value: ⚡ } }, len: 0 } @@ -182,21 +173,21 @@ fn vec_push_and_get_given() { Output: Trace: exit Vec.push => () Output: Trace: let _1_got : given Data = _1_v . give . get [given] (1) ; Output: Trace: enter Vec.get - Output: Trace: let _5_data : given_from [_5_self . data] Array[Data] = _5_self . data . give ; + Output: Trace: let _5_data : given [_5_self . data] Array[Data] = _5_self . data . give ; Output: Trace: _5_data = Array { flag: Given, rc: 1, Data { value: 10 }, Data { value: 20 }, Data { value: 30 }, Data { value: ⚡ } } Output: Trace: let _5_len : Int = _5_self . len . give ; Output: Trace: _5_len = 3 - Output: Trace: array_drop [Data, given_from [_5_self], ref [_5_data]](_5_data . ref , 0 , _5_index . give) ; + Output: Trace: array_drop [Data, given [_5_self], ref [_5_data]](_5_data . ref , 0 , _5_index . give) ; Output: Trace: drop Data Output: Trace: print(self . value . give) ; Output: -----> 10 - Output: Trace: array_drop [Data, given_from [_5_self], ref [_5_data]](_5_data . ref , _5_index . give + 1 , _5_len . give) ; + Output: Trace: array_drop [Data, given [_5_self], ref [_5_data]](_5_data . ref , _5_index . give + 1 , _5_len . give) ; Output: Trace: drop Data Output: Trace: print(self . value . give) ; Output: -----> 30 - Output: Trace: array_give [Data, given_from [_5_self], ref [_5_data]](_5_data . ref , _5_index . give) ; - Output: Trace: exit Vec.get => given_from [_5_self] Data { value: 20 } - Output: Trace: _1_got = given_from [_5_self] Data { value: 20 } + Output: Trace: array_give [Data, given [_5_self], ref [_5_data]](_5_data . ref , _5_index . give) ; + Output: Trace: exit Vec.get => Data { value: 20 } + Output: Trace: _1_got = Data { value: 20 } Output: Trace: print(_1_got . value . give) ; Output: -----> 20 Output: Trace: () ; @@ -204,7 +195,8 @@ fn vec_push_and_get_given() { Output: Trace: print(self . value . give) ; Output: -----> 20 Output: Trace: exit Main.main => () - Result: Ok: ()"#]]); + Result: Ok: ()"#]]) + ); } // --------------------------------------------------------------- @@ -213,7 +205,8 @@ fn vec_push_and_get_given() { #[test] fn vec_drop_cleans_all_elements() { - vec_test!({ + crate::assert_interpret!(prefix: vec_prelude(), + { class Item { val: Int; @@ -231,7 +224,8 @@ fn vec_drop_cleans_all_elements() { (); } } - }, expect_test::expect![[r#" + }, + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_v : given Vec[Item] = new Vec [Item] (array_new [Item](4), 0) ; Output: Trace: _1_v = Vec { data: Array { flag: Given, rc: 1, Item { val: ⚡ }, Item { val: ⚡ }, Item { val: ⚡ }, Item { val: ⚡ } }, len: 0 } @@ -270,7 +264,8 @@ fn vec_drop_cleans_all_elements() { Output: Trace: print(self . val . give) ; Output: -----> 300 Output: Trace: exit Main.main => () - Result: Ok: ()"#]]); + Result: Ok: ()"#]]) + ); } // --------------------------------------------------------------- @@ -280,7 +275,8 @@ fn vec_drop_cleans_all_elements() { #[test] fn vec_iter_and_next() { // Consuming iterator: next() moves element 0, drop cleans 1 and 2. - vec_test!({ + crate::assert_interpret!(prefix: vec_prelude(), + { class Item { val: Int; @@ -301,7 +297,8 @@ fn vec_iter_and_next() { (); } } - }, expect_test::expect![[r#" + }, + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_v : given Vec[Item] = new Vec [Item] (array_new [Item](4), 0) ; Output: Trace: _1_v = Vec { data: Array { flag: Given, rc: 1, Item { val: ⚡ }, Item { val: ⚡ }, Item { val: ⚡ }, Item { val: ⚡ } }, len: 0 } @@ -347,7 +344,7 @@ fn vec_iter_and_next() { Output: Trace: print(self . val . give) ; Output: -----> 10 Output: Trace: drop Iterator - Output: Trace: let data : given_from [self . vec . data] Array[Item] = self . vec . data . give ; + Output: Trace: let data : given [self . vec . data] Array[Item] = self . vec . data . give ; Output: Trace: data = ref [@ magic] Array { flag: Borrowed, rc: 1, Item { val: ⚡ }, Item { val: 20 }, Item { val: 30 }, Item { val: ⚡ } } Output: Trace: let start : Int = self . start . give ; Output: Trace: start = 1 @@ -361,7 +358,8 @@ fn vec_iter_and_next() { Output: Trace: print(self . val . give) ; Output: -----> 30 Output: Trace: exit Main.main => () - Result: Ok: ()"#]]); + Result: Ok: ()"#]]) + ); } // --------------------------------------------------------------- @@ -371,7 +369,8 @@ fn vec_iter_and_next() { #[test] fn shared_vec_get() { // P=shared: array_drop is no-op, array_give copies. - vec_test!({ + crate::assert_interpret!(prefix: vec_prelude(), + { class Data { value: Int; } @@ -387,7 +386,8 @@ fn shared_vec_get() { (); } } - }, expect_test::expect![[r#" + }, + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_v : given Vec[Data] = new Vec [Data] (array_new [Data](4), 0) ; Output: Trace: _1_v = Vec { data: Array { flag: Given, rc: 1, Data { value: ⚡ }, Data { value: ⚡ }, Data { value: ⚡ }, Data { value: ⚡ } }, len: 0 } @@ -409,26 +409,27 @@ fn shared_vec_get() { Output: Trace: _1_sv = shared Vec { data: Array { flag: Shared, rc: 1, Data { value: 10 }, Data { value: 20 }, Data { value: ⚡ }, Data { value: ⚡ } }, len: 2 } Output: Trace: let _1_got : shared Data = _1_sv . give . get [shared] (0) ; Output: Trace: enter Vec.get - Output: Trace: let _4_data : given_from [_4_self . data] Array[Data] = _4_self . data . give ; + Output: Trace: let _4_data : given [_4_self . data] Array[Data] = _4_self . data . give ; Output: Trace: _4_data = shared Array { flag: Shared, rc: 3, Data { value: 10 }, Data { value: 20 }, Data { value: ⚡ }, Data { value: ⚡ } } Output: Trace: let _4_len : Int = _4_self . len . give ; Output: Trace: _4_len = 2 - Output: Trace: array_drop [Data, given_from [_4_self], ref [_4_data]](_4_data . ref , 0 , _4_index . give) ; - Output: Trace: array_drop [Data, given_from [_4_self], ref [_4_data]](_4_data . ref , _4_index . give + 1 , _4_len . give) ; - Output: Trace: array_give [Data, given_from [_4_self], ref [_4_data]](_4_data . ref , _4_index . give) ; + Output: Trace: array_drop [Data, given [_4_self], ref [_4_data]](_4_data . ref , 0 , _4_index . give) ; + Output: Trace: array_drop [Data, given [_4_self], ref [_4_data]](_4_data . ref , _4_index . give + 1 , _4_len . give) ; + Output: Trace: array_give [Data, given [_4_self], ref [_4_data]](_4_data . ref , _4_index . give) ; Output: Trace: drop Vec Output: Trace: if is_last_ref [ref [self . data]](self . data . ref) { array_drop [Data, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; } else { () ; } ; Output: Trace: () ; - Output: Trace: exit Vec.get => given_from [_4_self] Data { value: 10 } - Output: Trace: _1_got = given_from [_4_self] Data { value: 10 } + Output: Trace: exit Vec.get => shared Data { value: 10 } + Output: Trace: _1_got = shared Data { value: 10 } Output: Trace: print(_1_got . give) ; - Output: -----> given_from [_4_self] Data { value: 10 } + Output: -----> shared Data { value: 10 } Output: Trace: () ; Output: Trace: drop Vec Output: Trace: if is_last_ref [ref [self . data]](self . data . ref) { array_drop [Data, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; } else { () ; } ; Output: Trace: array_drop [Data, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]]); + Result: Ok: ()"#]]) + ); } // --------------------------------------------------------------- @@ -438,7 +439,8 @@ fn shared_vec_get() { #[test] fn ref_vec_get() { // P=ref: elements are borrows, Vec remains intact. - vec_test!({ + crate::assert_interpret!(prefix: vec_prelude(), + { class Data { value: Int; } @@ -453,7 +455,8 @@ fn ref_vec_get() { (); } } - }, expect_test::expect![[r#" + }, + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_v : given Vec[Data] = new Vec [Data] (array_new [Data](4), 0) ; Output: Trace: _1_v = Vec { data: Array { flag: Given, rc: 1, Data { value: ⚡ }, Data { value: ⚡ }, Data { value: ⚡ }, Data { value: ⚡ } }, len: 0 } @@ -473,15 +476,15 @@ fn ref_vec_get() { Output: Trace: exit Vec.push => () Output: Trace: let _1_got : ref [_1_v] Data = _1_v . ref . get [ref [_1_v]] (1) ; Output: Trace: enter Vec.get - Output: Trace: let _4_data : given_from [_4_self . data] Array[Data] = _4_self . data . give ; + Output: Trace: let _4_data : given [_4_self . data] Array[Data] = _4_self . data . give ; Output: Trace: _4_data = ref [_1_v] Array { flag: Borrowed, rc: 1, Data { value: 10 }, Data { value: 20 }, Data { value: ⚡ }, Data { value: ⚡ } } Output: Trace: let _4_len : Int = _4_self . len . give ; Output: Trace: _4_len = 2 - Output: Trace: array_drop [Data, given_from [_4_self], ref [_4_data]](_4_data . ref , 0 , _4_index . give) ; - Output: Trace: array_drop [Data, given_from [_4_self], ref [_4_data]](_4_data . ref , _4_index . give + 1 , _4_len . give) ; - Output: Trace: array_give [Data, given_from [_4_self], ref [_4_data]](_4_data . ref , _4_index . give) ; - Output: Trace: exit Vec.get => given_from [_4_self] Data { value: 20 } - Output: Trace: _1_got = given_from [_4_self] Data { value: 20 } + Output: Trace: array_drop [Data, given [_4_self], ref [_4_data]](_4_data . ref , 0 , _4_index . give) ; + Output: Trace: array_drop [Data, given [_4_self], ref [_4_data]](_4_data . ref , _4_index . give + 1 , _4_len . give) ; + Output: Trace: array_give [Data, given [_4_self], ref [_4_data]](_4_data . ref , _4_index . give) ; + Output: Trace: exit Vec.get => ref [_1_v] Data { value: 20 } + Output: Trace: _1_got = ref [_1_v] Data { value: 20 } Output: Trace: print(_1_got . value . give) ; Output: -----> 20 Output: Trace: () ; @@ -489,7 +492,8 @@ fn ref_vec_get() { Output: Trace: if is_last_ref [ref [self . data]](self . data . ref) { array_drop [Data, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; } else { () ; } ; Output: Trace: array_drop [Data, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]]); + Result: Ok: ()"#]]) + ); } // --------------------------------------------------------------- @@ -501,7 +505,8 @@ fn nested_vec_get_given_drops_others() { // Vec[Vec[Int]]: push 3 inner Vecs, get(1) with P=given. // Inner Vecs at 0 and 2 are dropped (their drop bodies run, // cleaning their elements). Element 1 is returned. - vec_test!({ + crate::assert_interpret!(prefix: vec_prelude(), + { class Main { fn main(given self) -> () { let inner0: given Vec[Int] = new Vec[Int](array_new[Int](2), 0); @@ -523,7 +528,8 @@ fn nested_vec_get_given_drops_others() { (); } } - }, expect_test::expect![[r#" + }, + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_inner0 : given Vec[Int] = new Vec [Int] (array_new [Int](2), 0) ; Output: Trace: _1_inner0 = Vec { data: Array { flag: Given, rc: 1, ⚡, ⚡ }, len: 0 } @@ -577,21 +583,21 @@ fn nested_vec_get_given_drops_others() { Output: Trace: exit Vec.push => () Output: Trace: let _1_got : given Vec[Int] = _1_outer . give . get [given] (1) ; Output: Trace: enter Vec.get - Output: Trace: let _8_data : given_from [_8_self . data] Array[Vec[Int]] = _8_self . data . give ; + Output: Trace: let _8_data : given [_8_self . data] Array[Vec[Int]] = _8_self . data . give ; Output: Trace: _8_data = Array { flag: Given, rc: 1, Vec { data: Array { flag: Given, rc: 1, 100, ⚡ }, len: 1 }, Vec { data: Array { flag: Given, rc: 1, 200, ⚡ }, len: 1 }, Vec { data: Array { flag: Given, rc: 1, 300, ⚡ }, len: 1 }, Vec { data: ⚡, len: ⚡ } } Output: Trace: let _8_len : Int = _8_self . len . give ; Output: Trace: _8_len = 3 - Output: Trace: array_drop [Vec[Int], given_from [_8_self], ref [_8_data]](_8_data . ref , 0 , _8_index . give) ; + Output: Trace: array_drop [Vec[Int], given [_8_self], ref [_8_data]](_8_data . ref , 0 , _8_index . give) ; Output: Trace: drop Vec Output: Trace: if is_last_ref [ref [self . data]](self . data . ref) { array_drop [Int, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; } else { () ; } ; Output: Trace: array_drop [Int, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; - Output: Trace: array_drop [Vec[Int], given_from [_8_self], ref [_8_data]](_8_data . ref , _8_index . give + 1 , _8_len . give) ; + Output: Trace: array_drop [Vec[Int], given [_8_self], ref [_8_data]](_8_data . ref , _8_index . give + 1 , _8_len . give) ; Output: Trace: drop Vec Output: Trace: if is_last_ref [ref [self . data]](self . data . ref) { array_drop [Int, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; } else { () ; } ; Output: Trace: array_drop [Int, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; - Output: Trace: array_give [Vec[Int], given_from [_8_self], ref [_8_data]](_8_data . ref , _8_index . give) ; - Output: Trace: exit Vec.get => given_from [_8_self] Vec { data: Array { flag: Given, rc: 1, 200, ⚡ }, len: 1 } - Output: Trace: _1_got = given_from [_8_self] Vec { data: Array { flag: Given, rc: 1, 200, ⚡ }, len: 1 } + Output: Trace: array_give [Vec[Int], given [_8_self], ref [_8_data]](_8_data . ref , _8_index . give) ; + Output: Trace: exit Vec.get => Vec { data: Array { flag: Given, rc: 1, 200, ⚡ }, len: 1 } + Output: Trace: _1_got = Vec { data: Array { flag: Given, rc: 1, 200, ⚡ }, len: 1 } Output: Trace: print(_1_got . len . give) ; Output: -----> 1 Output: Trace: () ; @@ -599,7 +605,8 @@ fn nested_vec_get_given_drops_others() { Output: Trace: if is_last_ref [ref [self . data]](self . data . ref) { array_drop [Int, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; } else { () ; } ; Output: Trace: array_drop [Int, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]]); + Result: Ok: ()"#]]) + ); } // --------------------------------------------------------------- @@ -610,7 +617,8 @@ fn nested_vec_get_given_drops_others() { fn vec_mut_ref_to_flat_element() { // array_give with P=mut on a flat Data class. // Returns a MutRef pointing into the array backing. - vec_test!({ + crate::assert_interpret!(prefix: vec_prelude(), + { class Data { x: Int; } @@ -624,7 +632,8 @@ fn vec_mut_ref_to_flat_element() { (); } } - }, expect_test::expect![[r#" + }, + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_v : given Vec[Data] = new Vec [Data] (array_new [Data](4), 0) ; Output: Trace: _1_v = Vec { data: Array { flag: Given, rc: 1, Data { x: ⚡ }, Data { x: ⚡ }, Data { x: ⚡ }, Data { x: ⚡ } }, len: 0 } @@ -644,7 +653,8 @@ fn vec_mut_ref_to_flat_element() { Output: Trace: if is_last_ref [ref [self . data]](self . data . ref) { array_drop [Data, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; } else { () ; } ; Output: Trace: array_drop [Data, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]]); + Result: Ok: ()"#]]) + ); } // --------------------------------------------------------------- @@ -655,7 +665,8 @@ fn vec_mut_ref_to_flat_element() { fn vec_mut_ref_to_boxed_element() { // array_give with P=mut on a boxed Array[Int] element. // Returns a MutRef to the inner array's data. - vec_test!({ + crate::assert_interpret!(prefix: vec_prelude(), + { class Main { fn main(given self) -> () { let outer: given Vec[Array[Int]] = new Vec[Array[Int]](array_new[Array[Int]](4), 0); @@ -667,7 +678,8 @@ fn vec_mut_ref_to_boxed_element() { (); } } - }, expect_test::expect![[r#" + }, + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_outer : given Vec[Array[Int]] = new Vec [Array[Int]] (array_new [Array[Int]](4), 0) ; Output: Trace: _1_outer = Vec { data: Array { flag: Given, rc: 1, ⚡, ⚡, ⚡, ⚡ }, len: 0 } @@ -690,7 +702,8 @@ fn vec_mut_ref_to_boxed_element() { Output: Trace: if is_last_ref [ref [self . data]](self . data . ref) { array_drop [Array[Int], given, ref [self . data]](self . data . ref , 0 , self . len . give) ; } else { () ; } ; Output: Trace: array_drop [Array[Int], given, ref [self . data]](self . data . ref , 0 , self . len . give) ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]]); + Result: Ok: ()"#]]) + ); } // --------------------------------------------------------------- @@ -698,9 +711,9 @@ fn vec_mut_ref_to_boxed_element() { // --------------------------------------------------------------- /// Calls Vec.get through a mut ref. Inside the method body, P is -/// substituted to mut[v], and array_give receives given_from[self] +/// substituted to mut[v], and array_give receives given[self] /// where self: mut[v] Vec[Data]. perm_to_operms must classify -/// given_from[self] as MutRef, which requires resolving through +/// given[self] as MutRef, which requires resolving through /// self -> mut[v] -> v (a caller-scope variable). /// /// EXPECTED: elem should be a mut ref to the array element. @@ -713,7 +726,8 @@ fn vec_mut_ref_to_boxed_element() { /// method's env. Fixed by Phase 3 (fresh names + caller env extension). #[test] fn vec_get_through_mut_ref() { - vec_test!({ + crate::assert_interpret!(prefix: vec_prelude(), + { class Data { x: Int; } @@ -727,7 +741,8 @@ fn vec_get_through_mut_ref() { (); } } - }, expect_test::expect![[r#" + }, + type: ok, interpret: ok(expect_test::expect![[r#" Output: Trace: enter Main.main Output: Trace: let _1_v : given Vec[Data] = new Vec [Data] (array_new [Data](4), 0) ; Output: Trace: _1_v = Vec { data: Array { flag: Given, rc: 1, Data { x: ⚡ }, Data { x: ⚡ }, Data { x: ⚡ }, Data { x: ⚡ } }, len: 0 } @@ -740,15 +755,15 @@ fn vec_get_through_mut_ref() { Output: Trace: exit Vec.push => () Output: Trace: let _1_elem = _1_v . mut . get [mut [_1_v]] (0) ; Output: Trace: enter Vec.get - Output: Trace: let _3_data : given_from [_3_self . data] Array[Data] = _3_self . data . give ; + Output: Trace: let _3_data : given [_3_self . data] Array[Data] = _3_self . data . give ; Output: Trace: _3_data = mut [_1_v] Output: Trace: let _3_len : Int = _3_self . len . give ; Output: Trace: _3_len = 1 - Output: Trace: array_drop [Data, given_from [_3_self], ref [_3_data]](_3_data . ref , 0 , _3_index . give) ; - Output: Trace: array_drop [Data, given_from [_3_self], ref [_3_data]](_3_data . ref , _3_index . give + 1 , _3_len . give) ; - Output: Trace: array_give [Data, given_from [_3_self], ref [_3_data]](_3_data . ref , _3_index . give) ; - Output: Trace: exit Vec.get => given_from [_3_self] Data { x: 42 } - Output: Trace: _1_elem = given_from [_3_self] Data { x: 42 } + Output: Trace: array_drop [Data, given [_3_self], ref [_3_data]](_3_data . ref , 0 , _3_index . give) ; + Output: Trace: array_drop [Data, given [_3_self], ref [_3_data]](_3_data . ref , _3_index . give + 1 , _3_len . give) ; + Output: Trace: array_give [Data, given [_3_self], ref [_3_data]](_3_data . ref , _3_index . give) ; + Output: Trace: exit Vec.get => mut [_1_v] Data { x: 42 } + Output: Trace: _1_elem = mut [_1_v] Data { x: 42 } Output: Trace: print(_1_elem . x . give) ; Output: -----> 42 Output: Trace: () ; @@ -756,5 +771,6 @@ fn vec_get_through_mut_ref() { Output: Trace: if is_last_ref [ref [self . data]](self . data . ref) { array_drop [Data, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; } else { () ; } ; Output: Trace: array_drop [Data, given, ref [self . data]](self . data . ref , 0 , self . len . give) ; Output: Trace: exit Main.main => () - Result: Ok: ()"#]]); + Result: Ok: ()"#]]) + ); } diff --git a/src/lib.rs b/src/lib.rs index 5f92318..deaf6ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,8 +4,10 @@ use clap::Parser; use dada_lang::FormalityLang; use fn_error_context::context; use formality_core::Fallible; +use elaborator::ElaboratedProgram; use grammar::Program; +pub mod elaborator; pub mod grammar; pub mod interpreter; pub mod test_util; @@ -38,7 +40,6 @@ formality_core::declare_language! { "fn", "give", "given", - "given_from", "if", "Int", "is", @@ -81,6 +82,7 @@ pub fn main() -> Fallible<()> { fn check_file(path: &str) -> Fallible<()> { let text: String = std::fs::read_to_string(path)?; let program: Arc = dada_lang::try_term(&text)?; - let ((), _proof_tree) = type_system::check_program(&program).into_singleton()?; + let elaborated = ElaboratedProgram::elaborate(&program); + let ((), _proof_tree) = type_system::check_program(&elaborated).into_singleton()?; Ok(()) } diff --git a/src/test_util.rs b/src/test_util.rs index 5b8fac4..1c902ea 100644 --- a/src/test_util.rs +++ b/src/test_util.rs @@ -4,13 +4,15 @@ use formality_core::judgment::{FailedJudgment, ProofTree}; use formality_core::Fallible; use crate::dada_lang; +use crate::elaborator::ElaboratedProgram; use crate::grammar::Program; use crate::interpreter::Interpreter; use crate::type_system; pub fn test_program_ok(input: &str) -> Fallible { let program: Arc = dada_lang::try_term(input)?; - let ((), proof_tree) = type_system::check_program(&program).into_singleton()?; + let elaborated = ElaboratedProgram::elaborate(&program); + let ((), proof_tree) = type_system::check_program(&elaborated).into_singleton()?; Ok(proof_tree) } @@ -41,21 +43,46 @@ impl InterpretResult { } } -pub fn test_interpret(input: &str) -> anyhow::Result { - let program: Arc = dada_lang::try_term(input)?; - let ((), _proof_tree) = type_system::check_program(&program).into_singleton()?; - Ok(run_interpreter(&program)) +/// Parse input fragments (concatenated), return the program. Panics on parse error. +pub fn parse_program(inputs: &[&str]) -> ElaboratedProgram { + let combined: String = inputs.concat(); + let program: Arc = dada_lang::try_term(&combined).expect("parse error"); + ElaboratedProgram::elaborate(&program) } -/// Interpret without type-checking first. -/// Useful for testing interpreter behavior on programs the type checker would reject. -pub fn test_interpret_only(input: &str) -> anyhow::Result { - let program: Arc = dada_lang::try_term(input)?; - Ok(run_interpreter(&program)) +/// Assert the type checker passes. Panics with the error if it fails. +pub fn assert_type_ok(program: &ElaboratedProgram) { + match type_system::check_program(program).into_singleton() { + Ok(_proof_tree) => {} + Err(e) => { + panic!("expected type checker to pass, but it failed:\n{e}"); + } + } +} + +/// Assert the type checker fails. Returns the error string for snapshot comparison. +/// Panics if the type checker passes. +pub fn assert_type_err(program: &ElaboratedProgram) -> String { + match type_system::check_program(program).into_singleton() { + Ok(proof_tree) => panic!("expected type checker to fail, but it passed: {proof_tree:?}"), + Err(e) => { + println!("full error:\n\n{e}"); + formality_core::test_util::normalize_paths(e.format_leaves()) + } + } +} + +/// Assert the interpreter result starts with the given prefix ("Ok:" or "Fault:"). +pub fn assert_interpret_result(r: &InterpretResult, expected_prefix: &str) { + assert!( + r.result.starts_with(expected_prefix), + "expected interpreter result starting with {expected_prefix:?}, got:\n{}", + r.to_snapshot(), + ); } -fn run_interpreter(program: &Arc) -> InterpretResult { - let mut interp = Interpreter::new(program); +pub fn run_interpreter(program: &ElaboratedProgram) -> InterpretResult { + let mut interp = Interpreter::new(program.clone()); let result = interp.interpret(); let result_str = result .and_then(|v| interp.display_value(&crate::type_system::env::Env::new(program.clone()), &v)) @@ -127,47 +154,41 @@ macro_rules! assert_err { #[macro_export] macro_rules! assert_interpret { - ({ $($input:tt)* }, $expect:expr) => {{ - let r = $crate::test_util::test_interpret(stringify!($($input)*)) - .expect("parse/typecheck error"); - assert!( - r.result.starts_with("Ok:"), - "unexpected interpreter fault: {}", - r.result, - ); - $expect.assert_eq(&r.to_snapshot()); + // type: ok, interpret: ok + ($(prefix: $prefix:expr,)? { $($input:tt)* }, type: ok, interpret: ok($interp_expect:expr)) => {{ + let program = $crate::test_util::parse_program(&[$($prefix,)? stringify!($($input)*)]); + $crate::test_util::assert_type_ok(&program); + let r = $crate::test_util::run_interpreter(&program); + $crate::test_util::assert_interpret_result(&r, "Ok:"); + $interp_expect.assert_eq(&r.to_snapshot()); }}; -} -/// Like `assert_interpret!` but skips type-checking. -/// Use this to test interpreter behavior on programs the type checker would reject. -#[macro_export] -macro_rules! assert_interpret_only { - ({ $($input:tt)* }, $expect:expr) => {{ - let r = $crate::test_util::test_interpret_only(stringify!($($input)*)) - .expect("parse error"); - assert!( - r.result.starts_with("Ok:"), - "unexpected interpreter fault:\n{}", - r.to_snapshot(), - ); - $expect.assert_eq(&r.to_snapshot()); + // type: ok, interpret: fault + ($(prefix: $prefix:expr,)? { $($input:tt)* }, type: ok, interpret: fault($interp_expect:expr)) => {{ + let program = $crate::test_util::parse_program(&[$($prefix,)? stringify!($($input)*)]); + $crate::test_util::assert_type_ok(&program); + let r = $crate::test_util::run_interpreter(&program); + $crate::test_util::assert_interpret_result(&r, "Fault:"); + $interp_expect.assert_eq(&r.to_snapshot()); }}; -} -/// Like `assert_interpret_only!` but expects the interpreter to fault. -/// Skips type-checking — use this to verify that UB programs are caught at runtime. -/// Panics if the result does not contain "Fault:", preventing UPDATE_EXPECT drift. -#[macro_export] -macro_rules! assert_interpret_fault { - ({ $($input:tt)* }, $expect:expr) => {{ - let r = $crate::test_util::test_interpret_only(stringify!($($input)*)) - .expect("parse error"); - assert!( - r.result.starts_with("Fault:"), - "expected interpreter fault, got:\n{}", - r.to_snapshot(), - ); - $expect.assert_eq(&r.to_snapshot()); + // type: error, interpret: ok + ($(prefix: $prefix:expr,)? { $($input:tt)* }, type: error($type_expect:expr), interpret: ok($interp_expect:expr)) => {{ + let program = $crate::test_util::parse_program(&[$($prefix,)? stringify!($($input)*)]); + let type_err = $crate::test_util::assert_type_err(&program); + $type_expect.assert_eq(&type_err); + let r = $crate::test_util::run_interpreter(&program); + $crate::test_util::assert_interpret_result(&r, "Ok:"); + $interp_expect.assert_eq(&r.to_snapshot()); + }}; + + // type: error, interpret: fault + ($(prefix: $prefix:expr,)? { $($input:tt)* }, type: error($type_expect:expr), interpret: fault($interp_expect:expr)) => {{ + let program = $crate::test_util::parse_program(&[$($prefix,)? stringify!($($input)*)]); + let type_err = $crate::test_util::assert_type_err(&program); + $type_expect.assert_eq(&type_err); + let r = $crate::test_util::run_interpreter(&program); + $crate::test_util::assert_interpret_result(&r, "Fault:"); + $interp_expect.assert_eq(&r.to_snapshot()); }}; } diff --git a/src/type_system.rs b/src/type_system.rs index eb3d004..5d363f7 100644 --- a/src/type_system.rs +++ b/src/type_system.rs @@ -1,8 +1,7 @@ -use std::sync::Arc; - use formality_core::judgment_fn; -use crate::grammar::{Decl, Program}; +use crate::elaborator::ElaboratedProgram; +use crate::grammar::Decl; mod accesses; mod blocks; @@ -10,16 +9,17 @@ mod classes; pub mod env; mod expressions; pub mod in_flight; -mod liveness; +pub mod liveness; mod local_liens; mod methods; mod perm_matcher; mod places; +pub mod pop_normalize; pub mod predicates; mod redperms; mod statements; mod subtypes; -mod types; +pub mod types; #[cfg(test)] mod tests; @@ -27,7 +27,7 @@ mod tests; // ANCHOR: check_program judgment_fn! { pub fn check_program( - program: Arc, + program: ElaboratedProgram, ) => () { debug(program) @@ -42,7 +42,7 @@ judgment_fn! { judgment_fn! { fn check_decl( - program: Arc, + program: ElaboratedProgram, decl: Decl, ) => () { debug(decl, program) diff --git a/src/type_system/blocks.rs b/src/type_system/blocks.rs index 648279d..efc5766 100644 --- a/src/type_system/blocks.rs +++ b/src/type_system/blocks.rs @@ -1,8 +1,13 @@ use formality_core::judgment_fn; use crate::{ - grammar::{Block, Ty}, - type_system::{env::Env, liveness::LivePlaces, statements::type_statements}, + grammar::{Block, Ty, Var}, + type_system::{ + env::Env, + liveness::LivePlaces, + pop_normalize::normalize_ty_for_pop, + statements::type_statements, + }, }; judgment_fn! { @@ -14,7 +19,23 @@ judgment_fn! { debug(block, env, live_after) ( + // Snapshot the set of local variables before the block. + (let vars_before = env.local_variable_names()) + (type_statements(env, live_after, statements) => (env, ty)) + + // Identify block-scoped variables (introduced during the block). + (let vars_after = env.local_variable_names()) + (let block_vars: Vec = vars_after.difference(&vars_before).cloned().collect()) + + // Normalize the result type against block-scoped variables. + // This resolves permissions referencing block-locals that are about to be popped. + // Dangling borrows (ref/mut from owned block-locals) are detected here. + (normalize_ty_for_pop(env, live_after, ty, block_vars) => ty) + + // Pop block-scoped variables from the env. + (let env = env.pop_block_variables(block_vars)?) + ----------------------------------- ("place") (type_block(env, live_after, Block { statements }) => (env, ty)) ) diff --git a/src/type_system/classes.rs b/src/type_system/classes.rs index 80530a5..27d497d 100644 --- a/src/type_system/classes.rs +++ b/src/type_system/classes.rs @@ -1,10 +1,9 @@ -use std::sync::Arc; - use formality_core::judgment_fn; +use crate::elaborator::ElaboratedProgram; use crate::grammar::{ Atomic, ClassDecl, ClassDeclBoundData, ClassPredicate, DropBody, FieldDecl, Kind, NamedTy, - Perm, Predicate, Program, Ty, UniversalVar, Var, VarianceKind, + Perm, Predicate, Ty, UniversalVar, Var, VarianceKind, }; use super::{ @@ -19,7 +18,7 @@ use super::{ // ANCHOR: check_class judgment_fn! { pub fn check_class( - program: Arc, + program: ElaboratedProgram, decl: ClassDecl, ) => () { debug(decl, program) @@ -41,9 +40,9 @@ judgment_fn! { (check_field(class_ty, env, substitution, class_predicate, field) => ())) (for_all(method in methods) - (check_method(class_ty, env, method) => ())) + (check_method(class_ty, env, substitution, method) => ())) - (check_drop_body(class_ty, class_predicate, env, drop_body) => ()) + (check_drop_body(class_ty, class_predicate, env, substitution, drop_body) => ()) ----------------------------------- ("check_class") (check_class(program, decl) => ()) @@ -57,6 +56,7 @@ judgment_fn! { class_ty: NamedTy, class_predicate: ClassPredicate, env: Env, + class_vars: Vec, drop_body: DropBody, ) => () { debug(drop_body, class_ty, class_predicate, env) @@ -65,20 +65,26 @@ judgment_fn! { ( (if drop_body.block.statements.is_empty())! ----------------------------------- ("empty_drop") - (check_drop_body(_class_ty, _class_predicate, _env, drop_body) => ()) + (check_drop_body(_class_ty, _class_predicate, _env, _class_vars, drop_body) => ()) ) // Given class: self has type `given Class[...]`. ( + // Drop bodies don't care about variance, so assume all class + // parameters are relative/atomic for WF checking. + (let env = env.with_variance_assumed(class_vars)) (let env = env.push_local_variable(Var::This, class_ty)?) (can_type_expr_as(env, LivePlaces::default(), &drop_body.block, Ty::unit()) => ()) ----------------------------------- ("given_class_drop") - (check_drop_body(class_ty, ClassPredicate::Given, env, drop_body) => ()) + (check_drop_body(class_ty, ClassPredicate::Given, env, class_vars, drop_body) => ()) ) // Share or Shared class: introduce a universal perm variable P with `P is ref` assumed, // then type-check with `self: P Class[...]`. ( + // Drop bodies don't care about variance, so assume all class + // parameters are relative/atomic for WF checking. + (let env = env.with_variance_assumed(class_vars)) (let (env, perm_var) = env.open_universal_perm_var()) (let env = env.add_assumptions(vec![Predicate::parameter( crate::grammar::ParameterPredicate::Copy, perm_var @@ -87,7 +93,7 @@ judgment_fn! { (let env = env.push_local_variable(Var::This, self_ty)?) (can_type_expr_as(env, LivePlaces::default(), &drop_body.block, Ty::unit()) => ()) ----------------------------------- ("share_class_drop") - (check_drop_body(class_ty, ClassPredicate::Share | ClassPredicate::Shared, env, drop_body) => ()) + (check_drop_body(class_ty, ClassPredicate::Share | ClassPredicate::Shared, env, class_vars, drop_body) => ()) ) } } diff --git a/src/type_system/env.rs b/src/type_system/env.rs index ef5703d..50af9ee 100644 --- a/src/type_system/env.rs +++ b/src/type_system/env.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use anyhow::bail; use formality_core::{set, term, Fallible, Map, Set, To, Upcast}; @@ -8,6 +6,7 @@ use crate::{ grammar::{Binder, ExistentialVar, UniversalVar, VarIndex, Variable}, Term, }, + elaborator::ElaboratedProgram, grammar::{ ClassPredicate, Kind, LocalVariableDecl, ParameterPredicate, Predicate, Program, Ty, TypeName, Var, VarianceKind, @@ -19,7 +18,7 @@ use super::in_flight::{InFlight, Transform}; // ANCHOR: Env #[derive(Clone, Ord, Eq, PartialEq, PartialOrd, Hash)] pub struct Env { - program: Arc, + program: ElaboratedProgram, universe: Universe, in_scope_vars: Vec, local_variables: Map, @@ -35,7 +34,7 @@ pub struct Universe(usize); formality_core::cast_impl!(Env); impl Env { - pub fn new(program: impl Upcast>) -> Self { + pub fn new(program: impl Upcast) -> Self { Env { program: program.upcast(), universe: Universe(0), @@ -55,6 +54,22 @@ impl Env { } } + /// Assume all given variables satisfy both `relative` and `atomic` variance predicates. + /// Used in methods and drop bodies where variance is irrelevant. + pub fn with_variance_assumed(&self, vars: impl Upcast>) -> Env { + let vars: Vec = vars.upcast(); + self.add_assumptions( + vars.iter() + .flat_map(|v| { + vec![ + VarianceKind::Relative.apply(v), + VarianceKind::Atomic.apply(v), + ] + }) + .collect::>(), + ) + } + pub fn add_assumptions(&self, assumptions: impl Upcast>) -> Env { let mut env = self.clone(); let assumptions: Vec = assumptions.upcast(); @@ -114,6 +129,11 @@ impl Env { } } + /// Returns the set of currently bound local variable names. + pub fn local_variable_names(&self) -> Set { + self.local_variables.keys().cloned().collect() + } + /// Lookup a program variable named `var` and returns its type (if any). pub fn var_ty(&self, var: impl Upcast) -> Fallible<&Ty> { let var: Var = var.upcast(); @@ -228,6 +248,15 @@ impl Env { Ok(()) } + + /// Pop block-scoped variables, returning a new env without them. + /// Unlike `pop_local_variables`, this takes `&self` and returns a new `Env`, + /// which is needed inside `judgment_fn!` macros where the env is immutable. + pub fn pop_block_variables(&self, vars: impl Upcast>) -> Fallible { + let mut env = self.clone(); + env.pop_local_variables(vars)?; + Ok(env) + } } impl InFlight for Env { diff --git a/src/type_system/expressions.rs b/src/type_system/expressions.rs index dcb4eb6..acfef0c 100644 --- a/src/type_system/expressions.rs +++ b/src/type_system/expressions.rs @@ -12,10 +12,12 @@ use crate::{ env::Env, in_flight::InFlight, liveness::LivePlaces, + pop_normalize::normalize_ty_for_pop, predicates::{ prove_is_copy, prove_is_move, prove_is_mut, prove_is_shareable, prove_predicates, }, subtypes::sub, + types::check_type, }, }; @@ -262,16 +264,32 @@ judgment_fn! { (let input_names: Vec = inputs.iter().map(|input| input.name.clone()).collect()) (let input_tys: Vec = inputs.iter().map(|input| input.ty.clone()).collect()) - // The self type must match what method expects - (let (this_input_ty, input_tys) = (this_input_ty.clone(), input_tys.clone()).with_this_stored_to(this_var)) + // The self type must match what method expects. + // Also rename output's `self` references to this_var. + (let (this_input_ty, input_tys, output) = (this_input_ty.clone(), input_tys.clone(), output.clone()).with_this_stored_to(this_var)) (sub(env, live_after_receiver, receiver_ty, this_input_ty) => ()) - // Type each of the method arguments, remapping them to `temp(i)` appropriately as well - (type_method_arguments_as(env, live_after, exprs, (this_var,), input_names, input_tys) => (env, input_temps)) + // Type each of the method arguments, remapping them to `temp(i)` appropriately as well. + // Also thread output through to rename named parameter references. + (type_method_arguments_as(env, live_after, exprs, (this_var,), input_names, input_tys, output) => (env, input_temps, output)) // Prove predicates (prove_predicates(env, predicates) => ()) + // Normalize output before popping (env still has all bindings for fresh vars). + // This resolves place-based permissions referencing the about-to-be-popped temporaries. + (let pre_norm_output = output.clone()) + (normalize_ty_for_pop(env, live_after, output, input_temps) => output) + + // Sanity check: normalization only weakens (strips dead links, Rfd→Shared), + // so the original output must be a subtype of the normalized result. + // If this fails, our normalization rules are buggy — not a user error. + (let () = assert!(sub(&env, &live_after, &pre_norm_output, &output).is_proven(), + "normalization soundness check failed: {:?} is not a subtype of {:?}", pre_norm_output, output)) + + // Validate the normalized output in the current env (still has fresh vars). + (check_type(env, output) => ()) + // Drop all the temporaries (accesses_permitted(env, live_after, Access::Drop, input_temps) => env) (let env = env.pop_fresh_variables(input_temps)) @@ -420,12 +438,13 @@ judgment_fn! { input_temps: Vec, input_names: Vec, input_tys: Vec, - ) => (Env, Vec) { - debug(exprs, input_temps, input_names, input_tys, env, live_after) + output: Ty, + ) => (Env, Vec, Ty) { + debug(exprs, input_temps, input_names, input_tys, output, env, live_after) ( ----------------------------------- ("none") - (type_method_arguments_as(env, _live_after, (), temps, (), ()) => (env, temps)) + (type_method_arguments_as(env, _live_after, (), temps, (), (), output) => (env, temps, output)) ) ( @@ -463,8 +482,10 @@ judgment_fn! { (let input_ty = input_ty.with_var_stored_to(input_name, input_temp)) (sub(env, live_after_expr, expr_ty, input_ty) => ()) + // Also rename in remaining input types and the output type (let input_tys = input_tys.with_var_stored_to(input_name, input_temp)) - (type_method_arguments_as(env, live_after, exprs, Cons(input_temp, input_temps), input_names, input_tys) => pair) + (let output = output.with_var_stored_to(input_name, input_temp)) + (type_method_arguments_as(env, live_after, exprs, Cons(input_temp, input_temps), input_names, input_tys, output) => triple) ----------------------------------- ("cons") (type_method_arguments_as( env, @@ -473,7 +494,8 @@ judgment_fn! { input_temps, Cons(input_name, input_names), Cons(input_ty, input_tys), - ) => pair) + output, + ) => triple) ) } } diff --git a/src/type_system/methods.rs b/src/type_system/methods.rs index 0f87656..48505de 100644 --- a/src/type_system/methods.rs +++ b/src/type_system/methods.rs @@ -2,7 +2,7 @@ use formality_core::judgment_fn; use crate::grammar::{ LocalVariableDecl, MethodBody, MethodDecl, MethodDeclBoundData, NamedTy, ThisDecl, Ty, - Var::This, VarianceKind, + UniversalVar, Var::This, }; use super::{ @@ -15,22 +15,19 @@ judgment_fn! { pub fn check_method( class_ty: NamedTy, env: Env, + class_vars: Vec, decl: MethodDecl, ) => () { debug(decl, class_ty, env) ( (let MethodDecl { name: _, binder } = decl) - (let (env, vars, MethodDeclBoundData { this, inputs, output, predicates, body }) = + (let (env, method_vars, MethodDeclBoundData { this, inputs, output, predicates, body }) = env.open_universally(binder)) // Methods don't really care about variance, so they can assume all their - // parameters are relative/atomic for purposes of WF checking. - (let env = env.add_assumptions( - vars.iter() - .flat_map(|v| vec![VarianceKind::Relative.apply(v), VarianceKind::Atomic.apply(v)]) - .collect::>(), - )) + // parameters (and the class's parameters) are relative/atomic for purposes of WF checking. + (let env = env.with_variance_assumed(class_vars).with_variance_assumed(method_vars)) (check_predicates(env, predicates) => ()) (let env = env.add_assumptions(predicates)) @@ -49,7 +46,7 @@ judgment_fn! { (check_body(env, output, body) => ()) ----------------------------------- ("check_method") - (check_method(class_ty, env, decl) => ()) + (check_method(class_ty, env, class_vars, decl) => ()) ) } } diff --git a/src/type_system/pop_normalize.rs b/src/type_system/pop_normalize.rs new file mode 100644 index 0000000..be51fa0 --- /dev/null +++ b/src/type_system/pop_normalize.rs @@ -0,0 +1,264 @@ +//! Normalization of types at scope-pop boundaries. +//! +//! When a method call returns, the fresh temporary variables used for +//! parameters go out of scope. The return type may reference those +//! temporaries (via `ref[temp]`, `mut[temp]`, `given[temp]`). +//! This module resolves those references by: +//! +//! 1. Expanding permissions via `red_perm` (which handles `given` +//! replacement and ref/mut chain extension) +//! 2. Stripping dead links to popped variables +//! 3. Converting back to `Perm` (multiple chains become `Perm::Or`) + +use formality_core::{judgment_fn, Cons, Set, Upcast, Upcasted}; + +use crate::grammar::{NamedTy, Parameter, Perm, Ty, Var}; + +use super::{ + env::Env, + liveness::LivePlaces, + predicates::prove_is_copy, + redperms::{dead_link_is_strippable, red_perm, Given, Head, RedChain, RedLink, Tail}, +}; + +judgment_fn! { + /// Normalize a type for popping the given variables. + /// + /// Walks the type structure, normalizing each permission that references + /// any of the `popped_vars`. Permissions that don't reference popped vars + /// are left unchanged. + pub fn normalize_ty_for_pop( + env: Env, + live_after: LivePlaces, + ty: Ty, + popped_vars: Vec, + ) => Ty { + debug(ty, popped_vars, env, live_after) + + // NamedTy: normalize each parameter recursively. + ( + (normalize_params_for_pop(env, live_after, parameters, popped_vars) => norm_params) + --- ("named") + (normalize_ty_for_pop(env, live_after, NamedTy { name, parameters }, popped_vars) + => NamedTy::new(name, norm_params)) + ) + + // Type variable: pass through unchanged. + ( + --- ("var") + (normalize_ty_for_pop(_env, _live_after, Ty::Var(v), _popped_vars) => Ty::var(v)) + ) + + // ApplyPerm where inner type is copy: strip the permission entirely. + ( + (prove_is_copy(env, &**inner_ty) => ()) + (normalize_ty_for_pop(env, live_after, &**inner_ty, popped_vars) => new_ty) + --- ("apply_perm_copy") + (normalize_ty_for_pop(env, live_after, Ty::ApplyPerm(_perm, inner_ty), popped_vars) => new_ty) + ) + + // ApplyPerm where inner type is NOT copy: normalize both perm and inner type. + ( + (if !prove_is_copy(env, &**inner_ty).is_proven())! + (normalize_perm_for_pop(env, live_after, perm, popped_vars) => new_perm) + (normalize_ty_for_pop(env, live_after, &**inner_ty, popped_vars) => new_ty) + --- ("apply_perm") + (normalize_ty_for_pop(env, live_after, Ty::ApplyPerm(perm, inner_ty), popped_vars) + => Ty::apply_perm(new_perm, new_ty)) + ) + } +} + +judgment_fn! { + /// Normalize a list of parameters for popping. + fn normalize_params_for_pop( + env: Env, + live_after: LivePlaces, + params: Vec, + popped_vars: Vec, + ) => Vec { + debug(params, popped_vars, env, live_after) + + // Base case: empty parameter list. + ( + --- ("nil") + (normalize_params_for_pop(_env, _live_after, (), _popped_vars) => ()) + ) + + // Recursive: normalize head, then tail, then prepend. + ( + (normalize_param_for_pop(env, live_after, param, popped_vars) => norm_param) + (normalize_params_for_pop(env, live_after, rest, popped_vars) => norm_rest) + --- ("cons") + (normalize_params_for_pop(env, live_after, Cons(param, rest), popped_vars) => Cons(norm_param, norm_rest)) + ) + } +} + +judgment_fn! { + /// Normalize a single parameter for popping. + fn normalize_param_for_pop( + env: Env, + live_after: LivePlaces, + param: Parameter, + popped_vars: Vec, + ) => Parameter { + debug(param, popped_vars, env, live_after) + + ( + (normalize_ty_for_pop(env, live_after, ty, popped_vars) => norm_ty) + --- ("ty") + (normalize_param_for_pop(env, live_after, Parameter::Ty(ty), popped_vars) => norm_ty) + ) + + ( + (normalize_perm_for_pop(env, live_after, perm, popped_vars) => norm_perm) + --- ("perm") + (normalize_param_for_pop(env, live_after, Parameter::Perm(perm), popped_vars) => norm_perm) + ) + } +} + +judgment_fn! { + /// Normalize a permission for popping. + /// + /// If the permission doesn't reference any popped vars, returns it unchanged. + /// Otherwise, expands via `red_perm`, strips dead links to popped vars, + /// and converts back to `Perm`. + fn normalize_perm_for_pop( + env: Env, + live_after: LivePlaces, + perm: Perm, + popped_vars: Vec, + ) => Perm { + debug(perm, popped_vars, env, live_after) + + // Perm doesn't reference popped vars → return unchanged. + ( + (if !perm_references_vars(&perm, &popped_vars))! + --- ("no popped refs") + (normalize_perm_for_pop(_env, _live_after, perm, _popped_vars) => perm) + ) + + // Perm references popped vars → expand via red_perm, strip all chains, convert back. + ( + (if perm_references_vars(&perm, &popped_vars))! + (red_perm(env, live_after, perm) => red) + (strip_all_chains(env, &red.chains, popped_vars) => stripped_vec) + --- ("normalize via red_perm") + (normalize_perm_for_pop(env, live_after, perm, popped_vars) => red_chains_to_perm(stripped_vec)) + ) + } +} + +judgment_fn! { + /// Strip all chains in a list. Every chain must strip successfully — + /// if any chain can't be stripped (dangling borrow), the judgment fails. + fn strip_all_chains( + env: Env, + chains: Set, + popped_vars: Vec, + ) => Set { + debug(chains, popped_vars, env) + + ( + --- ("nil") + (strip_all_chains(_env, (), _popped_vars) => ()) + ) + + ( + (strip_popped_dead_links(env, chain, popped_vars) => stripped) + (strip_all_chains(env, rest, popped_vars) => stripped_rest) + --- ("cons") + (strip_all_chains(env, Cons(chain, rest), popped_vars) => Cons(stripped, stripped_rest)) + ) + } +} + +judgment_fn! { + /// Strip dead links to popped variables from a single chain. + /// + /// Recursively processes the chain, applying stripping rules for dead links + /// whose place is in `popped_vars`: + /// - `Mtd(popped) :: tail` → drop `Mtd(popped)`, keep stripped tail + /// (requires shareable type + mut-based tail via `dead_link_is_strippable`) + /// - `Rfd(popped) :: tail` → replace `Rfd(popped)` with `Shared`, keep stripped tail + /// (same conditions via `dead_link_is_strippable`) + /// + /// Links NOT referencing popped vars are kept as-is. + /// Dangling borrows (live links to popped vars, or dead links that can't be + /// stripped) cause judgment failure — no applicable rule matches. + fn strip_popped_dead_links( + env: Env, + chain: RedChain, + popped_vars: Vec, + ) => RedChain { + debug(chain, popped_vars, env) + + // Base case: empty chain (given) — nothing to strip. + ( + --- ("given") + (strip_popped_dead_links(_env, Given(), _popped_vars) => RedChain::given()) + ) + + // Dead mut to popped var, strippable → drop the Mtd link, strip the tail. + ( + (if popped_vars.contains(&place.var))! + (dead_link_is_strippable(env, place, tail) => ()) + (strip_popped_dead_links(env, tail, popped_vars) => stripped) + --- ("drop dead mut to popped") + (strip_popped_dead_links(env, Head(RedLink::Mtd(place), Tail(tail)), popped_vars) => stripped) + ) + + // Dead ref to popped var, strippable → replace Rfd with Shared, strip the tail. + ( + (if popped_vars.contains(&place.var))! + (dead_link_is_strippable(env, place, tail) => ()) + (strip_popped_dead_links(env, tail, popped_vars) => stripped) + --- ("weaken dead ref to shared") + (strip_popped_dead_links(env, Head(RedLink::Rfd(place), Tail(tail)), popped_vars) => RedChain::cons(RedLink::Shared, stripped)) + ) + + // Link does NOT reference a popped var → keep it and stop normalizing. + ( + (if !link_references_popped(&link, &popped_vars)) + --- ("keep non-popped link") + (strip_popped_dead_links(env, Head(link, Tail(tail)), popped_vars) => RedChain::cons(link, tail)) + ) + } +} + +/// Check if a permission references any of the given variables. +fn perm_references_vars(perm: &Perm, vars: &[Var]) -> bool { + match perm { + Perm::Given | Perm::Shared => false, + Perm::Var(_) => false, + Perm::Mv(places) | Perm::Rf(places) | Perm::Mt(places) => { + places.iter().any(|p| vars.contains(&p.var)) + } + Perm::Apply(l, r) => perm_references_vars(l, vars) || perm_references_vars(r, vars), + Perm::Or(perms) => perms.iter().any(|p| perm_references_vars(p, vars)), + } +} + +/// Check if a link references any of the popped variables. +fn link_references_popped(link: &RedLink, popped_vars: &[Var]) -> bool { + match link { + RedLink::Rfl(p) | RedLink::Rfd(p) | RedLink::Mtl(p) | RedLink::Mtd(p) | RedLink::Mv(p) => { + popped_vars.contains(&p.var) + } + RedLink::Shared | RedLink::Var(_) => false, + } +} + +/// Convert a list of stripped chains back to a single `Perm`. +/// Single chain → unwrap via `UpcastFrom`. +/// Multiple chains → `Perm::Or`. +fn red_chains_to_perm(chains: impl IntoIterator>) -> Perm { + let mut chains: Vec = chains.into_iter().upcasted().collect(); + match chains.len() { + 0 => Perm::Given, // empty set → given (shouldn't happen in practice) + 1 => chains.pop().expect("len should be 1"), + _ => Perm::flat_or(chains), + } +} diff --git a/src/type_system/predicates.rs b/src/type_system/predicates.rs index fd2128d..3abc17a 100644 --- a/src/type_system/predicates.rs +++ b/src/type_system/predicates.rs @@ -357,7 +357,7 @@ judgment_fn! { (prove_copy_predicate(_env, Parameter::Perm(Perm::Rf(_places))) => ()) ) - // given_from[places] is copy if all places' types are copy + // given[places] is copy if all places' types are copy ( (for_all(place in places) (let ty = env.place_ty(place)?) @@ -453,7 +453,7 @@ judgment_fn! { (prove_move_predicate(_env, Parameter::Perm(Perm::Given)) => ()) ) - // given_from[places] is move if all places' types are move + // given[places] is move if all places' types are move ( (for_all(place in places) (let ty = env.place_ty(place)?) @@ -546,7 +546,7 @@ judgment_fn! { (prove_owned_predicate(_env, Parameter::Perm(Perm::Shared)) => ()) ) - // given_from[places] is owned if all places' types are owned + // given[places] is owned if all places' types are owned ( (for_all(place in places) (let ty = env.place_ty(place)?) @@ -636,7 +636,7 @@ judgment_fn! { // ref is never mut (read-only borrow strips mutability) - // given_from[places] is mut if all places' types are mut + // given[places] is mut if all places' types are mut ( (for_all(place in places) (let ty = env.place_ty(place)?) @@ -862,7 +862,7 @@ judgment_fn! { ) // FIXME: Is this right? What about e.g. `shared class Foo[perm P, ty T] { x: T, y: P ref[x] String }` - // or other such things? and what about `given_from[x]`? + // or other such things? and what about `given[x]`? ( ----------------------------- ("shared") diff --git a/src/type_system/redperms.rs b/src/type_system/redperms.rs index 6eafab0..93f30a2 100644 --- a/src/type_system/redperms.rs +++ b/src/type_system/redperms.rs @@ -12,7 +12,7 @@ use formality_core::{cast_impl, judgment::ProofTree, judgment_fn, ProvenSet, Set use super::{env::Env, liveness::LivePlaces}; /// A reduced permission: the complete set of possible reduction chains for a -/// permission expression. Because permissions like `given_from[x, y]` produce +/// permission expression. Because permissions like `given[x, y]` produce /// one chain per place (existential choice), a single `Perm` can reduce to /// multiple `RedChain`s. Subtyping requires that *every* chain in `a` is a /// subchain of *some* chain in `b`. @@ -61,10 +61,10 @@ pub enum RedLink { /// and the tail is mut-based. Mtd(Place), - /// `given_from[place]` — ownership derived from `place`. Unlike ref/mut, + /// `given[place]` — ownership derived from `place`. Unlike ref/mut, /// this link is *replaced* during expansion (not appended to): the Mv link /// is popped and substituted with the permission of `place`'s type. - /// This is the key mechanism that resolves `given_from` references. + /// This is the key mechanism that resolves `given` references. Mv(Place), /// A universal permission variable (from generic parameters). @@ -81,11 +81,11 @@ pub struct Given(); /// Pattern-match helper for destructuring a chain into its first link and the rest. /// Used in `judgment_fn!` rules to match chain shapes like `(Shared :: Mtl(p) :: tail)`. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Head(H, T); +pub struct Head(pub H, pub T); /// Pattern-match helper: the tail portion of a chain after a `Head` match. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Tail(T); +pub struct Tail(pub T); judgment_fn! { /// Reduces `perm_a` and `perm_b` and then checks that `sub_perms` holds. @@ -126,6 +126,41 @@ judgment_fn! { } } +judgment_fn! { + /// Proves that a dead link at `place_dead` can be stripped or weakened, + /// given that `tail` is the remainder of the chain after the dead link. + /// + /// Two conditions must hold: + /// + /// 1. **Shareable type**: `place_dead`'s type must be shareable (`share(G)`). + /// Given classes are not shareable — their dead links cannot be stripped + /// because they implement the **guard pattern**: e.g., a `Lock` yields a + /// `given class Guard`, and data accessed through the guard has type + /// `mut[guard] L Data`. Even when `guard` is dead, its existence mediates + /// access. Stripping `mut[guard]` would bypass the guard — unsound. + /// See: `src/type_system/tests/given_classes/lock_given.rs` + /// + /// 2. **Mut-based tail**: the remaining chain must be mut-based. This prevents + /// upgrading owned values: `Mtd(g) :: given` → `given` would turn a borrowed + /// value into an owned one. Similarly, `Rfd(g) :: given` → `Shared :: given` + /// would turn an owned-but-ref-borrowed value into a shared (copyable) one. + pub fn dead_link_is_strippable( + env: Env, + place_dead: Place, + tail: RedChain, + ) => () { + debug(place_dead, tail, env) + + ( + (let ty_dead = env.place_ty(place_dead)?) + (prove_is_shareable(env, ty_dead) => ()) + (prove_is_mut(env, tail) => ()) + --- ("shareable type + mut tail") + (dead_link_is_strippable(env, place_dead, tail) => ()) + ) + } +} + judgment_fn! { /// Reduces `perm_a` and `perm_b` and then checks that `sub_perms` holds. pub fn red_chain_sub_chain( @@ -168,42 +203,14 @@ judgment_fn! { ) ( - // NB: We can only drop a dead `mut[g]` link under two conditions: - // - // 1. `share(G)` (where `g: G`): given classes are not shareable, and their - // `mut[g]` links cannot be dropped even when dead. This is the **guard pattern**: - // e.g., a `Lock` yields a `given class Guard`, and data accessed through the guard - // has type `mut[guard] L Data`. Even when `guard` is dead (no more explicit uses), - // the guard is still alive — its existence is what mediates access. Stripping - // `mut[guard]` would bypass the guard and grant direct `L Data` access, which is - // unsound (the guard's destructor will revoke access when it runs). - // See: `src/type_system/tests/given_classes/lock_given.rs` - // - // 2. The tail permission is leased (mut-based): this ensures we're not dropping a - // lien when the underlying permission is owned. `Mtd(g) :: given` → `given` would - // upgrade a borrowed value to owned, which is unsound. - (let ty_dead = env.place_ty(place_dead)?) - (prove_is_shareable(env, ty_dead) => ()) - (prove_is_mut(env, tail_a) => ()) + (dead_link_is_strippable(env, place_dead, tail_a) => ()) (red_chain_sub_chain(env, tail_a, red_chain_b) => ()) --- ("(mut-dead::P) vs Q ~~> (P) vs Q") (red_chain_sub_chain(env, Head(RedLink::Mtd(place_dead), Tail(tail_a)), red_chain_b) => ()) ) ( - // NB: We can only weaken a dead `ref[g]` to `shared` under two conditions: - // - // 1. `share(G)` (where `g: G`): same guard pattern reasoning as for `mut[g]` above. - // A `ref[guard]` link in a chain means access is mediated through the guard; - // weakening it to `shared` would discard that dependency. - // See: `src/type_system/tests/given_classes/lock_given.rs` - // - // 2. The tail permission is leased (mut-based): prevents converting owned - // permissions to shared. `Rfd(g) :: given` → `Shared :: given` would turn - // an owned-but-ref-borrowed value into a shared (copyable) one. - (let ty_dead = env.place_ty(place_dead)?) - (prove_is_shareable(env, ty_dead) => ()) - (prove_is_mut(env, tail_a) => ()) + (dead_link_is_strippable(env, place_dead, tail_a) => ()) (red_chain_sub_chain(env, Head(RedLink::Shared, Tail(tail_a)), red_chain_b) => ()) --- ("(ref-dead::P) vs Q ~~> (shared::P) vs Q") (red_chain_sub_chain(env, Head(RedLink::Rfd(place_dead), Tail(tail_a)), red_chain_b) => ()) @@ -398,7 +405,7 @@ judgment_fn! { ( (place in places) - --- ("given_from") + --- ("given") (some_red_chain(_env, _live_after, Perm::Mv(places)) => RedLink::Mv(Place::clone(place))) ) diff --git a/src/type_system/redperms/cast_impls.rs b/src/type_system/redperms/cast_impls.rs index d2ae63e..289c779 100644 --- a/src/type_system/redperms/cast_impls.rs +++ b/src/type_system/redperms/cast_impls.rs @@ -22,6 +22,15 @@ impl RedChain { pub fn given() -> Self { RedChain { links: vec![] } } + + /// Prepend a link to the front of this chain. + pub fn cons(link: impl Upcast, tail: impl Upcast) -> Self { + let link: RedLink = link.upcast(); + let tail: RedChain = tail.upcast(); + let mut links = vec![link]; + links.extend(tail.links); + RedChain { links } + } } impl UpcastFrom for Parameter { diff --git a/src/type_system/tests.rs b/src/type_system/tests.rs index 4fa3fae..a15b462 100644 --- a/src/type_system/tests.rs +++ b/src/type_system/tests.rs @@ -17,6 +17,7 @@ mod shared_classes_permissions; mod shared_classes_subtyping; mod subpermission; mod subtyping; +mod block_normalization; mod normalization; mod or_perm; mod type_check; diff --git a/src/type_system/tests/array_ops.rs b/src/type_system/tests/array_ops.rs index e2441b6..582a7ac 100644 --- a/src/type_system/tests/array_ops.rs +++ b/src/type_system/tests/array_ops.rs @@ -47,7 +47,7 @@ fn array_new_bad_length_type() { array_new[Int](d.give); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { x : Int ; } class TheClass { fn go (given self) -> Array[Int] { let d = new Data (1) ; array_new [Int](d . give) ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: Data, b: Int, live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, d: Data}, assumptions: {}, fresh: 0 } }"#]]); } /// array_new with two type parameters should fail @@ -153,7 +153,12 @@ fn array_capacity_wrong_type_param() { array_capacity[Data, given](a.give); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { x : Int ; } class TheClass { fn go (given self) -> Int { let a = array_new [Int](5) ; array_capacity [Data, given](a . give) ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int]}, assumptions: {}, fresh: 0 } } + + src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: given Int, b: given Data, live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int]}, assumptions: {}, fresh: 0 } } + + src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: Int, b: Data, live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int]}, assumptions: {}, fresh: 0 } }"#]]); } /// array_capacity on a non-array should fail @@ -165,7 +170,7 @@ fn array_capacity_not_an_array() { array_capacity[Int, given](22); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class TheClass { fn go (given self) -> Int { array_capacity [Int, given](22) ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: Int, b: given Array[Int], live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass}, assumptions: {}, fresh: 0 } }"#]]); } // ============================================================================= @@ -217,8 +222,12 @@ fn array_write_shared() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int], b: shared Array[Int]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int], b: shared Array[Int]}, assumptions: {}, fresh: 0 } } + the rule "isnt copy" at (predicates.rs) failed because - condition evaluted to false: `!prove_is_copy(env, p).is_proven()`"#]]); + condition evaluated to false: `!prove_is_copy(env, p).is_proven()`"#]]); } /// array_write on a ref array should fail — requires mut @@ -231,7 +240,7 @@ fn array_write_ref() { array_write[Int, ref[a]](a.ref, 0, 42); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class TheClass { fn go (given self) -> () { let a = array_new [Int](5) ; array_write [Int, ref [a]](a . ref , 0 , 42) ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [a], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int]}, assumptions: {}, fresh: 0 } }"#]]); } /// ref strips mutability — ref of mut should not satisfy prove_is_mut @@ -245,7 +254,7 @@ fn array_write_ref_of_mut() { array_write[Int, ref[array_mut]](array_mut.ref, 0, 42); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class TheClass { fn go (given self) -> () { let a = array_new [Int](5) ; let array_mut = a . mut ; array_write [Int, ref [array_mut]](array_mut . ref , 0 , 42) ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [array_mut], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int], array_mut: mut [a] Array[Int]}, assumptions: {}, fresh: 0 } }"#]]); } /// array_write on a mut array should work @@ -275,7 +284,12 @@ fn array_write_wrong_type_param() { array_write[Data, mut[a]](a.mut, 0, 42); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { x : Int ; } class TheClass { fn go (given self) -> () { let a = array_new [Int](5) ; array_write [Data, mut [a]](a . mut , 0 , 42) ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [a], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Array[Int], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int]}, assumptions: {}, fresh: 0 } } + + src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: Int, b: Data, live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int]}, assumptions: {}, fresh: 0 } }"#]]); } /// array_write with wrong value type should fail @@ -293,7 +307,7 @@ fn array_write_wrong_value_type() { array_write[Int, mut[a]](a.mut, 0, d.give); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { x : Int ; } class TheClass { fn go (given self) -> () { let a = array_new [Int](5) ; let d = new Data (10) ; array_write [Int, mut [a]](a . mut , 0 , d . give) ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: Data, b: Int, live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int], d: Data}, assumptions: {}, fresh: 0 } }"#]]); } /// array_write on a non-array should fail @@ -305,7 +319,7 @@ fn array_write_not_an_array() { array_write[Int, given](22, 0, 42); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class TheClass { fn go (given self) -> () { array_write [Int, given](22 , 0 , 42) ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass}, assumptions: {}, fresh: 0 } }"#]]); } /// array_write with non-Int index should fail @@ -323,7 +337,7 @@ fn array_write_bad_index_type() { array_write[Int, mut[a]](a.mut, d.give, 42); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { x : Int ; } class TheClass { fn go (given self) -> () { let a = array_new [Int](5) ; let d = new Data (10) ; array_write [Int, mut [a]](a . mut , d . give , 42) ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: Data, b: Int, live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int], d: Data}, assumptions: {}, fresh: 0 } }"#]]); } // ============================================================================= @@ -422,7 +436,12 @@ fn array_give_wrong_type_param() { array_give[Data, given, given](a.give, 0); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { x : Int ; } class TheClass { fn go (given self) -> Int { let a = array_new [Int](5) ; array_write [Int, mut [a]](a . mut , 0 , 42) ; array_give [Data, given, given](a . give , 0) ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int]}, assumptions: {}, fresh: 0 } } + + src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: given Int, b: given Data, live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int]}, assumptions: {}, fresh: 0 } } + + src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: Int, b: Data, live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int]}, assumptions: {}, fresh: 0 } }"#]]); } /// array_give on a non-array should fail @@ -434,7 +453,7 @@ fn array_give_not_an_array() { array_give[Int, given, given](22, 0); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class TheClass { fn go (given self) -> Int { array_give [Int, given, given](22 , 0) ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: Int, b: given Array[Int], live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass}, assumptions: {}, fresh: 0 } }"#]]); } /// array_give with non-Int index should fail @@ -453,7 +472,7 @@ fn array_give_bad_index_type() { array_give[Int, given, given](a.give, d.give); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { x : Int ; } class TheClass { fn go (given self) -> Int { let a = array_new [Int](5) ; array_write [Int, mut [a]](a . mut , 0 , 42) ; let d = new Data (10) ; array_give [Int, given, given](a . give , d . give) ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: Data, b: Int, live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int], d: Data}, assumptions: {}, fresh: 0 } }"#]]); } /// array_give return type should be the element type, not Int — using it where Data expected should work @@ -472,7 +491,7 @@ fn array_give_returns_element_type() { array_give[Int, given, given](a.give, 0); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { x : Int ; } class TheClass { fn go (given self) -> Data { let a = array_new [Int](5) ; array_write [Int, mut [a]](a . mut , 0 , 42) ; array_give [Int, given, given](a . give , 0) ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: Int, b: Data, live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass}, assumptions: {}, fresh: 0 } }"#]]); } // ============================================================================= @@ -525,7 +544,12 @@ fn array_drop_shared() { array_drop[Int, given, mut[b]](b.mut, 0, 1); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class TheClass { fn go (given self) -> () { let a = array_new [Int](5) ; array_write [Int, mut [a]](a . mut , 0 , 42) ; let b = a . give . share ; array_drop [Int, given, mut [b]](b . mut , 0 , 1) ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int], b: shared Array[Int]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int], b: shared Array[Int]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int], b: shared Array[Int]}, assumptions: {}, fresh: 0 } }"#]]); } /// array_drop on a ref array should work (A is ref is satisfied) @@ -571,7 +595,12 @@ fn array_drop_wrong_type_param() { array_drop[Data, given, mut[a]](a.mut, 0, 1); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { x : Int ; } class TheClass { fn go (given self) -> () { let a = array_new [Int](5) ; array_write [Int, mut [a]](a . mut , 0 , 42) ; array_drop [Data, given, mut [a]](a . mut , 0 , 1) ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [a], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Array[Int], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int]}, assumptions: {}, fresh: 0 } } + + src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: Int, b: Data, live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int]}, assumptions: {}, fresh: 0 } }"#]]); } /// array_drop on a non-array should fail @@ -583,7 +612,7 @@ fn array_drop_not_an_array() { array_drop[Int, given, given](22, 0, 1); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class TheClass { fn go (given self) -> () { array_drop [Int, given, given](22 , 0 , 1) ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: Int, b: given Array[Int], live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass}, assumptions: {}, fresh: 0 } }"#]]); } /// array_drop with non-Int index should fail @@ -603,8 +632,10 @@ fn array_drop_bad_index_type() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, a: Array[Int], d: Data}, assumptions: {}, fresh: 0 } } + the rule "give" at (expressions.rs) failed because - condition evaluted to false: `!live_after.is_live(place)` + condition evaluated to false: `!live_after.is_live(place)` live_after = LivePlaces { accessed: {d}, traversed: {} } place = d"#]]); } @@ -620,5 +651,5 @@ fn array_drop_returns_unit() { array_drop[Int, given, mut[a]](a.mut, 0, 1); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class TheClass { fn go (given self) -> Int { let a = array_new [Int](5) ; array_write [Int, mut [a]](a . mut , 0 , 42) ; array_drop [Int, given, mut [a]](a . mut , 0 , 1) ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: (), b: Int, live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass}, assumptions: {}, fresh: 0 } }"#]]); } diff --git a/src/type_system/tests/assignment.rs b/src/type_system/tests/assignment.rs index 3a6c49f..d20082b 100644 --- a/src/type_system/tests/assignment.rs +++ b/src/type_system/tests/assignment.rs @@ -17,7 +17,7 @@ fn assign_leased_to_field_of_lease_that_is_typed_as_given() { (); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Pair { d1 : Data ; d2 : Data ; } class Main { fn test [perm] (given self pair : ^perm0_0 Pair, data : ^perm0_0 Data) -> () where ^perm0_0 is mut { pair . d1 = data . give ; () ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, data: !perm_0 Data, pair: !perm_0 Pair}, assumptions: {!perm_0 is mut, !perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } /// Pair is leased from P, but when you assign to its fields, @@ -56,7 +56,12 @@ fn forall_shared_P_assign_to_field_of_P_pair() { (); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Pair { d1 : Data ; d2 : Data ; } class Main { fn test [perm] (given self pair : ^perm0_0 Pair, data : given Data) -> () where ^perm0_0 is copy { pair . d1 = data . give ; () ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, @ fresh(0): Data, data: given Data, pair: !perm_0 Pair}, assumptions: {!perm_0 is copy, !perm_0 is relative, !perm_0 is atomic}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, @ fresh(0): Data, data: given Data, pair: !perm_0 Pair}, assumptions: {!perm_0 is copy, !perm_0 is relative, !perm_0 is atomic}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, @ fresh(0): Data, data: given Data, pair: !perm_0 Pair}, assumptions: {!perm_0 is copy, !perm_0 is relative, !perm_0 is atomic}, fresh: 1 } }"#]]); } /// Test that field is not assignable when using a perm var that is not shared. @@ -72,5 +77,10 @@ fn forall_P_assign_to_field_of_P_pair() { (); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Pair { d1 : Data ; d2 : Data ; } class Main { fn test [perm] (given self pair : ^perm0_0 Pair, data : given Data) -> () { pair . d1 = data . give ; () ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, @ fresh(0): Data, data: given Data, pair: !perm_0 Pair}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, @ fresh(0): Data, data: given Data, pair: !perm_0 Pair}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, @ fresh(0): Data, data: given Data, pair: !perm_0 Pair}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 1 } }"#]]); } diff --git a/src/type_system/tests/block_normalization.rs b/src/type_system/tests/block_normalization.rs new file mode 100644 index 0000000..37a72f2 --- /dev/null +++ b/src/type_system/tests/block_normalization.rs @@ -0,0 +1,292 @@ +use formality_core::test; + +// ============================================================================= +// Phase 4a: Tests for type system block-exit normalization +// +// These tests exercise normalization of result types when block-scoped +// variables go out of scope. The type system should pop let-bound variables +// at block exit and normalize the block's result type against them. +// +// Currently the type system never pops let-bound variables — they stay in +// the env indefinitely. These tests verify that: +// - Block-local variables are popped at block exit +// - Result types referencing block-locals are normalized +// - Dangling borrows from block-locals are detected +// ============================================================================= + +// --------------------------------------------------------------------------- +// given resolution through block-local variables +// --------------------------------------------------------------------------- + +/// Block returns a value obtained via given[local] where local is +/// a let-bound variable inside the block. After normalization, the +/// given should resolve to given (ownership transferred). +#[test] +fn block_given_local_resolves_to_given() { + crate::assert_ok!({ + class Data {} + class Container { + fn get(given self) -> given[self] Data { + new Data(); + } + } + class Sink { + fn consume(given self, d: given Data) { + (); + } + } + class Main { + fn go(given self) { + let result = { + let c = new Container(); + c.give.get(); + }; + let sink = new Sink(); + sink.give.consume(result.give); + (); + } + } + }); +} + +/// Same as above but with a named parameter: block calls a method +/// with given[x] return type where x is bound to a block-local value. +#[test] +fn block_given_local_param_resolves_to_given() { + crate::assert_ok!({ + class Data {} + class Funcs { + fn take(given self, x: given Data) -> given[x] Data { + x.give; + } + } + class Sink { + fn consume(given self, d: given Data) { + (); + } + } + class Main { + fn go(given self) { + let result = { + let d = new Data(); + let f = new Funcs(); + f.give.take(d.give); + }; + let sink = new Sink(); + sink.give.consume(result.give); + (); + } + } + }); +} + +// --------------------------------------------------------------------------- +// Dangling borrows from block-locals (should error) +// --------------------------------------------------------------------------- + +/// Block returns ref[local] where local is an owned block-scoped variable. +/// This is a dangling borrow — the local will be dropped at block exit. +#[test] +fn block_dangling_borrow_ref_from_local() { + crate::assert_err!({ + class Data {} + class Container { + d: given Data; + fn get[perm P](P self) -> ref[self] Data + where P is copy + { + self.d.ref; + } + } + class Main { + fn go(given self) { + let result = { + let c = new Container(new Data()); + c.ref.get[ref[c]](); + }; + (); + } + } + }, expect_test::expect![[r#" + the rule "keep non-popped link" at (pop_normalize.rs) failed because + condition evaluated to false: `!link_references_popped(&link, &popped_vars)` + &link = Rfd(c) + &popped_vars = [c] + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, c: Container}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, c: Container}, assumptions: {}, fresh: 0 } }"#]]); +} + +/// Block returns mut[local] where local is an owned block-scoped variable. +/// Same dangling borrow issue. +#[test] +fn block_dangling_borrow_mut_from_local() { + crate::assert_err!({ + class Data {} + class Container { + d: given Data; + fn get_mut[perm P](P self) -> mut[self] Data + where P is mut + { + self.d.mut; + } + } + class Main { + fn go(given self) { + let result = { + let c = new Container(new Data()); + c.mut.get_mut[mut[c]](); + }; + (); + } + } + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, c: Container}, assumptions: {}, fresh: 0 } } + + the rule "keep non-popped link" at (pop_normalize.rs) failed because + condition evaluated to false: `!link_references_popped(&link, &popped_vars)` + &link = Mtd(c) + &popped_vars = [c] + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, c: Container}, assumptions: {}, fresh: 0 } }"#]]); +} + +// --------------------------------------------------------------------------- +// Borrow chaining through block-locals +// --------------------------------------------------------------------------- + +/// Block-local borrows from outer variable. The ref chain goes: +/// result -> block-local -> outer variable. +/// After normalizing the block-local away, result should be ref[outer]. +#[test] +fn block_borrow_chain_ref_through_local_to_outer() { + crate::assert_ok!({ + class Data {} + class Funcs { + fn borrow[perm P](given self, x: P Data) -> ref[x] Data + where P is copy + { + x.ref; + } + } + class Main { + fn go(given self) { + let outer = new Data(); + let result = { + let f = new Funcs(); + f.give.borrow[ref[outer]](outer.ref); + }; + (); + } + } + }); +} + +// --------------------------------------------------------------------------- +// Block-local variables should not leak into outer scope +// --------------------------------------------------------------------------- + +/// After a block exits, its local variables should not be in the env. +/// This test creates a block-local and then tries to use it after the block. +/// This should fail because the variable is out of scope. +/// (This tests that variables are actually popped, not just normalization.) +#[test] +fn block_local_not_accessible_after_block() { + crate::assert_err!({ + class Data {} + class Main { + fn go(given self) { + let result = { + let d = new Data(); + (); + }; + d.give; + (); + } + } + }, expect_test::expect![[r#" + the rule "give place" at (expressions.rs) failed because + no variable named `d`"#]]); +} + +// --------------------------------------------------------------------------- +// Nested blocks +// --------------------------------------------------------------------------- + +/// Inner block's local is normalized before outer block sees it. +/// The inner block produces given Data (from given[inner_local]). +/// The outer block can then give it away. +#[test] +fn nested_block_given_inner_local() { + crate::assert_ok!({ + class Data {} + class Container { + fn get(given self) -> given[self] Data { + new Data(); + } + } + class Sink { + fn consume(given self, d: given Data) { + (); + } + } + class Main { + fn go(given self) { + let result = { + let inner_result = { + let c = new Container(); + c.give.get(); + }; + inner_result.give; + }; + let sink = new Sink(); + sink.give.consume(result.give); + (); + } + } + }); +} + +// --------------------------------------------------------------------------- +// Block result type doesn't reference locals — passes through unchanged +// --------------------------------------------------------------------------- + +/// Block creates a local but returns a freshly constructed value. +/// No normalization needed — should just work. +#[test] +fn block_result_no_local_refs() { + crate::assert_ok!({ + class Data {} + class Main { + fn go(given self) { + let result = { + let unused = new Data(); + new Data(); + }; + (); + } + } + }); +} + +// --------------------------------------------------------------------------- +// Copy types through block boundaries +// --------------------------------------------------------------------------- + +/// Copy type (Int) returned from block — permission stripping makes +/// this trivially fine regardless of how it was obtained. +#[test] +fn block_copy_type_through_boundary() { + crate::assert_ok!({ + class Main { + fn go(given self) { + let result = { + let x = 42; + x.give; + }; + (); + } + } + }); +} diff --git a/src/type_system/tests/cancellation.rs b/src/type_system/tests/cancellation.rs index e44f6dc..658e06f 100644 --- a/src/type_system/tests/cancellation.rs +++ b/src/type_system/tests/cancellation.rs @@ -43,9 +43,13 @@ fn shared_live_leased_to_our_leased() { } }, expect_test::expect![[r#" the rule "(ref::P) vs (shared::mut::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d - place_a = p"#]]); + place_a = p + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [d], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, p: mut [d] Data, q: ref [p] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, p: mut [d] Data, q: ref [p] Data}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -93,9 +97,11 @@ fn leased_live_leased_to_leased() { } }, expect_test::expect![[r#" the rule "(mut::P) vs (mut::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d - place_a = p"#]]); + place_a = p + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [p], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, p: mut [d] Data, q: mut [p] Data}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -144,7 +150,7 @@ fn return_leased_dead_leased_to_leased_and_use_while_leased() { } }, expect_test::expect![[r#" the rule "lease-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, leased_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, leased_place)` accessed_place = p leased_place = p"#]]); } diff --git a/src/type_system/tests/class_defn_wf.rs b/src/type_system/tests/class_defn_wf.rs index aa1e373..391f065 100644 --- a/src/type_system/tests/class_defn_wf.rs +++ b/src/type_system/tests/class_defn_wf.rs @@ -18,7 +18,7 @@ fn create_PairSh_with_non_shared_type() { (); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class PairSh [ty] where ^ty0_0 is copy { } class Main { fn test (given self) -> () { new PairSh [Data] () ; () ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -36,7 +36,7 @@ fn take_PairSh_with_non_shared_type() { (); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class PairSh [ty] where ^ty0_0 is copy { } class Main { fn test (given self input : PairSh[Data]) -> () { () ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, input: PairSh[Data]}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -65,7 +65,7 @@ fn forall_P_T_PT_requires_relative() { { field: P T; } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Ref [perm, ty] { field : ^perm0_0 ^ty0_1 ; } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:832:1: no applicable rules for variance_predicate { kind: relative, parameter: !ty_1, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !ty_1], local_variables: {self: Ref[!perm_0, !ty_1]}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -112,13 +112,13 @@ fn forall_P_T_f1_T_f2_P_leased_f1_err() { f1: T; f2: P mut[self.f1] Data; } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Ref [perm, ty] { f1 : ^ty0_1 ; f2 : ^perm0_0 mut [self . f1] Data ; } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:832:1: no applicable rules for variance_predicate { kind: relative, parameter: !ty_1, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !ty_1], local_variables: {self: Ref[!perm_0, !ty_1]}, assumptions: {}, fresh: 0 } }"#]]); } #[test] #[allow(non_snake_case)] -fn forall_P_T_f1_T_f2_P_given_from_f1_err() { - // Applying P to given_from[self.f1] requires T to be relative: +fn forall_P_T_f1_T_f2_P_given_f1_err() { + // Applying P to given[self.f1] requires T to be relative: // consider `shared Ref[mut[foo], Data]`. If we transformed // that to `shared Ref[shared mut[foo], shared Data]`, the type of // `f2` would change in important ways. @@ -127,14 +127,14 @@ fn forall_P_T_f1_T_f2_P_given_from_f1_err() { class Ref[perm P, ty T] { f1: T; - f2: P given_from[self.f1] Data; + f2: P given[self.f1] Data; } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Ref [perm, ty] { f1 : ^ty0_1 ; f2 : ^perm0_0 given_from [self . f1] Data ; } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:832:1: no applicable rules for variance_predicate { kind: relative, parameter: !ty_1, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !ty_1], local_variables: {self: Ref[!perm_0, !ty_1]}, assumptions: {}, fresh: 0 } }"#]]); } #[test] #[allow(non_snake_case)] -fn forall_P_rel_T_f1_T_f2_P_given_from_f1_ok() { +fn forall_P_rel_T_f1_T_f2_P_given_f1_ok() { crate::assert_ok!({ class Data { } class Ref[perm P, ty T] @@ -142,7 +142,7 @@ fn forall_P_rel_T_f1_T_f2_P_given_from_f1_ok() { T is relative, { f1: T; - f2: P given_from[self.f1] Data; + f2: P given[self.f1] Data; } }); } @@ -159,7 +159,7 @@ fn forall_P_T_P_Vec_T_err() { { f1: P Vec[T]; } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Vec [ty] { f1 : ^ty0_0 ; } class Ref [perm, ty] { f1 : ^perm0_0 Vec[^ty0_1] ; } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:832:1: no applicable rules for variance_predicate { kind: relative, parameter: !ty_1, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !ty_1], local_variables: {self: Ref[!perm_0, !ty_1]}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -175,7 +175,7 @@ fn Ref1_requires_rel_Ref2_does_not_err() { class Ref2[ty T] { f1: Ref1[shared, T]; } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Ref1 [perm, ty] where ^ty0_1 is relative { f1 : ^perm0_0 ^ty0_1 ; } class Ref2 [ty] { f1 : Ref1[shared, ^ty0_0] ; } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:832:1: no applicable rules for variance_predicate { kind: relative, parameter: !ty_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!ty_0], local_variables: {self: Ref2[!ty_0]}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -188,7 +188,7 @@ fn sh_from_arena() { arena: Arena; f1: ref[self.arena] T; } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Arena { } class Ref [ty] { arena : Arena ; f1 : ref [self . arena] ^ty0_0 ; } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:832:1: no applicable rules for variance_predicate { kind: relative, parameter: !ty_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!ty_0], local_variables: {self: Ref[!ty_0]}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -203,7 +203,7 @@ fn atomic_field_req_atomic_err() { the rule "check_field" at (classes.rs) failed because judgment `prove_predicate { predicate: !ty_0 is atomic, env: Env { program: "...", universe: universe(1), in_scope_vars: [!ty_0], local_variables: {self: Atomic[!ty_0]}, assumptions: {!ty_0 is share}, fresh: 0 } }` failed at the following rule(s): the rule "variance" at (predicates.rs) failed because - judgment had no applicable rules: `variance_predicate { kind: atomic, parameter: !ty_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!ty_0], local_variables: {self: Atomic[!ty_0]}, assumptions: {!ty_0 is share}, fresh: 0 } }`"#]]); + src/type_system/predicates.rs:832:1: judgment had no applicable rules: `variance_predicate { kind: atomic, parameter: !ty_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!ty_0], local_variables: {self: Atomic[!ty_0]}, assumptions: {!ty_0 is share}, fresh: 0 } }`"#]]); } #[test] diff --git a/src/type_system/tests/class_defn_wf/shared_vs_share.rs b/src/type_system/tests/class_defn_wf/shared_vs_share.rs index 49dfa1d..6f0ffcc 100644 --- a/src/type_system/tests/class_defn_wf/shared_vs_share.rs +++ b/src/type_system/tests/class_defn_wf/shared_vs_share.rs @@ -18,7 +18,7 @@ fn our_class_cannot_hold_a_share_class_directly() { the rule "is" at (predicates.rs) failed because judgment `prove_predicate { predicate: RegularClass is copy, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: OurClass}, assumptions: {}, fresh: 0 } }` failed at the following rule(s): the rule "copy" at (predicates.rs) failed because - judgment had no applicable rules: `prove_copy_predicate { p: RegularClass, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: OurClass}, assumptions: {}, fresh: 0 } }`"#]]); + src/type_system/predicates.rs:324:1: judgment had no applicable rules: `prove_copy_predicate { p: RegularClass, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: OurClass}, assumptions: {}, fresh: 0 } }`"#]]); } #[test] diff --git a/src/type_system/tests/drop_body.rs b/src/type_system/tests/drop_body.rs index 39d38d1..2dc67b8 100644 --- a/src/type_system/tests/drop_body.rs +++ b/src/type_system/tests/drop_body.rs @@ -41,7 +41,12 @@ fn share_class_drop_body_cannot_move_field() { self.x = 42; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Foo { x : Int ; drop { let v = self . x . give ; self . x = 42 ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: !perm_0 Foo, @ fresh(0): Int, v: !perm_0 Int}, assumptions: {!perm_0 is copy}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: !perm_0 Foo, @ fresh(0): Int, v: !perm_0 Int}, assumptions: {!perm_0 is copy}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: !perm_0 Foo, @ fresh(0): Int, v: !perm_0 Int}, assumptions: {!perm_0 is copy}, fresh: 1 } }"#]]); } /// A shared class drop body gets `self: P Class` where `P is ref`. @@ -89,7 +94,12 @@ fn share_class_drop_body_cannot_mut_field() { let v = self.x.mut; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Foo { x : Int ; drop { let v = self . x . mut ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: !perm_0 Foo}, assumptions: {!perm_0 is copy}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: !perm_0 Foo}, assumptions: {!perm_0 is copy}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: !perm_0 Foo}, assumptions: {!perm_0 is copy}, fresh: 0 } }"#]]); } /// Array index projection does not type-check as a place expression. diff --git a/src/type_system/tests/fn_calls.rs b/src/type_system/tests/fn_calls.rs index 59d9a95..d400f82 100644 --- a/src/type_system/tests/fn_calls.rs +++ b/src/type_system/tests/fn_calls.rs @@ -56,8 +56,10 @@ fn send_same_message_twice() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Bar, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, @ fresh(0): mut [channel] Channel[Bar], bar: Bar, channel: Channel[Bar]}, assumptions: {}, fresh: 1 } } + the rule "give" at (expressions.rs) failed because - condition evaluted to false: `!live_after.is_live(place)` + condition evaluated to false: `!live_after.is_live(place)` live_after = LivePlaces { accessed: {@ fresh(0), bar, channel}, traversed: {} } place = bar"#]]) } @@ -85,7 +87,7 @@ fn needs_leased_got_shared_self() { (); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Bar { } class Channel [ty] { fn send [perm] (^perm0_0 self msg : ^ty1_0) -> () where ^perm0_0 is mut { } } class TheClass { fn empty_method (given self) -> () { let channel = new Channel [Bar] () ; let bar = new Bar () ; channel . ref . send [ref [channel]] (bar . give) ; () ; } } }`"]) + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [channel], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, @ fresh(0): ref [channel] Channel[Bar], @ fresh(1): Bar, bar: Bar, channel: Channel[Bar]}, assumptions: {}, fresh: 2 } }"#]]) } /// Test where function expects a `Pair` and data borrowed from `pair`. @@ -175,7 +177,7 @@ fn take_pair_and_data__give_pair_share_data_share_later() { } }, expect_test::expect![[r#" the rule "share-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, shared_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, shared_place)` accessed_place = @ fresh(1) shared_place = @ fresh(1) . a"#]]) } @@ -209,7 +211,7 @@ fn take_pair_and_data__give_pair_give_data_give_later() { } }, expect_test::expect![[r#" the rule "share-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, shared_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, shared_place)` accessed_place = @ fresh(1) shared_place = @ fresh(1) . a"#]]) } @@ -297,7 +299,9 @@ fn pair_method__expect_leased_self_a__got_leased_self_b() { } }, expect_test::expect![[r#" the rule "(mut::P) vs (mut::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = @ fresh(0) . a - place_a = @ fresh(0) . b"#]]) + place_a = @ fresh(0) . b + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [@ fresh(0) . b], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Pair, @ fresh(1): mut [@ fresh(0) . b] Data, data: mut [@ fresh(0) . b] Data, pair: Pair}, assumptions: {}, fresh: 2 } }"#]]) } diff --git a/src/type_system/tests/given_classes.rs b/src/type_system/tests/given_classes.rs index 799bf9d..8046993 100644 --- a/src/type_system/tests/given_classes.rs +++ b/src/type_system/tests/given_classes.rs @@ -66,7 +66,19 @@ fn regular_class_cannot_hold_P_guard_class() { the rule "share" at (predicates.rs) failed because judgment `prove_share_predicate { p: GivenClass, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: RegularClass[!perm_0]}, assumptions: {}, fresh: 0 } }` failed at the following rule(s): the rule "share class" at (predicates.rs) failed because - pattern `true` did not match value `false`"#]]); + pattern `true` did not match value `false` + the rule "share copy T" at (predicates.rs) failed because + judgment `prove_is_copy { a: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: RegularClass[!perm_0]}, assumptions: {}, fresh: 0 } }` failed at the following rule(s): + the rule "is" at (predicates.rs) failed because + judgment `prove_predicate { predicate: !perm_0 is copy, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: RegularClass[!perm_0]}, assumptions: {}, fresh: 0 } }` failed at the following rule(s): + the rule "copy" at (predicates.rs) failed because + src/type_system/predicates.rs:324:1: judgment had no applicable rules: `prove_copy_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: RegularClass[!perm_0]}, assumptions: {}, fresh: 0 } }` + the rule "share mut T" at (predicates.rs) failed because + judgment `prove_is_mut { a: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: RegularClass[!perm_0]}, assumptions: {}, fresh: 0 } }` failed at the following rule(s): + the rule "is-mut" at (predicates.rs) failed because + judgment `prove_predicate { predicate: !perm_0 is mut, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: RegularClass[!perm_0]}, assumptions: {}, fresh: 0 } }` failed at the following rule(s): + the rule "mut" at (predicates.rs) failed because + src/type_system/predicates.rs:623:1: judgment had no applicable rules: `prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: RegularClass[!perm_0]}, assumptions: {}, fresh: 0 } }`"#]]); } // FIXME: We use `P is mut` here but would be better served with a predicate diff --git a/src/type_system/tests/given_classes/lock_given.rs b/src/type_system/tests/given_classes/lock_given.rs index 2e541d4..bacef76 100644 --- a/src/type_system/tests/given_classes/lock_given.rs +++ b/src/type_system/tests/given_classes/lock_given.rs @@ -70,6 +70,15 @@ fn lock_guard_cancellation() { " ), expect_test::expect![[r#" the rule "share class" at (predicates.rs) failed because - pattern `true` did not match value `false`"#]]); + pattern `true` did not match value `false` + + the rule "keep non-popped link" at (pop_normalize.rs) failed because + condition evaluated to false: `!link_references_popped(&link, &popped_vars)` + &link = Mtd(guard) + &popped_vars = [data, guard] + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: !perm_1, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !perm_1], local_variables: {self: given Main, data: mut [guard] !perm_1 Data, guard: Guard[ref [lock], !perm_1 Data], lock: !perm_0 Lock[!perm_1 Data]}, assumptions: {!perm_0 is copy, !perm_1 is mut, !perm_0 is relative, !perm_1 is relative, !perm_0 is atomic, !perm_1 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !perm_1], local_variables: {self: given Main, data: mut [guard] !perm_1 Data, guard: Guard[ref [lock], !perm_1 Data], lock: !perm_0 Lock[!perm_1 Data]}, assumptions: {!perm_0 is copy, !perm_1 is mut, !perm_0 is relative, !perm_1 is relative, !perm_0 is atomic, !perm_1 is atomic}, fresh: 0 } }"#]]); } diff --git a/src/type_system/tests/mdbook.rs b/src/type_system/tests/mdbook.rs index 3022478..9092065 100644 --- a/src/type_system/tests/mdbook.rs +++ b/src/type_system/tests/mdbook.rs @@ -90,8 +90,10 @@ fn giving_a_value_twice_is_error() { } }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data}, assumptions: {}, fresh: 0 } } + the rule "give" at (expressions.rs) failed because - condition evaluted to false: `!live_after.is_live(place)` + condition evaluated to false: `!live_after.is_live(place)` live_after = LivePlaces { accessed: {d}, traversed: {} } place = d"#]] ); @@ -143,8 +145,10 @@ fn giving_field_then_whole_is_error() { } }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, p: Pair}, assumptions: {}, fresh: 0 } } + the rule "give" at (expressions.rs) failed because - condition evaluted to false: `!live_after.is_live(place)` + condition evaluated to false: `!live_after.is_live(place)` live_after = LivePlaces { accessed: {p}, traversed: {} } place = p . a"#]] ); @@ -172,8 +176,10 @@ fn giving_whole_then_field_is_error() { } }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Pair, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, p: Pair}, assumptions: {}, fresh: 0 } } + the rule "give" at (expressions.rs) failed because - condition evaluted to false: `!live_after.is_live(place)` + condition evaluated to false: `!live_after.is_live(place)` live_after = LivePlaces { accessed: {p . a}, traversed: {} } place = p"#]] ); @@ -334,7 +340,7 @@ fn mutation_through_ref_is_error() { }, expect_test::expect![[r#" the rule "share-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, shared_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, shared_place)` accessed_place = foo . i shared_place = foo"#]] ); @@ -364,7 +370,7 @@ fn giving_field_while_refd_is_error() { }, expect_test::expect![[r#" the rule "share-give" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from_or_prefix_of(accessed_place, shared_place)` + condition evaluated to false: `place_disjoint_from_or_prefix_of(accessed_place, shared_place)` accessed_place = foo . i shared_place = foo"#]] ); @@ -418,7 +424,7 @@ fn mut_borrow_blocks_read() { }, expect_test::expect![[r#" the rule "lease-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, leased_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, leased_place)` accessed_place = foo . i leased_place = foo"#]] ); @@ -471,7 +477,7 @@ fn transitive_restrictions() { }, expect_test::expect![[r#" the rule "lease-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, leased_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, leased_place)` accessed_place = p . i leased_place = p"#]] ); @@ -635,7 +641,7 @@ fn subtyping_different_classes_fail() { } } }, - expect_test::expect!["judgment had no applicable rules: `check_program { program: class Foo { } class Bar { } class Main { fn test (given self) -> () { let f = new Foo () ; let b : Bar = f . give ; () ; } } }`"] + expect_test::expect![[r#"src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: Foo, b: Bar, live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, f: Foo}, assumptions: {}, fresh: 0 } }"#]] ); // ANCHOR_END: subtyping_different_classes_fail } @@ -655,10 +661,16 @@ fn subtyping_narrowing_ref_fails() { } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data}, assumptions: {}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d1 - place_a = d2"#]] + place_a = d2 + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data}, assumptions: {}, fresh: 0 } }"#]] ); // ANCHOR_END: subtyping_narrowing_ref_fails } @@ -735,7 +747,18 @@ fn subtyping_non_copy_params_block_erasure() { } } }, - expect_test::expect!["judgment had no applicable rules: `check_program { program: shared class Box [ty] { value : ^ty0_0 ; } class Data { } class Main { fn test (given self d : given Data) -> Box[Data] { let b : ref [d] Box[Data] = new Box [Data] (new Data ()) ; b . give ; } } }`"] + expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [d], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [d], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } }"#]] ); // ANCHOR_END: subtyping_non_copy_params_block_erasure } @@ -777,10 +800,16 @@ fn subtyping_place_refinement_reverse_fails() { } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d . left - place_a = d"#]] + place_a = d + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } }"#]] ); // ANCHOR_END: subtyping_place_refinement_reverse_fails } @@ -823,7 +852,12 @@ fn copy_perm_ref_not_subtype_shared() { } } }, - expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test (given self d : given Data) -> () { let r : ref [d] Data = d . ref ; let s : shared Data = r . give ; () ; } } }`"] + expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data, r: ref [d] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data, r: ref [d] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data, r: ref [d] Data}, assumptions: {}, fresh: 0 } }"#]] ); // ANCHOR_END: copy_perm_ref_not_subtype_shared } @@ -882,7 +916,7 @@ fn copy_perm_shared_mut_not_subtype_ref() { } } }, - expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test (given self d : given Data) -> () { let p : mut [d] Data = d . mut ; let sm : shared mut [d] Data = p . ref ; let r : ref [d] Data = sm . give ; () ; } } }`"] + expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data, p: mut [d] Data, sm: shared mut [d] Data}, assumptions: {}, fresh: 0 } }"#]] ); // ANCHOR_END: copy_perm_shared_mut_not_subtype_ref } @@ -942,7 +976,10 @@ fn copy_perm_mut_not_subtype_ref() { } } }, - expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test (given self d : given Data) -> () { let p : mut [d] Data = d . mut ; let q : ref [d] Data = p . give ; () ; } } }`"] + expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data, p: mut [d] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [d], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data, p: mut [d] Data}, assumptions: {}, fresh: 0 } }"#]] ); // ANCHOR_END: copy_perm_mut_not_subtype_ref } @@ -961,7 +998,7 @@ fn copy_perm_given_not_subtype_shared() { } } }, - expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test (given self d : given Data) -> () { let s : shared Data = d . give ; () ; } } }`"] + expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } }"#]] ); // ANCHOR_END: copy_perm_given_not_subtype_shared } @@ -1030,10 +1067,16 @@ fn place_ordering_reverse_fails() { } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d . left - place_a = d"#]] + place_a = d + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } }"#]] ); // ANCHOR_END: place_ordering_reverse_fails } @@ -1072,10 +1115,16 @@ fn place_ordering_dropping_source_fails() { } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data, r: ref [d1, d2] Data}, assumptions: {}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d1 - place_a = d2"#]] + place_a = d2 + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data, r: ref [d1, d2] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data, r: ref [d1, d2] Data}, assumptions: {}, fresh: 0 } }"#]] ); // ANCHOR_END: place_ordering_dropping_source_fails } @@ -1170,9 +1219,11 @@ fn liveness_live_mut_no_cancel() { }, expect_test::expect![[r#" the rule "(mut::P) vs (mut::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d - place_a = p"#]] + place_a = p + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [p], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, p: mut [d] Data, q: mut [p] Data}, assumptions: {}, fresh: 0 } }"#]] ); // ANCHOR_END: liveness_live_mut_no_cancel } @@ -1219,9 +1270,13 @@ fn liveness_live_ref_no_promote() { }, expect_test::expect![[r#" the rule "(ref::P) vs (shared::mut::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d - place_a = p"#]] + place_a = p + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [d], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, p: mut [d] Data, q: ref [p] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, p: mut [d] Data, q: ref [p] Data}, assumptions: {}, fresh: 0 } }"#]] ); // ANCHOR_END: liveness_live_ref_no_promote } @@ -1261,7 +1316,12 @@ fn liveness_ref_shared_no_cancel() { } } }, - expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test (given self) -> () { let d = new Data () ; let p : mut [d] Data = d . mut ; let q : ref [p] mut [d] Data = p . ref ; let r : mut [d] Data = q . give ; () ; } } }`"] + expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [d], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, p: mut [d] Data, q: ref [p] mut [d] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [d], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, p: mut [d] Data, q: ref [p] mut [d] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, p: mut [d] Data, q: ref [p] mut [d] Data}, assumptions: {}, fresh: 0 } }"#]] ); // ANCHOR_END: liveness_ref_shared_no_cancel } @@ -1284,7 +1344,15 @@ fn liveness_all_places_must_be_dead() { } } }, - expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test (given self) -> () { let d = new Data () ; let p : ref [d] Data = d . ref ; let q : ref [d] Data = d . ref ; let r : ref [p, q] ref [d] Data = p . ref ; let s : ref [d] Data = r . give ; q . give ; } } }`"] + expect_test::expect![[r#" + the rule "keep non-popped link" at (pop_normalize.rs) failed because + condition evaluated to false: `!link_references_popped(&link, &popped_vars)` + &link = Rfd(d) + &popped_vars = [d, p, q, r, s] + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, p: ref [d] Data, q: ref [d] Data, r: ref [@ fresh(0), p] ref [d] Data, s: ref [d] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, p: ref [d] Data, q: ref [d] Data, r: ref [@ fresh(0), p] ref [d] Data, s: ref [d] Data}, assumptions: {}, fresh: 0 } }"#]] ); // ANCHOR_END: liveness_all_places_must_be_dead } diff --git a/src/type_system/tests/move_check.rs b/src/type_system/tests/move_check.rs index 9378aa3..76f86bd 100644 --- a/src/type_system/tests/move_check.rs +++ b/src/type_system/tests/move_check.rs @@ -19,8 +19,10 @@ fn give_same_field_twice() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, foo: Foo}, assumptions: {}, fresh: 0 } } + the rule "give" at (expressions.rs) failed because - condition evaluted to false: `!live_after.is_live(place)` + condition evaluated to false: `!live_after.is_live(place)` live_after = LivePlaces { accessed: {foo . i}, traversed: {} } place = foo . i"#]]) } @@ -44,8 +46,10 @@ fn give_field_of_moved_variable() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Foo, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, foo: Foo}, assumptions: {}, fresh: 0 } } + the rule "give" at (expressions.rs) failed because - condition evaluted to false: `!live_after.is_live(place)` + condition evaluated to false: `!live_after.is_live(place)` live_after = LivePlaces { accessed: {foo . i}, traversed: {} } place = foo"#]]) } @@ -69,8 +73,10 @@ fn give_variable_with_moved_field() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, foo: Foo}, assumptions: {}, fresh: 0 } } + the rule "give" at (expressions.rs) failed because - condition evaluted to false: `!live_after.is_live(place)` + condition evaluated to false: `!live_after.is_live(place)` live_after = LivePlaces { accessed: {foo}, traversed: {} } place = foo . i"#]]) } @@ -119,8 +125,12 @@ fn give_leased_value() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [foo], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, bar: mut [foo] Foo, foo: Foo}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Foo, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, bar: mut [foo] Foo, foo: Foo}, assumptions: {}, fresh: 0 } } + the rule "give" at (expressions.rs) failed because - condition evaluted to false: `!live_after.is_live(place)` + condition evaluated to false: `!live_after.is_live(place)` live_after = LivePlaces { accessed: {bar}, traversed: {} } place = bar"#]]) } diff --git a/src/type_system/tests/move_tracking.rs b/src/type_system/tests/move_tracking.rs index 79f482a..e6bb71d 100644 --- a/src/type_system/tests/move_tracking.rs +++ b/src/type_system/tests/move_tracking.rs @@ -75,7 +75,7 @@ fn give_while_shared_then_move_while_shared() { } }, expect_test::expect![[r#" the rule "share-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, shared_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, shared_place)` accessed_place = @ fresh(0) shared_place = @ fresh(0)"#]]) } @@ -147,7 +147,7 @@ fn give_while_shared_then_assign_while_shared_then_mutate_new_place() { } }, expect_test::expect![[r#" the rule "share-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, shared_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, shared_place)` accessed_place = d shared_place = d"#]]) } diff --git a/src/type_system/tests/new_with_self_references.rs b/src/type_system/tests/new_with_self_references.rs index 31567f5..dbdf486 100644 --- a/src/type_system/tests/new_with_self_references.rs +++ b/src/type_system/tests/new_with_self_references.rs @@ -86,10 +86,14 @@ fn choice_with_non_self_ref() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, @ fresh(0): Choice, d1: Data, d2: Data, d3: Data, pair: Pair, r: ref [d3] Data}, assumptions: {}, fresh: 1 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = @ fresh(0) . pair - place_a = d3"#]]) + place_a = d3 + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, @ fresh(0): Choice, d1: Data, d2: Data, d3: Data, pair: Pair, r: ref [d3] Data}, assumptions: {}, fresh: 1 } }"#]]) } /// Test that we can create a `Choice`, @@ -174,7 +178,7 @@ fn unpack_and_reconstruct_wrong_order() { } }, expect_test::expect![[r#" the rule "share-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, shared_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, shared_place)` accessed_place = choice1 . pair shared_place = choice1 . pair"#]]) } @@ -206,7 +210,7 @@ fn lease_when_internally_leased() { } }, expect_test::expect![[r#" the rule "lease-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, leased_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, leased_place)` accessed_place = choice . pair leased_place = choice . pair"#]]) } @@ -240,7 +244,7 @@ fn unpack_and_reconstruct_drop_then_access() { } }, expect_test::expect![[r#" the rule "share-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, shared_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, shared_place)` accessed_place = choice . pair shared_place = choice . pair"#]]) } diff --git a/src/type_system/tests/normalization.rs b/src/type_system/tests/normalization.rs index 73eec8d..17b1dff 100644 --- a/src/type_system/tests/normalization.rs +++ b/src/type_system/tests/normalization.rs @@ -5,8 +5,8 @@ use formality_core::test; // // These tests exercise call-site resolution of return types that reference // method parameters. They cover: -// - given_from[self] resolution (currently works by accident via Var::This bug) -// - given_from[self] where caller has different self permission (exposes bug) +// - given[self] resolution (currently works by accident via Var::This bug) +// - given[self] where caller has different self permission (exposes bug) // - Dangling borrows (ref from given — should error) // - Borrow chaining (ref through ref — should succeed) // - Multi-place resolution producing Or @@ -15,18 +15,18 @@ use formality_core::test; // ============================================================================= // --------------------------------------------------------------------------- -// given_from[self] resolution +// given[self] resolution // --------------------------------------------------------------------------- -/// Basic: method returns given_from[self] with given self. +/// Basic: method returns given[self] with given self. /// Currently passes by accident (Var::This collision). /// After fix, should still pass with correct resolution. #[test] -fn given_from_self_basic() { +fn given_self_basic() { crate::assert_ok!({ class Data {} class Container { - fn get(given self) -> given_from[self] Data { + fn get(given self) -> given[self] Data { new Data(); } } @@ -41,16 +41,16 @@ fn given_from_self_basic() { } /// Bug exposer: caller's self has ref permission, but the method's self -/// is given. The return type given_from[self] should resolve to given (from +/// is given. The return type given[self] should resolve to given (from /// Container's given self), NOT ref (from Caller's ref self). /// /// After the call, `result` should be `given Data`, so giving it away should work. #[test] -fn given_from_self_different_caller_perm() { +fn given_self_different_caller_perm() { crate::assert_ok!({ class Data {} class Container { - fn get(given self) -> given_from[self] Data { + fn get(given self) -> given[self] Data { new Data(); } } @@ -70,14 +70,14 @@ fn given_from_self_different_caller_perm() { }); } -/// Named parameter: method returns given_from[x] where x is a named parameter. +/// Named parameter: method returns given[x] where x is a named parameter. /// The return type should resolve based on x's binding at the call site. #[test] -fn given_from_named_param() { +fn given_named_param() { crate::assert_ok!({ class Data {} class Funcs { - fn take(given self, x: given Data) -> given_from[x] Data { + fn take(given self, x: given Data) -> given[x] Data { x.give; } } @@ -92,14 +92,14 @@ fn given_from_named_param() { }); } -/// Named parameter given_from resolution: result should be given, so +/// Named parameter given resolution: result should be given, so /// giving it to a consumer should work. #[test] -fn given_from_named_param_give_result() { +fn given_named_param_give_result() { crate::assert_ok!({ class Data {} class Funcs { - fn take(given self, x: given Data) -> given_from[x] Data { + fn take(given self, x: given Data) -> given[x] Data { x.give; } } @@ -144,7 +144,15 @@ fn dangling_borrow_ref_from_given_self() { (); } } - }, expect_test::expect![[""]]); + }, expect_test::expect![[r#" + the rule "keep non-popped link" at (pop_normalize.rs) failed because + condition evaluated to false: `!link_references_popped(&link, &popped_vars)` + &link = Rfd(@ fresh(0)) + &popped_vars = [@ fresh(0)] + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Container, c: Container}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Container, c: Container}, assumptions: {}, fresh: 1 } }"#]]); } /// Method returns ref[x] where x is a given parameter → dangling borrow. @@ -165,7 +173,15 @@ fn dangling_borrow_ref_from_given_param() { (); } } - }, expect_test::expect![[""]]); + }, expect_test::expect![[r#" + the rule "keep non-popped link" at (pop_normalize.rs) failed because + condition evaluated to false: `!link_references_popped(&link, &popped_vars)` + &link = Rfd(@ fresh(1)) + &popped_vars = [@ fresh(1), @ fresh(0)] + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Funcs, @ fresh(1): Data, d: Data, f: Funcs}, assumptions: {}, fresh: 2 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Funcs, @ fresh(1): Data, d: Data, f: Funcs}, assumptions: {}, fresh: 2 } }"#]]); } /// Multi-place ref[x, y] where both x and y are given → dangling borrow. @@ -188,7 +204,15 @@ fn dangling_borrow_ref_from_two_given_params() { (); } } - }, expect_test::expect![[""]]); + }, expect_test::expect![[r#" + the rule "keep non-popped link" at (pop_normalize.rs) failed because + condition evaluated to false: `!link_references_popped(&link, &popped_vars)` + &link = Rfd(@ fresh(1)) + &popped_vars = [@ fresh(2), @ fresh(1), @ fresh(0)] + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Funcs, @ fresh(1): Data, @ fresh(2): Data, d1: Data, d2: Data, f: Funcs}, assumptions: {}, fresh: 3 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Funcs, @ fresh(1): Data, @ fresh(2): Data, d1: Data, d2: Data, f: Funcs}, assumptions: {}, fresh: 3 } }"#]]); } /// Mixed: ref[x, y] where x is ref (ok) but y is given (dangles). @@ -213,7 +237,15 @@ fn dangling_borrow_ref_mixed_ref_and_given() { (); } } - }, expect_test::expect![[""]]); + }, expect_test::expect![[r#" + the rule "keep non-popped link" at (pop_normalize.rs) failed because + condition evaluated to false: `!link_references_popped(&link, &popped_vars)` + &link = Rfd(@ fresh(2)) + &popped_vars = [@ fresh(2), @ fresh(1), @ fresh(0)] + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Funcs, @ fresh(1): ref [d1] Data, @ fresh(2): Data, d1: Data, d2: Data, f: Funcs}, assumptions: {}, fresh: 3 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Funcs, @ fresh(1): ref [d1] Data, @ fresh(2): Data, d1: Data, d2: Data, f: Funcs}, assumptions: {}, fresh: 3 } }"#]]); } // --------------------------------------------------------------------------- @@ -247,14 +279,16 @@ fn perm_dependent_borrow_ref_arg_ok() { /// fn foo[perm P](x: P Data) -> ref[x] Data /// Called with given arg → dangling borrow. The ref borrows from an /// owned value that will be dropped when the method's fresh temp is popped. +/// +/// Note: no `where P is copy` constraint — that would reject `P = given` +/// at the predicate level before the call site is reached, hiding the +/// dangling borrow error we're testing for. #[test] fn perm_dependent_borrow_given_arg_dangles() { crate::assert_err!({ class Data {} class Funcs { - fn foo[perm P](given self, x: P Data) -> ref[x] Data - where P is copy - { + fn foo[perm P](given self, x: P Data) -> ref[x] Data { x.ref; } } @@ -266,7 +300,15 @@ fn perm_dependent_borrow_given_arg_dangles() { (); } } - }, expect_test::expect![[""]]); + }, expect_test::expect![[r#" + the rule "keep non-popped link" at (pop_normalize.rs) failed because + condition evaluated to false: `!link_references_popped(&link, &popped_vars)` + &link = Rfd(@ fresh(1)) + &popped_vars = [@ fresh(1), @ fresh(0)] + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Funcs, @ fresh(1): Data, d: Data, f: Funcs}, assumptions: {}, fresh: 2 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Funcs, @ fresh(1): Data, d: Data, f: Funcs}, assumptions: {}, fresh: 2 } }"#]]); } // --------------------------------------------------------------------------- @@ -349,14 +391,14 @@ fn multi_place_ref_produces_or() { }); } -/// given_from[x, y] with both given args → produces or(given, given) = given. +/// given[x, y] with both given args → produces or(given, given) = given. /// Result should be fully owned. #[test] -fn multi_place_given_from_both_given() { +fn multi_place_given_both_given() { crate::assert_ok!({ class Data {} class Funcs { - fn pick(given self, x: given Data, y: given Data) -> given_from[x, y] Data { + fn pick(given self, x: given Data, y: given Data) -> given[x, y] Data { x.give; } } @@ -433,7 +475,11 @@ fn norm_or_ref_blocks_give_d1() { (); } } - }, expect_test::expect![[""]]); + }, expect_test::expect![[r#" + the rule "share-mutation" at (accesses.rs) failed because + condition evaluated to false: `place_disjoint_from(accessed_place, shared_place)` + accessed_place = @ fresh(0) + shared_place = @ fresh(0)"#]]); } /// Normalized or(mut[d1], mut[d2]) should block mutating d1 while result is live. @@ -461,7 +507,11 @@ fn norm_or_mut_blocks_mut_d1() { (); } } - }, expect_test::expect![[""]]); + }, expect_test::expect![[r#" + the rule "lease-mutation" at (accesses.rs) failed because + condition evaluated to false: `place_disjoint_from(accessed_place, leased_place)` + accessed_place = d1 + leased_place = d1"#]]); } /// Normalized or(shared mut[d1], shared mut[d2]) from ref-through-mut @@ -490,7 +540,11 @@ fn norm_or_shared_mut_blocks_mut_d1() { (); } } - }, expect_test::expect![[""]]); + }, expect_test::expect![[r#" + the rule "lease-mutation" at (accesses.rs) failed because + condition evaluated to false: `place_disjoint_from(accessed_place, leased_place)` + accessed_place = d1 + leased_place = d1"#]]); } /// After normalized or-borrowed result is dead, d1 and d2 should be accessible. diff --git a/src/type_system/tests/or_perm.rs b/src/type_system/tests/or_perm.rs index d8b1171..d9c54f4 100644 --- a/src/type_system/tests/or_perm.rs +++ b/src/type_system/tests/or_perm.rs @@ -133,7 +133,7 @@ fn wf_or_nested_rejected() { } }, expect_test::expect![[r#" the rule "or" at (types.rs) failed because - condition evaluted to false: `perms.iter().all(|p| !matches!(p, Perm::Or(_)))`"#]]); + condition evaluated to false: `perms.iter().all(|p| !matches!(p, Perm::Or(_)))`"#]]); } // --------------------------------------------------------------------------- @@ -200,7 +200,7 @@ fn predicate_or_not_copy_when_mut() { (); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn check [perm] (given self) -> () where ^perm0_0 is copy { () ; } fn test (given self x : given Data, y : given Data) -> () { self . give . check [or(mut [x], mut [y])] () ; () ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [x], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): given Main, x: given Data, y: given Data}, assumptions: {}, fresh: 1 } }"#]]); } /// or(given, given) is given ✅ @@ -245,7 +245,7 @@ fn predicate_or_not_copy_when_given() { (); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn check [perm] (given self) -> () where ^perm0_0 is copy { () ; } fn test (given self) -> () { self . give . check [or(given)] () ; () ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): given Main}, assumptions: {}, fresh: 1 } }"#]]); } /// or(given, given) is move ✅ — given implies move @@ -275,7 +275,7 @@ fn predicate_or_not_copy_mixed_ref_mut() { (); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn check [perm] (given self) -> () where ^perm0_0 is copy { () ; } fn test (given self x : given Data, y : given Data) -> () { self . give . check [or(ref [x], mut [y])] () ; () ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [y], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): given Main, x: given Data, y: given Data}, assumptions: {}, fresh: 1 } }"#]]); } // --------------------------------------------------------------------------- @@ -325,10 +325,16 @@ fn subtype_or_ref_not_subtype_single_ref() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: or(ref [x], ref [y]) Data, x: given Data, y: given Data}, assumptions: {}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = x - place_a = y"#]]); + place_a = y + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: or(ref [x], ref [y]) Data, x: given Data, y: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: or(ref [x], ref [y]) Data, x: given Data, y: given Data}, assumptions: {}, fresh: 0 } }"#]]); } /// or(ref[x], ref[y]) <: or(ref[x], ref[y], ref[z]) ✅ @@ -361,15 +367,27 @@ fn subtype_or_superset_fails() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: or(ref [x], ref [y], ref [z]) Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = x place_a = z + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: or(ref [x], ref [y], ref [z]) Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: or(ref [x], ref [y], ref [z]) Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: or(ref [x], ref [y], ref [z]) Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = y - place_a = z"#]]); + place_a = z + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: or(ref [x], ref [y], ref [z]) Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: or(ref [x], ref [y], ref [z]) Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } }"#]]); } /// ref[x] <: or(ref[x], ref[y]) ✅ @@ -401,15 +419,27 @@ fn subtype_or_partial_overlap_fails() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: or(ref [x], ref [y]) Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = y place_a = x + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: or(ref [x], ref [y]) Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: or(ref [x], ref [y]) Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: or(ref [x], ref [y]) Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = z - place_a = x"#]]); + place_a = x + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: or(ref [x], ref [y]) Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: or(ref [x], ref [y]) Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } }"#]]); } /// or(ref[x], ref[y]) <: or(ref[x], ref[y]) ✅ @@ -473,15 +503,27 @@ fn subtype_wider_multi_ref_to_or_fails() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: ref [x, y, z] Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = x place_a = z + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: ref [x, y, z] Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: ref [x, y, z] Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: ref [x, y, z] Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = y - place_a = z"#]]); + place_a = z + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: ref [x, y, z] Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: ref [x, y, z] Data, x: given Data, y: given Data, z: given Data}, assumptions: {}, fresh: 0 } }"#]]); } // --------------------------------------------------------------------------- @@ -513,7 +555,7 @@ fn or_ref_blocks_give_d1() { } }, expect_test::expect![[r#" the rule "share-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, shared_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, shared_place)` accessed_place = @ fresh(0) shared_place = @ fresh(0)"#]]); } @@ -535,7 +577,7 @@ fn or_ref_blocks_give_d2() { } }, expect_test::expect![[r#" the rule "share-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, shared_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, shared_place)` accessed_place = @ fresh(0) shared_place = @ fresh(0)"#]]); } @@ -559,7 +601,7 @@ fn or_mut_blocks_mut_d1() { } }, expect_test::expect![[r#" the rule "lease-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, leased_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, leased_place)` accessed_place = d1 leased_place = d1"#]]); } @@ -585,7 +627,7 @@ fn or_shared_mut_blocks_mut_d1() { } }, expect_test::expect![[r#" the rule "lease-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, leased_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, leased_place)` accessed_place = d1 leased_place = d1"#]]); } diff --git a/src/type_system/tests/permission_check.rs b/src/type_system/tests/permission_check.rs index def5834..757a2b7 100644 --- a/src/type_system/tests/permission_check.rs +++ b/src/type_system/tests/permission_check.rs @@ -24,7 +24,7 @@ fn share_field_of_leased_value() { } }, expect_test::expect![[r#" the rule "lease-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, leased_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, leased_place)` accessed_place = foo . i leased_place = foo"#]]) } @@ -74,7 +74,7 @@ fn lease_field_of_shared_value() { } }, expect_test::expect![[r#" the rule "share-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, shared_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, shared_place)` accessed_place = foo . i shared_place = foo"#]]) } @@ -98,7 +98,16 @@ fn ref_then_mut_errors() { (); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Foo { i : Data ; } class Main { fn main (given self) -> () { let foo = new Foo (new Data ()) ; let bar = foo . ref ; let i = bar . i . mut ; () ; } } }`"]) + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [foo], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, bar: ref [foo] Foo, foo: Foo}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Foo, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, bar: ref [foo] Foo, foo: Foo}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [foo], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, bar: ref [foo] Foo, foo: Foo}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [foo], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, bar: ref [foo] Foo, foo: Foo}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Foo, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, bar: ref [foo] Foo, foo: Foo}, assumptions: {}, fresh: 0 } }"#]]) } /// Check giving a field from a shared value is not ok. @@ -123,7 +132,7 @@ fn give_field_of_shared_value() { } }, expect_test::expect![[r#" the rule "share-give" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from_or_prefix_of(accessed_place, shared_place)` + condition evaluated to false: `place_disjoint_from_or_prefix_of(accessed_place, shared_place)` accessed_place = foo . i shared_place = foo"#]]) } @@ -197,7 +206,7 @@ fn share_field_of_leased_value_but_lease_variable_is_dead() { } }, expect_test::expect![[r#" the rule "lease-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, leased_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, leased_place)` accessed_place = p . i leased_place = p"#]]) } @@ -225,7 +234,7 @@ fn share_field_of_leased_value_but_lease_variable_is_dead_explicit_ty() { } }, expect_test::expect![[r#" the rule "lease-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, leased_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, leased_place)` accessed_place = p . i leased_place = p"#]]) } @@ -250,7 +259,7 @@ fn pair_method__leased_self__use_self() { } }, expect_test::expect![[r#" the rule "lease-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, leased_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, leased_place)` accessed_place = self . a leased_place = self"#]]) } @@ -272,7 +281,20 @@ fn mutate_field_of_shared_pair() { (); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Pair { a : Data ; b : Data ; fn method (given self data : given Data) -> () { let me = self . ref ; me . a = data . give ; () ; } } }`"]) + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [self], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Pair, @ fresh(0): Data, data: given Data, me: ref [self] Pair}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Pair, @ fresh(0): Data, data: given Data, me: ref [self] Pair}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Pair, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Pair, @ fresh(0): Data, data: given Data, me: ref [self] Pair}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [self], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Pair, @ fresh(0): Data, data: given Data, me: ref [self] Pair}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [self], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Pair, @ fresh(0): Data, data: given Data, me: ref [self] Pair}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Pair, @ fresh(0): Data, data: given Data, me: ref [self] Pair}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Pair, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Pair, @ fresh(0): Data, data: given Data, me: ref [self] Pair}, assumptions: {}, fresh: 1 } }"#]]) } /// Test that we cannot mutate fields of a shared class. @@ -291,7 +313,12 @@ fn mutate_field_of_our_pair() { (); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Pair { a : Data ; b : Data ; fn method (given self pair : shared Pair, data : given Data) -> () { pair . a = data . give ; () ; } } }`"]) + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Pair, @ fresh(0): Data, data: given Data, pair: shared Pair}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Pair, @ fresh(0): Data, data: given Data, pair: shared Pair}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Pair, @ fresh(0): Data, data: given Data, pair: shared Pair}, assumptions: {}, fresh: 1 } }"#]]) } /// Test that we can mutate fields of a leased class. @@ -374,10 +401,14 @@ fn take_given_and_shared_move_given_then_return_shared() { } } }, expect_test::expect![[r#" - the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` - place_b = owner - place_a = owner1"#]]) + the rule "keep non-popped link" at (pop_normalize.rs) failed because + condition evaluated to false: `!link_references_popped(&link, &popped_vars)` + &link = Rfd(owner1) + &popped_vars = [d, owner1] + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Pair, d: ref [owner1] Data, data: ref [owner1] Data, owner: given Data, owner1: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Pair, d: ref [owner1] Data, data: ref [owner1] Data, owner: given Data, owner1: given Data}, assumptions: {}, fresh: 0 } }"#]]) } /// Interesting example from [conversation with Isaac][r]. In this example, @@ -451,7 +482,7 @@ fn escapes_err_use_again() { y.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class R [ty] { value : ^ty0_0 ; } class Main { fn foo [perm, perm] (given self x : ^perm0_0 R[^perm0_1 R[Int]], y : ^perm0_1 R[Int]) -> () where ^perm0_0 is mut, ^perm0_1 is mut { () ; } fn bar [perm, perm] (given self x : ^perm0_0 R[^perm0_1 R[Int]], y : ^perm0_1 R[Int]) -> () where ^perm0_0 is mut, ^perm0_1 is mut { self . give . foo [^perm0_0, ^perm0_1] (x . give, y . mut) ; y . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [y], env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !perm_1], local_variables: {self: given Main, @ fresh(0): given Main, @ fresh(1): !perm_0 R[!perm_1 R[Int]], @ fresh(2): mut [y] R[Int], x: !perm_0 R[!perm_1 R[Int]], y: !perm_1 R[Int]}, assumptions: {!perm_0 is mut, !perm_1 is mut, !perm_0 is relative, !perm_1 is relative, !perm_0 is atomic, !perm_1 is atomic}, fresh: 3 } }"#]]); } /// See `escapes_ok`, but here we don't know that `B` is leased (and hence get an error). @@ -488,7 +519,12 @@ fn escapes_err_not_leased() { self.give.foo[A, B](x.give, y.mut); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class R [ty] { value : ^ty0_0 ; } class Main { fn foo [perm, perm] (given self x : ^perm0_0 R[^perm0_1 R[Int]], y : ^perm0_1 R[Int]) -> () where ^perm0_0 is mut { () ; } fn bar [perm, perm] (given self x : ^perm0_0 R[^perm0_1 R[Int]], y : ^perm0_1 R[Int]) -> () where ^perm0_0 is mut { self . give . foo [^perm0_0, ^perm0_1] (x . give, y . mut) ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_1, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !perm_1], local_variables: {self: given Main, @ fresh(0): given Main, @ fresh(1): !perm_0 R[!perm_1 R[Int]], x: !perm_0 R[!perm_1 R[Int]], y: !perm_1 R[Int]}, assumptions: {!perm_0 is mut, !perm_0 is relative, !perm_1 is relative, !perm_0 is atomic, !perm_1 is atomic}, fresh: 2 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_1, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !perm_1], local_variables: {self: given Main, @ fresh(0): given Main, @ fresh(1): !perm_0 R[!perm_1 R[Int]], x: !perm_0 R[!perm_1 R[Int]], y: !perm_1 R[Int]}, assumptions: {!perm_0 is mut, !perm_0 is relative, !perm_1 is relative, !perm_0 is atomic, !perm_1 is atomic}, fresh: 2 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_1, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !perm_1], local_variables: {self: given Main, @ fresh(0): given Main, @ fresh(1): !perm_0 R[!perm_1 R[Int]], x: !perm_0 R[!perm_1 R[Int]], y: !perm_1 R[Int]}, assumptions: {!perm_0 is mut, !perm_0 is relative, !perm_1 is relative, !perm_0 is atomic, !perm_1 is atomic}, fresh: 2 } }"#]]); } /// Check that a `ref[d1, d2]` in parameters prohibits writes to `d1`. @@ -513,7 +549,7 @@ fn shared_d1_in_parameters() { } }, expect_test::expect![[r#" the rule "share-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, shared_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, shared_place)` accessed_place = d1 shared_place = d1"#]]); } @@ -540,7 +576,7 @@ fn shared_d2_in_parameters() { } }, expect_test::expect![[r#" the rule "share-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, shared_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, shared_place)` accessed_place = d2 shared_place = d2"#]]); } @@ -567,7 +603,7 @@ fn leased_d1_in_parameters() { } }, expect_test::expect![[r#" the rule "lease-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, leased_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, leased_place)` accessed_place = d1 leased_place = d1"#]]); } @@ -597,5 +633,12 @@ fn ref_of_mut_is_not_mut() { m.ref.needs_mut[ref[m] mut[foo]](); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Foo { d : Data ; fn needs_mut [perm] (^perm0_0 self) -> () where ^perm0_0 is mut { () ; } } class Main { fn main (given self) -> () { let foo = new Foo (new Data ()) ; let m = foo . mut ; m . ref . needs_mut [ref [m] mut [foo]] () ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [m], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): ref [m] Foo, foo: Foo, m: mut [foo] Foo}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [m], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): ref [m] Foo, foo: Foo, m: mut [foo] Foo}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [foo], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): ref [m] Foo, foo: Foo, m: mut [foo] Foo}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Foo, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): ref [m] Foo, foo: Foo, m: mut [foo] Foo}, assumptions: {}, fresh: 1 } }"#]]); } diff --git a/src/type_system/tests/permission_check/borrowck_loan_kills.rs b/src/type_system/tests/permission_check/borrowck_loan_kills.rs index 0268a23..d71011a 100644 --- a/src/type_system/tests/permission_check/borrowck_loan_kills.rs +++ b/src/type_system/tests/permission_check/borrowck_loan_kills.rs @@ -75,7 +75,7 @@ fn walk_linked_list_1step_p_live() { } }, expect_test::expect![[r#" the rule "share-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, shared_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, shared_place)` accessed_place = p shared_place = p . value"#]]); } diff --git a/src/type_system/tests/predicate_quantifiers.rs b/src/type_system/tests/predicate_quantifiers.rs index d644e6d..907b198 100644 --- a/src/type_system/tests/predicate_quantifiers.rs +++ b/src/type_system/tests/predicate_quantifiers.rs @@ -2,23 +2,23 @@ use formality_core::test; // Tests for predicate quantifier correctness on multi-place permissions. // -// given_from[p1, p2] means "could have been given from either p1 or p2." +// given[p1, p2] means "could have been given from either p1 or p2." // A predicate must hold for ALL places, not just ANY, because we don't // know which place the value actually came from. // // Similarly, mut[p1, p2] means "borrowed mutably from one of these places." -// --- Copy predicate on given_from --- +// --- Copy predicate on given --- -/// given_from[p1, p2] where BOTH places have copy types → copy. +/// given[p1, p2] where BOTH places have copy types → copy. /// Giving twice should succeed because the value is copy. #[test] -fn given_from_copy_when_all_copy() { +fn given_copy_when_all_copy() { crate::assert_ok!({ class Data {} class Main { fn test(given self, d1: shared Data, d2: shared Data) { - let result: given_from[d1, d2] Data = d1.give; + let result: given[d1, d2] Data = d1.give; let a = result.give; let b = result.give; (); @@ -27,78 +27,96 @@ fn given_from_copy_when_all_copy() { }); } -/// given_from with a single non-copy place → not copy. +/// given with a single non-copy place → not copy. /// Giving twice should fail. #[test] -fn given_from_not_copy_single_place() { +fn given_not_copy_single_place() { crate::assert_err!({ class Data {} class Main { fn test(given self, d1: given Data, d2: shared Data) { - let x: given_from[d1, d2] Data = d1.give; + let x: given[d1, d2] Data = d1.give; let a = x.give; let b = x.give; (); } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: shared Data, x: given [d1, d2] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: shared Data, x: given [d1, d2] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: shared Data, x: given [d1, d2] Data}, assumptions: {}, fresh: 0 } } + the rule "give" at (expressions.rs) failed because - condition evaluted to false: `!live_after.is_live(place)` + condition evaluated to false: `!live_after.is_live(place)` live_after = LivePlaces { accessed: {x}, traversed: {} } place = x"#]]); } -/// given_from[d1, d2] where d1 is copy but d2 is NOT copy → NOT copy. -/// This is the key bug: with the old ANY rule, given_from[d1, d2] would be +/// given[d1, d2] where d1 is copy but d2 is NOT copy → NOT copy. +/// This is the key bug: with the old ANY rule, given[d1, d2] would be /// considered copy because d1 is copy. But the value could have come from d2, /// which is move-only. /// -/// We use a function parameter typed as given_from[d1, d2] Data to get +/// We use a function parameter typed as given[d1, d2] Data to get /// the multi-place permission, then try to use it twice. #[test] -fn given_from_not_copy_when_mixed_copy_and_move() { +fn given_not_copy_when_mixed_copy_and_move() { crate::assert_err!({ class Data {} class Main { - fn test(given self, d1: shared Data, d2: given Data, x: given_from[d1, d2] Data) { + fn test(given self, d1: shared Data, d2: given Data, x: given[d1, d2] Data) { let a = x.give; let b = x.give; (); } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: shared Data, d2: given Data, x: given [d1, d2] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: shared Data, d2: given Data, x: given [d1, d2] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: shared Data, d2: given Data, x: given [d1, d2] Data}, assumptions: {}, fresh: 0 } } + the rule "give" at (expressions.rs) failed because - condition evaluted to false: `!live_after.is_live(place)` + condition evaluated to false: `!live_after.is_live(place)` live_after = LivePlaces { accessed: {x}, traversed: {} } place = x"#]]); } -/// Symmetric: given_from[d1, d2] where d1 is NOT copy but d2 IS copy → NOT copy. +/// Symmetric: given[d1, d2] where d1 is NOT copy but d2 IS copy → NOT copy. #[test] -fn given_from_not_copy_when_mixed_move_and_copy() { +fn given_not_copy_when_mixed_move_and_copy() { crate::assert_err!({ class Data {} class Main { - fn test(given self, d1: given Data, d2: shared Data, x: given_from[d1, d2] Data) { + fn test(given self, d1: given Data, d2: shared Data, x: given[d1, d2] Data) { let a = x.give; let b = x.give; (); } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: shared Data, x: given [d1, d2] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: shared Data, x: given [d1, d2] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: shared Data, x: given [d1, d2] Data}, assumptions: {}, fresh: 0 } } + the rule "give" at (expressions.rs) failed because - condition evaluted to false: `!live_after.is_live(place)` + condition evaluated to false: `!live_after.is_live(place)` live_after = LivePlaces { accessed: {x}, traversed: {} } place = x"#]]); } -/// given_from[d1, d2] where BOTH are copy → copy. Double use should succeed. +/// given[d1, d2] where BOTH are copy → copy. Double use should succeed. #[test] -fn given_from_copy_when_both_copy() { +fn given_copy_when_both_copy() { crate::assert_ok!({ class Data {} class Main { - fn test(given self, d1: shared Data, d2: shared Data, x: given_from[d1, d2] Data) { + fn test(given self, d1: shared Data, d2: shared Data, x: given[d1, d2] Data) { let a = x.give; let b = x.give; (); @@ -107,17 +125,17 @@ fn given_from_copy_when_both_copy() { }); } -// --- Mut predicate on given_from --- +// --- Mut predicate on given --- -/// given_from[d1, d2] where both places have non-copy (given) types → mut. +/// given[d1, d2] where both places have non-copy (given) types → mut. /// Field assignment requires mut permission on the object. -/// given is non-copy, so given_from[given_place] composes to mut. +/// given is non-copy, so given[given_place] composes to mut. #[test] -fn given_from_mut_when_all_noncopy() { +fn given_mut_when_all_noncopy() { crate::assert_ok!({ class Wrapper { value: Int; } class Main { - fn test(given self, d1: given Wrapper, d2: given Wrapper, x: given_from[d1, d2] Wrapper) { + fn test(given self, d1: given Wrapper, d2: given Wrapper, x: given[d1, d2] Wrapper) { x.value = 42; (); } @@ -125,18 +143,41 @@ fn given_from_mut_when_all_noncopy() { }); } -/// given_from[d1, d2] where d1 is non-copy (mut) but d2 is copy (not mut) → NOT mut. -/// Field assignment should fail because given_from might have come from d2 (shared), +/// given[d1, d2] where d1 is non-copy (mut) but d2 is copy (not mut) → NOT mut. +/// Field assignment should fail because given might have come from d2 (shared), /// and shared permissions don't allow mutation. #[test] -fn given_from_not_mut_when_mixed() { +fn given_not_mut_when_mixed() { crate::assert_err!({ class Wrapper { value: Int; } class Main { - fn test(given self, d1: given Wrapper, d2: shared Wrapper, x: given_from[d1, d2] Wrapper) { + fn test(given self, d1: given Wrapper, d2: shared Wrapper, x: given[d1, d2] Wrapper) { x.value = 42; (); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Wrapper { value : Int ; } class Main { fn test (given self d1 : given Wrapper, d2 : shared Wrapper, x : given_from [d1, d2] Wrapper) -> () { x . value = 42 ; () ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Int, d1: given Wrapper, d2: shared Wrapper, x: given [d1, d2] Wrapper}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: Wrapper, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Int, d1: given Wrapper, d2: shared Wrapper, x: given [d1, d2] Wrapper}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Int, d1: given Wrapper, d2: shared Wrapper, x: given [d1, d2] Wrapper}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Int, d1: given Wrapper, d2: shared Wrapper, x: given [d1, d2] Wrapper}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Int, d1: given Wrapper, d2: shared Wrapper, x: given [d1, d2] Wrapper}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Int, d1: given Wrapper, d2: shared Wrapper, x: given [d1, d2] Wrapper}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: Wrapper, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Int, d1: given Wrapper, d2: shared Wrapper, x: given [d1, d2] Wrapper}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Int, d1: given Wrapper, d2: shared Wrapper, x: given [d1, d2] Wrapper}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: Wrapper, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Int, d1: given Wrapper, d2: shared Wrapper, x: given [d1, d2] Wrapper}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Int, d1: given Wrapper, d2: shared Wrapper, x: given [d1, d2] Wrapper}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Int, d1: given Wrapper, d2: shared Wrapper, x: given [d1, d2] Wrapper}, assumptions: {}, fresh: 1 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Int, d1: given Wrapper, d2: shared Wrapper, x: given [d1, d2] Wrapper}, assumptions: {}, fresh: 1 } }"#]]); } diff --git a/src/type_system/tests/shared_classes_permissions.rs b/src/type_system/tests/shared_classes_permissions.rs index fa095b2..a3b3d86 100644 --- a/src/type_system/tests/shared_classes_permissions.rs +++ b/src/type_system/tests/shared_classes_permissions.rs @@ -80,8 +80,10 @@ fn move_our_class_of_regular_class_twice() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Elem, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, p: Pair[Elem]}, assumptions: {}, fresh: 0 } } + the rule "give" at (expressions.rs) failed because - condition evaluted to false: `!live_after.is_live(place)` + condition evaluated to false: `!live_after.is_live(place)` live_after = LivePlaces { accessed: {p}, traversed: {} } place = p"#]]) } @@ -109,9 +111,13 @@ fn mutate_field_of_our_class_applied_to_our() { the rule "class move" at (predicates.rs) failed because pattern `false` did not match value `true` + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: Pair[Elem], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Elem, p: Pair[Elem]}, assumptions: {}, fresh: 1 } } + the rule "class move" at (predicates.rs) failed because pattern `false` did not match value `true` + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: Elem, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, @ fresh(0): Elem, p: Pair[Elem]}, assumptions: {}, fresh: 1 } } + the rule "shared-class move" at (predicates.rs) failed because expression evaluated to an empty collection: `parameters`"#]]) } diff --git a/src/type_system/tests/shared_classes_subtyping.rs b/src/type_system/tests/shared_classes_subtyping.rs index f3d0584..fac3b64 100644 --- a/src/type_system/tests/shared_classes_subtyping.rs +++ b/src/type_system/tests/shared_classes_subtyping.rs @@ -23,7 +23,14 @@ fn pair_our_Data_our_Data_to_pair_given_Data_given_Data() { d1.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self d1 : (shared Data, shared Data)) -> (given Data, given Data) { d1 . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: (shared Data, shared Data)}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: (shared Data, shared Data)}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: (shared Data, shared Data)}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: (shared Data, shared Data)}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -36,7 +43,10 @@ fn our_pair_Data_Data_to_pair_Data_Data() { d1.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self d1 : shared (Data, Data)) -> (Data, Data) { d1 . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: shared (Data, Data)}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: shared (Data, Data)}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -49,7 +59,10 @@ fn our_pair_Data_Data_to_given_pair_Data_Data() { d1.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self d1 : shared (Data, Data)) -> given (Data, Data) { d1 . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: shared (Data, Data)}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: shared (Data, Data)}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] diff --git a/src/type_system/tests/subpermission.rs b/src/type_system/tests/subpermission.rs index 3d55c7d..d27d8f6 100644 --- a/src/type_system/tests/subpermission.rs +++ b/src/type_system/tests/subpermission.rs @@ -37,7 +37,12 @@ fn PermDataMy_not_subtype_of_PermDataOur() { let m: PermData[shared] = data.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class PermData [perm] { data : ^perm0_0 Data ; } class Main { fn test (given self data : PermData[given]) -> () { let m : PermData[shared] = data . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: PermData[given]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: PermData[given]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: PermData[given]}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -56,7 +61,12 @@ fn PermDataMy_is_not_subtype_of_PermDataLeased() { let m: PermData[mut[d]] = data.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class PermData [perm] { data : ^perm0_0 Data ; } class Main { fn test (given self data : PermData[given]) -> () { let d = new Data () ; let m : PermData[mut [d]] = data . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, data: PermData[given]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, data: PermData[given]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, data: PermData[given]}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -75,7 +85,16 @@ fn PermDataMy_is_not_subtype_of_PermDataShared() { let m: PermData[ref[d]] = data.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class PermData [perm] { data : ^perm0_0 Data ; } class Main { fn test (given self data : PermData[given]) -> () { let d = new Data () ; let m : PermData[ref [d]] = data . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, data: PermData[given]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [d], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, data: PermData[given]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, data: PermData[given]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [d], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, data: PermData[given]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, data: PermData[given]}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -100,7 +119,12 @@ fn unsound_upgrade() { b.mut.mutate[mut[q1]](); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { fn mutate [perm] (^perm0_0 self) -> () where ^perm0_0 is mut { } } class Query { data : shared Data ; } class Main { fn test (given self q1 : Query, q2 : Query) -> () { let a : mut [q1 . data] Data = q1 . data . mut ; let b : mut [q1] Data = a . give ; b . mut . mutate [mut [q1]] () ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, q1: Query, q2: Query}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, q1: Query, q2: Query}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, q1: Query, q2: Query}, assumptions: {}, fresh: 0 } }"#]]); } #[test] diff --git a/src/type_system/tests/subtyping.rs b/src/type_system/tests/subtyping.rs index 0fbf41c..62ce41e 100644 --- a/src/type_system/tests/subtyping.rs +++ b/src/type_system/tests/subtyping.rs @@ -25,7 +25,20 @@ fn forall__P__give__from__given_d1__to__ref_to_shared_d2() { d1.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self d1 : given Data, d2 : ^perm0_0 Data) -> ref [d2] Data { d1 . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [d2], env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: !perm_0 Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [d2], env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: !perm_0 Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: !perm_0 Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: !perm_0 Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [d2], env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: !perm_0 Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: !perm_0 Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: !perm_0 Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -82,7 +95,7 @@ fn move_from_given_d1_to_our_d2() { d1.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test (given self d1 : given Data) -> shared Data { d1 . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -142,7 +155,7 @@ fn give_from_our_Data_to_any_P() { d.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self) -> ^perm0_0 Data { let d : shared Data = new Data () ; d . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } /// `shared` is not a subtype of arbitrary P. @@ -160,15 +173,15 @@ fn give_from_our_Data_to_leased_P() { d.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self) -> ^perm0_0 Data where ^perm0_0 is mut { let d : shared Data = new Data () ; d . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main}, assumptions: {!perm_0 is mut, !perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] -fn share_from_given_d1_our_d2_to_given_from_d2() { +fn share_from_given_d1_our_d2_to_given_d2() { crate::assert_ok!({ class Data { } class Main { - fn test(given self, d1: given Data, d2: shared Data) -> given_from[d2] Data { + fn test(given self, d1: given Data, d2: shared Data) -> given[d2] Data { d1.give.share; } } @@ -178,11 +191,11 @@ fn share_from_given_d1_our_d2_to_given_from_d2() { /// Return "given" from `d1` and give from `d1`. /// It is indistinguishable as both of them are `shared` Data, so the result is `shared`. #[test] -fn share_from_our_d1_our_d2_to_given_from_d1() { +fn share_from_our_d1_our_d2_to_given_d1() { crate::assert_ok!({ class Data { } class Main { - fn test(given self, d1: shared Data, d2: shared Data) -> given_from[d1] Data { + fn test(given self, d1: shared Data, d2: shared Data) -> given[d1] Data { d1.ref; } } @@ -192,11 +205,11 @@ fn share_from_our_d1_our_d2_to_given_from_d1() { /// Return "given" from `d2` even though we really give from `d1`. /// It is indistinguishable as both of them are `shared` Data, so the result is `shared`. #[test] -fn share_from_our_d1_our_d2_to_given_from_d2() { +fn share_from_our_d1_our_d2_to_given_d2() { crate::assert_ok!({ class Data { } class Main { - fn test(given self, d1: shared Data, d2: shared Data) -> given_from[d2] Data { + fn test(given self, d1: shared Data, d2: shared Data) -> given[d2] Data { d1.ref; } } @@ -210,12 +223,20 @@ fn share_from_local_to_our() { crate::assert_err!({ class Data { } class Main { - fn test(given self, d1: shared Data, d2: shared Data) -> given_from[d2] Data { + fn test(given self, d1: shared Data, d2: shared Data) -> given[d2] Data { let d = new Data(); d.ref; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test (given self d1 : shared Data, d2 : shared Data) -> given_from [d2] Data { let d = new Data () ; d . ref ; } } }`"]); + }, expect_test::expect![[r#" + the rule "keep non-popped link" at (pop_normalize.rs) failed because + condition evaluated to false: `!link_references_popped(&link, &popped_vars)` + &link = Rfd(d) + &popped_vars = [d] + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, d1: shared Data, d2: shared Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: Data, d1: shared Data, d2: shared Data}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -240,10 +261,16 @@ fn provide_shared_from_d2_expect_shared_from_d1() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data}, assumptions: {}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d1 - place_a = d2"#]]); + place_a = d2 + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -286,10 +313,18 @@ fn provide_shared_from_d1_next_expect_shared_from_d2() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data}, assumptions: {}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d2 - place_a = d1 . next"#]]); + place_a = d1 . next + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -305,10 +340,16 @@ fn provide_shared_from_d1_expect_shared_from_d1_next() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data}, assumptions: {}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d1 . next - place_a = d1"#]]); + place_a = d1 + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -323,29 +364,37 @@ fn provide_leased_from_d1_next_expect_shared_from_d1() { d1.next.mut; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { next : given Data ; } class Main { fn test (given self d1 : given Data, d2 : given Data) -> ref [d1] Data { d1 . next . mut ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [d1 . next], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: given Data, d2: given Data}, assumptions: {}, fresh: 0 } }"#]]); } #[test] #[allow(non_snake_case)] -fn shared_from_P_d1_to_given_from_P_d1() { +fn shared_from_P_d1_to_given_P_d1() { crate::assert_err!({ class Data { } class Main { - fn test[perm P](given self, d1: P Data, d2: shared Data) -> given_from[d1] Data { + fn test[perm P](given self, d1: P Data, d2: shared Data) -> given[d1] Data { d1.ref; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self d1 : ^perm0_0 Data, d2 : shared Data) -> given_from [d1] Data { d1 . ref ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: !perm_0 Data, d2: shared Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: !perm_0 Data, d2: shared Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: !perm_0 Data, d2: shared Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] #[allow(non_snake_case)] -fn given_from_P_d1_to_given_from_P_d1() { +fn given_P_d1_to_given_P_d1() { crate::assert_ok!({ class Data { } class Main { - fn test[perm P](given self, d1: P Data, d2: shared Data) -> given_from[d1] Data { + fn test[perm P](given self, d1: P Data, d2: shared Data) -> given[d1] Data { d1.give; } } @@ -354,11 +403,11 @@ fn given_from_P_d1_to_given_from_P_d1() { #[test] #[allow(non_snake_case)] -fn given_from_P_d1_to_given_from_P_d2() { +fn given_P_d1_to_given_P_d2() { crate::assert_ok!({ class Data { } class Main { - fn test[perm P, perm Q](given self, d1: P Data, d2: P Data) -> given_from[d2] Data { + fn test[perm P, perm Q](given self, d1: P Data, d2: P Data) -> given[d2] Data { d1.give; } } @@ -367,15 +416,18 @@ fn given_from_P_d1_to_given_from_P_d2() { #[test] #[allow(non_snake_case)] -fn given_from_P_d1_to_given_from_Q_d2() { +fn given_P_d1_to_given_Q_d2() { crate::assert_err!({ class Data { } class Main { - fn test[perm P, perm Q](given self, d1: P Data, d2: Q Data) -> given_from[d2] Data { + fn test[perm P, perm Q](given self, d1: P Data, d2: Q Data) -> given[d2] Data { d1.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm, perm] (given self d1 : ^perm0_0 Data, d2 : ^perm0_1 Data) -> given_from [d2] Data { d1 . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: !perm_0, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !perm_1], local_variables: {self: given Main, d1: !perm_0 Data, d2: !perm_1 Data}, assumptions: {!perm_0 is relative, !perm_1 is relative, !perm_0 is atomic, !perm_1 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: !perm_0, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !perm_1], local_variables: {self: given Main, d1: !perm_0 Data, d2: !perm_1 Data}, assumptions: {!perm_0 is relative, !perm_1 is relative, !perm_0 is atomic, !perm_1 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -407,10 +459,16 @@ fn shared_from_P_d1_to_shared_from_P_d2() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !perm_1], local_variables: {self: given Main, d1: !perm_0 Data, d2: !perm_0 Data}, assumptions: {!perm_0 is relative, !perm_1 is relative, !perm_0 is atomic, !perm_1 is atomic}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d2 - place_a = d1"#]]); + place_a = d1 + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: !perm_0, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !perm_1], local_variables: {self: given Main, d1: !perm_0 Data, d2: !perm_0 Data}, assumptions: {!perm_0 is relative, !perm_1 is relative, !perm_0 is atomic, !perm_1 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !perm_1], local_variables: {self: given Main, d1: !perm_0 Data, d2: !perm_0 Data}, assumptions: {!perm_0 is relative, !perm_1 is relative, !perm_0 is atomic, !perm_1 is atomic}, fresh: 0 } }"#]]); } /// This case is wacky. The type of `data` is not really possible, as it indicates that data which was `mut[pair2]` was @@ -433,7 +491,12 @@ fn shared_pair1_leased_pair2_to_shared_pair1() { data.give.share; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Pair { d1 : Data ; d2 : Data ; } class Data { } class Main { fn test (given self pair1 : Pair, pair2 : Pair, data : ref [pair1] mut [pair2] Data) -> ref [pair1] Data { data . give . share ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: ref [pair1] mut [pair2] Data, pair1: Pair, pair2: Pair}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: ref [pair1] mut [pair2] Data, pair1: Pair, pair2: Pair}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Pair, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: ref [pair1] mut [pair2] Data, pair1: Pair, pair2: Pair}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -451,7 +514,7 @@ fn our_leased_to_our() { data.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Pair { d1 : Data ; d2 : Data ; } class Data { } class Main { fn test (given self pair : Pair, data : shared mut [pair] Data) -> shared Data { data . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: shared mut [pair] Data, pair: Pair}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -551,7 +614,16 @@ fn leased_vec_given_Data_to_leased_vec_leased_Data() { data.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Vec [ty] { } class Data { } class Main { fn test (given self source : given Vec[given Data], data : mut [source] Vec[given Data]) -> mut [source] Vec[mut [source] Data] { data . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [source], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: mut [source] Vec[given Data], source: given Vec[given Data]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: mut [source] Vec[given Data], source: given Vec[given Data]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Vec[given Data], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: mut [source] Vec[given Data], source: given Vec[given Data]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: mut [source] Vec[given Data], source: given Vec[given Data]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Vec[given Data], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: mut [source] Vec[given Data], source: given Vec[given Data]}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -567,7 +639,14 @@ fn leased_vec_leased_Data_to_leased_vec_given_Data() { data.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Vec [ty] { } class Data { } class Main { fn test (given self source : given Vec[given Data], data : mut [source] Vec[mut [source] Data]) -> mut [source] Vec[given Data] { data . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [source], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: mut [source] Vec[mut [source] Data], source: given Vec[given Data]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: mut [source] Vec[mut [source] Data], source: given Vec[given Data]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Vec[given Data], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: mut [source] Vec[mut [source] Data], source: given Vec[given Data]}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, data: mut [source] Vec[mut [source] Data], source: given Vec[given Data]}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -599,7 +678,12 @@ fn forall_P_vec_given_Data_to_P_vec_P_Data() { data.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Vec [ty] { } class Data { } class Main { fn test [perm] (given self source : given Vec[given Data], data : ^perm0_0 Vec[Data]) -> ^perm0_0 Vec[^perm0_0 Data] { data . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, data: !perm_0 Vec[Data], source: given Vec[given Data]}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:505:1: no applicable rules for prove_owned_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, data: !perm_0 Vec[Data], source: given Vec[given Data]}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, data: !perm_0 Vec[Data], source: given Vec[given Data]}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -686,7 +770,7 @@ fn ordering_matters() { pair.first.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Pair [ty] { first : ^ty0_0 ; second : ^ty0_0 ; } class Main { fn test [perm, perm] (given self pair : ^perm0_0 Pair[^perm0_1 Data]) -> ^perm0_1 ^perm0_0 Data { pair . first . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: !perm_0, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !perm_1], local_variables: {self: given Main, pair: !perm_0 Pair[!perm_1 Data]}, assumptions: {!perm_0 is relative, !perm_1 is relative, !perm_0 is atomic, !perm_1 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -754,5 +838,16 @@ fn pair_a_to_pair_bad() { let p: mut[pair] Data = pair.a.mut; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Pair [ty] { a : ^ty0_0 ; b : ^ty0_0 ; } class Data { } class Main { fn main [perm] (given self pair : given Pair[^perm0_0 Data]) -> () { let p : mut [pair] Data = pair . a . mut ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, pair: given Pair[!perm_0 Data]}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, pair: given Pair[!perm_0 Data]}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, pair: given Pair[!perm_0 Data]}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, pair: given Pair[!perm_0 Data]}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, pair: given Pair[!perm_0 Data]}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, pair: given Pair[!perm_0 Data]}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } diff --git a/src/type_system/tests/subtyping/copy_types.rs b/src/type_system/tests/subtyping/copy_types.rs index 1ae03c5..c1acd29 100644 --- a/src/type_system/tests/subtyping/copy_types.rs +++ b/src/type_system/tests/subtyping/copy_types.rs @@ -78,12 +78,12 @@ fn given_int_to_int() { } #[test] -fn given_from_int_to_int() { - // given_from[self] Int <: Int +fn given_place_int_to_int() { + // given[self] Int <: Int crate::assert_ok!({ class Main { fn test(given self) -> Int { - let x: given_from[self] Int = 0; + let x: given[self] Int = 0; x.give; } } @@ -164,7 +164,18 @@ fn ref_generic_struct_noncopy_param_fails() { b.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: shared class Box [ty] { value : ^ty0_0 ; } class Data { } class Main { fn test (given self d : given Data) -> Box[Data] { let b : ref [d] Box[Data] = new Box [Data] (new Data ()) ; b . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [d], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [d], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d: given Data}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -179,5 +190,8 @@ fn shared_generic_struct_noncopy_param_fails() { b.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: shared class Box [ty] { value : ^ty0_0 ; } class Data { } class Main { fn test (given self) -> Box[Data] { let b : shared Box[Data] = new Box [Data] (new Data ()) . share ; b . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main}, assumptions: {}, fresh: 0 } }"#]]); } diff --git a/src/type_system/tests/subtyping/liskov/cancellation.rs b/src/type_system/tests/subtyping/liskov/cancellation.rs index ab3da2f..c13d4fc 100644 --- a/src/type_system/tests/subtyping/liskov/cancellation.rs +++ b/src/type_system/tests/subtyping/liskov/cancellation.rs @@ -82,7 +82,7 @@ fn c1_remove_our() { let q: given Data = p.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self) -> () { let m : given Data = new Data () ; let p : shared given Data = m . give ; let q : given Data = p . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, m: given Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } // C1. Cancellation cannot remove generic permissions `shared`. @@ -96,7 +96,7 @@ fn c1_remove_generic_permissions() { let q: given Data = p.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self p : ^perm0_0 given Data) -> () { let q : given Data = p . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, p: !perm_0 given Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } // C2. Cancellation can only occur if all variables in the permission are dead. @@ -146,7 +146,15 @@ fn c2_shared_shared_one_of_two_variables_dead() { q.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self) -> () { let m : given Data = new Data () ; let p : ref [m] Data = m . ref ; let q : ref [m] Data = m . ref ; let r : ref [p, q] ref [m] Data = p . ref ; let s : ref [m] Data = r . give ; q . give ; } } }`"]); + }, expect_test::expect![[r#" + the rule "keep non-popped link" at (pop_normalize.rs) failed because + condition evaluated to false: `!link_references_popped(&link, &popped_vars)` + &link = Rfd(m) + &popped_vars = [m, p, q, r, s] + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, m: given Data, p: ref [m] Data, q: ref [m] Data, r: ref [@ fresh(0), p] ref [m] Data, s: ref [m] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, m: given Data, p: ref [m] Data, q: ref [m] Data, r: ref [@ fresh(0), p] ref [m] Data, s: ref [m] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -200,7 +208,7 @@ fn c2_leased_leased_one_of_two_variables_dead() { } }, expect_test::expect![[r#" the rule "lease-mutation" at (accesses.rs) failed because - condition evaluted to false: `place_disjoint_from(accessed_place, leased_place)` + condition evaluated to false: `place_disjoint_from(accessed_place, leased_place)` accessed_place = m leased_place = m"#]]); } @@ -219,7 +227,12 @@ fn c3_shared_leased_one_of_one_variables_dead() { let r: mut[m] Data = q.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self) -> () { let m : given Data = new Data () ; let p : mut [m] Data = m . mut ; let q : ref [p] mut [m] Data = p . ref ; let r : mut [m] Data = q . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [m], env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, m: given Data, p: mut [m] Data, q: ref [p] mut [m] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [m], env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, m: given Data, p: mut [m] Data, q: ref [p] mut [m] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, m: given Data, p: mut [m] Data, q: ref [p] mut [m] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -235,7 +248,10 @@ fn c3_shared_leased_two_of_two_variables_dead() { let s: ref[m] Data = r.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self) -> () { let m : given Data = new Data () ; let p : mut [m] Data = m . ref ; let q : mut [m] Data = m . ref ; let r : ref [p, q] mut [m] Data = p . ref ; let s : ref [m] Data = r . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, m: given Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, m: given Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -252,7 +268,15 @@ fn c3_shared_leased_one_of_two_variables_dead() { q.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self) -> () { let m : given Data = new Data () ; let p : ref [m] Data = m . ref ; let q : ref [m] Data = m . ref ; let r : ref [p, q] ref [m] Data = p . ref ; let s : ref [m] Data = r . give ; q . give ; } } }`"]); + }, expect_test::expect![[r#" + the rule "keep non-popped link" at (pop_normalize.rs) failed because + condition evaluated to false: `!link_references_popped(&link, &popped_vars)` + &link = Rfd(m) + &popped_vars = [m, p, q, r, s] + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, m: given Data, p: ref [m] Data, q: ref [m] Data, r: ref [@ fresh(0), p] ref [m] Data, s: ref [m] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, m: given Data, p: ref [m] Data, q: ref [m] Data, r: ref [@ fresh(0), p] ref [m] Data, s: ref [m] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } // C4. Subtyping must account for future cancellation. @@ -273,15 +297,27 @@ fn c4_shared_d1d2d3_not_subtype_of_shared_d1_shared_d2d3() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: given Data, d3: given Data, s1: ref [d1, d2, d3] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d2 place_a = d1 + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: given Data, d3: given Data, s1: ref [d1, d2, d3] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: given Data, d3: given Data, s1: ref [d1, d2, d3] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: given Data, d3: given Data, s1: ref [d1, d2, d3] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + the rule "(ref::P) vs (ref::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d3 - place_a = d1"#]]); + place_a = d1 + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: given Data, d3: given Data, s1: ref [d1, d2, d3] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: given Data, d3: given Data, s1: ref [d1, d2, d3] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -299,7 +335,22 @@ fn c4_leased_d1d2d3_subtype_of_leased_d1_leased_d2d3() { let s2: mut[d1] mut[d2, d3] Data = s1.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self) -> () { let d1 : given Data = new Data () ; let d2 : given Data = new Data () ; let d3 : given Data = new Data () ; let s1 : mut [d1, d2, d3] Data = d1 . mut ; let s2 : mut [d1] mut [d2, d3] Data = s1 . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: given Data, d3: given Data, s1: mut [d1, d2, d3] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: given Data, d3: given Data, s1: mut [d1, d2, d3] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: given Data, d3: given Data, s1: mut [d1, d2, d3] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [d1], env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: given Data, d3: given Data, s1: mut [d1, d2, d3] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: given Data, d3: given Data, s1: mut [d1, d2, d3] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: given Data, d3: given Data, s1: mut [d1, d2, d3] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: given Data, d3: given Data, s1: mut [d1, d2, d3] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [d1], env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: given Data, d2: given Data, d3: given Data, s1: mut [d1, d2, d3] Data}, assumptions: {!perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -324,12 +375,16 @@ fn c4_leased_d1d2_leased_pair_not_subtype_of_leased_d2() { } }, expect_test::expect![[r#" the rule "(mut::P) vs (mut::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d2 place_a = pair . a + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [pair . a], env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: mut [pair . a] Data, d2: mut [pair . b] Data, pair: !perm_0 Pair, s1: mut [d1, d2] Data}, assumptions: {!perm_0 is mut, !perm_0 is relative, !perm_0 is atomic}, fresh: 0 } } + the rule "(mut::P) vs (mut::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = d2 - place_a = d1"#]]); + place_a = d1 + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [d1], env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, d1: mut [pair . a] Data, d2: mut [pair . b] Data, pair: !perm_0 Pair, s1: mut [d1, d2] Data}, assumptions: {!perm_0 is mut, !perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } diff --git a/src/type_system/tests/subtyping/liskov/subpermission.rs b/src/type_system/tests/subtyping/liskov/subpermission.rs index b1365a7..5f7b74a 100644 --- a/src/type_system/tests/subtyping/liskov/subpermission.rs +++ b/src/type_system/tests/subtyping/liskov/subpermission.rs @@ -24,7 +24,7 @@ fn c1_given_subtype_of_our() { let p: shared Data = m.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test (given self) -> () { let m : given Data = new Data () ; let p : shared Data = m . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, m: given Data}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -37,7 +37,7 @@ fn c1_our_not_subtype_of_given() { let p: given Data = m.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test (given self) -> () { let m : shared Data = new Data () ; let p : given Data = m . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -53,7 +53,12 @@ fn c1_given_subtype_of_shared() { let p: ref[m] Data = n.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test (given self) -> () { let m : given Data = new Data () ; let n : given Data = new Data () ; let p : ref [m] Data = n . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: ref [m], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, m: given Data, n: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, m: given Data, n: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, m: given Data, n: given Data}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -104,7 +109,7 @@ fn c1_given_subtype_of_P_where_P_shared() { let p: P Data = m.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self) -> () where ^perm0_0 is copy { let m : given Data = new Data () ; let p : ^perm0_0 Data = m . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main, m: given Data}, assumptions: {!perm_0 is copy, !perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -119,7 +124,7 @@ fn c1_newData_assignable_to_P_where_P_shared() { let m: P Data = new Data(); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self) -> () where ^perm0_0 is copy { let m : ^perm0_0 Data = new Data () ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main}, assumptions: {!perm_0 is copy, !perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -151,7 +156,7 @@ fn c1_P_not_subtype_of_given_where_P_shared() { let p: given Data = n.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self) -> () where ^perm0_0 is copy { let m : ^perm0_0 Data = new Data () ; let p : given Data = n . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main}, assumptions: {!perm_0 is copy, !perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -166,7 +171,7 @@ fn c1_P_not_subtype_of_our_where_P_shared() { let p: shared Data = n.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm] (given self) -> () where ^perm0_0 is copy { let m : ^perm0_0 Data = new Data () ; let p : shared Data = n . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(1), in_scope_vars: [!perm_0], local_variables: {self: given Main}, assumptions: {!perm_0 is copy, !perm_0 is relative, !perm_0 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -181,7 +186,7 @@ fn c1_P_not_subtype_of_Q_where_PQ_shared() { let p: Q Data = m.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test [perm, perm] (given self) -> () where ^perm0_0 is copy, ^perm0_1 is copy { let m : ^perm0_0 Data = new Data () ; let p : ^perm0_1 Data = m . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: !perm_0, env: Env { program: "...", universe: universe(2), in_scope_vars: [!perm_0, !perm_1], local_variables: {self: given Main}, assumptions: {!perm_0 is copy, !perm_1 is copy, !perm_0 is relative, !perm_1 is relative, !perm_0 is atomic, !perm_1 is atomic}, fresh: 0 } }"#]]); } #[test] @@ -214,7 +219,10 @@ fn c1_given_not_subtype_of_leased() { let p: mut[m] Data = new Data(); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test (given self) -> () { let m : given Data = new Data () ; let p : mut [m] Data = new Data () ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, m: given Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, m: given Data}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -228,7 +236,10 @@ fn c1_leased_not_subtype_of_shared() { let q: ref[m] Data = p.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test (given self) -> () { let m : given Data = new Data () ; let p : mut [m] Data = m . mut ; let q : ref [m] Data = p . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, m: given Data, p: mut [m] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [m], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, m: given Data, p: mut [m] Data}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -242,7 +253,12 @@ fn c1_shared_not_subtype_of_leased() { let q: mut[m] Data = p.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Main { fn test (given self) -> () { let m : given Data = new Data () ; let p : ref [m] Data = m . ref ; let q : mut [m] Data = p . give ; } } }`"]); + }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, m: given Data, p: ref [m] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, m: given Data, p: ref [m] Data}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Data, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, m: given Data, p: ref [m] Data}, assumptions: {}, fresh: 0 } }"#]]); } // C2. This also includes restrictions on what can be done in the environment. So `ref[d1] Foo` cannot @@ -300,8 +316,12 @@ fn c2_leased_mn_not_subtype_of_leased_m() { } } }, expect_test::expect![[r#" + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, m: given Data, n: given Data, p: mut [m, n] Data}, assumptions: {}, fresh: 0 } } + the rule "(mut::P) vs (mut::P)" at (redperms.rs) failed because - condition evaluted to false: `place_b.is_prefix_of(place_a)` + condition evaluated to false: `place_b.is_prefix_of(place_a)` place_b = m - place_a = n"#]]); + place_a = n + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: mut [n], env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, m: given Data, n: given Data, p: mut [m, n] Data}, assumptions: {}, fresh: 0 } }"#]]); } diff --git a/src/type_system/tests/type_check.rs b/src/type_system/tests/type_check.rs index 3ddaf13..5decb37 100644 --- a/src/type_system/tests/type_check.rs +++ b/src/type_system/tests/type_check.rs @@ -17,7 +17,7 @@ fn bad_int_return_value() { class TheClass { fn empty_method(given self) -> Int {} } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class TheClass { fn empty_method (given self) -> Int { } } }`"]) + }, expect_test::expect![[r#"src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: (), b: Int, live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass}, assumptions: {}, fresh: 0 } }"#]]) } /// Check that empty blocks return unit (and that is not assignable to Int) @@ -29,7 +29,7 @@ fn bad_int_ascription() { let x: Int = (); } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class TheClass { fn empty_method (given self) -> () { let x : Int = () ; } } }`"]) + }, expect_test::expect![[r#"src/type_system/subtypes.rs:38:1: no applicable rules for sub { a: (), b: Int, live_after: LivePlaces { accessed: {}, traversed: {} }, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass}, assumptions: {}, fresh: 0 } }"#]]) } /// Check returning an integer with return type of Int. @@ -88,7 +88,15 @@ fn return_shared_not_give() { foo.ref; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Foo { } class TheClass { fn empty_method (given self) -> Foo { let foo = new Foo () ; foo . ref ; } } }`"]) + }, expect_test::expect![[r#" + the rule "keep non-popped link" at (pop_normalize.rs) failed because + condition evaluated to false: `!link_references_popped(&link, &popped_vars)` + &link = Rfd(foo) + &popped_vars = [foo] + + src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: given, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, foo: Foo}, assumptions: {}, fresh: 0 } } + + src/type_system/predicates.rs:324:1: no applicable rules for prove_copy_predicate { p: Foo, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given TheClass, foo: Foo}, assumptions: {}, fresh: 0 } }"#]]) } /// Check returning a shared instance of a class when an owned instance is expected. diff --git a/src/type_system/tests/variance_subtyping.rs b/src/type_system/tests/variance_subtyping.rs index c57de7b..e78d8a9 100644 --- a/src/type_system/tests/variance_subtyping.rs +++ b/src/type_system/tests/variance_subtyping.rs @@ -34,7 +34,7 @@ fn Cell_atomic_T_our_Cell_Data_to_our_Cell_our_Data() { d1.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Cell [ty] where ^ty0_0 is atomic { atomic f : ^ty0_0 ; } class Main { fn test (given self d1 : shared Cell[Data]) -> shared Cell[shared Data] { d1 . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: shared Cell[Data]}, assumptions: {}, fresh: 0 } }"#]]); } #[test] @@ -53,5 +53,5 @@ fn Cell_rel_T_our_Cell_Data_to_our_Cell_our_Data() { d1.give; } } - }, expect_test::expect!["judgment had no applicable rules: `check_program { program: class Data { } class Cell [ty] where ^ty0_0 is relative { } class Main { fn test (given self d1 : shared Cell[Data]) -> shared Cell[shared Data] { d1 . give ; } } }`"]); + }, expect_test::expect![[r#"src/type_system/predicates.rs:623:1: no applicable rules for prove_mut_predicate { p: shared, env: Env { program: "...", universe: universe(0), in_scope_vars: [], local_variables: {self: given Main, d1: shared Cell[Data]}, assumptions: {}, fresh: 0 } }"#]]); } diff --git a/src/type_system/types.rs b/src/type_system/types.rs index 9beb20a..bb929ba 100644 --- a/src/type_system/types.rs +++ b/src/type_system/types.rs @@ -97,7 +97,7 @@ judgment_fn! { (if !places.is_empty()) (for_all(place in places) (check_place(env, place) => ())) - ----------------------- ("given_from") + ----------------------- ("given") (check_perm(env, Perm::Mv(places)) => ()) )