From 11a49ed44627a36a8b7b5ff23cad6433c8f7bedc Mon Sep 17 00:00:00 2001 From: David Edey Date: Mon, 9 Feb 2026 13:04:47 +0000 Subject: [PATCH 01/25] docs: Document the disabled abstraction issue --- plans/TODO.md | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/plans/TODO.md b/plans/TODO.md index 94fb0b8b..a79f44f0 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -251,7 +251,6 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] Create `Map` and `Filter` types on top of it, to be able to implement `map` and `filter` - [ ] See if new iterators on iterator value can be fixed to be lazy - [ ] Salvage half-baked `FunctionValue` changes to allow invocation - - [ ] Consider if IteratorValue should have `Item = ReturnedValue` - [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` - [ ] Add tests for iterable methods - [ ] Add `array.sort`, `array.sort_by` @@ -270,6 +269,51 @@ Possible punted: - `move(x)` / `move(a.b.as_ref())` / `move(a.b.as_mut())` => we hoist up the content into the previous frame (effectively temporarily change `current_frame_id` to be the parent in the `FlowAnalysisState` - pretty easy). - These can be an anonymous definition in the root frame of the closure, which is referenced inline. +## Fix broken "Disabled" abstraction + +Suddenly dawned on me - my Disabled arguments might not be safe. +* Imagine if I get `my_arr = [[]]` a Shared `my_arr[0]` then do `my_arr.pop()` +* Then we enable `my_arr[0]` and get a use after free(!!). e.g. `my_arr[0].push(my_arr.pop())`. + +Instead, as per Rust two-phase borrows https://rustc-dev-guide.rust-lang.org/borrow-check/two-phase-borrows.html - +- We shouldn't be able to disable a `Shared` +- A `Disabled` becomes a `Shared`... + +I have a branch `spike/fix-disabled-abstraction` to explore this... Which converts a Shared back to a Mutable (which I think is technically UB and can't be done without it). Regardless, it works for a spike, and we get a few failures: +* `stream_append_can_use_self_in_appender` - these are OK to fail! +* This test is *not* OK to fail: +```rust +#[test] +fn can_pass_owned_to_mutable_argument() { + run! { + let push_twice_and_return_mut = |arr: &mut any| { + arr.push(0); + arr.push(0); + arr + }; + %[_].assert_eq(push_twice_and_return_mut([1, 2, 3]).len(), 5); + } +} +``` + +The issue is that we get a variable stored as `arr := DisabledMutable` and then when we do `arr.push()` we clone the DisabledMutable, and then enable it... +What we actually need is a sense of "Delegating" the mut-ness to a new `Mutable`, which then on drop re-enables its parent. + +... actually in our model, really the bug only appears if: +* A *parent* gets modified, which breaks our pointer +* We have a `&mut T` for some `T != any` and someone assigns to `self as &mut any` a different type + +We could imagine a world where we are more clever over our mutation: +* From a referenceable root (wrapping an `UnsafeCell`), we store paths => pointers +* It is illegal to mutate if there is an existing pointer further along a path that you in either direction: + * A value + * A more specific type of the current value +* We allow x.a and x.b to both be read/mutated independently + * I can reference `x.a` and `x.b` separately, or `x[0]` and `x[1]` but in that case, can't mutate `x` itself to create new fields. +* We track a path to which pointers are active; and don't allow mutations which could break existing pointers. + +- [ ] Consider if IteratorValue, Object and Array should have `Item = DisabledReturnedValue` + ## Parser - Methods using closures Future methods once we have closures: From 9dbaeb1d25fe4b7aa62e87b082daaa3c9f96b329 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 22 Feb 2026 10:59:47 +0000 Subject: [PATCH 02/25] feat: Begin references rework --- plans/TODO.md | 55 ++-- src/expressions/type_resolution/type_kinds.rs | 33 +++ .../dynamic_references/inactive_reference.rs | 36 +++ src/misc/dynamic_references/mod.rs | 85 ++++++ .../dynamic_references/mutable_reference.rs | 34 +++ src/misc/dynamic_references/reference_core.rs | 42 +++ src/misc/dynamic_references/referenceable.rs | 246 ++++++++++++++++++ .../dynamic_references/shared_reference.rs | 24 ++ src/misc/mod.rs | 2 + 9 files changed, 535 insertions(+), 22 deletions(-) create mode 100644 src/misc/dynamic_references/inactive_reference.rs create mode 100644 src/misc/dynamic_references/mod.rs create mode 100644 src/misc/dynamic_references/mutable_reference.rs create mode 100644 src/misc/dynamic_references/reference_core.rs create mode 100644 src/misc/dynamic_references/referenceable.rs create mode 100644 src/misc/dynamic_references/shared_reference.rs diff --git a/plans/TODO.md b/plans/TODO.md index a79f44f0..18aa09e2 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -241,10 +241,8 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] Separation of `ExecutionInterrupt` and `FunctionError` - incorporated below - [x] `Iterator` returns `Result` change -> replaced with below * To implement `.map()`, we have a few things we need first: - -[x] An iterator trait where Interpreter is passed at next time. - -[ ] Possibly - not require `Clone` on iterators: - - Make `TryClone -> Result` - - Make `to_string` for iterator return `Iterator[?]` + - [x] An iterator trait where Interpreter is passed at next time. + - [x] Make `to_string` for iterator return `Iterator[?]` - [x] Create new iterator trait `PreinterpretIterator` and `IntoPreinterpretIterator` with `.next(&mut Interpreter)` - [x] Blanket implement it for `Iterator + Clone` - [x] Then replace e.g. for loop impl with it. @@ -254,23 +252,22 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` - [ ] Add tests for iterable methods - [ ] Add `array.sort`, `array.sort_by` -- [ ] Look into if a closure like `|| { }()` can evade `attempt` block statue mutation checks. Maybe lean into it as a way to avoid htem, and mention it in the error message +- [ ] Look into if a closure like `|| { }()` can evade `attempt` block statue mutation checks. Maybe lean into it as a way to avoid them, and mention it in the error message - [ ] Resolve all `TODO[functions]` Possible punted: -- [ ] Allow variables to be marked owned / shared / mutable - - [ ] ... and maybe no marking accepts any? - ... so e.g. push_one_and_return_mut can have `let y = arr; y` without erroring - - [ ] Else - improve the errors so that the conversion error has some hint from the target explaining that the target can be changed to allow - shared/mutable instead. - [ ] Allow destructuring shared and mutable variables and arguments - [ ] Support optional arguments in closures - [ ] Support for `move()` expressions in closures. - `move(x)` / `move(a.b.as_ref())` / `move(a.b.as_mut())` => we hoist up the content into the previous frame (effectively temporarily change `current_frame_id` to be the parent in the `FlowAnalysisState` - pretty easy). - These can be an anonymous definition in the root frame of the closure, which is referenced inline. +-[ ] Possibly - not require `Clone` on iterators: + - Make `TryClone -> Result` ## Fix broken "Disabled" abstraction +### Background + Suddenly dawned on me - my Disabled arguments might not be safe. * Imagine if I get `my_arr = [[]]` a Shared `my_arr[0]` then do `my_arr.pop()` * Then we enable `my_arr[0]` and get a use after free(!!). e.g. `my_arr[0].push(my_arr.pop())`. @@ -297,22 +294,36 @@ fn can_pass_owned_to_mutable_argument() { ``` The issue is that we get a variable stored as `arr := DisabledMutable` and then when we do `arr.push()` we clone the DisabledMutable, and then enable it... -What we actually need is a sense of "Delegating" the mut-ness to a new `Mutable`, which then on drop re-enables its parent. -... actually in our model, really the bug only appears if: -* A *parent* gets modified, which breaks our pointer -* We have a `&mut T` for some `T != any` and someone assigns to `self as &mut any` a different type +### Task list + +- [x] Write up model in `dynamic_references/mod.rs` +- [x] Add structure for `dynamic_references` +- [x] Create Referenceable, and Reference types +- [ ] Create error messages for rule breaks in `referenceable.rs` +- [ ] Add the following to `ReferenceCore` and maybe others: + - [ ] Ability to map deeper. Should take a `PathExtension` and a new span. + See rule (c) from `dynamic_references/mod.rs` + - [ ] Various mapping functions (e.g. array offset; object key) + - [ ] Try map + - [ ] Emplacing +- [ ] Check for missing features from `Shared` / `SharedSubRcRefCell` and `Mutable` / `MutableSubRcRefCell` + + +### Other ideas We could imagine a world where we are more clever over our mutation: -* From a referenceable root (wrapping an `UnsafeCell`), we store paths => pointers -* It is illegal to mutate if there is an existing pointer further along a path that you in either direction: - * A value - * A more specific type of the current value -* We allow x.a and x.b to both be read/mutated independently - * I can reference `x.a` and `x.b` separately, or `x[0]` and `x[1]` but in that case, can't mutate `x` itself to create new fields. -* We track a path to which pointers are active; and don't allow mutations which could break existing pointers. +* Note that I can reference `x.a` and `x.b` separately, or `x[0]` and `x[1]` but in that case, can't mutate `x` itself to create new fields. + * Or we use a `ExpandableVec` which allocates a `Vec>` and doesn't move the inner chunks; allowing us to add new fields without breaking pointers to existing ones. + Maybe some SmallVec or SegVec like library has this feature? Stores an initial chunk `[_; N]` and then a `Vec>` - [ ] Consider if IteratorValue, Object and Array should have `Item = DisabledReturnedValue` + - [ ] And add `iter()` and `iter_mut()` methods +- [ ] Allow variables to be marked owned / shared / mutable + - [ ] ... and maybe no marking accepts any? + ... so e.g. push_one_and_return_mut can have `let y = arr; y` without erroring + - [ ] Else - improve the errors so that the conversion error has some hint from the target explaining that the target can be changed to allow + shared/mutable instead. ## Parser - Methods using closures @@ -345,7 +356,7 @@ input.repeated( ) ``` - [ ] `input.any_group(|inner| { })` -- [ ] `input.group('()', |inner| { })` +- [ ] `input.group("()", |inner| { })` - [ ] `input.transparent_group(|inner| { })` ## Parser - Better Types for Tokens diff --git a/src/expressions/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index df4d25ce..b255c505 100644 --- a/src/expressions/type_resolution/type_kinds.rs +++ b/src/expressions/type_resolution/type_kinds.rs @@ -41,6 +41,7 @@ impl AnyValueLeafKind { // A AnyValueLeafKind represents a kind of leaf value. // But a TypeKind represents a type in the hierarchy, which points at a type data. +#[derive(PartialEq, Eq, Clone, Copy)] pub(crate) enum TypeKind { Leaf(AnyValueLeafKind), Parent(ParentTypeKind), @@ -91,6 +92,37 @@ impl TypeKind { } } +/// This is the subtyping ordering. +/// - I define that X <= Y if Y is a subtype of X. +/// - Think "X is shallower than Y in the enum representation" +/// +/// This must be accurate so that the Dynamic References works correctly. +/// e.g. if I have a `a: &mut AnyValue` and a `b: &mut IntegerValue` pointing +/// to the same memory, then I can't use a to change to a String. +impl PartialOrd for TypeKind { + fn partial_cmp(&self, other: &Self) -> Option { + if self == other { + return Some(std::cmp::Ordering::Equal); + } + // TODO: implement a more complete partial ord that reflects the actual subtyping relationships. + match (self, other) { + // None-equal dyns are not subtypes + (TypeKind::Dyn(_), _) => None, + (_, TypeKind::Dyn(_)) => None, + // All non-any-values are strict subtypes of AnyValue + (TypeKind::Parent(ParentTypeKind::Value(_)), _) => Some(std::cmp::Ordering::Less), + (_, TypeKind::Parent(ParentTypeKind::Value(_))) => Some(std::cmp::Ordering::Greater), + // TODO: Non-equal leave types should kinda be an error, assuming they're pointing + // at the same value + (TypeKind::Leaf(_), _) => None, + (_, TypeKind::Leaf(_)) => None, + // TODO: Handle intermediate parents + _ => None, + } + } +} + +#[derive(PartialEq, Eq, Clone, Copy)] pub(crate) enum ParentTypeKind { Value(AnyValueTypeKind), Integer(IntegerTypeKind), @@ -123,6 +155,7 @@ impl ParentTypeKind { } } +#[derive(PartialEq, Eq, Clone, Copy)] pub(crate) enum DynTypeKind { Iterable, } diff --git a/src/misc/dynamic_references/inactive_reference.rs b/src/misc/dynamic_references/inactive_reference.rs new file mode 100644 index 00000000..15d711c0 --- /dev/null +++ b/src/misc/dynamic_references/inactive_reference.rs @@ -0,0 +1,36 @@ +use super::*; + +#[derive(Clone)] +pub(crate) struct InactiveReference(ReferenceCore); + +impl InactiveReference { + pub(super) fn new_root(core: Rc>) -> Self { + let new_id = core.data_mut() + .new_reference(TrackedReference { + path: ReferencePath::leaf(AnyType::type_kind()), + reference_kind: ReferenceKind::Inactive, + creation_span: core.root_span, + }); + Self(ReferenceCore { + pointer: core.root.get(), + id: new_id, + core, + }) + } + + pub(super) fn activate_mutable(self) -> FunctionResult> { + self.0.activate_mutable_reference()?; + Ok(MutableReference(self.0)) + } + + pub(super) fn activate_shared(self) -> FunctionResult> { + self.0.activate_shared_reference()?; + Ok(SharedReference(self.0)) + } +} + +#[derive(Clone)] +pub(crate) struct InactiveSharedReference(pub(super) ReferenceCore); + +#[derive(Clone)] +pub(crate) struct InactiveMutableReference(pub(super) ReferenceCore); \ No newline at end of file diff --git a/src/misc/dynamic_references/mod.rs b/src/misc/dynamic_references/mod.rs new file mode 100644 index 00000000..b98fea21 --- /dev/null +++ b/src/misc/dynamic_references/mod.rs @@ -0,0 +1,85 @@ +//! ## Overview +//! +//! We wish to define a dynamic reference model which upholds the rules of rust +//! references when those references are active, but which is flexible to the +//! needs of the dynamic preinterpret language. +//! +//! In other words, it should catch real bugs, with clear error messages, but +//! not get in the way of things you might want to do in a dynamic language. +//! +//! ## Model +//! +//! Our model is: +//! - When calling a method, all references in arguments must +//! be in an active mode which upholds the rust reference invariants. +//! - This ensures that Rust methods can be called without UB +//! - We do this for user-defined methods to, for consistency, and to allow +//! sensible interface design which matches expectations. +//! - Inside a user-defined method, we don't really want users caring about +//! what a variable points to, as it adds a strictness which could be +//! frustrating without a helpful compiler pointing the way. So, +//! when variables/references aren't in active use, we relax the invariants +//! to a weaker "InactiveReference" which upholds validity invariants +//! (i.e. a reference can't point to a value which isn't of the correct type) +//! but doesn't maintain mutability-related safety invariants (i.e. a +//! shared reference does not prevent mutation if it is inactive). +//! +//! As some examples, it should: +//! - Allow x.a: &mut AnyValue and x.b: &mut AnyValue to exist as active mutable +//! references at the same time +//! - Prevent x: &mut AnyValue and x.a: &AnyValue from existing as active references +//! at the same time +//! - Allow x: &mut Integer to mutate x whilst a currently unused variable +//! y: &Integer exists in preinterpret, but not as an active rust reference. +//! +//! ## Implementation +//! +//! We define a concept of a [`ReferencePath`] with a partial order, roughly +//! signalling a depth/subtyping relation to do with mutable ownership. +//! It is more strictly defined on the ReferencePath docs. +//! +//! There are three reference kinds, each wraps a `*mut T` for ease, and each +//! has the *validity* invariant that the pointer will always point to memory +//! representing the type `T`: +//! - `InactiveReference` +//! - Represents an inactive shared/mutable, or disabled mutable during a two-phase borrow +//! - "Active" `Shared` +//! - Implements `Deref` +//! - The existence of this grants the following safety-invariants: +//! - T is immutable +//! - "Active" `Mutable` +//! - Implemented `DerefMut` +//! - The existence of this implies the following safety-invariants: +//! - No other reference can mutate T +//! +//! This gives rise to the following rules: +//! (a) Can't create a Mutable from an InactiveReference if there is: +//! (a.1) A strictly deeper reference of any kind; which could end up pointing to invalid memory +//! (a.2) Any equal or shallower active reference; as that could break the other reference's safety invariant +//! (b) Can't create an Shared from an InactiveReference if there is: +//! (b.1) Any equal or deeper ActiveMutable, as they could break our safety invariant +//! (b.2) (NOTE: There can't be a strictly shallower ActiveMutable, as that would contravene a.1) +//! (c) Can't map an InactiveReference from A to deeper B if: +//! (c.1) There is a shallower ActiveMutable than B, which could leave us pointing at invalid memory, as per a.1) +//! (c.2) (NOTE: There can't be a strictly shallower ActiveMutable than A, as that would contravene a.1 already) +//! +//! Basically, a.1 and a.2 are our two key invariants, and all our actions must preserve them. +//! +//! Okay, but then -- how do we create `&mut x.a` and `&mut x.b`? As follows: +//! - Start with ActiveMutable(x), deactivate to InactiveReference(x) +//! - Duplicate InactiveReference(x), map InactiveReference(x) => InactiveReference(x.a), Activate +//! - Duplicate InactiveReference(x), map InactiveReference(x) => InactiveReference(x.b), Activate + +mod referenceable; +mod reference_core; +mod inactive_reference; +mod mutable_reference; +mod shared_reference; + +use crate::internal_prelude::*; +use reference_core::*; + +pub(crate) use referenceable::*; +pub(crate) use inactive_reference::*; +pub(crate) use mutable_reference::*; +pub(crate) use shared_reference::*; \ No newline at end of file diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs new file mode 100644 index 00000000..b947ff4a --- /dev/null +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -0,0 +1,34 @@ +use super::*; + +pub(crate) struct MutableReference(pub(super) ReferenceCore); + +impl MutableReference { + pub(crate) fn deactivate(self) -> InactiveMutableReference { + self.0.deactivate_reference(); + InactiveMutableReference(self.0) + } +} + +impl Deref for MutableReference { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: See the rustdoc on dynamic_references/mod.rs for full details. + // To summarize: + // - Provided by the safety invariants of `ReferenceCore` and `ReferenceableCore`. + // - The pointer is guaranteed to be valid and properly aligned for the duration of the reference's lifetime. + // - The reference kind checks ensure that it is not mutably aliased while active. + unsafe { &*self.0.pointer } + } +} + +impl DerefMut for MutableReference { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: See the rustdoc on dynamic_references/mod.rs for full details. + // To summarize: + // - Provided by the safety invariants of `ReferenceCore` and `ReferenceableCore`. + // - The pointer is guaranteed to be valid and properly aligned for the duration of the reference's lifetime. + // - The reference kind checks ensure that it is not aliased while active. + unsafe { &mut *self.0.pointer } + } +} \ No newline at end of file diff --git a/src/misc/dynamic_references/reference_core.rs b/src/misc/dynamic_references/reference_core.rs new file mode 100644 index 00000000..19aaeec1 --- /dev/null +++ b/src/misc/dynamic_references/reference_core.rs @@ -0,0 +1,42 @@ +use super::*; + +pub(super) struct ReferenceCore { + pub(super) pointer: *mut T, + pub(super) id: LocalReferenceId, + pub(super) core: Rc>, +} + +impl ReferenceCore { + pub(super) fn activate_mutable_reference(&self) -> FunctionResult<()> { + self.core.data_mut().activate_mutable_reference(self.id) + } + + pub(super) fn activate_shared_reference(&self) -> FunctionResult<()> { + self.core.data_mut().activate_shared_reference(self.id) + } + + pub(super) fn deactivate_reference(&self) { + self.core.data_mut().deactivate_reference(self.id); + } +} + +impl Clone for ReferenceCore { + fn clone(&self) -> Self { + let mut data = self.core.data_mut(); + let copied_data = data.for_reference(self.id).clone(); + if copied_data.reference_kind == ReferenceKind::ActiveMutable { + panic!("Cannot clone a mutable reference"); + } + Self { + pointer: self.pointer, + id: data.new_reference(copied_data), + core: Rc::clone(&self.core), + } + } +} + +impl Drop for ReferenceCore { + fn drop(&mut self) { + self.core.data_mut().drop_reference(self.id); + } +} \ No newline at end of file diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs new file mode 100644 index 00000000..117ca769 --- /dev/null +++ b/src/misc/dynamic_references/referenceable.rs @@ -0,0 +1,246 @@ +use crate::internal_prelude::*; +use std::cell::UnsafeCell; +use std::cmp::Ordering; +use slotmap::{new_key_type, SlotMap}; + +pub(crate) struct Referenceable { + core: Rc>, +} + +impl Referenceable { + pub(crate) fn new_reference(&self) -> InactiveReference { + InactiveReference::new_root(Rc::clone(&self.core)) + } +} + +pub(super) struct ReferenceableCore { + pub(super) root: UnsafeCell, + pub(super) root_name: String, + pub(super) root_span: SpanRange, + pub(super) data: RefCell, +} + +impl ReferenceableCore { + pub(super) fn data(&self) -> Ref<'_, ReferenceableData> { + self.data.borrow() + } + + pub(super) fn data_mut(&self) -> RefMut<'_, ReferenceableData> { + self.data.borrow_mut() + } + + pub(super) fn display_path(&self, mut f: impl std::fmt::Write, id: LocalReferenceId) -> std::fmt::Result { + f.write_str(&self.root_name)?; + let data = self.data(); + let data = data.for_reference(id); + for part in data.path.parts.iter() { + match part { + PathPart::Value { bound_as } => { + f.write_str(&format!(" (of type {})", bound_as.source_name()))?; + } + PathPart::ArrayChild(i) => { + f.write_char('[')?; + write!(f, "{}", i)?; + f.write_char(']')?; + } + PathPart::ObjectChild(key) => { + if syn::parse_str::(key).is_ok() { + f.write_char('.')?; + f.write_str(key)?; + } else { + f.write_char('[')?; + write!(f, "{:?}", key)?; + f.write_char(']')?; + } + } + } + } + Ok(()) + } +} + +new_key_type! { + pub(crate) struct LocalReferenceId; +} + +pub(super) struct ReferenceableData { + arena: SlotMap, +} + +impl ReferenceableData { + pub(super) fn new_reference(&mut self, data: TrackedReference) -> LocalReferenceId { + self.arena.insert(data) + } + + pub(super) fn for_reference(&self, id: LocalReferenceId) -> &TrackedReference { + self.arena + .get(id) + .expect("reference id not found in map") + } + + fn for_reference_mut(&mut self, id: LocalReferenceId) -> &mut TrackedReference { + self.arena + .get_mut(id) + .expect("reference id not found in map") + } + + pub(super) fn drop_reference(&mut self, id: LocalReferenceId) { + self.arena.remove(id); + } + + pub(super) fn deactivate_reference(&mut self, id: LocalReferenceId) { + self.for_reference_mut(id).reference_kind = ReferenceKind::Inactive; + } + + pub(super) fn activate_mutable_reference(&mut self, id: LocalReferenceId) -> FunctionResult<()> { + let data = self.for_reference(id); + // Perform checks as per the module doc on `dynamic_references` + for (other_id, other_data) in self.arena.iter() { + if other_id != id { + match other_data.path.partial_cmp(&data.path) { + None => continue, + Some(Ordering::Equal | Ordering::Less) => { + if other_data.reference_kind.is_active() { + todo!("// BETTER ERROR: Safety invariant break") + } + }, + Some(Ordering::Greater) => { + todo!("// BETTER ERROR: Validity invariant break") + }, + } + } + } + let data = self.for_reference_mut(id); + data.reference_kind = match data.reference_kind { + ReferenceKind::Inactive => ReferenceKind::ActiveMutable, + _ => panic!("cannot mut-activate an active reference"), + }; + Ok(()) + } + + pub(super) fn activate_shared_reference(&mut self, id: LocalReferenceId) -> FunctionResult<()> { + let data = self.for_reference(id); + // Perform checks as per the module doc on `dynamic_references` + for (other_id, other_data) in self.arena.iter() { + if other_id != id && other_data.reference_kind == ReferenceKind::ActiveMutable { + match other_data.path.partial_cmp(&data.path) { + Some(Ordering::Equal | Ordering::Greater) => { + todo!("// BETTER ERROR: Safety invariant break") + }, + Some(Ordering::Less) => { + panic!("Unexpected mutability invariant break: shared-activating a reference with an active mutable parent. This should already be prevented by the mutable reference's activation checks, so this likely indicates a bug in the implementation.") + }, + _ => continue, + } + } + } + let data = self.for_reference_mut(id); + data.reference_kind = match data.reference_kind { + ReferenceKind::Inactive => ReferenceKind::ActiveShared, + _ => panic!("cannot shared-activate an active reference"), + }; + Ok(()) + } +} + +#[derive(Clone)] +pub(super) struct TrackedReference { + pub(super) path: ReferencePath, + pub(super) reference_kind: ReferenceKind, + pub(super) creation_span: SpanRange, +} + +#[derive(PartialEq, Eq, Copy, Clone)] +pub(super) enum ReferenceKind { + Inactive, + ActiveShared, + ActiveMutable, +} + +impl ReferenceKind { + fn is_active(&self) -> bool { + match self { + ReferenceKind::Inactive => false, + ReferenceKind::ActiveShared | ReferenceKind::ActiveMutable => true, + } + } +} + +/// ## Partial Order +/// +/// Has a partial order defined which says all of the following: +/// - P1 and P2 are incomparable if they can exist as distinct mutable reference +/// - P1 < P2 if P2 is "deeper" than P1, i.e. that: +/// - A mutation of P1 could invalidate a reference to P2 so must be banned +/// - P1 could observe a mutation of P2, but would not invalidate it +/// - P1 == P2 if they are equivalent to the same reference, i.e. that: +/// - A mutation of P1 is possible without invalidating a reference to P2 +/// - P1 could observe a mutation of P2 (and vice versa) +/// +/// If any of these assumptions are wrong, we'll need to revisit this. +#[derive(PartialEq, Eq, Clone)] +pub(super) struct ReferencePath { + parts: Vec, +} + +impl ReferencePath { + pub(super) fn leaf(bound_as: TypeKind) -> Self { + Self { + parts: vec![PathPart::Value { bound_as }], + } + } +} + +impl PartialOrd for ReferencePath { + fn partial_cmp(&self, other: &Self) -> Option { + for (own, other) in self.parts.iter().zip(&other.parts) { + match own.partial_cmp(other) { + // If incomparable, the paths have diverged so are incomparable + None => return None, + Some(Ordering::Equal) => continue, + Some(ordered) => return Some(ordered), + } + } + return Some(self.parts.len().cmp(&other.parts.len())); + } +} + +#[derive(PartialEq, Eq, Clone)] +enum PathPart { + Value { + bound_as: TypeKind, + }, + ArrayChild(usize), + ObjectChild(String), +} + +impl PathPart { + fn bound_type_kind(&self) -> TypeKind { + match self { + PathPart::Value { bound_as } => *bound_as, + PathPart::ArrayChild(_) => ArrayType::type_kind(), + PathPart::ObjectChild(_) => ObjectType::type_kind(), + } + } +} + +impl PartialOrd for PathPart { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (PathPart::ArrayChild(i), PathPart::ArrayChild(j)) if i == j => Some(Ordering::Equal), + (PathPart::ArrayChild(_), PathPart::ArrayChild(_)) => None, + (PathPart::ObjectChild(a), PathPart::ObjectChild(b)) if a == b => Some(Ordering::Equal), + (PathPart::ObjectChild(_), PathPart::ObjectChild(_)) => None, + // TODO: I'm not sure this is right + // - A path being incomparable is a divergence + // - A type being incomparable is: + // - A broken invariant if they are incompatible (e.g. String and Int) + // - ?? if it's compatible dyn and a concrete type (e.g. dyn Iterator and Array) + // - So we probably don't want to make PathPart and TypeKind implement PartialOrd, + // we probably want a more senamtically meaningful output enum which we can handle + // correctly in upstream logic + (this, other) => this.bound_type_kind().partial_cmp(&other.bound_type_kind()), + } + } +} + diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs new file mode 100644 index 00000000..6b98034a --- /dev/null +++ b/src/misc/dynamic_references/shared_reference.rs @@ -0,0 +1,24 @@ +use super::*; + +#[derive(Clone)] +pub(crate) struct SharedReference(pub(super) ReferenceCore); + +impl SharedReference { + pub(crate) fn deactivate(self) -> InactiveSharedReference { + self.0.deactivate_reference(); + InactiveSharedReference(self.0) + } +} + +impl Deref for SharedReference { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: See the rustdoc on dynamic_references/mod.rs for full details. + // To summarize: + // - Provided by the safety invariants of `ReferenceCore` and `ReferenceableCore`. + // - The pointer is guaranteed to be valid and properly aligned for the duration of the reference's lifetime. + // - The reference kind checks ensure that it is not mutably aliased while active. + unsafe { &*self.0.pointer } + } +} diff --git a/src/misc/mod.rs b/src/misc/mod.rs index 36aa4457..5ebfb9a4 100644 --- a/src/misc/mod.rs +++ b/src/misc/mod.rs @@ -1,5 +1,6 @@ mod arena; mod errors; +mod dynamic_references; mod field_inputs; mod iterators; mod keywords; @@ -8,6 +9,7 @@ mod parse_traits; pub(crate) mod string_conversion; pub(crate) use arena::*; +pub(crate) use dynamic_references::*; pub(crate) use errors::*; pub(crate) use field_inputs::*; pub(crate) use iterators::*; From f8473b7294150b017710dbd3922fd8eb69977e3d Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 22 Feb 2026 19:45:07 +0000 Subject: [PATCH 03/25] feat: Add emplacing to the new references --- plans/TODO.md | 4 +- .../dynamic_references/inactive_reference.rs | 36 ------ src/misc/dynamic_references/mod.rs | 53 +++++--- .../dynamic_references/mutable_reference.rs | 117 ++++++++++++++++-- src/misc/dynamic_references/reference_core.rs | 104 ++++++++++++++-- src/misc/dynamic_references/referenceable.rs | 96 ++++++++++---- .../dynamic_references/shared_reference.rs | 100 ++++++++++++++- src/misc/mod.rs | 3 +- 8 files changed, 414 insertions(+), 99 deletions(-) delete mode 100644 src/misc/dynamic_references/inactive_reference.rs diff --git a/plans/TODO.md b/plans/TODO.md index 18aa09e2..4620e002 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -302,12 +302,12 @@ The issue is that we get a variable stored as `arr := DisabledMutable` and then - [x] Create Referenceable, and Reference types - [ ] Create error messages for rule breaks in `referenceable.rs` - [ ] Add the following to `ReferenceCore` and maybe others: + - [x] Emplacing - [ ] Ability to map deeper. Should take a `PathExtension` and a new span. See rule (c) from `dynamic_references/mod.rs` - [ ] Various mapping functions (e.g. array offset; object key) - [ ] Try map - - [ ] Emplacing -- [ ] Check for missing features from `Shared` / `SharedSubRcRefCell` and `Mutable` / `MutableSubRcRefCell` +- [ ] Try replacing `Shared` / `Mutable` ### Other ideas diff --git a/src/misc/dynamic_references/inactive_reference.rs b/src/misc/dynamic_references/inactive_reference.rs deleted file mode 100644 index 15d711c0..00000000 --- a/src/misc/dynamic_references/inactive_reference.rs +++ /dev/null @@ -1,36 +0,0 @@ -use super::*; - -#[derive(Clone)] -pub(crate) struct InactiveReference(ReferenceCore); - -impl InactiveReference { - pub(super) fn new_root(core: Rc>) -> Self { - let new_id = core.data_mut() - .new_reference(TrackedReference { - path: ReferencePath::leaf(AnyType::type_kind()), - reference_kind: ReferenceKind::Inactive, - creation_span: core.root_span, - }); - Self(ReferenceCore { - pointer: core.root.get(), - id: new_id, - core, - }) - } - - pub(super) fn activate_mutable(self) -> FunctionResult> { - self.0.activate_mutable_reference()?; - Ok(MutableReference(self.0)) - } - - pub(super) fn activate_shared(self) -> FunctionResult> { - self.0.activate_shared_reference()?; - Ok(SharedReference(self.0)) - } -} - -#[derive(Clone)] -pub(crate) struct InactiveSharedReference(pub(super) ReferenceCore); - -#[derive(Clone)] -pub(crate) struct InactiveMutableReference(pub(super) ReferenceCore); \ No newline at end of file diff --git a/src/misc/dynamic_references/mod.rs b/src/misc/dynamic_references/mod.rs index b98fea21..5efc3ae3 100644 --- a/src/misc/dynamic_references/mod.rs +++ b/src/misc/dynamic_references/mod.rs @@ -1,12 +1,12 @@ //! ## Overview -//! +//! //! We wish to define a dynamic reference model which upholds the rules of rust //! references when those references are active, but which is flexible to the //! needs of the dynamic preinterpret language. -//! +//! //! In other words, it should catch real bugs, with clear error messages, but //! not get in the way of things you might want to do in a dynamic language. -//! +//! //! ## Model //! //! Our model is: @@ -23,7 +23,7 @@ //! (i.e. a reference can't point to a value which isn't of the correct type) //! but doesn't maintain mutability-related safety invariants (i.e. a //! shared reference does not prevent mutation if it is inactive). -//! +//! //! As some examples, it should: //! - Allow x.a: &mut AnyValue and x.b: &mut AnyValue to exist as active mutable //! references at the same time @@ -37,7 +37,7 @@ //! We define a concept of a [`ReferencePath`] with a partial order, roughly //! signalling a depth/subtyping relation to do with mutable ownership. //! It is more strictly defined on the ReferencePath docs. -//! +//! //! There are three reference kinds, each wraps a `*mut T` for ease, and each //! has the *validity* invariant that the pointer will always point to memory //! representing the type `T`: @@ -59,27 +59,44 @@ //! (b) Can't create an Shared from an InactiveReference if there is: //! (b.1) Any equal or deeper ActiveMutable, as they could break our safety invariant //! (b.2) (NOTE: There can't be a strictly shallower ActiveMutable, as that would contravene a.1) -//! (c) Can't map an InactiveReference from A to deeper B if: -//! (c.1) There is a shallower ActiveMutable than B, which could leave us pointing at invalid memory, as per a.1) -//! (c.2) (NOTE: There can't be a strictly shallower ActiveMutable than A, as that would contravene a.1 already) //! //! Basically, a.1 and a.2 are our two key invariants, and all our actions must preserve them. //! -//! Okay, but then -- how do we create `&mut x.a` and `&mut x.b`? As follows: -//! - Start with ActiveMutable(x), deactivate to InactiveReference(x) -//! - Duplicate InactiveReference(x), map InactiveReference(x) => InactiveReference(x.a), Activate -//! - Duplicate InactiveReference(x), map InactiveReference(x) => InactiveReference(x.b), Activate +//! We also need to be able to map/deepen references, e.g. map x to x.a or &any to &integer. +//! We _could_ do this from/via/to InactiveReference with pointer mappings, and a rule to check (a.1) +//! against the resultant state. This would allow us to map references with a present active +//! mutable on a different path. But it's painful to write and reason about, and we don't really +//! need it. +//! +//! Instead, we map via Active references, and then deactivate when we stop actively using a reference. +//! +//! Okay, but then -- how do we create `&mut x.a` and `&mut x.b` at the same time? As follows: +//! - Start with InactiveReference(x) +//! - Duplicate InactiveReference(x), activate to MutableReference(x), map to MutableReference(x.a), deactivate to InactiveReference(x.a) +//! - Duplicate InactiveReference(x), activate to MutableReference(x), map to MutableReference(x.b), deactivate to InactiveReference(x.b) +//! - Activate both into Mutable references +//! +//! ## Previous Model +//! +//! Was based off an `Rc>` which we could use to create Active/Mutable references, and deactivate them. +//! However, it didn't respect (a.1) and it didn't permit mutiple mutable references on different paths. -mod referenceable; -mod reference_core; -mod inactive_reference; +// TODO[references]: Remove once integrated +#![allow(unused)] mod mutable_reference; +mod reference_core; +mod referenceable; mod shared_reference; +// Prelude for everything under dynamic_references use crate::internal_prelude::*; use reference_core::*; +use slotmap::{new_key_type, SlotMap}; +use std::cell::UnsafeCell; +use std::cmp::Ordering; +use std::mem::ManuallyDrop; +use std::ptr::NonNull; -pub(crate) use referenceable::*; -pub(crate) use inactive_reference::*; pub(crate) use mutable_reference::*; -pub(crate) use shared_reference::*; \ No newline at end of file +pub(crate) use referenceable::*; +pub(crate) use shared_reference::*; diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index b947ff4a..7133c999 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -1,15 +1,60 @@ use super::*; -pub(crate) struct MutableReference(pub(super) ReferenceCore); +pub(crate) struct MutableReference(pub(super) ReferenceCore); -impl MutableReference { +impl MutableReference { pub(crate) fn deactivate(self) -> InactiveMutableReference { self.0.deactivate_reference(); InactiveMutableReference(self.0) } + + /// A powerful map method which lets you place the resultant mapped reference inside + /// a structure arbitrarily. + pub(crate) fn emplace_map( + mut self, + f: impl for<'e> FnOnce(&'e mut T, &mut MutableEmplacerV2<'e, T>) -> O, + ) -> O { + // SAFETY: The validity + safety invariants are upheld by `ReferenceableCore` + // ... assuming this id is marked as a mutable reference for the duration. + // The emplacer ensures that this is upheld whilst it is alive; via either: + // - Delegating to the created MutableReference if it is emplaced + // - Surviving until Drop at the end of this method if it is not emplaced + let copied_mut = unsafe { self.0.pointer.as_mut() }; + let mut emplacer = MutableEmplacerV2(self.0.into_emplacer()); + f(copied_mut, &mut emplacer) + } + + /// SAFETY: + /// - The caller must ensure that the ReferencePathExtension is correct + /// (an overly-specific ReferencePathExtension may cause safety issues) + pub(crate) unsafe fn map( + self, + f: impl FnOnce(&mut T) -> &mut V, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> MutableReference { + self.emplace_map(move |input, emplacer| { + emplacer.emplace(f(input), path_extension, new_span) + }) + } + + /// SAFETY: + /// - The caller must ensure that the ReferencePathExtension is correct + /// (an overly-specific ReferencePathExtension may cause safety issues) + pub(crate) unsafe fn try_map( + self, + f: impl FnOnce(&mut T) -> Result<&mut V, E>, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> Result, (E, MutableReference)> { + self.emplace_map(|input, emplacer| match f(input) { + Ok(output) => Ok(emplacer.emplace(output, path_extension, new_span)), + Err(e) => Err((e, emplacer.revert())), + }) + } } -impl Deref for MutableReference { +impl Deref for MutableReference { type Target = T; fn deref(&self) -> &Self::Target { @@ -18,17 +63,75 @@ impl Deref for MutableReference { // - Provided by the safety invariants of `ReferenceCore` and `ReferenceableCore`. // - The pointer is guaranteed to be valid and properly aligned for the duration of the reference's lifetime. // - The reference kind checks ensure that it is not mutably aliased while active. - unsafe { &*self.0.pointer } + unsafe { self.0.pointer.as_ref() } } } -impl DerefMut for MutableReference { +impl DerefMut for MutableReference { fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: See the rustdoc on dynamic_references/mod.rs for full details. // To summarize: // - Provided by the safety invariants of `ReferenceCore` and `ReferenceableCore`. // - The pointer is guaranteed to be valid and properly aligned for the duration of the reference's lifetime. // - The reference kind checks ensure that it is not aliased while active. - unsafe { &mut *self.0.pointer } + unsafe { self.0.pointer.as_mut() } } -} \ No newline at end of file +} + +#[derive(Clone)] +pub(crate) struct InactiveMutableReference(pub(super) ReferenceCore); + +impl InactiveMutableReference { + pub(crate) fn activate(self) -> FunctionResult> { + self.0.activate_mutable_reference()?; + Ok(MutableReference(self.0)) + } + + pub(crate) fn into_shared(self) -> InactiveSharedReference { + // As an inactive reference, we are free to map between them + // ... we could even enable the other way around, but that'd likely allow breaking + // application invariants which we want to respect. + InactiveSharedReference(self.0) + } +} + +pub(crate) struct MutableEmplacerV2<'e, T: ?Sized>(EmplacerCore<'e, T>); + +impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { + pub(crate) fn revert(&mut self) -> MutableReference { + MutableReference(self.0.revert()) + } + + /// SAFETY: + /// - The caller must ensure that the ReferencePathExtension is correct + /// (an overly-specific ReferencePathExtension may cause safety issues) + pub(crate) unsafe fn emplace( + &mut self, + value: &'e mut V, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> MutableReference { + unsafe { + // SAFETY: The lifetime 'e is equal to the &'e content argument in replace + // So this guarantees that the returned reference is valid as long as the SharedSubRcRefCell exists + self.emplace_unchecked(value, path_extension, new_span) + } + } + + /// SAFETY: + /// - The caller must ensure that the value's lifetime is derived from the original content + /// - The caller must ensure that the ReferencePathExtension is correct + pub(crate) unsafe fn emplace_unchecked( + &mut self, + value: &mut V, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> MutableReference { + // SAFETY: The pointer is from a reference so non-null + let pointer = unsafe { NonNull::new_unchecked(value as *mut V) }; + // SAFETY: + // - The caller ensures that the reference is derived from the original content + // - The caller ensures that the ReferencePathExtension is correct + unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } + } +} diff --git a/src/misc/dynamic_references/reference_core.rs b/src/misc/dynamic_references/reference_core.rs index 19aaeec1..0e0e1fcb 100644 --- a/src/misc/dynamic_references/reference_core.rs +++ b/src/misc/dynamic_references/reference_core.rs @@ -1,12 +1,30 @@ use super::*; -pub(super) struct ReferenceCore { - pub(super) pointer: *mut T, +pub(super) struct ReferenceCore { + pub(super) pointer: NonNull, pub(super) id: LocalReferenceId, pub(super) core: Rc>, } -impl ReferenceCore { +impl ReferenceCore { + pub(super) fn new_root( + core: Rc>, + reference_kind: ReferenceKind, + ) -> Self { + let new_id = core.data_mut().new_reference(TrackedReference { + path: ReferencePath::leaf(AnyType::type_kind()), + reference_kind, + creation_span: core.root_span, + }); + Self { + pointer: core.root(), + id: new_id, + core, + } + } +} + +impl ReferenceCore { pub(super) fn activate_mutable_reference(&self) -> FunctionResult<()> { self.core.data_mut().activate_mutable_reference(self.id) } @@ -18,14 +36,30 @@ impl ReferenceCore { pub(super) fn deactivate_reference(&self) { self.core.data_mut().deactivate_reference(self.id); } + + pub(super) fn into_emplacer<'e>(self) -> EmplacerCore<'e, T> { + // NOTE: The clean-up duty is delegated to the emplacer + let reference = ManuallyDrop::new(self); + let manual_drop = ManualDropReferenceCore { + pointer: reference.pointer, + id: reference.id, + // SAFETY: read's invariants are upheld, and we are manually dropping the reference, + // so we won't run the drop implementation that would mess with the reference count. + core: unsafe { std::ptr::read(&reference.core) }, + }; + EmplacerCore { + inner: Some(manual_drop), + encapsulation_lifetime: std::marker::PhantomData, + } + } } -impl Clone for ReferenceCore { +impl Clone for ReferenceCore { fn clone(&self) -> Self { let mut data = self.core.data_mut(); let copied_data = data.for_reference(self.id).clone(); if copied_data.reference_kind == ReferenceKind::ActiveMutable { - panic!("Cannot clone a mutable reference"); + panic!("Cannot clone an active mutable reference"); } Self { pointer: self.pointer, @@ -35,8 +69,64 @@ impl Clone for ReferenceCore { } } -impl Drop for ReferenceCore { +impl Drop for ReferenceCore { fn drop(&mut self) { self.core.data_mut().drop_reference(self.id); } -} \ No newline at end of file +} + +/// Essentially the same as a [ReferenceCore], but doesn't have the drop implementation. +/// Must be a temporary, whose lifetime exists only as long as the pointer is valid in the given context. +pub(super) struct ManualDropReferenceCore { + pub(super) pointer: NonNull, + pub(super) id: LocalReferenceId, + pub(super) core: Rc>, +} + +pub(super) struct EmplacerCore<'e, T: ?Sized> { + inner: Option>, + encapsulation_lifetime: std::marker::PhantomData<&'e ()>, +} + +impl<'e, T: ?Sized> EmplacerCore<'e, T> { + pub(crate) fn revert(&mut self) -> ReferenceCore { + let core = self.take(); + ReferenceCore { + pointer: core.pointer, + id: core.id, + core: core.core, + } + } + + /// SAFETY: + /// - The caller must ensure that the pointer is derived from the original content + /// - The caller must ensure that the ReferencePathExtension is correct + pub(crate) unsafe fn emplace_unchecked( + &mut self, + pointer: NonNull, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> ReferenceCore { + let emplacer_core = self.take(); + let id = emplacer_core.id; + let core = emplacer_core.core; + core.data_mut() + .derive_reference(id, path_extension, new_span); + ReferenceCore { pointer, id, core } + } + + fn take(&mut self) -> ManualDropReferenceCore { + self.inner + .take() + .expect("You may only emplace or revert once from an emplacer") + } +} + +impl<'e, T: ?Sized> Drop for EmplacerCore<'e, T> { + fn drop(&mut self) { + if let Some(core) = self.inner.take() { + // If the emplacer is still around, we need to manually drop it + core.core.data_mut().drop_reference(core.id); + }; + } +} diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 117ca769..d1f7f244 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -1,26 +1,52 @@ -use crate::internal_prelude::*; -use std::cell::UnsafeCell; -use std::cmp::Ordering; -use slotmap::{new_key_type, SlotMap}; +use super::*; pub(crate) struct Referenceable { core: Rc>, } impl Referenceable { - pub(crate) fn new_reference(&self) -> InactiveReference { - InactiveReference::new_root(Rc::clone(&self.core)) + pub(crate) fn new_inactive_shared(&self) -> InactiveSharedReference { + InactiveSharedReference(ReferenceCore::new_root( + self.core.clone(), + ReferenceKind::Inactive, + )) + } + + pub(crate) fn new_inactive_mutable(&self) -> InactiveMutableReference { + InactiveMutableReference(ReferenceCore::new_root( + self.core.clone(), + ReferenceKind::Inactive, + )) } } pub(super) struct ReferenceableCore { - pub(super) root: UnsafeCell, - pub(super) root_name: String, + // Guaranteed not-null + root: UnsafeCell, + root_name: String, pub(super) root_span: SpanRange, - pub(super) data: RefCell, + data: RefCell, } impl ReferenceableCore { + pub(super) fn new(root: T, root_name: String, root_span: SpanRange) -> Self { + Self { + root: UnsafeCell::new(root), + root_name, + root_span, + data: RefCell::new(ReferenceableData { + arena: SlotMap::with_key(), + }), + } + } + + pub(super) fn root(&self) -> NonNull { + unsafe { + // SAFETY: This is guaranteed non-null + NonNull::new_unchecked(self.root.get()) + } + } + pub(super) fn data(&self) -> Ref<'_, ReferenceableData> { self.data.borrow() } @@ -29,7 +55,11 @@ impl ReferenceableCore { self.data.borrow_mut() } - pub(super) fn display_path(&self, mut f: impl std::fmt::Write, id: LocalReferenceId) -> std::fmt::Result { + pub(super) fn display_path( + &self, + mut f: impl std::fmt::Write, + id: LocalReferenceId, + ) -> std::fmt::Result { f.write_str(&self.root_name)?; let data = self.data(); let data = data.for_reference(id); @@ -73,9 +103,7 @@ impl ReferenceableData { } pub(super) fn for_reference(&self, id: LocalReferenceId) -> &TrackedReference { - self.arena - .get(id) - .expect("reference id not found in map") + self.arena.get(id).expect("reference id not found in map") } fn for_reference_mut(&mut self, id: LocalReferenceId) -> &mut TrackedReference { @@ -92,7 +120,10 @@ impl ReferenceableData { self.for_reference_mut(id).reference_kind = ReferenceKind::Inactive; } - pub(super) fn activate_mutable_reference(&mut self, id: LocalReferenceId) -> FunctionResult<()> { + pub(super) fn activate_mutable_reference( + &mut self, + id: LocalReferenceId, + ) -> FunctionResult<()> { let data = self.for_reference(id); // Perform checks as per the module doc on `dynamic_references` for (other_id, other_data) in self.arena.iter() { @@ -101,12 +132,14 @@ impl ReferenceableData { None => continue, Some(Ordering::Equal | Ordering::Less) => { if other_data.reference_kind.is_active() { + // TODO[references]: Fix this todo!("// BETTER ERROR: Safety invariant break") } - }, + } Some(Ordering::Greater) => { + // TODO[references]: Fix this todo!("// BETTER ERROR: Validity invariant break") - }, + } } } } @@ -125,11 +158,12 @@ impl ReferenceableData { if other_id != id && other_data.reference_kind == ReferenceKind::ActiveMutable { match other_data.path.partial_cmp(&data.path) { Some(Ordering::Equal | Ordering::Greater) => { + // TODO[references] todo!("// BETTER ERROR: Safety invariant break") - }, + } Some(Ordering::Less) => { panic!("Unexpected mutability invariant break: shared-activating a reference with an active mutable parent. This should already be prevented by the mutable reference's activation checks, so this likely indicates a bug in the implementation.") - }, + } _ => continue, } } @@ -141,6 +175,17 @@ impl ReferenceableData { }; Ok(()) } + + pub(super) fn derive_reference( + &mut self, + id: LocalReferenceId, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) { + let data = self.for_reference_mut(id); + data.creation_span = new_span; + // TODO[references]: Extend the path + } } #[derive(Clone)] @@ -167,7 +212,7 @@ impl ReferenceKind { } /// ## Partial Order -/// +/// /// Has a partial order defined which says all of the following: /// - P1 and P2 are incomparable if they can exist as distinct mutable reference /// - P1 < P2 if P2 is "deeper" than P1, i.e. that: @@ -176,7 +221,7 @@ impl ReferenceKind { /// - P1 == P2 if they are equivalent to the same reference, i.e. that: /// - A mutation of P1 is possible without invalidating a reference to P2 /// - P1 could observe a mutation of P2 (and vice versa) -/// +/// /// If any of these assumptions are wrong, we'll need to revisit this. #[derive(PartialEq, Eq, Clone)] pub(super) struct ReferencePath { @@ -201,15 +246,15 @@ impl PartialOrd for ReferencePath { Some(ordered) => return Some(ordered), } } - return Some(self.parts.len().cmp(&other.parts.len())); + Some(self.parts.len().cmp(&other.parts.len())) } } +pub(crate) struct ReferencePathExtension; + #[derive(PartialEq, Eq, Clone)] enum PathPart { - Value { - bound_as: TypeKind, - }, + Value { bound_as: TypeKind }, ArrayChild(usize), ObjectChild(String), } @@ -231,7 +276,7 @@ impl PartialOrd for PathPart { (PathPart::ArrayChild(_), PathPart::ArrayChild(_)) => None, (PathPart::ObjectChild(a), PathPart::ObjectChild(b)) if a == b => Some(Ordering::Equal), (PathPart::ObjectChild(_), PathPart::ObjectChild(_)) => None, - // TODO: I'm not sure this is right + // TODO[references]: I'm not sure this is right // - A path being incomparable is a divergence // - A type being incomparable is: // - A broken invariant if they are incompatible (e.g. String and Int) @@ -243,4 +288,3 @@ impl PartialOrd for PathPart { } } } - diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 6b98034a..7428d5f9 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -1,13 +1,58 @@ use super::*; #[derive(Clone)] -pub(crate) struct SharedReference(pub(super) ReferenceCore); +pub(crate) struct SharedReference(pub(super) ReferenceCore); impl SharedReference { pub(crate) fn deactivate(self) -> InactiveSharedReference { self.0.deactivate_reference(); InactiveSharedReference(self.0) } + + /// A powerful map method which lets you place the resultant mapped reference inside + /// a structure arbitrarily. + pub(crate) fn emplace_map( + self, + f: impl for<'e> FnOnce(&'e T, &mut SharedEmplacerV2<'e, T>) -> O, + ) -> O { + // SAFETY: The validity + safety invariants are upheld by `ReferenceableCore` + // ... assuming this id is marked as a shared reference for the duration. + // The emplacer ensures that this is upheld whilst it is alive; via either: + // - Delegating to the created SharedReference if it is emplaced + // - Surviving until Drop at the end of this method if it is not emplaced + let copied_ref = unsafe { self.0.pointer.as_ref() }; + let mut emplacer = SharedEmplacerV2(self.0.into_emplacer()); + f(copied_ref, &mut emplacer) + } + + /// SAFETY: + /// - The caller must ensure that the ReferencePathExtension is correct + /// (an overly-specific ReferencePathExtension may cause safety issues) + pub(crate) unsafe fn map( + self, + f: impl FnOnce(&T) -> &V, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> SharedReference { + self.emplace_map(move |input, emplacer| { + emplacer.emplace(f(input), path_extension, new_span) + }) + } + + /// SAFETY: + /// - The caller must ensure that the ReferencePathExtension is correct + /// (an overly-specific ReferencePathExtension may cause safety issues) + pub(crate) unsafe fn try_map( + self, + f: impl FnOnce(&T) -> Result<&V, E>, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> Result, (E, SharedReference)> { + self.emplace_map(|input, emplacer| match f(input) { + Ok(output) => Ok(emplacer.emplace(output, path_extension, new_span)), + Err(e) => Err((e, emplacer.revert())), + }) + } } impl Deref for SharedReference { @@ -19,6 +64,57 @@ impl Deref for SharedReference { // - Provided by the safety invariants of `ReferenceCore` and `ReferenceableCore`. // - The pointer is guaranteed to be valid and properly aligned for the duration of the reference's lifetime. // - The reference kind checks ensure that it is not mutably aliased while active. - unsafe { &*self.0.pointer } + unsafe { self.0.pointer.as_ref() } + } +} + +#[derive(Clone)] +pub(crate) struct InactiveSharedReference(pub(super) ReferenceCore); + +impl InactiveSharedReference { + pub(crate) fn activate(self) -> FunctionResult> { + self.0.activate_shared_reference()?; + Ok(SharedReference(self.0)) + } +} + +pub(crate) struct SharedEmplacerV2<'e, T: ?Sized>(EmplacerCore<'e, T>); + +impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { + pub(crate) fn revert(&mut self) -> SharedReference { + SharedReference(self.0.revert()) + } + + /// SAFETY: + /// - The caller must ensure that the ReferencePathExtension is correct + /// (an overly-specific ReferencePathExtension may cause safety issues) + pub(crate) unsafe fn emplace( + &mut self, + value: &'e V, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> SharedReference { + unsafe { + // SAFETY: The lifetime 'e is equal to the &'e content argument in replace + // So this guarantees that the returned reference is valid as long as the SharedSubRcRefCell exists + self.emplace_unchecked(value, path_extension, new_span) + } + } + + /// SAFETY: + /// - The caller must ensure that the value's lifetime is derived from the original content + /// - The caller must ensure that the ReferencePathExtension is correct + pub(crate) unsafe fn emplace_unchecked( + &mut self, + value: &V, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> SharedReference { + // SAFETY: The pointer is from a reference so non-null + let pointer = unsafe { NonNull::new_unchecked(value as *const V as *mut V) }; + // SAFETY: + // - The caller ensures that the reference is derived from the original content + // - The caller ensures that the ReferencePathExtension is correct + unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } } diff --git a/src/misc/mod.rs b/src/misc/mod.rs index 5ebfb9a4..d1d17792 100644 --- a/src/misc/mod.rs +++ b/src/misc/mod.rs @@ -1,6 +1,6 @@ mod arena; -mod errors; mod dynamic_references; +mod errors; mod field_inputs; mod iterators; mod keywords; @@ -9,6 +9,7 @@ mod parse_traits; pub(crate) mod string_conversion; pub(crate) use arena::*; +#[allow(unused)] // TODO: Remove unused once integrated pub(crate) use dynamic_references::*; pub(crate) use errors::*; pub(crate) use field_inputs::*; From e8b3e7446ddc77c7232b34838a37372929180ce9 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 22 Feb 2026 21:51:03 +0000 Subject: [PATCH 04/25] feat: Error messages when reference activation fails --- plans/2026-01-types-and-forms.md | 2 + plans/TODO.md | 5 +- src/expressions/type_resolution/type_kinds.rs | 82 ++-- .../dynamic_references/mutable_reference.rs | 8 +- src/misc/dynamic_references/reference_core.rs | 26 +- src/misc/dynamic_references/referenceable.rs | 357 +++++++++++++----- .../dynamic_references/shared_reference.rs | 7 +- 7 files changed, 345 insertions(+), 142 deletions(-) diff --git a/plans/2026-01-types-and-forms.md b/plans/2026-01-types-and-forms.md index f98009de..c1dc59d5 100644 --- a/plans/2026-01-types-and-forms.md +++ b/plans/2026-01-types-and-forms.md @@ -83,6 +83,8 @@ Honestly, it's really hard. Would need to think about what operations can be per Might come back to this at some point. +Relevant todos are marked as `TODO[non-leaf-form]` + ## What's next? Let's put the "concepts" to-leaf on indefinite pause. It's definitely resulted in a lot of good cleaning up, BUT it's also created a lot of mess. diff --git a/plans/TODO.md b/plans/TODO.md index 4620e002..4d1e071b 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -300,13 +300,12 @@ The issue is that we get a variable stored as `arr := DisabledMutable` and then - [x] Write up model in `dynamic_references/mod.rs` - [x] Add structure for `dynamic_references` - [x] Create Referenceable, and Reference types -- [ ] Create error messages for rule breaks in `referenceable.rs` +- [x] Create error messages for rule breaks in `referenceable.rs` - [ ] Add the following to `ReferenceCore` and maybe others: - [x] Emplacing + - [x] Map, Try map - [ ] Ability to map deeper. Should take a `PathExtension` and a new span. See rule (c) from `dynamic_references/mod.rs` - - [ ] Various mapping functions (e.g. array offset; object key) - - [ ] Try map - [ ] Try replacing `Shared` / `Mutable` diff --git a/src/expressions/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index b255c505..fe9f5de9 100644 --- a/src/expressions/type_resolution/type_kinds.rs +++ b/src/expressions/type_resolution/type_kinds.rs @@ -90,38 +90,74 @@ impl TypeKind { TypeKind::Dyn(dyn_kind) => dyn_kind.source_name(), } } -} -/// This is the subtyping ordering. -/// - I define that X <= Y if Y is a subtype of X. -/// - Think "X is shallower than Y in the enum representation" -/// -/// This must be accurate so that the Dynamic References works correctly. -/// e.g. if I have a `a: &mut AnyValue` and a `b: &mut IntegerValue` pointing -/// to the same memory, then I can't use a to change to a String. -impl PartialOrd for TypeKind { - fn partial_cmp(&self, other: &Self) -> Option { + /// This is the subtyping ordering. + /// - I define that X <= Y if Y is a subtype of X. + /// - Think "X is shallower than Y in the enum representation" + /// + /// This must be accurate so that the Dynamic References works correctly. + /// e.g. if I have a `a: &mut AnyValue` and a `b: &mut IntegerValue` pointing + /// to the same memory, then I can't use a to change to a String. + pub(crate) fn compare_bindings(&self, other: &Self) -> TypeBindingComparison { if self == other { - return Some(std::cmp::Ordering::Equal); + return TypeBindingComparison::Equal; } - // TODO: implement a more complete partial ord that reflects the actual subtyping relationships. match (self, other) { - // None-equal dyns are not subtypes - (TypeKind::Dyn(_), _) => None, - (_, TypeKind::Dyn(_)) => None, + // Dyns are not binding-comparable to other bindings/dyns except themself + (TypeKind::Dyn(_), _) => TypeBindingComparison::Incomparable, + (_, TypeKind::Dyn(_)) => TypeBindingComparison::Incomparable, // All non-any-values are strict subtypes of AnyValue - (TypeKind::Parent(ParentTypeKind::Value(_)), _) => Some(std::cmp::Ordering::Less), - (_, TypeKind::Parent(ParentTypeKind::Value(_))) => Some(std::cmp::Ordering::Greater), - // TODO: Non-equal leave types should kinda be an error, assuming they're pointing - // at the same value - (TypeKind::Leaf(_), _) => None, - (_, TypeKind::Leaf(_)) => None, - // TODO: Handle intermediate parents - _ => None, + (TypeKind::Parent(ParentTypeKind::Value(_)), _) => { + TypeBindingComparison::RightIsMoreSpecific + } + (_, TypeKind::Parent(ParentTypeKind::Value(_))) => { + TypeBindingComparison::LeftIsMoreSpecific + } + // Integer types + ( + TypeKind::Parent(ParentTypeKind::Integer(_)), + TypeKind::Leaf(AnyValueLeafKind::Integer(_)), + ) => TypeBindingComparison::RightIsMoreSpecific, + ( + TypeKind::Leaf(AnyValueLeafKind::Integer(_)), + TypeKind::Parent(ParentTypeKind::Integer(_)), + ) => TypeBindingComparison::LeftIsMoreSpecific, + (TypeKind::Parent(ParentTypeKind::Integer(_)), _) => { + TypeBindingComparison::Incompatible + } + (_, TypeKind::Parent(ParentTypeKind::Integer(_))) => { + TypeBindingComparison::Incompatible + } + // Float types + ( + TypeKind::Parent(ParentTypeKind::Float(_)), + TypeKind::Leaf(AnyValueLeafKind::Float(_)), + ) => TypeBindingComparison::RightIsMoreSpecific, + ( + TypeKind::Leaf(AnyValueLeafKind::Float(_)), + TypeKind::Parent(ParentTypeKind::Float(_)), + ) => TypeBindingComparison::LeftIsMoreSpecific, + (TypeKind::Parent(ParentTypeKind::Float(_)), _) => TypeBindingComparison::Incompatible, + (_, TypeKind::Parent(ParentTypeKind::Float(_))) => TypeBindingComparison::Incompatible, + // Non-equal leaf types are incomparable + (TypeKind::Leaf(_), TypeKind::Leaf(_)) => TypeBindingComparison::Incompatible, } } } +// TODO[non-leaf-form]: This abstraction isn't quite right. +// We probably need to reference whether the form we're looking at +// is compatible with some other form. +pub(crate) enum TypeBindingComparison { + Equal, + RightIsMoreSpecific, + LeftIsMoreSpecific, + Incomparable, + // Indicates that the types are incompatible. + // Such a comparison shouldn't arise between valid references. + Incompatible, +} + #[derive(PartialEq, Eq, Clone, Copy)] pub(crate) enum ParentTypeKind { Value(AnyValueTypeKind), diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index 7133c999..2a811a06 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -4,7 +4,7 @@ pub(crate) struct MutableReference(pub(super) ReferenceCore); impl MutableReference { pub(crate) fn deactivate(self) -> InactiveMutableReference { - self.0.deactivate_reference(); + self.0.core.data_mut().deactivate_reference(self.0.id); InactiveMutableReference(self.0) } @@ -83,7 +83,10 @@ pub(crate) struct InactiveMutableReference(pub(super) ReferenceCore InactiveMutableReference { pub(crate) fn activate(self) -> FunctionResult> { - self.0.activate_mutable_reference()?; + self.0 + .core + .data_mut() + .activate_mutable_reference(self.0.id)?; Ok(MutableReference(self.0)) } @@ -91,6 +94,7 @@ impl InactiveMutableReference { // As an inactive reference, we are free to map between them // ... we could even enable the other way around, but that'd likely allow breaking // application invariants which we want to respect. + self.0.core.data_mut().make_shared(self.0.id); InactiveSharedReference(self.0) } } diff --git a/src/misc/dynamic_references/reference_core.rs b/src/misc/dynamic_references/reference_core.rs index 0e0e1fcb..b587aceb 100644 --- a/src/misc/dynamic_references/reference_core.rs +++ b/src/misc/dynamic_references/reference_core.rs @@ -11,11 +11,15 @@ impl ReferenceCore { core: Rc>, reference_kind: ReferenceKind, ) -> Self { - let new_id = core.data_mut().new_reference(TrackedReference { - path: ReferencePath::leaf(AnyType::type_kind()), - reference_kind, - creation_span: core.root_span, - }); + let new_id = { + let mut data_mut = core.data_mut(); + let creation_span = data_mut.root_span(); + data_mut.new_reference(TrackedReference { + path: ReferencePath::leaf(AnyType::type_kind()), + reference_kind, + creation_span, + }) + }; Self { pointer: core.root(), id: new_id, @@ -25,18 +29,6 @@ impl ReferenceCore { } impl ReferenceCore { - pub(super) fn activate_mutable_reference(&self) -> FunctionResult<()> { - self.core.data_mut().activate_mutable_reference(self.id) - } - - pub(super) fn activate_shared_reference(&self) -> FunctionResult<()> { - self.core.data_mut().activate_shared_reference(self.id) - } - - pub(super) fn deactivate_reference(&self) { - self.core.data_mut().deactivate_reference(self.id); - } - pub(super) fn into_emplacer<'e>(self) -> EmplacerCore<'e, T> { // NOTE: The clean-up duty is delegated to the emplacer let reference = ManuallyDrop::new(self); diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index d1f7f244..55dec454 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -1,3 +1,5 @@ +use std::process::Child; + use super::*; pub(crate) struct Referenceable { @@ -8,14 +10,14 @@ impl Referenceable { pub(crate) fn new_inactive_shared(&self) -> InactiveSharedReference { InactiveSharedReference(ReferenceCore::new_root( self.core.clone(), - ReferenceKind::Inactive, + ReferenceKind::InactiveShared, )) } pub(crate) fn new_inactive_mutable(&self) -> InactiveMutableReference { InactiveMutableReference(ReferenceCore::new_root( self.core.clone(), - ReferenceKind::Inactive, + ReferenceKind::InactiveMutable, )) } } @@ -23,8 +25,6 @@ impl Referenceable { pub(super) struct ReferenceableCore { // Guaranteed not-null root: UnsafeCell, - root_name: String, - pub(super) root_span: SpanRange, data: RefCell, } @@ -32,9 +32,9 @@ impl ReferenceableCore { pub(super) fn new(root: T, root_name: String, root_span: SpanRange) -> Self { Self { root: UnsafeCell::new(root), - root_name, - root_span, data: RefCell::new(ReferenceableData { + root_name, + root_span, arena: SlotMap::with_key(), }), } @@ -54,39 +54,6 @@ impl ReferenceableCore { pub(super) fn data_mut(&self) -> RefMut<'_, ReferenceableData> { self.data.borrow_mut() } - - pub(super) fn display_path( - &self, - mut f: impl std::fmt::Write, - id: LocalReferenceId, - ) -> std::fmt::Result { - f.write_str(&self.root_name)?; - let data = self.data(); - let data = data.for_reference(id); - for part in data.path.parts.iter() { - match part { - PathPart::Value { bound_as } => { - f.write_str(&format!(" (of type {})", bound_as.source_name()))?; - } - PathPart::ArrayChild(i) => { - f.write_char('[')?; - write!(f, "{}", i)?; - f.write_char(']')?; - } - PathPart::ObjectChild(key) => { - if syn::parse_str::(key).is_ok() { - f.write_char('.')?; - f.write_str(key)?; - } else { - f.write_char('[')?; - write!(f, "{:?}", key)?; - f.write_char(']')?; - } - } - } - } - Ok(()) - } } new_key_type! { @@ -94,10 +61,18 @@ new_key_type! { } pub(super) struct ReferenceableData { + // Could be in Referencable Core, but having it here makes the error message API easier + root_name: String, + // Could be in Referencable Core, but having it here makes the error message API easier + root_span: SpanRange, arena: SlotMap, } impl ReferenceableData { + pub(super) fn root_span(&self) -> SpanRange { + self.root_span + } + pub(super) fn new_reference(&mut self, data: TrackedReference) -> LocalReferenceId { self.arena.insert(data) } @@ -117,7 +92,24 @@ impl ReferenceableData { } pub(super) fn deactivate_reference(&mut self, id: LocalReferenceId) { - self.for_reference_mut(id).reference_kind = ReferenceKind::Inactive; + let data = self.for_reference_mut(id); + data.reference_kind = match data.reference_kind { + ReferenceKind::ActiveMutable => ReferenceKind::InactiveMutable, + ReferenceKind::ActiveShared => ReferenceKind::InactiveShared, + _ => panic!("cannot deactivate an inactive reference"), + } + } + + pub(super) fn make_shared(&mut self, id: LocalReferenceId) { + let data = self.for_reference_mut(id); + data.reference_kind = match data.reference_kind { + ReferenceKind::InactiveMutable | ReferenceKind::InactiveShared => { + ReferenceKind::InactiveShared + } + ReferenceKind::ActiveMutable | ReferenceKind::ActiveShared => { + ReferenceKind::ActiveShared + } + } } pub(super) fn activate_mutable_reference( @@ -128,26 +120,35 @@ impl ReferenceableData { // Perform checks as per the module doc on `dynamic_references` for (other_id, other_data) in self.arena.iter() { if other_id != id { - match other_data.path.partial_cmp(&data.path) { + let error_reason = data + .path + .compare(&other_data.path) + .error_comparing_mutable_with_other(other_data.reference_kind.is_active()); + let error_reason = match error_reason { + Some(reason) => reason, None => continue, - Some(Ordering::Equal | Ordering::Less) => { - if other_data.reference_kind.is_active() { - // TODO[references]: Fix this - todo!("// BETTER ERROR: Safety invariant break") - } - } - Some(Ordering::Greater) => { - // TODO[references]: Fix this - todo!("// BETTER ERROR: Validity invariant break") - } - } + }; + let mut error_message = + "Cannot create mutable reference because it clashes with another reference: " + .to_string(); + error_message.push_str(error_reason); + let _ = write!(error_message, "This reference-: "); + self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveMutable)); + let _ = write!(error_message, "Other reference: "); + self.display_path(&mut error_message, other_id, None); + return data.creation_span.ownership_err(error_message); } } + let data = self.for_reference_mut(id); data.reference_kind = match data.reference_kind { - ReferenceKind::Inactive => ReferenceKind::ActiveMutable, + ReferenceKind::InactiveMutable => ReferenceKind::ActiveMutable, + ReferenceKind::InactiveShared => { + panic!("cannot mut-activate an inactive shared reference") + } _ => panic!("cannot mut-activate an active reference"), }; + Ok(()) } @@ -156,21 +157,30 @@ impl ReferenceableData { // Perform checks as per the module doc on `dynamic_references` for (other_id, other_data) in self.arena.iter() { if other_id != id && other_data.reference_kind == ReferenceKind::ActiveMutable { - match other_data.path.partial_cmp(&data.path) { - Some(Ordering::Equal | Ordering::Greater) => { - // TODO[references] - todo!("// BETTER ERROR: Safety invariant break") - } - Some(Ordering::Less) => { - panic!("Unexpected mutability invariant break: shared-activating a reference with an active mutable parent. This should already be prevented by the mutable reference's activation checks, so this likely indicates a bug in the implementation.") - } - _ => continue, - } + let error_reason = other_data + .path + .compare(&data.path) + .error_comparing_mutable_with_other(true); + let error_reason = match error_reason { + Some(reason) => reason, + None => continue, + }; + let mut error_message = + "Cannot create shared reference because it clashes with a mutable reference: " + .to_string(); + error_message.push_str(error_reason); + let _ = write!(error_message, "This reference-: "); + self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveShared)); + let _ = write!(error_message, "Other reference: "); + self.display_path(&mut error_message, other_id, None); + return data.creation_span.ownership_err(error_message); } } let data = self.for_reference_mut(id); data.reference_kind = match data.reference_kind { - ReferenceKind::Inactive => ReferenceKind::ActiveShared, + ReferenceKind::InactiveShared | ReferenceKind::InactiveMutable => { + ReferenceKind::ActiveShared + } _ => panic!("cannot shared-activate an active reference"), }; Ok(()) @@ -186,6 +196,49 @@ impl ReferenceableData { data.creation_span = new_span; // TODO[references]: Extend the path } + + fn display_path( + &self, + mut f: impl std::fmt::Write, + id: LocalReferenceId, + override_kind: Option, + ) -> std::fmt::Result { + let data = self.for_reference(id); + let kind = override_kind.unwrap_or(data.reference_kind); + match kind { + // These are the same width to allow alignment in the error message + ReferenceKind::InactiveShared => f.write_str("[inactive] &")?, + ReferenceKind::InactiveMutable => f.write_str("[inactive] &mut ")?, + ReferenceKind::ActiveShared => f.write_str("[*active*] &")?, + ReferenceKind::ActiveMutable => f.write_str("[*active*] &mut ")?, + } + f.write_str(&self.root_name)?; + for part in data.path.parts.iter() { + match part { + PathPart::Value { bound_as } => { + f.write_str(&format!(" (of type {})", bound_as.source_name()))?; + } + PathPart::Child(child) => match child { + ChildSpecifier::ArrayChild(i) => { + f.write_char('[')?; + write!(f, "{}", i)?; + f.write_char(']')?; + } + ChildSpecifier::ObjectChild(key) => { + if syn::parse_str::(key).is_ok() { + f.write_char('.')?; + f.write_str(key)?; + } else { + f.write_char('[')?; + write!(f, "{:?}", key)?; + f.write_char(']')?; + } + } + }, + } + } + Ok(()) + } } #[derive(Clone)] @@ -197,7 +250,8 @@ pub(super) struct TrackedReference { #[derive(PartialEq, Eq, Copy, Clone)] pub(super) enum ReferenceKind { - Inactive, + InactiveShared, + InactiveMutable, ActiveShared, ActiveMutable, } @@ -205,7 +259,7 @@ pub(super) enum ReferenceKind { impl ReferenceKind { fn is_active(&self) -> bool { match self { - ReferenceKind::Inactive => false, + ReferenceKind::InactiveShared | ReferenceKind::InactiveMutable => false, ReferenceKind::ActiveShared | ReferenceKind::ActiveMutable => true, } } @@ -228,25 +282,86 @@ pub(super) struct ReferencePath { parts: Vec, } +enum PathComparison { + /// The paths have diverged in a manner which permits mutual mutability + /// e.g. left = x.a and right = x.b + Divergent, + /// The paths parts overlap in a manner which forbids mutual mutability, + /// but they are not identical or in an ancestor/descendant relationship. + /// e.g. left = x[0..10], right = x[5..15] + Overlapping, + /// The right path is a descendent of the left path. + /// e.g. right = left.x or right = left[0]["key"] + RightIsDescendent, + /// The left path is a descendent of the right path. + /// e.g. left = right.x or left = right[0]["key"] + LeftIsDescendent, + /// The left and right path refer to the same leaf value. + /// But they may be in different forms. e.g. left = &any and right = &integer + ReferencesEqual(TypeBindingComparison), + /// Represents an impossible comparison which indicates a broken invariant + /// (e.g. left: &integer and right: &string) + Incompatible, +} + +impl PathComparison { + fn error_comparing_mutable_with_other(self, other_is_active: bool) -> Option<&'static str> { + Some(match self { + PathComparison::Divergent => return None, + PathComparison::Overlapping => { + "they overlap, so mutation may invalidate the other reference" + } + PathComparison::RightIsDescendent => { + "mutation may invalidate the other descendent reference" + } + PathComparison::ReferencesEqual(TypeBindingComparison::RightIsMoreSpecific) => { + "mutation may invalidate the other reference with more specific type" + } + PathComparison::ReferencesEqual(TypeBindingComparison::Incomparable) => { + "mutation may invalidate the other reference with an incompatible type" + } + // Activated reference is descendent of existing reference + PathComparison::ReferencesEqual(TypeBindingComparison::Equal) + | PathComparison::ReferencesEqual(TypeBindingComparison::LeftIsMoreSpecific) + | PathComparison::LeftIsDescendent => { + if other_is_active { + "the mutable reference is observable from the other reference, which breaks aliasing rules" + } else { + return None; + } + } + PathComparison::ReferencesEqual(TypeBindingComparison::Incompatible) => { + panic!("Unexpected incompatible type comparison. This indicates a bug in preinterpret.") + } + PathComparison::Incompatible => { + panic!("Unexpected incompatible reference comparison. This indicates a bug in preinterpret.") + } + }) + } +} + impl ReferencePath { pub(super) fn leaf(bound_as: TypeKind) -> Self { Self { parts: vec![PathPart::Value { bound_as }], } } -} -impl PartialOrd for ReferencePath { - fn partial_cmp(&self, other: &Self) -> Option { + fn compare(&self, other: &Self) -> PathComparison { for (own, other) in self.parts.iter().zip(&other.parts) { - match own.partial_cmp(other) { - // If incomparable, the paths have diverged so are incomparable - None => return None, - Some(Ordering::Equal) => continue, - Some(ordered) => return Some(ordered), - } + return match own.compare(other) { + PathPartComparison::Divergent => PathComparison::Divergent, + PathPartComparison::IdenticalChildReference => continue, + PathPartComparison::OverlappingChildReference => PathComparison::Overlapping, + PathPartComparison::RightIsDescendent => PathComparison::RightIsDescendent, + PathPartComparison::LeftIsDescendent => PathComparison::LeftIsDescendent, + PathPartComparison::ReferencesEqual(inner) => { + PathComparison::ReferencesEqual(inner) + } + PathPartComparison::Incompatible => PathComparison::Incompatible, + }; } - Some(self.parts.len().cmp(&other.parts.len())) + unreachable!("BUG: PathParts should be [Child* Value] and so can't end with a comparison of PathPartComparison::IdenticalDeeperReference") } } @@ -255,36 +370,88 @@ pub(crate) struct ReferencePathExtension; #[derive(PartialEq, Eq, Clone)] enum PathPart { Value { bound_as: TypeKind }, + Child(ChildSpecifier), +} + +#[derive(PartialEq, Eq, Clone)] +enum ChildSpecifier { ArrayChild(usize), ObjectChild(String), } -impl PathPart { +impl ChildSpecifier { fn bound_type_kind(&self) -> TypeKind { match self { - PathPart::Value { bound_as } => *bound_as, - PathPart::ArrayChild(_) => ArrayType::type_kind(), - PathPart::ObjectChild(_) => ObjectType::type_kind(), + ChildSpecifier::ArrayChild(_) => ArrayType::type_kind(), + ChildSpecifier::ObjectChild(_) => ObjectType::type_kind(), } } } -impl PartialOrd for PathPart { - fn partial_cmp(&self, other: &Self) -> Option { +enum PathPartComparison { + /// The paths have diverged in a manner which permits mutual mutability + /// e.g. left = x.a and right = x.b + Divergent, + /// The path parts match. + /// e.g. left = root.x.?? and right = root.x.?? + IdenticalChildReference, + /// The paths parts overlap in a manner which forbids mutual mutability, + /// but they are not identical or in an ancestor/descendant relationship. + /// e.g. left = x[0..10], right = x[5..15] + #[allow(unused)] // Kept for future, and to ensure we have the correct abstraction + OverlappingChildReference, + /// The right path is a descendent of the left path. + /// e.g. right = left.x or right = left[0]["key"] + RightIsDescendent, + /// The left path is a descendent of the right path. + /// e.g. left = right.x or left = right[0]["key"] + LeftIsDescendent, + /// The left and right path refer to the same leaf value. + /// But they may be in different forms. e.g. left = &any and right = &integer + ReferencesEqual(TypeBindingComparison), + /// Represents an impossible comparison which indicates a broken invariant + /// (e.g. left: &integer and right: &string) + Incompatible, +} + +impl PathPart { + fn compare(&self, other: &Self) -> PathPartComparison { match (self, other) { - (PathPart::ArrayChild(i), PathPart::ArrayChild(j)) if i == j => Some(Ordering::Equal), - (PathPart::ArrayChild(_), PathPart::ArrayChild(_)) => None, - (PathPart::ObjectChild(a), PathPart::ObjectChild(b)) if a == b => Some(Ordering::Equal), - (PathPart::ObjectChild(_), PathPart::ObjectChild(_)) => None, - // TODO[references]: I'm not sure this is right - // - A path being incomparable is a divergence - // - A type being incomparable is: - // - A broken invariant if they are incompatible (e.g. String and Int) - // - ?? if it's compatible dyn and a concrete type (e.g. dyn Iterator and Array) - // - So we probably don't want to make PathPart and TypeKind implement PartialOrd, - // we probably want a more senamtically meaningful output enum which we can handle - // correctly in upstream logic - (this, other) => this.bound_type_kind().partial_cmp(&other.bound_type_kind()), + (PathPart::Child(a), PathPart::Child(b)) => match (a, b) { + (ChildSpecifier::ArrayChild(i), ChildSpecifier::ArrayChild(j)) if i == j => { + PathPartComparison::IdenticalChildReference + } + (ChildSpecifier::ArrayChild(_), ChildSpecifier::ArrayChild(_)) => { + PathPartComparison::Divergent + } + (ChildSpecifier::ObjectChild(a), ChildSpecifier::ObjectChild(b)) if a == b => { + PathPartComparison::IdenticalChildReference + } + (ChildSpecifier::ObjectChild(_), ChildSpecifier::ObjectChild(_)) => { + PathPartComparison::Divergent + } + _ => PathPartComparison::Incompatible, + }, + (PathPart::Child(a), PathPart::Value { bound_as }) => { + if a.bound_type_kind() == *bound_as { + PathPartComparison::LeftIsDescendent + } else { + PathPartComparison::Incompatible + } + } + (PathPart::Value { bound_as }, PathPart::Child(b)) => { + if b.bound_type_kind() == *bound_as { + PathPartComparison::RightIsDescendent + } else { + PathPartComparison::Incompatible + } + } + ( + PathPart::Value { bound_as }, + PathPart::Value { + bound_as: other_bound_as, + }, + ) => PathPartComparison::ReferencesEqual(bound_as.compare_bindings(other_bound_as)), } } } diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 7428d5f9..44663b16 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -5,7 +5,7 @@ pub(crate) struct SharedReference(pub(super) ReferenceCore); impl SharedReference { pub(crate) fn deactivate(self) -> InactiveSharedReference { - self.0.deactivate_reference(); + self.0.core.data_mut().deactivate_reference(self.0.id); InactiveSharedReference(self.0) } @@ -73,7 +73,10 @@ pub(crate) struct InactiveSharedReference(pub(super) ReferenceCore impl InactiveSharedReference { pub(crate) fn activate(self) -> FunctionResult> { - self.0.activate_shared_reference()?; + self.0 + .core + .data_mut() + .activate_shared_reference(self.0.id)?; Ok(SharedReference(self.0)) } } From 24c6459b1d7e8a3e9390897e6621fea9153f1721 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 22 Feb 2026 23:10:31 +0000 Subject: [PATCH 05/25] feat: Path extensions work --- plans/TODO.md | 4 +- src/expressions/type_resolution/type_kinds.rs | 35 ++++++++---- .../dynamic_references/mutable_reference.rs | 24 ++++----- src/misc/dynamic_references/reference_core.rs | 4 +- src/misc/dynamic_references/referenceable.rs | 54 ++++++++++++++++--- .../dynamic_references/shared_reference.rs | 24 ++++----- 6 files changed, 99 insertions(+), 46 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index 4d1e071b..832a0f90 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -304,11 +304,9 @@ The issue is that we get a variable stored as `arr := DisabledMutable` and then - [ ] Add the following to `ReferenceCore` and maybe others: - [x] Emplacing - [x] Map, Try map - - [ ] Ability to map deeper. Should take a `PathExtension` and a new span. - See rule (c) from `dynamic_references/mod.rs` + - [x] Ability to map deeper. Should take a `PathExtension` and a new span. - [ ] Try replacing `Shared` / `Mutable` - ### Other ideas We could imagine a world where we are more clever over our mutation: diff --git a/src/expressions/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index fe9f5de9..c089e77e 100644 --- a/src/expressions/type_resolution/type_kinds.rs +++ b/src/expressions/type_resolution/type_kinds.rs @@ -91,6 +91,16 @@ impl TypeKind { } } + pub(crate) fn is_tightening_to(&self, other: &Self) -> bool { + match self.compare_bindings(other) { + TypeBindingComparison::Equal => true, + TypeBindingComparison::RightDerivesFromLeft => true, + TypeBindingComparison::LeftDerivesFromRight => false, + TypeBindingComparison::Incomparable => false, + TypeBindingComparison::Incompatible => false, + } + } + /// This is the subtyping ordering. /// - I define that X <= Y if Y is a subtype of X. /// - Think "X is shallower than Y in the enum representation" @@ -103,25 +113,28 @@ impl TypeKind { return TypeBindingComparison::Equal; } match (self, other) { - // Dyns are not binding-comparable to other bindings/dyns except themself - (TypeKind::Dyn(_), _) => TypeBindingComparison::Incomparable, - (_, TypeKind::Dyn(_)) => TypeBindingComparison::Incomparable, + // Dyns are not binding-comparable to other dyns + (TypeKind::Dyn(_), TypeKind::Dyn(_)) => TypeBindingComparison::Incomparable, + // Assuming the values are compatible, a dyn can be derived from any leaf/parent, + // but not the other way around (at present at least) + (TypeKind::Dyn(_), _) => TypeBindingComparison::LeftDerivesFromRight, + (_, TypeKind::Dyn(_)) => TypeBindingComparison::RightDerivesFromLeft, // All non-any-values are strict subtypes of AnyValue (TypeKind::Parent(ParentTypeKind::Value(_)), _) => { - TypeBindingComparison::RightIsMoreSpecific + TypeBindingComparison::RightDerivesFromLeft } (_, TypeKind::Parent(ParentTypeKind::Value(_))) => { - TypeBindingComparison::LeftIsMoreSpecific + TypeBindingComparison::LeftDerivesFromRight } // Integer types ( TypeKind::Parent(ParentTypeKind::Integer(_)), TypeKind::Leaf(AnyValueLeafKind::Integer(_)), - ) => TypeBindingComparison::RightIsMoreSpecific, + ) => TypeBindingComparison::RightDerivesFromLeft, ( TypeKind::Leaf(AnyValueLeafKind::Integer(_)), TypeKind::Parent(ParentTypeKind::Integer(_)), - ) => TypeBindingComparison::LeftIsMoreSpecific, + ) => TypeBindingComparison::LeftDerivesFromRight, (TypeKind::Parent(ParentTypeKind::Integer(_)), _) => { TypeBindingComparison::Incompatible } @@ -132,11 +145,11 @@ impl TypeKind { ( TypeKind::Parent(ParentTypeKind::Float(_)), TypeKind::Leaf(AnyValueLeafKind::Float(_)), - ) => TypeBindingComparison::RightIsMoreSpecific, + ) => TypeBindingComparison::RightDerivesFromLeft, ( TypeKind::Leaf(AnyValueLeafKind::Float(_)), TypeKind::Parent(ParentTypeKind::Float(_)), - ) => TypeBindingComparison::LeftIsMoreSpecific, + ) => TypeBindingComparison::LeftDerivesFromRight, (TypeKind::Parent(ParentTypeKind::Float(_)), _) => TypeBindingComparison::Incompatible, (_, TypeKind::Parent(ParentTypeKind::Float(_))) => TypeBindingComparison::Incompatible, // Non-equal leaf types are incomparable @@ -150,8 +163,8 @@ impl TypeKind { // is compatible with some other form. pub(crate) enum TypeBindingComparison { Equal, - RightIsMoreSpecific, - LeftIsMoreSpecific, + RightDerivesFromLeft, + LeftDerivesFromRight, Incomparable, // Indicates that the types are incompatible. // Such a comparison shouldn't arise between valid references. diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index 2a811a06..329deb1d 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -25,12 +25,12 @@ impl MutableReference { } /// SAFETY: - /// - The caller must ensure that the ReferencePathExtension is correct - /// (an overly-specific ReferencePathExtension may cause safety issues) + /// - The caller must ensure that the PathExtension is correct + /// (an overly-specific PathExtension may cause safety issues) pub(crate) unsafe fn map( self, f: impl FnOnce(&mut T) -> &mut V, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> MutableReference { self.emplace_map(move |input, emplacer| { @@ -39,12 +39,12 @@ impl MutableReference { } /// SAFETY: - /// - The caller must ensure that the ReferencePathExtension is correct - /// (an overly-specific ReferencePathExtension may cause safety issues) + /// - The caller must ensure that the PathExtension is correct + /// (an overly-specific PathExtension may cause safety issues) pub(crate) unsafe fn try_map( self, f: impl FnOnce(&mut T) -> Result<&mut V, E>, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> Result, (E, MutableReference)> { self.emplace_map(|input, emplacer| match f(input) { @@ -107,12 +107,12 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { } /// SAFETY: - /// - The caller must ensure that the ReferencePathExtension is correct - /// (an overly-specific ReferencePathExtension may cause safety issues) + /// - The caller must ensure that the PathExtension is correct + /// (an overly-specific PathExtension may cause safety issues) pub(crate) unsafe fn emplace( &mut self, value: &'e mut V, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> MutableReference { unsafe { @@ -124,18 +124,18 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { /// SAFETY: /// - The caller must ensure that the value's lifetime is derived from the original content - /// - The caller must ensure that the ReferencePathExtension is correct + /// - The caller must ensure that the PathExtension is correct pub(crate) unsafe fn emplace_unchecked( &mut self, value: &mut V, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> MutableReference { // SAFETY: The pointer is from a reference so non-null let pointer = unsafe { NonNull::new_unchecked(value as *mut V) }; // SAFETY: // - The caller ensures that the reference is derived from the original content - // - The caller ensures that the ReferencePathExtension is correct + // - The caller ensures that the PathExtension is correct unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } } diff --git a/src/misc/dynamic_references/reference_core.rs b/src/misc/dynamic_references/reference_core.rs index b587aceb..52eed74e 100644 --- a/src/misc/dynamic_references/reference_core.rs +++ b/src/misc/dynamic_references/reference_core.rs @@ -92,11 +92,11 @@ impl<'e, T: ?Sized> EmplacerCore<'e, T> { /// SAFETY: /// - The caller must ensure that the pointer is derived from the original content - /// - The caller must ensure that the ReferencePathExtension is correct + /// - The caller must ensure that the PathExtension is correct pub(crate) unsafe fn emplace_unchecked( &mut self, pointer: NonNull, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> ReferenceCore { let emplacer_core = self.take(); diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 55dec454..cf45a735 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -189,12 +189,49 @@ impl ReferenceableData { pub(super) fn derive_reference( &mut self, id: LocalReferenceId, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) { let data = self.for_reference_mut(id); data.creation_span = new_span; - // TODO[references]: Extend the path + let mut last_path_part = data.path.parts.last_mut().expect("path is non-empty"); + match (last_path_part, path_extension) { + (last_path_part, PathExtension::Child(specifier, child_bound_as)) => { + let parent_type = specifier.bound_type_kind(); + match last_path_part { + PathPart::Value { bound_as } => { + if !bound_as.is_tightening_to(&parent_type) { + panic!( + "Invalid path extension: cannot derive {} from {}", + parent_type.source_name(), + bound_as.source_name() + ); + } + } + PathPart::Child(_) => { + panic!("Invalid path extension: paths are expected to end in a value") + } + } + *last_path_part = PathPart::Child(specifier); + data.path.parts.push(PathPart::Value { + bound_as: child_bound_as, + }); + } + (PathPart::Value { bound_as }, PathExtension::Tightened(new_bound_as)) => { + if bound_as.is_tightening_to(&new_bound_as) { + *bound_as = new_bound_as; + } else { + panic!( + "Invalid path extension: cannot derive {} from {}", + new_bound_as.source_name(), + bound_as.source_name() + ); + } + } + (PathPart::Child(_), _) => { + panic!("Invalid path extension: paths are expected to end in a value"); + } + } } fn display_path( @@ -314,7 +351,7 @@ impl PathComparison { PathComparison::RightIsDescendent => { "mutation may invalidate the other descendent reference" } - PathComparison::ReferencesEqual(TypeBindingComparison::RightIsMoreSpecific) => { + PathComparison::ReferencesEqual(TypeBindingComparison::RightDerivesFromLeft) => { "mutation may invalidate the other reference with more specific type" } PathComparison::ReferencesEqual(TypeBindingComparison::Incomparable) => { @@ -322,7 +359,7 @@ impl PathComparison { } // Activated reference is descendent of existing reference PathComparison::ReferencesEqual(TypeBindingComparison::Equal) - | PathComparison::ReferencesEqual(TypeBindingComparison::LeftIsMoreSpecific) + | PathComparison::ReferencesEqual(TypeBindingComparison::LeftDerivesFromRight) | PathComparison::LeftIsDescendent => { if other_is_active { "the mutable reference is observable from the other reference, which breaks aliasing rules" @@ -365,7 +402,12 @@ impl ReferencePath { } } -pub(crate) struct ReferencePathExtension; +pub(crate) enum PathExtension { + /// Extends the path with a child reference (e.g. .x or [0]) + Child(ChildSpecifier, TypeKind), + /// Extends the path with a value of a certain type (e.g. dereferencing a pointer) + Tightened(TypeKind), +} #[derive(PartialEq, Eq, Clone)] enum PathPart { @@ -374,7 +416,7 @@ enum PathPart { } #[derive(PartialEq, Eq, Clone)] -enum ChildSpecifier { +pub(crate) enum ChildSpecifier { ArrayChild(usize), ObjectChild(String), } diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 44663b16..5a783fdf 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -26,12 +26,12 @@ impl SharedReference { } /// SAFETY: - /// - The caller must ensure that the ReferencePathExtension is correct - /// (an overly-specific ReferencePathExtension may cause safety issues) + /// - The caller must ensure that the PathExtension is correct + /// (an overly-specific PathExtension may cause safety issues) pub(crate) unsafe fn map( self, f: impl FnOnce(&T) -> &V, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> SharedReference { self.emplace_map(move |input, emplacer| { @@ -40,12 +40,12 @@ impl SharedReference { } /// SAFETY: - /// - The caller must ensure that the ReferencePathExtension is correct - /// (an overly-specific ReferencePathExtension may cause safety issues) + /// - The caller must ensure that the PathExtension is correct + /// (an overly-specific PathExtension may cause safety issues) pub(crate) unsafe fn try_map( self, f: impl FnOnce(&T) -> Result<&V, E>, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> Result, (E, SharedReference)> { self.emplace_map(|input, emplacer| match f(input) { @@ -89,12 +89,12 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { } /// SAFETY: - /// - The caller must ensure that the ReferencePathExtension is correct - /// (an overly-specific ReferencePathExtension may cause safety issues) + /// - The caller must ensure that the PathExtension is correct + /// (an overly-specific PathExtension may cause safety issues) pub(crate) unsafe fn emplace( &mut self, value: &'e V, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> SharedReference { unsafe { @@ -106,18 +106,18 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { /// SAFETY: /// - The caller must ensure that the value's lifetime is derived from the original content - /// - The caller must ensure that the ReferencePathExtension is correct + /// - The caller must ensure that the PathExtension is correct pub(crate) unsafe fn emplace_unchecked( &mut self, value: &V, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> SharedReference { // SAFETY: The pointer is from a reference so non-null let pointer = unsafe { NonNull::new_unchecked(value as *const V as *mut V) }; // SAFETY: // - The caller ensures that the reference is derived from the original content - // - The caller ensures that the ReferencePathExtension is correct + // - The caller ensures that the PathExtension is correct unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } } From b583151c0c614f04646299e9ea0e0a757e233107 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 22 Feb 2026 23:16:45 +0000 Subject: [PATCH 06/25] docs: Update docs --- plans/TODO.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plans/TODO.md b/plans/TODO.md index 832a0f90..7776dff5 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -305,7 +305,13 @@ The issue is that we get a variable stored as `arr := DisabledMutable` and then - [x] Emplacing - [x] Map, Try map - [x] Ability to map deeper. Should take a `PathExtension` and a new span. -- [ ] Try replacing `Shared` / `Mutable` +- [ ] Try replacing existing RefCell based abstractions with new custom dynamic references, + and report on what breaks: + - `pub(crate) type Referenceable = Rc>;` becomes `pub(crate) type Referenceable = dynamic_references::Referenceable` + - `Shared` / `SharedSubRcRefCell` - both become type aliases for `SharedReference` + - `DisabledShared` becomes `InactiveSharedReference` + - `Mutable` / `MutableSubRcRefCell` - becomes type alias for `MutableReference` + - `DisabledMutable` becomes `InactiveMutableReference` ### Other ideas From eb8fb817973a7f23585ef81852d00918cf73038d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Feb 2026 23:56:00 +0000 Subject: [PATCH 07/25] refactor: Replace RefCell-based abstractions with new dynamic references Replace the old Rc>-based reference system with the new custom dynamic reference types: - Shared becomes a type alias for SharedReference - Mutable becomes a type alias for MutableReference - DisabledShared becomes InactiveSharedReference - DisabledMutable becomes InactiveMutableReference - VariableContent::Referenceable uses new Referenceable struct Key changes: - Added bridge methods (_legacy suffix) to SharedReference and MutableReference for backward compatibility with old map/try_map patterns that didn't require PathExtension - QqqShared and QqqMutable now alias directly to the reference types, collapsing the old form/wrapper type distinction - Added explicit IsArgument impls for SharedReference and MutableReference since the blanket impl only covers leaf types - Manual Clone impls for InactiveSharedReference/InactiveMutableReference to avoid unnecessary T: Clone bound from derive - CopyOnWrite conversions use replace_legacy pattern instead of into_content() for non-leaf types - Updated trybuild test for improved aliasing error message from new reference tracking system https://claude.ai/code/session_01XaCsWwXFYntkKqZNf8kizU --- src/expressions/concepts/forms/any_mut.rs | 3 +- src/expressions/concepts/forms/any_ref.rs | 3 +- src/expressions/concepts/forms/assignee.rs | 12 +- .../concepts/forms/copy_on_write.rs | 8 +- src/expressions/concepts/forms/mutable.rs | 12 +- .../concepts/forms/referenceable.rs | 12 +- src/expressions/concepts/forms/shared.rs | 12 +- src/expressions/concepts/forms/simple_mut.rs | 8 +- src/expressions/concepts/forms/simple_ref.rs | 6 +- src/expressions/evaluation/value_frames.rs | 10 +- src/expressions/type_resolution/arguments.rs | 42 ++- src/interpretation/bindings.rs | 305 ++++-------------- src/interpretation/refs.rs | 30 +- src/interpretation/variable.rs | 6 +- src/misc/dynamic_references/mod.rs | 3 - .../dynamic_references/mutable_reference.rs | 149 ++++++++- src/misc/dynamic_references/referenceable.rs | 23 +- .../dynamic_references/shared_reference.rs | 158 ++++++++- src/misc/mod.rs | 4 +- src/misc/mut_rc_ref_cell.rs | 8 + .../expressions/swap_itself.stderr | 15 +- 21 files changed, 508 insertions(+), 321 deletions(-) diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index 43f623e9..7bd47f25 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -60,7 +60,6 @@ impl MapFromArgument for BeAnyMut { ) -> FunctionResult> { Ok(value .expect_mutable() - .0 - .replace(|inner, emplacer| inner.as_mut_value().into_mutable_any_mut(emplacer))) + .replace_legacy(|inner, emplacer| inner.as_mut_value().into_mutable_any_mut(emplacer))) } } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index b813264b..c316fe8d 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -55,7 +55,6 @@ impl MapFromArgument for BeAnyRef { ) -> FunctionResult> { Ok(value .expect_shared() - .0 - .replace(|inner, emplacer| inner.as_ref_value().into_shared_any_ref(emplacer))) + .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared_any_ref(emplacer))) } } diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index 489bb712..e673d517 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -1,6 +1,6 @@ use super::*; -pub(crate) struct QqqAssignee(pub(crate) MutableSubRcRefCell); +pub(crate) struct QqqAssignee(pub(crate) MutableReference); impl IsValueContent for QqqAssignee { type Type = L::Type; @@ -37,9 +37,13 @@ impl IsDynCompatibleForm for BeAssignee { T::Leaf: CastDyn, { leaf.0 - .replace(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(QqqAssignee(emplacer.emplace(mapped))), - Err(this) => Err(QqqAssignee(emplacer.emplace(this))), + .replace_legacy(|content, emplacer| match ::map_mut(content) { + Ok(mapped) => Ok(QqqAssignee(unsafe { + emplacer.emplace_unchecked_legacy(mapped) + })), + Err(this) => Err(QqqAssignee(unsafe { + emplacer.emplace_unchecked_legacy(this) + })), }) } } diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index 8a644ddb..e76deb5e 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -80,10 +80,14 @@ impl MapFromArgument for BeCopyOnWrite { match value.expect_copy_on_write().inner { CopyOnWriteInner::Owned(owned) => Ok(BeCopyOnWrite::new_owned(owned)), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - Ok(BeCopyOnWrite::new_shared_in_place_of_owned(shared)) + let content = shared + .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + Ok(BeCopyOnWrite::new_shared_in_place_of_owned(content)) } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - Ok(BeCopyOnWrite::new_shared_in_place_of_shared(shared)) + let content = shared + .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + Ok(BeCopyOnWrite::new_shared_in_place_of_shared(content)) } } } diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index d8c79110..944354d9 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -1,6 +1,6 @@ use super::*; -pub(crate) type QqqMutable = MutableSubRcRefCell; +pub(crate) type QqqMutable = MutableReference; impl IsValueContent for QqqMutable { type Type = L::Type; @@ -36,9 +36,9 @@ impl IsDynCompatibleForm for BeMutable { where T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.replace_legacy(|content, emplacer| match ::map_mut(content) { + Ok(mapped) => Ok(unsafe { emplacer.emplace_unchecked_legacy(mapped) }), + Err(this) => Err(unsafe { emplacer.emplace_unchecked_legacy(this) }), }) } } @@ -61,7 +61,9 @@ impl MapFromArgument for BeMutable { fn from_argument_value( value: ArgumentValue, ) -> FunctionResult> { - Ok(value.expect_mutable().into_content()) + Ok(value + .expect_mutable() + .replace_legacy(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer))) } } diff --git a/src/expressions/concepts/forms/referenceable.rs b/src/expressions/concepts/forms/referenceable.rs index 47b7b622..470eb455 100644 --- a/src/expressions/concepts/forms/referenceable.rs +++ b/src/expressions/concepts/forms/referenceable.rs @@ -1,19 +1,21 @@ use super::*; -pub(crate) type Referenceable = Rc>; +// The old `pub(crate) type Referenceable = Rc>;` has been replaced by +// `dynamic_references::Referenceable` which is NOT generic (always wraps AnyValue). +// For the form system, we use Rc> directly as the leaf type. -impl IsValueContent for Referenceable { +impl IsValueContent for Rc> { type Type = L::Type; type Form = BeReferenceable; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for Referenceable { +impl<'a, L: IsValueLeaf> IntoValueContent<'a> for Rc> { fn into_content(self) -> Content<'a, Self::Type, Self::Form> { self } } -impl<'a, L: IsValueLeaf> FromValueContent<'a> for Referenceable { +impl<'a, L: IsValueLeaf> FromValueContent<'a> for Rc> { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } @@ -29,5 +31,5 @@ pub(crate) struct BeReferenceable; impl IsForm for BeReferenceable {} impl IsHierarchicalForm for BeReferenceable { - type Leaf<'a, T: IsLeafType> = Referenceable; + type Leaf<'a, T: IsLeafType> = Rc>; } diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index e9a21302..538ec497 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -1,6 +1,6 @@ use super::*; -pub(crate) type QqqShared = SharedSubRcRefCell; +pub(crate) type QqqShared = SharedReference; impl IsValueContent for QqqShared { type Type = L::Type; @@ -36,9 +36,9 @@ impl IsDynCompatibleForm for BeShared { where T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_ref(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.replace_legacy(|content, emplacer| match ::map_ref(content) { + Ok(mapped) => Ok(unsafe { emplacer.emplace_unchecked_legacy(mapped) }), + Err(this) => Err(unsafe { emplacer.emplace_unchecked_legacy(this) }), }) } } @@ -55,7 +55,9 @@ impl MapFromArgument for BeShared { fn from_argument_value( value: ArgumentValue, ) -> FunctionResult> { - Ok(value.expect_shared().into_content()) + Ok(value + .expect_shared() + .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer))) } } diff --git a/src/expressions/concepts/forms/simple_mut.rs b/src/expressions/concepts/forms/simple_mut.rs index fcd4bbe4..2cced381 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -76,7 +76,7 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { self.emplacer.emplace_unchecked(leaf) } + unsafe { self.emplacer.emplace_unchecked_legacy(leaf) } } }; let __mapper = __InlineMapper { emplacer }; @@ -107,7 +107,7 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { QqqAssignee(self.emplacer.emplace_unchecked(leaf)) } + unsafe { QqqAssignee(self.emplacer.emplace_unchecked_legacy(leaf)) } } }; let __mapper = __InlineMapper { emplacer }; @@ -138,7 +138,9 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { Mutable(self.emplacer.emplace_unchecked(leaf)).into() } + let mutable_ref: MutableReference<_> = + unsafe { self.emplacer.emplace_unchecked_legacy(leaf) }; + mutable_ref.into() } }; let __mapper = __InlineMapper { emplacer }; diff --git a/src/expressions/concepts/forms/simple_ref.rs b/src/expressions/concepts/forms/simple_ref.rs index d466b492..82b99eb5 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -76,7 +76,7 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { self.emplacer.emplace_unchecked(leaf) } + unsafe { self.emplacer.emplace_unchecked_legacy(leaf) } } }; let __mapper = __InlineMapper { emplacer }; @@ -107,7 +107,9 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { Shared(self.emplacer.emplace_unchecked(leaf)).into() } + let shared_ref: SharedReference<_> = + unsafe { self.emplacer.emplace_unchecked_legacy(leaf) }; + shared_ref.into() } }; let __mapper = __InlineMapper { emplacer }; diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index f32972a8..1f1a76a6 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -1056,10 +1056,12 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { let mapped = value.expect_any_value_and_map( |shared| { shared - .try_map(|value| (interface.shared_access)(ctx, value)) + .try_map_legacy(|value| (interface.shared_access)(ctx, value)) .map_err(|(e, _)| e) }, - |mutable| mutable.try_map(|value| (interface.mutable_access)(ctx, value, auto_create)), + |mutable| { + mutable.try_map_legacy(|value| (interface.mutable_access)(ctx, value, auto_create)) + }, |owned| (interface.owned_access)(ctx, owned), )?; @@ -1153,11 +1155,11 @@ impl EvaluationFrame for ValueIndexAccessBuilder { let result = source.expect_any_value_and_map( |shared| { shared - .try_map(|value| (interface.shared_access)(ctx, value, index)) + .try_map_legacy(|value| (interface.shared_access)(ctx, value, index)) .map_err(|(e, _)| e) }, |mutable| { - mutable.try_map(|value| { + mutable.try_map_legacy(|value| { (interface.mutable_access)(ctx, value, index, auto_create) }) }, diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index 57f97a28..f3b61b1e 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -58,12 +58,16 @@ impl IsArgument for ArgumentValue { } } -impl + ResolvableArgumentTarget + ?Sized> IsArgument for Shared { - type ValueType = T::ValueType; +// NOTE: IsArgument for SharedReference with leaf T is now provided by the blanket impl +// in content.rs via FromValueContent + MapFromArgument (BeShared). +// For the parent type AnyValue (which is not IsValueLeaf), we provide an explicit impl: +impl IsArgument for SharedReference { + type ValueType = AnyType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; fn from_argument(argument: Spanned) -> FunctionResult { - T::resolve_shared(argument.expect_shared(), "This argument") + let Spanned(shared, _span) = argument.expect_shared(); + Ok(shared) } } @@ -90,12 +94,16 @@ impl + ResolvableArgumentTarget + ?Sized> IsArgum } } -impl + ResolvableArgumentTarget + ?Sized> IsArgument for Mutable { - type ValueType = T::ValueType; +// NOTE: IsArgument for MutableReference with leaf T is now provided by the blanket impl +// in content.rs via FromValueContent + MapFromArgument (BeMutable). +// For the parent type AnyValue (which is not IsValueLeaf), we provide an explicit impl: +impl IsArgument for MutableReference { + type ValueType = AnyType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; fn from_argument(argument: Spanned) -> FunctionResult { - T::resolve_mutable(argument.expect_mutable(), "This argument") + let Spanned(mutable, _span) = argument.expect_mutable(); + Ok(mutable) } } @@ -165,7 +173,9 @@ impl> ResolveAs for Spanned { } } -impl + ?Sized> ResolveAs> for Spanned { +impl + ?Sized + 'static> ResolveAs> + for Spanned +{ fn resolve_as(self, resolution_target: &str) -> FunctionResult> { T::resolve_shared(self, resolution_target) } @@ -185,7 +195,9 @@ impl<'a, T: ResolvableShared + ?Sized> ResolveAs> } } -impl + ?Sized> ResolveAs> for Spanned { +impl + ?Sized + 'static> ResolveAs> + for Spanned +{ fn resolve_as(self, resolution_target: &str) -> FunctionResult> { T::resolve_mutable(self, resolution_target) } @@ -242,9 +254,12 @@ pub(crate) trait ResolvableShared { fn resolve_shared( Spanned(value, span): Spanned>, resolution_target: &str, - ) -> FunctionResult> { + ) -> FunctionResult> + where + Self: 'static, + { value - .try_map(|v| { + .try_map_legacy(|v| { Self::resolve_from_ref( v, ResolutionContext { @@ -304,9 +319,12 @@ pub(crate) trait ResolvableMutable { fn resolve_mutable( Spanned(value, span): Spanned>, resolution_target: &str, - ) -> FunctionResult> { + ) -> FunctionResult> + where + Self: 'static, + { value - .try_map(|v| { + .try_map_legacy(|v| { Self::resolve_from_mut( v, ResolutionContext { diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index c83c0355..aa1fa17f 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -1,7 +1,6 @@ use super::*; use std::borrow::{Borrow, ToOwned}; -use std::rc::Rc; pub(super) enum VariableState { Uninitialized, @@ -16,7 +15,7 @@ pub(super) enum VariableState { #[derive(Clone)] pub(crate) enum VariableContent { // Owned, but possibly with pre-existing references - Referenceable(Referenceable), + Referenceable(Referenceable), Shared(DisabledShared), Mutable(DisabledMutable), } @@ -24,8 +23,8 @@ pub(crate) enum VariableContent { impl VariableContent { fn into_owned_as_only_owner(self) -> Result, VariableContent> { match self { - VariableContent::Referenceable(data) => match Rc::try_unwrap(data) { - Ok(unique) => Ok(unique.into_inner()), + VariableContent::Referenceable(data) => match data.try_into_inner() { + Ok(owned) => Ok(owned), Err(original) => Err(VariableContent::Referenceable(original)), }, other => Err(other), @@ -182,13 +181,13 @@ impl VariableBinding { fn into_mut(self) -> FunctionResult { match self.content { - VariableContent::Referenceable(referenceable) => { - let inner = MutableSubRcRefCell::new(referenceable).map_err(|_| { + VariableContent::Referenceable(referenceable) => referenceable + .new_inactive_mutable() + .activate() + .map_err(|_| { self.variable_span .ownership_error::(MUTABLE_ERROR_MESSAGE) - })?; - Ok(Mutable(inner)) - } + }), VariableContent::Mutable(disabled) => disabled.enable(self.variable_span.span_range()), VariableContent::Shared(shared) => self .variable_span @@ -199,11 +198,10 @@ impl VariableBinding { fn into_shared(self) -> FunctionResult { match self.content { VariableContent::Referenceable(referenceable) => { - let inner = SharedSubRcRefCell::new(referenceable).map_err(|_| { + referenceable.new_inactive_shared().activate().map_err(|_| { self.variable_span .ownership_error::(SHARED_ERROR_MESSAGE) - })?; - Ok(Shared(inner)) + }) } VariableContent::Mutable(mutable) => mutable .into_shared() @@ -215,15 +213,18 @@ impl VariableBinding { fn into_late_bound(self) -> FunctionResult { match self.content { VariableContent::Referenceable(referenceable) => { - match MutableSubRcRefCell::new(referenceable) { - Ok(mutable) => Ok(LateBoundValue::Mutable(Mutable(mutable))), - Err(referenceable) => { - let shared = SharedSubRcRefCell::new(referenceable).map_err(|_| { + let inactive_mut = referenceable.new_inactive_mutable(); + match inactive_mut.activate() { + Ok(mutable) => Ok(LateBoundValue::Mutable(mutable)), + Err(_) => { + // Mutable failed, try shared + let inactive_shared = referenceable.new_inactive_shared(); + let shared = inactive_shared.activate().map_err(|_| { self.variable_span .ownership_error::(SHARED_ERROR_MESSAGE) })?; Ok(LateBoundValue::Shared(LateBoundSharedValue::new( - Shared(shared), + shared, self.variable_span.syn_error(SHARED_ERROR_MESSAGE), ))) } @@ -353,17 +354,34 @@ impl Deref for LateBoundValue { } } +// ============================================================================ +// Type aliases: Old names → New dynamic reference types +// ============================================================================ + +/// Type alias: `Shared` is now `SharedReference`. +pub(crate) type Shared = SharedReference; + +/// Type alias: `Mutable` is now `MutableReference`. +pub(crate) type Mutable = MutableReference; + +/// Type alias: `DisabledShared` is now `InactiveSharedReference`. +pub(crate) type DisabledShared = InactiveSharedReference; + +/// Type alias: `DisabledMutable` is now `InactiveMutableReference`. +pub(crate) type DisabledMutable = InactiveMutableReference; + pub(crate) type AssigneeValue = AnyValueAssignee; +pub(crate) type SharedValue = AnyValueShared; /// A binding of a unique (mutable) reference to a value. /// See [`ArgumentOwnership::Assignee`] for more details. /// /// If you need span information, wrap with `Spanned>`. -pub(crate) struct Assignee(pub Mutable); +pub(crate) struct Assignee(pub MutableReference); impl AssigneeValue { pub(crate) fn set(&mut self, content: impl IntoAnyValue) { - *self.0 .0 = content.into_any_value(); + *self.0 = content.into_any_value(); } } @@ -380,8 +398,7 @@ where { fn into_content(self) -> Content<'static, Self::Type, Self::Form> { self.0 - .0 - .replace(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer)) + .replace_legacy(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer)) } } @@ -399,69 +416,6 @@ impl DerefMut for Assignee { } } -/// A simple wrapper for a mutable reference to a value. -/// -/// Can be destructured as: `Mutable(cell): Mutable` -/// -/// If you need span information, wrap with `Spanned>`. -pub(crate) struct Mutable(pub(crate) MutableSubRcRefCell); - -impl Mutable { - pub(crate) fn into_shared(self) -> Shared { - Shared(self.0.into_shared()) - } - - #[allow(unused)] - pub(crate) fn map( - self, - value_map: impl for<'a> FnOnce(&'a mut T) -> &'a mut V, - ) -> Mutable { - Mutable(self.0.map(value_map)) - } - - /// Maps the mutable reference, returning the error and original reference on failure. - pub(crate) fn try_map( - self, - value_map: impl for<'a> FnOnce(&'a mut T) -> Result<&'a mut V, E>, - ) -> Result, (E, Mutable)> { - match self.0.try_map(value_map) { - Ok(mapped) => Ok(Mutable(mapped)), - Err((e, original)) => Err((e, Mutable(original))), - } - } - - /// Disables this mutable reference, releasing the borrow on the RefCell. - /// Returns a `DisabledMutable` which can be cloned and later re-enabled. - pub(crate) fn disable(self) -> DisabledMutable { - DisabledMutable(self.0.disable()) - } -} - -/// A disabled mutable reference that can be safely cloned and dropped. -pub(crate) struct DisabledMutable( - pub(crate) DisabledMutableSubRcRefCell, -); - -impl Clone for DisabledMutable { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl DisabledMutable { - /// Re-enables this disabled mutable reference by re-acquiring the borrow. - pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { - self.0 - .enable() - .map(Mutable) - .map_err(|_| span.ownership_error(MUTABLE_ERROR_MESSAGE)) - } - - pub(crate) fn into_shared(self) -> DisabledShared { - DisabledShared(self.0.into_shared()) - } -} - pub(crate) static MUTABLE_ERROR_MESSAGE: &str = "The variable cannot be modified as it is already being modified"; pub(crate) static SHARED_TO_MUTABLE_ERROR_MESSAGE: &str = @@ -474,116 +428,6 @@ impl Spanned { } } -impl AnyValueMutable { - pub(crate) fn new_from_owned(value: AnyValue) -> Self { - Mutable(MutableSubRcRefCell::new_from_owned(value)) - } -} - -impl IsValueContent for Mutable { - type Type = X::Type; - type Form = BeMutable; -} - -impl> IntoValueContent<'static> for Mutable -where - X::Type: IsHierarchicalType = X>, - X::Form: IsHierarchicalForm, - X::Form: LeafAsMutForm, -{ - fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - self.0 - .replace(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer)) - } -} - -impl AsMut for Mutable { - fn as_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl AsRef for Mutable { - fn as_ref(&self) -> &T { - &self.0 - } -} - -impl Deref for Mutable { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Mutable { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -pub(crate) type SharedValue = AnyValueShared; - -/// A simple wrapper for a shared (immutable) reference to a value. -/// -/// Can be destructured as: `Shared(cell): Shared` -/// -/// If you need span information, wrap with `Spanned>`. -pub(crate) struct Shared(pub(crate) SharedSubRcRefCell); - -#[allow(unused)] -impl Shared { - pub(crate) fn clone(this: &Shared) -> Self { - Shared(SharedSubRcRefCell::clone(&this.0)) - } - - /// Maps the shared reference, returning the error and original reference on failure. - pub(crate) fn try_map( - self, - value_map: impl for<'a> FnOnce(&'a T) -> Result<&'a V, E>, - ) -> Result, (E, Shared)> { - match self.0.try_map(value_map) { - Ok(mapped) => Ok(Shared(mapped)), - Err((e, original)) => Err((e, Shared(original))), - } - } - - pub(crate) fn map(self, value_map: impl FnOnce(&T) -> &V) -> Shared { - Shared(self.0.map(value_map)) - } - - /// Disables this shared reference, releasing the borrow on the RefCell. - /// Returns a `DisabledShared` which can be cloned and later re-enabled. - pub(crate) fn disable(self) -> DisabledShared { - DisabledShared(self.0.disable()) - } -} - -/// A disabled shared reference that can be safely cloned and dropped. -pub(crate) struct DisabledShared( - pub(crate) DisabledSharedSubRcRefCell, -); - -impl Clone for DisabledShared { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl DisabledShared { - /// Re-enables this disabled shared reference by re-acquiring the borrow. - pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { - self.0 - .enable() - .map(Shared) - .map_err(|_| span.ownership_error(SHARED_ERROR_MESSAGE)) - } -} - -pub(crate) static SHARED_ERROR_MESSAGE: &str = - "The variable cannot be read as it is already being modified"; - impl Spanned { pub(crate) fn transparent_clone(&self) -> FunctionResult { let value = self.0.as_ref().try_transparent_clone(self.1)?; @@ -591,46 +435,19 @@ impl Spanned { } } -impl AnyValueShared { - pub(crate) fn new_from_owned(value: AnyValue) -> Self { - Shared(SharedSubRcRefCell::new_from_owned(value)) - } - - pub(crate) fn infallible_clone(&self) -> AnyValue { - self.0.clone() - } -} - -impl IsValueContent for Shared { - type Type = X::Type; - type Form = BeShared; -} - -impl> IntoValueContent<'static> for Shared -where - X::Type: IsHierarchicalType = X>, - X::Form: IsHierarchicalForm, - X::Form: LeafAsRefForm, -{ - fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - self.0 - .replace(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)) - } -} +pub(crate) static SHARED_ERROR_MESSAGE: &str = + "The variable cannot be read as it is already being modified"; -impl AsRef for Shared { - fn as_ref(&self) -> &T { - &self.0 - } -} +// ============================================================================ +// IntoValueContent impls for the new types (formerly on Shared / Mutable) +// ============================================================================ -impl Deref for Shared { - type Target = T; +// Note: IsValueContent and IntoValueContent are implemented via QqqShared/QqqMutable +// in the forms system, since SharedReference IS the leaf type now. - fn deref(&self) -> &Self::Target { - &self.0 - } -} +// ============================================================================ +// CopyOnWrite +// ============================================================================ /// Copy-on-write value that can be either owned or shared pub(crate) struct CopyOnWrite { @@ -642,20 +459,20 @@ pub(crate) enum CopyOnWriteInner { Owned(Owned), /// For use when the CopyOnWrite value effectively represents the owned value (post-clone). /// In this case, returning a Cow is just an optimization and we can always clone infallibly. - SharedWithInfallibleCloning(Shared), + SharedWithInfallibleCloning(SharedReference), /// For use when the CopyOnWrite value represents a pre-cloned read-only value. /// A transparent clone may fail in this case at use time. - SharedWithTransparentCloning(Shared), + SharedWithTransparentCloning(SharedReference), } impl CopyOnWrite { - pub(crate) fn shared_in_place_of_owned(shared: Shared) -> Self { + pub(crate) fn shared_in_place_of_owned(shared: SharedReference) -> Self { Self { inner: CopyOnWriteInner::SharedWithInfallibleCloning(shared), } } - pub(crate) fn shared_in_place_of_shared(shared: Shared) -> Self { + pub(crate) fn shared_in_place_of_shared(shared: SharedReference) -> Self { Self { inner: CopyOnWriteInner::SharedWithTransparentCloning(shared), } @@ -693,7 +510,7 @@ impl CopyOnWrite { pub(crate) fn map( self, - map_shared: impl FnOnce(Shared) -> FunctionResult>, + map_shared: impl FnOnce(SharedReference) -> FunctionResult>, map_owned: impl FnOnce(Owned) -> FunctionResult>, ) -> FunctionResult> { let inner = match self.inner { @@ -710,7 +527,7 @@ impl CopyOnWrite { pub(crate) fn map_into( self, - map_shared: impl FnOnce(Shared) -> U, + map_shared: impl FnOnce(SharedReference) -> U, map_owned: impl FnOnce(Owned) -> U, ) -> U { match self.inner { @@ -720,7 +537,7 @@ impl CopyOnWrite { } } - /// Disables this copy-on-write value, releasing any borrow on the RefCell. + /// Disables this copy-on-write value, releasing any borrow. /// Returns a `DisabledCopyOnWrite` which can be cloned and later re-enabled. pub(crate) fn disable(self) -> DisabledCopyOnWrite { let inner = match self.inner { @@ -743,8 +560,8 @@ pub(crate) struct DisabledCopyOnWrite { enum DisabledCopyOnWriteInner { Owned(Owned), - SharedWithInfallibleCloning(DisabledShared), - SharedWithTransparentCloning(DisabledShared), + SharedWithInfallibleCloning(InactiveSharedReference), + SharedWithTransparentCloning(InactiveSharedReference), } impl Clone for DisabledCopyOnWrite @@ -799,11 +616,15 @@ where AnyLevelCopyOnWrite::::Owned(owned).into_copy_on_write() } CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(shared.into_content()) + let content = shared + .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(content) .into_copy_on_write() } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - AnyLevelCopyOnWrite::::SharedWithTransparentCloning(shared.into_content()) + let content = shared + .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + AnyLevelCopyOnWrite::::SharedWithTransparentCloning(content) .into_copy_on_write() } } diff --git a/src/interpretation/refs.rs b/src/interpretation/refs.rs index 216638ef..dba9b3bf 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -3,7 +3,7 @@ use std::mem::transmute; use super::*; /// A flexible type which can either be a reference to a value of type `T`, -/// or an emplaced reference from a [`Shared`]. +/// or an emplaced reference from a [`SharedReference`]. pub(crate) struct AnyRef<'a, T: ?Sized + 'static> { inner: AnyRefInner<'a, T>, } @@ -16,7 +16,7 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { inner: AnyRefInner::Direct(f(value)), }, AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.map(|x| f(x))), + inner: AnyRefInner::Encapsulated(shared.map_legacy(|x| f(x))), }, } } @@ -31,7 +31,7 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { inner: AnyRefInner::Direct(f(value)?), }, AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.map_optional(f)?), + inner: AnyRefInner::Encapsulated(shared.map_optional_legacy(f)?), }, }) } @@ -107,15 +107,15 @@ impl<'a, T: ?Sized> ToSpannedRef<'a> for &'a T { } } -impl<'a, T: ?Sized> From> for AnyRef<'a, T> { - fn from(value: Shared) -> Self { +impl<'a, T: ?Sized> From> for AnyRef<'a, T> { + fn from(value: SharedReference) -> Self { Self { - inner: AnyRefInner::Encapsulated(value.0), + inner: AnyRefInner::Encapsulated(value), } } } -impl ToSpannedRef<'static> for Shared { +impl ToSpannedRef<'static> for SharedReference { type Target = T; fn into_ref(self) -> AnyRef<'static, Self::Target> { self.into() @@ -127,7 +127,7 @@ impl ToSpannedRef<'static> for Shared { enum AnyRefInner<'a, T: 'static + ?Sized> { Direct(&'a T), - Encapsulated(SharedSubRcRefCell), + Encapsulated(SharedReference), } impl<'a, T: 'static + ?Sized> Deref for AnyRef<'a, T> { @@ -142,7 +142,7 @@ impl<'a, T: 'static + ?Sized> Deref for AnyRef<'a, T> { } /// A flexible type which can either be a mutable reference to a value of type `T`, -/// or an emplaced reference from a [`Mutable`]. +/// or an emplaced reference from a [`MutableReference`]. pub(crate) struct AnyMut<'a, T: 'static + ?Sized> { inner: AnyMutInner<'a, T>, } @@ -166,7 +166,7 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { inner: AnyMutInner::Direct(f(value)), }, AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.map(|x| f(x))), + inner: AnyMutInner::Encapsulated(mutable.map_legacy(|x| f(x))), }, } } @@ -181,7 +181,7 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { inner: AnyMutInner::Direct(f(value)?), }, AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.map_optional(f)?), + inner: AnyMutInner::Encapsulated(mutable.map_optional_legacy(f)?), }, }) } @@ -248,17 +248,17 @@ impl<'a, T: ?Sized + 'static> IntoAnyMut<'a> for &'a mut T { } } -impl<'a, T: ?Sized> From> for AnyMut<'a, T> { - fn from(value: Mutable) -> Self { +impl<'a, T: ?Sized> From> for AnyMut<'a, T> { + fn from(value: MutableReference) -> Self { Self { - inner: AnyMutInner::Encapsulated(value.0), + inner: AnyMutInner::Encapsulated(value), } } } enum AnyMutInner<'a, T: 'static + ?Sized> { Direct(&'a mut T), - Encapsulated(MutableSubRcRefCell), + Encapsulated(MutableReference), } impl<'a, T: 'static + ?Sized> Deref for AnyMut<'a, T> { diff --git a/src/interpretation/variable.rs b/src/interpretation/variable.rs index dd67fc24..b02e06bc 100644 --- a/src/interpretation/variable.rs +++ b/src/interpretation/variable.rs @@ -68,7 +68,11 @@ impl VariableDefinition { pub(crate) fn define(&self, interpreter: &mut Interpreter, value_source: impl IntoAnyValue) { interpreter.define_variable( self.id, - VariableContent::Referenceable(Rc::new(RefCell::new(value_source.into_any_value()))), + VariableContent::Referenceable(Referenceable::new( + value_source.into_any_value(), + "".to_string(), + SpanRange::new_single(Span::call_site()), + )), ); } } diff --git a/src/misc/dynamic_references/mod.rs b/src/misc/dynamic_references/mod.rs index 5efc3ae3..c07ac467 100644 --- a/src/misc/dynamic_references/mod.rs +++ b/src/misc/dynamic_references/mod.rs @@ -81,8 +81,6 @@ //! Was based off an `Rc>` which we could use to create Active/Mutable references, and deactivate them. //! However, it didn't respect (a.1) and it didn't permit mutiple mutable references on different paths. -// TODO[references]: Remove once integrated -#![allow(unused)] mod mutable_reference; mod reference_core; mod referenceable; @@ -93,7 +91,6 @@ use crate::internal_prelude::*; use reference_core::*; use slotmap::{new_key_type, SlotMap}; use std::cell::UnsafeCell; -use std::cmp::Ordering; use std::mem::ManuallyDrop; use std::ptr::NonNull; diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index 329deb1d..e70f7d8a 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -54,6 +54,115 @@ impl MutableReference { } } +impl MutableReference { + /// Creates a new MutableReference from an owned value, wrapping it in a Referenceable. + pub(crate) fn new_from_owned(value: AnyValue) -> Self { + let referenceable = Referenceable::new( + value, + "".to_string(), + SpanRange::new_single(Span::call_site()), + ); + referenceable + .new_inactive_mutable() + .activate() + .expect("Freshly created referenceable must be borrowable as mutable") + } +} + +impl MutableReference { + /// Disables this mutable reference (bridge for old `disable()` API). + /// Equivalent to `deactivate()` in the new naming. + pub(crate) fn disable(self) -> InactiveMutableReference { + self.deactivate() + } + + /// Converts this mutable reference into a shared reference. + /// Bridge for old `into_shared()` API. + pub(crate) fn into_shared(self) -> SharedReference { + let inactive = self.deactivate(); + let inactive_shared = inactive.into_shared(); + inactive_shared + .activate() + .expect("Converting mutable to shared should always succeed since we just released the mutable borrow") + } + + /// Safe map that uses a placeholder path extension. + /// Bridge method for migration. + pub(crate) fn map_legacy( + self, + value_map: impl FnOnce(&mut T) -> &mut V, + ) -> MutableReference { + self.emplace_map(move |input, emplacer| { + // SAFETY: Conservative path extension + unsafe { + emplacer.emplace( + value_map(input), + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + } + }) + } + + /// Safe try_map that uses a placeholder path extension. + /// Bridge method for migration. + pub(crate) fn try_map_legacy( + self, + value_map: impl FnOnce(&mut T) -> Result<&mut V, E>, + ) -> Result, (E, MutableReference)> { + self.emplace_map(|input, emplacer| match value_map(input) { + Ok(output) => { + // SAFETY: Conservative path extension + Ok(unsafe { + emplacer.emplace( + output, + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + }) + } + Err(e) => Err((e, emplacer.revert())), + }) + } + + /// Safe map_optional that uses a placeholder path extension. + /// Bridge method for migration. + #[allow(unused)] + pub(crate) fn map_optional_legacy( + self, + value_map: impl FnOnce(&mut T) -> Option<&mut V>, + ) -> Option> { + self.emplace_map(|input, emplacer| match value_map(input) { + Some(output) => Some(unsafe { + emplacer.emplace( + output, + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + }), + None => { + let _ = emplacer.revert(); + None + } + }) + } + + /// Bridge for the old `replace()` pattern. + pub(crate) fn replace_legacy( + self, + f: impl for<'e> FnOnce(&'e mut T, &mut MutableEmplacerV2<'e, T>) -> O, + ) -> O { + self.emplace_map(f) + } +} + +impl InactiveMutableReference { + /// Re-enables this inactive mutable reference (bridge for old `enable()` API). + pub(crate) fn enable(self, _span: SpanRange) -> FunctionResult> { + self.activate() + } +} + impl Deref for MutableReference { type Target = T; @@ -67,6 +176,18 @@ impl Deref for MutableReference { } } +impl AsRef for MutableReference { + fn as_ref(&self) -> &T { + self + } +} + +impl AsMut for MutableReference { + fn as_mut(&mut self) -> &mut T { + self + } +} + impl DerefMut for MutableReference { fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: See the rustdoc on dynamic_references/mod.rs for full details. @@ -78,9 +199,14 @@ impl DerefMut for MutableReference { } } -#[derive(Clone)] pub(crate) struct InactiveMutableReference(pub(super) ReferenceCore); +impl Clone for InactiveMutableReference { + fn clone(&self) -> Self { + InactiveMutableReference(self.0.clone()) + } +} + impl InactiveMutableReference { pub(crate) fn activate(self) -> FunctionResult> { self.0 @@ -138,4 +264,25 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { // - The caller ensures that the PathExtension is correct unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } + + /// Legacy bridge: emplace_unchecked without PathExtension. + /// Uses a conservative default path extension. + /// + /// SAFETY: + /// - The caller must ensure that the value's lifetime is derived from the original content + pub(crate) unsafe fn emplace_unchecked_legacy( + &mut self, + value: &mut V, + ) -> MutableReference { + unsafe { + self.emplace_unchecked( + value, + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + } + } } + +/// Legacy type alias for backward compatibility +pub(crate) type MutableEmplacer<'e, T> = MutableEmplacerV2<'e, T>; diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index cf45a735..6314a3c2 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -1,12 +1,17 @@ -use std::process::Child; - use super::*; +#[derive(Clone)] pub(crate) struct Referenceable { core: Rc>, } impl Referenceable { + pub(crate) fn new(root: AnyValue, root_name: String, root_span: SpanRange) -> Self { + Self { + core: Rc::new(ReferenceableCore::new(root, root_name, root_span)), + } + } + pub(crate) fn new_inactive_shared(&self) -> InactiveSharedReference { InactiveSharedReference(ReferenceCore::new_root( self.core.clone(), @@ -20,6 +25,14 @@ impl Referenceable { ReferenceKind::InactiveMutable, )) } + + /// Attempts to unwrap the inner value if this is the sole owner (no outstanding references). + pub(crate) fn try_into_inner(self) -> Result { + match Rc::try_unwrap(self.core) { + Ok(core) => Ok(core.into_inner()), + Err(core) => Err(Self { core }), + } + } } pub(super) struct ReferenceableCore { @@ -54,6 +67,10 @@ impl ReferenceableCore { pub(super) fn data_mut(&self) -> RefMut<'_, ReferenceableData> { self.data.borrow_mut() } + + pub(super) fn into_inner(self) -> T { + self.root.into_inner() + } } new_key_type! { @@ -194,7 +211,7 @@ impl ReferenceableData { ) { let data = self.for_reference_mut(id); data.creation_span = new_span; - let mut last_path_part = data.path.parts.last_mut().expect("path is non-empty"); + let last_path_part = data.path.parts.last_mut().expect("path is non-empty"); match (last_path_part, path_extension) { (last_path_part, PathExtension::Child(specifier, child_bound_as)) => { let parent_type = specifier.bound_type_kind(); diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 5a783fdf..d1f437bb 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -1,9 +1,14 @@ use super::*; -#[derive(Clone)] pub(crate) struct SharedReference(pub(super) ReferenceCore); -impl SharedReference { +impl Clone for SharedReference { + fn clone(&self) -> Self { + SharedReference(self.0.clone()) + } +} + +impl SharedReference { pub(crate) fn deactivate(self) -> InactiveSharedReference { self.0.core.data_mut().deactivate_reference(self.0.id); InactiveSharedReference(self.0) @@ -55,7 +60,120 @@ impl SharedReference { } } -impl Deref for SharedReference { +impl SharedReference { + /// Creates a new SharedReference from an owned value, wrapping it in a Referenceable. + /// Uses a placeholder name and span for the Referenceable root. + pub(crate) fn new_from_owned(value: AnyValue) -> Self { + let referenceable = Referenceable::new( + value, + "".to_string(), + SpanRange::new_single(Span::call_site()), + ); + referenceable + .new_inactive_shared() + .activate() + .expect("Freshly created referenceable must be borrowable as shared") + } + + /// Clones the inner value. This is infallible because AnyValue always supports clone. + pub(crate) fn infallible_clone(&self) -> AnyValue { + (**self).clone() + } +} + +impl SharedReference { + /// Disables this shared reference (bridge for old `disable()` API). + /// Equivalent to `deactivate()` in the new naming. + pub(crate) fn disable(self) -> InactiveSharedReference { + self.deactivate() + } + + /// Safe map that uses a placeholder path extension. + /// This is a bridge method for migration - callers should eventually switch to + /// the unsafe `map()` with proper PathExtension. + pub(crate) fn map_legacy( + self, + value_map: impl FnOnce(&T) -> &V, + ) -> SharedReference { + self.emplace_map(move |input, emplacer| { + // SAFETY: We use Tightened(AnyType::type_kind()) as a conservative path extension + // that says "we're still at the same depth, just changing our view". + // This may be overly conservative but won't cause memory safety issues. + unsafe { + emplacer.emplace( + value_map(input), + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + } + }) + } + + /// Safe try_map that uses a placeholder path extension. + /// Bridge method for migration. + pub(crate) fn try_map_legacy( + self, + value_map: impl FnOnce(&T) -> Result<&V, E>, + ) -> Result, (E, SharedReference)> { + self.emplace_map(|input, emplacer| match value_map(input) { + Ok(output) => { + // SAFETY: Same conservative path extension as map_legacy + Ok(unsafe { + emplacer.emplace( + output, + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + }) + } + Err(e) => Err((e, emplacer.revert())), + }) + } + + /// Safe map_optional that uses a placeholder path extension. + /// Bridge method for migration. + pub(crate) fn map_optional_legacy( + self, + value_map: impl FnOnce(&T) -> Option<&V>, + ) -> Option> { + self.emplace_map(|input, emplacer| match value_map(input) { + Some(output) => { + // SAFETY: Same conservative path extension as map_legacy + Some(unsafe { + emplacer.emplace( + output, + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + }) + } + None => { + let _ = emplacer.revert(); // drop the reverted reference + None + } + }) + } + + /// Bridge for the old `replace()` pattern. + /// Uses the emplace_map internally with a legacy path extension. + pub(crate) fn replace_legacy( + self, + f: impl for<'e> FnOnce(&'e T, &mut SharedEmplacerV2<'e, T>) -> O, + ) -> O { + self.emplace_map(f) + } +} + +impl InactiveSharedReference { + /// Re-enables this inactive shared reference (bridge for old `enable()` API). + /// The span parameter is kept for API compatibility but activation errors + /// now include span information from the reference itself. + pub(crate) fn enable(self, _span: SpanRange) -> FunctionResult> { + self.activate() + } +} + +impl Deref for SharedReference { type Target = T; fn deref(&self) -> &Self::Target { @@ -68,9 +186,20 @@ impl Deref for SharedReference { } } -#[derive(Clone)] +impl AsRef for SharedReference { + fn as_ref(&self) -> &T { + self + } +} + pub(crate) struct InactiveSharedReference(pub(super) ReferenceCore); +impl Clone for InactiveSharedReference { + fn clone(&self) -> Self { + InactiveSharedReference(self.0.clone()) + } +} + impl InactiveSharedReference { pub(crate) fn activate(self) -> FunctionResult> { self.0 @@ -120,4 +249,25 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { // - The caller ensures that the PathExtension is correct unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } + + /// Legacy bridge: emplace_unchecked without PathExtension. + /// Uses a conservative default path extension. + /// + /// SAFETY: + /// - The caller must ensure that the value's lifetime is derived from the original content + pub(crate) unsafe fn emplace_unchecked_legacy( + &mut self, + value: &V, + ) -> SharedReference { + unsafe { + self.emplace_unchecked( + value, + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + } + } } + +/// Legacy type alias for backward compatibility +pub(crate) type SharedEmplacer<'e, T> = SharedEmplacerV2<'e, T>; diff --git a/src/misc/mod.rs b/src/misc/mod.rs index d1d17792..11b389c5 100644 --- a/src/misc/mod.rs +++ b/src/misc/mod.rs @@ -9,13 +9,13 @@ mod parse_traits; pub(crate) mod string_conversion; pub(crate) use arena::*; -#[allow(unused)] // TODO: Remove unused once integrated pub(crate) use dynamic_references::*; pub(crate) use errors::*; pub(crate) use field_inputs::*; pub(crate) use iterators::*; pub(crate) use keywords::*; -pub(crate) use mut_rc_ref_cell::*; +// Old RefCell-based abstractions - no longer glob-exported; replaced by dynamic_references. +// pub(crate) use mut_rc_ref_cell::*; pub(crate) use parse_traits::*; use crate::internal_prelude::*; diff --git a/src/misc/mut_rc_ref_cell.rs b/src/misc/mut_rc_ref_cell.rs index 8bf147a1..2ff7912a 100644 --- a/src/misc/mut_rc_ref_cell.rs +++ b/src/misc/mut_rc_ref_cell.rs @@ -1,5 +1,13 @@ +#![allow(unused, dead_code)] +// This module is no longer exported - kept for reference during migration. +// All types have been replaced by dynamic_references equivalents. + use crate::internal_prelude::*; use std::cell::{BorrowError, BorrowMutError}; +use std::rc::Rc; + +// Local alias to avoid conflict with dynamic_references::Referenceable +type Referenceable = Rc>; /// A mutable reference to a sub-value `U` inside a [`Rc>`]. /// Only one [`MutableSubRcRefCell`] can exist at a time for a given [`Rc>`]. diff --git a/tests/compilation_failures/expressions/swap_itself.stderr b/tests/compilation_failures/expressions/swap_itself.stderr index 29c94dab..c8fab41e 100644 --- a/tests/compilation_failures/expressions/swap_itself.stderr +++ b/tests/compilation_failures/expressions/swap_itself.stderr @@ -1,5 +1,12 @@ -error: The variable cannot be modified as it is already being modified - --> tests/compilation_failures/expressions/swap_itself.rs:6:16 +error: Cannot create mutable reference because it clashes with another reference: the mutable reference is observable from the other reference, which breaks aliasing rulesThis reference-: [*active*] &mut (of type any)Other reference: [*active*] &mut (of type any) + --> tests/compilation_failures/expressions/swap_itself.rs:4:13 | -6 | a.swap(a); - | ^ +4 | let _ = run!{ + | _____________^ +5 | | let a = "a"; +6 | | a.swap(a); +7 | | a +8 | | }; + | |_____^ + | + = note: this error originates in the macro `run` (in Nightly builds, run with -Z macro-backtrace for more info) From 68f7692f4c920ce805e95303deec80ba25a14d6d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Feb 2026 00:34:33 +0000 Subject: [PATCH 08/25] refactor: Replace _legacy bridge methods with proper PathExtension and SpanRange Replace all _legacy bridge methods (map_legacy, try_map_legacy, map_optional_legacy, replace_legacy, emplace_unchecked_legacy) with calls using proper PathExtension values and real SpanRange values. Key changes: - Add current_span() to EmplacerCore, SharedEmplacerV2, MutableEmplacerV2, SharedReference, MutableReference, AnyRefEmplacer, and AnyMutEmplacer - Use PathExtension::Tightened(T::type_kind()) for type-narrowing operations (leaf_to_dyn, into_shared, into_mutable, into_assignee, etc.) - Use PathExtension::Child(ObjectChild(name), AnyType) for property access - Use real spans from emplacer.current_span() instead of Span::call_site() - Make AnyRef/AnyMut map methods and emplacers take PathExtension + SpanRange - Remove all _legacy bridge methods from SharedReference and MutableReference https://claude.ai/code/session_01XaCsWwXFYntkKqZNf8kizU --- src/expressions/concepts/forms/any_mut.rs | 15 +- src/expressions/concepts/forms/any_ref.rs | 15 +- src/expressions/concepts/forms/assignee.rs | 16 +- .../concepts/forms/copy_on_write.rs | 4 +- src/expressions/concepts/forms/mutable.rs | 19 ++- src/expressions/concepts/forms/shared.rs | 19 ++- src/expressions/concepts/forms/simple_mut.rs | 28 ++- src/expressions/concepts/forms/simple_ref.rs | 19 ++- src/expressions/evaluation/value_frames.rs | 56 ++++-- src/expressions/type_resolution/arguments.rs | 52 ++++-- src/interpretation/bindings.rs | 6 +- src/interpretation/refs.rs | 159 +++++++++++++++--- .../dynamic_references/mutable_reference.rs | 98 ++--------- src/misc/dynamic_references/reference_core.rs | 6 + .../dynamic_references/shared_reference.rs | 104 ++---------- 15 files changed, 350 insertions(+), 266 deletions(-) diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index 7bd47f25..e370292f 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -33,9 +33,16 @@ impl IsDynCompatibleForm for BeAnyMut { where T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.replace(|content, emplacer| { + let span = emplacer.current_span(); + match ::map_mut(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace(mapped, PathExtension::Tightened(T::type_kind()), span) + }), + Err(this) => Err(unsafe { + emplacer.emplace(this, PathExtension::Tightened(T::type_kind()), span) + }), + } }) } } @@ -60,6 +67,6 @@ impl MapFromArgument for BeAnyMut { ) -> FunctionResult> { Ok(value .expect_mutable() - .replace_legacy(|inner, emplacer| inner.as_mut_value().into_mutable_any_mut(emplacer))) + .emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable_any_mut(emplacer))) } } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index c316fe8d..61e690e1 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -34,9 +34,16 @@ impl IsDynCompatibleForm for BeAnyRef { where T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_ref(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.replace(|content, emplacer| { + let span = emplacer.current_span(); + match ::map_ref(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace(mapped, PathExtension::Tightened(T::type_kind()), span) + }), + Err(this) => Err(unsafe { + emplacer.emplace(this, PathExtension::Tightened(T::type_kind()), span) + }), + } }) } } @@ -55,6 +62,6 @@ impl MapFromArgument for BeAnyRef { ) -> FunctionResult> { Ok(value .expect_shared() - .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared_any_ref(emplacer))) + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared_any_ref(emplacer))) } } diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index e673d517..5a3065e9 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -36,15 +36,21 @@ impl IsDynCompatibleForm for BeAssignee { where T::Leaf: CastDyn, { - leaf.0 - .replace_legacy(|content, emplacer| match ::map_mut(content) { + leaf.0.emplace_map(|content, emplacer| { + let span = emplacer.current_span(); + match ::map_mut(content) { Ok(mapped) => Ok(QqqAssignee(unsafe { - emplacer.emplace_unchecked_legacy(mapped) + emplacer.emplace_unchecked( + mapped, + PathExtension::Tightened(T::type_kind()), + span, + ) })), Err(this) => Err(QqqAssignee(unsafe { - emplacer.emplace_unchecked_legacy(this) + emplacer.emplace_unchecked(this, PathExtension::Tightened(T::type_kind()), span) })), - }) + } + }) } } diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index e76deb5e..0cf03e50 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -81,12 +81,12 @@ impl MapFromArgument for BeCopyOnWrite { CopyOnWriteInner::Owned(owned) => Ok(BeCopyOnWrite::new_owned(owned)), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { let content = shared - .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); Ok(BeCopyOnWrite::new_shared_in_place_of_owned(content)) } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { let content = shared - .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); Ok(BeCopyOnWrite::new_shared_in_place_of_shared(content)) } } diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index 944354d9..4f8ce835 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -36,9 +36,20 @@ impl IsDynCompatibleForm for BeMutable { where T::Leaf: CastDyn, { - leaf.replace_legacy(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(unsafe { emplacer.emplace_unchecked_legacy(mapped) }), - Err(this) => Err(unsafe { emplacer.emplace_unchecked_legacy(this) }), + leaf.emplace_map(|content, emplacer| { + let span = emplacer.current_span(); + match ::map_mut(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace_unchecked( + mapped, + PathExtension::Tightened(T::type_kind()), + span, + ) + }), + Err(this) => Err(unsafe { + emplacer.emplace_unchecked(this, PathExtension::Tightened(T::type_kind()), span) + }), + } }) } } @@ -63,7 +74,7 @@ impl MapFromArgument for BeMutable { ) -> FunctionResult> { Ok(value .expect_mutable() - .replace_legacy(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer))) + .emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer))) } } diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index 538ec497..b402a824 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -36,9 +36,20 @@ impl IsDynCompatibleForm for BeShared { where T::Leaf: CastDyn, { - leaf.replace_legacy(|content, emplacer| match ::map_ref(content) { - Ok(mapped) => Ok(unsafe { emplacer.emplace_unchecked_legacy(mapped) }), - Err(this) => Err(unsafe { emplacer.emplace_unchecked_legacy(this) }), + leaf.emplace_map(|content, emplacer| { + let span = emplacer.current_span(); + match ::map_ref(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace_unchecked( + mapped, + PathExtension::Tightened(T::type_kind()), + span, + ) + }), + Err(this) => Err(unsafe { + emplacer.emplace_unchecked(this, PathExtension::Tightened(T::type_kind()), span) + }), + } }) } } @@ -57,7 +68,7 @@ impl MapFromArgument for BeShared { ) -> FunctionResult> { Ok(value .expect_shared() - .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer))) + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer))) } } diff --git a/src/expressions/concepts/forms/simple_mut.rs b/src/expressions/concepts/forms/simple_mut.rs index 2cced381..3100a8c9 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -76,7 +76,14 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { self.emplacer.emplace_unchecked_legacy(leaf) } + let span = self.emplacer.current_span(); + unsafe { + self.emplacer.emplace_unchecked( + leaf, + PathExtension::Tightened(T::type_kind()), + span, + ) + } } }; let __mapper = __InlineMapper { emplacer }; @@ -107,7 +114,14 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { QqqAssignee(self.emplacer.emplace_unchecked_legacy(leaf)) } + let span = self.emplacer.current_span(); + unsafe { + QqqAssignee(self.emplacer.emplace_unchecked( + leaf, + PathExtension::Tightened(T::type_kind()), + span, + )) + } } }; let __mapper = __InlineMapper { emplacer }; @@ -138,8 +152,14 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - let mutable_ref: MutableReference<_> = - unsafe { self.emplacer.emplace_unchecked_legacy(leaf) }; + let span = self.emplacer.current_span(); + let mutable_ref: MutableReference<_> = unsafe { + self.emplacer.emplace_unchecked( + leaf, + PathExtension::Tightened(T::type_kind()), + span, + ) + }; mutable_ref.into() } }; diff --git a/src/expressions/concepts/forms/simple_ref.rs b/src/expressions/concepts/forms/simple_ref.rs index 82b99eb5..072c79ab 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -76,7 +76,14 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { self.emplacer.emplace_unchecked_legacy(leaf) } + let span = self.emplacer.current_span(); + unsafe { + self.emplacer.emplace_unchecked( + leaf, + PathExtension::Tightened(T::type_kind()), + span, + ) + } } }; let __mapper = __InlineMapper { emplacer }; @@ -107,8 +114,14 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - let shared_ref: SharedReference<_> = - unsafe { self.emplacer.emplace_unchecked_legacy(leaf) }; + let span = self.emplacer.current_span(); + let shared_ref: SharedReference<_> = unsafe { + self.emplacer.emplace_unchecked( + leaf, + PathExtension::Tightened(T::type_kind()), + span, + ) + }; shared_ref.into() } }; diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index 1f1a76a6..321ad343 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -1053,14 +1053,35 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { }; let auto_create = context.requested_ownership().requests_auto_create(); + let property_name_for_shared = property_name.clone(); let mapped = value.expect_any_value_and_map( |shared| { - shared - .try_map_legacy(|value| (interface.shared_access)(ctx, value)) - .map_err(|(e, _)| e) + // SAFETY: Property access navigates to a named child of the source value. + unsafe { + shared + .try_map( + |value| (interface.shared_access)(ctx, value), + PathExtension::Child( + ChildSpecifier::ObjectChild(property_name_for_shared), + AnyType::type_kind(), + ), + result_span, + ) + .map_err(|(e, _)| e) + } }, |mutable| { - mutable.try_map_legacy(|value| (interface.mutable_access)(ctx, value, auto_create)) + // SAFETY: Property access navigates to a named child of the source value. + unsafe { + mutable.try_map( + |value| (interface.mutable_access)(ctx, value, auto_create), + PathExtension::Child( + ChildSpecifier::ObjectChild(property_name), + AnyType::type_kind(), + ), + result_span, + ) + } }, |owned| (interface.owned_access)(ctx, owned), )?; @@ -1152,20 +1173,33 @@ impl EvaluationFrame for ValueIndexAccessBuilder { }; let auto_create = context.requested_ownership().requests_auto_create(); + let result_span = SpanRange::new_between(source_span, self.access.span_range()); let result = source.expect_any_value_and_map( |shared| { - shared - .try_map_legacy(|value| (interface.shared_access)(ctx, value, index)) - .map_err(|(e, _)| e) + // SAFETY: Tightened(AnyType) is conservative for index access. + // A future improvement could derive ChildSpecifier from the index value. + unsafe { + shared + .try_map( + |value| (interface.shared_access)(ctx, value, index), + PathExtension::Tightened(AnyType::type_kind()), + result_span, + ) + .map_err(|(e, _)| e) + } }, |mutable| { - mutable.try_map_legacy(|value| { - (interface.mutable_access)(ctx, value, index, auto_create) - }) + // SAFETY: Tightened(AnyType) is conservative for index access. + unsafe { + mutable.try_map( + |value| (interface.mutable_access)(ctx, value, index, auto_create), + PathExtension::Tightened(AnyType::type_kind()), + result_span, + ) + } }, |owned| (interface.owned_access)(ctx, owned, index), )?; - let result_span = SpanRange::new_between(source_span, self.access.span_range()); context.return_not_necessarily_matching_requested(Spanned(result, result_span))? } }) diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index f3b61b1e..ca523672 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -258,17 +258,25 @@ pub(crate) trait ResolvableShared { where Self: 'static, { - value - .try_map_legacy(|v| { - Self::resolve_from_ref( - v, - ResolutionContext { - span_range: &span, - resolution_target, + // SAFETY: Tightened(AnyType) is conservative - it preserves the current path depth. + // The span is the actual source expression span for accurate error messages. + unsafe { + value + .try_map( + |v| { + Self::resolve_from_ref( + v, + ResolutionContext { + span_range: &span, + resolution_target, + }, + ) }, + PathExtension::Tightened(AnyType::type_kind()), + span, ) - }) - .map_err(|(err, _)| err) + .map_err(|(err, _)| err) + } } fn resolve_ref<'a>( @@ -323,17 +331,25 @@ pub(crate) trait ResolvableMutable { where Self: 'static, { - value - .try_map_legacy(|v| { - Self::resolve_from_mut( - v, - ResolutionContext { - span_range: &span, - resolution_target, + // SAFETY: Tightened(AnyType) is conservative - it preserves the current path depth. + // The span is the actual source expression span for accurate error messages. + unsafe { + value + .try_map( + |v| { + Self::resolve_from_mut( + v, + ResolutionContext { + span_range: &span, + resolution_target, + }, + ) }, + PathExtension::Tightened(AnyType::type_kind()), + span, ) - }) - .map_err(|(err, _)| err) + .map_err(|(err, _)| err) + } } fn resolve_ref_mut<'a>( diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index aa1fa17f..0afe92a1 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -398,7 +398,7 @@ where { fn into_content(self) -> Content<'static, Self::Type, Self::Form> { self.0 - .replace_legacy(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer)) + .emplace_map(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer)) } } @@ -617,13 +617,13 @@ where } CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { let content = shared - .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(content) .into_copy_on_write() } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { let content = shared - .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); AnyLevelCopyOnWrite::::SharedWithTransparentCloning(content) .into_copy_on_write() } diff --git a/src/interpretation/refs.rs b/src/interpretation/refs.rs index dba9b3bf..07b9ce7d 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -9,29 +9,50 @@ pub(crate) struct AnyRef<'a, T: ?Sized + 'static> { } impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { + /// SAFETY: The caller must ensure the PathExtension is correct. #[allow(unused)] - pub(crate) fn map(self, f: impl for<'r> FnOnce(&'r T) -> &'r S) -> AnyRef<'a, S> { + pub(crate) unsafe fn map( + self, + f: impl for<'r> FnOnce(&'r T) -> &'r S, + path_extension: PathExtension, + new_span: SpanRange, + ) -> AnyRef<'a, S> { match self.inner { AnyRefInner::Direct(value) => AnyRef { inner: AnyRefInner::Direct(f(value)), }, AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.map_legacy(|x| f(x))), + inner: AnyRefInner::Encapsulated(unsafe { + shared.map(|x| f(x), path_extension, new_span) + }), }, } } + /// SAFETY: The caller must ensure the PathExtension is correct. #[allow(unused)] - pub(crate) fn map_optional( + pub(crate) unsafe fn map_optional( self, f: impl for<'r> FnOnce(&'r T) -> Option<&'r S>, + path_extension: PathExtension, + new_span: SpanRange, ) -> Option> { Some(match self.inner { AnyRefInner::Direct(value) => AnyRef { inner: AnyRefInner::Direct(f(value)?), }, AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.map_optional_legacy(f)?), + inner: AnyRefInner::Encapsulated(shared.emplace_map(|input, emplacer| match f( + input, + ) { + Some(output) => { + Some(unsafe { emplacer.emplace(output, path_extension, new_span) }) + } + None => { + let _ = emplacer.revert(); + None + } + })?), }, }) } @@ -60,26 +81,62 @@ pub(crate) struct AnyRefEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { } impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { - pub(crate) fn emplace(&mut self, value: &'e V) -> AnyRef<'a, V> { + /// Returns the current span of the underlying reference (if encapsulated), + /// or a placeholder span (if direct). + pub(crate) fn current_span(&self) -> SpanRange { + match &self + .inner + .as_ref() + .expect("Emplacer already consumed") + .inner + { + AnyRefInner::Direct(_) => SpanRange::new_single(Span::call_site()), + AnyRefInner::Encapsulated(shared) => shared.current_span(), + } + } + + /// SAFETY: The caller must ensure the PathExtension is correct. + pub(crate) unsafe fn emplace( + &mut self, + value: &'e V, + path_extension: PathExtension, + new_span: SpanRange, + ) -> AnyRef<'a, V> { unsafe { // SAFETY: The lifetime 'e is equal to the &'e content argument in replace // So this guarantees that the returned reference is valid as long as the AnyRef exists - self.emplace_unchecked(value) + self.emplace_unchecked(value, path_extension, new_span) } } - // SAFETY: - // * The caller must ensure that the value's lifetime is derived from the original content + /// SAFETY: + /// - The caller must ensure that the value's lifetime is derived from the original content + /// - The caller must ensure the PathExtension is correct pub(crate) unsafe fn emplace_unchecked( &mut self, value: &V, + path_extension: PathExtension, + new_span: SpanRange, ) -> AnyRef<'a, V> { - self.inner + let any_ref = self + .inner .take() - .expect("You can only emplace to create a new AnyRef value once") - .map(|_| + .expect("You can only emplace to create a new AnyRef value once"); + match any_ref.inner { + AnyRefInner::Direct(_) => AnyRef { // SAFETY: As defined in the rustdoc above - unsafe { transmute::<&V, &'static V>(value) }) + inner: AnyRefInner::Direct(unsafe { transmute::<&V, &'static V>(value) }), + }, + AnyRefInner::Encapsulated(shared) => AnyRef { + inner: AnyRefInner::Encapsulated(unsafe { + shared.map( + |_| transmute::<&V, &'static V>(value), + path_extension, + new_span, + ) + }), + }, + } } } @@ -156,32 +213,50 @@ impl<'a, T: ?Sized> From<&'a mut T> for AnyMut<'a, T> { } impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { + /// SAFETY: The caller must ensure the PathExtension is correct. #[allow(unused)] - pub(crate) fn map( + pub(crate) unsafe fn map( self, f: impl for<'r> FnOnce(&'r mut T) -> &'r mut S, + path_extension: PathExtension, + new_span: SpanRange, ) -> AnyMut<'a, S> { match self.inner { AnyMutInner::Direct(value) => AnyMut { inner: AnyMutInner::Direct(f(value)), }, AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.map_legacy(|x| f(x))), + inner: AnyMutInner::Encapsulated(unsafe { + mutable.map(|x| f(x), path_extension, new_span) + }), }, } } + /// SAFETY: The caller must ensure the PathExtension is correct. #[allow(unused)] - pub(crate) fn map_optional( + pub(crate) unsafe fn map_optional( self, f: impl for<'r> FnOnce(&'r mut T) -> Option<&'r mut S>, + path_extension: PathExtension, + new_span: SpanRange, ) -> Option> { Some(match self.inner { AnyMutInner::Direct(value) => AnyMut { inner: AnyMutInner::Direct(f(value)?), }, AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.map_optional_legacy(f)?), + inner: AnyMutInner::Encapsulated(mutable.emplace_map( + |input, emplacer| match f(input) { + Some(output) => { + Some(unsafe { emplacer.emplace(output, path_extension, new_span) }) + } + None => { + let _ = emplacer.revert(); + None + } + }, + )?), }, }) } @@ -211,26 +286,62 @@ pub(crate) struct AnyMutEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { } impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { - pub(crate) fn emplace(&mut self, value: &'e mut V) -> AnyMut<'a, V> { + /// Returns the current span of the underlying reference (if encapsulated), + /// or a placeholder span (if direct). + pub(crate) fn current_span(&self) -> SpanRange { + match &self + .inner + .as_ref() + .expect("Emplacer already consumed") + .inner + { + AnyMutInner::Direct(_) => SpanRange::new_single(Span::call_site()), + AnyMutInner::Encapsulated(mutable) => mutable.current_span(), + } + } + + /// SAFETY: The caller must ensure the PathExtension is correct. + pub(crate) unsafe fn emplace( + &mut self, + value: &'e mut V, + path_extension: PathExtension, + new_span: SpanRange, + ) -> AnyMut<'a, V> { unsafe { // SAFETY: The lifetime 'e is equal to the &'e content argument in replace // So this guarantees that the returned reference is valid as long as the AnyMut exists - self.emplace_unchecked(value) + self.emplace_unchecked(value, path_extension, new_span) } } - // SAFETY: - // * The caller must ensure that the value's lifetime is derived from the original content + /// SAFETY: + /// - The caller must ensure that the value's lifetime is derived from the original content + /// - The caller must ensure the PathExtension is correct pub(crate) unsafe fn emplace_unchecked( &mut self, value: &mut V, + path_extension: PathExtension, + new_span: SpanRange, ) -> AnyMut<'a, V> { - self.inner + let any_mut = self + .inner .take() - .expect("You can only emplace to create a new AnyMut value once") - .map(|_| + .expect("You can only emplace to create a new AnyMut value once"); + match any_mut.inner { + AnyMutInner::Direct(_) => AnyMut { // SAFETY: As defined in the rustdoc above - unsafe { transmute::<&mut V, &'static mut V>(value) }) + inner: AnyMutInner::Direct(unsafe { transmute::<&mut V, &'static mut V>(value) }), + }, + AnyMutInner::Encapsulated(mutable) => AnyMut { + inner: AnyMutInner::Encapsulated(unsafe { + mutable.map( + |_| transmute::<&mut V, &'static mut V>(value), + path_extension, + new_span, + ) + }), + }, + } } } diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index e70f7d8a..f1c35220 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -70,6 +70,11 @@ impl MutableReference { } impl MutableReference { + /// Returns the current creation span of the tracked reference. + pub(crate) fn current_span(&self) -> SpanRange { + self.0.core.data().for_reference(self.0.id).creation_span + } + /// Disables this mutable reference (bridge for old `disable()` API). /// Equivalent to `deactivate()` in the new naming. pub(crate) fn disable(self) -> InactiveMutableReference { @@ -85,75 +90,6 @@ impl MutableReference { .activate() .expect("Converting mutable to shared should always succeed since we just released the mutable borrow") } - - /// Safe map that uses a placeholder path extension. - /// Bridge method for migration. - pub(crate) fn map_legacy( - self, - value_map: impl FnOnce(&mut T) -> &mut V, - ) -> MutableReference { - self.emplace_map(move |input, emplacer| { - // SAFETY: Conservative path extension - unsafe { - emplacer.emplace( - value_map(input), - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - } - }) - } - - /// Safe try_map that uses a placeholder path extension. - /// Bridge method for migration. - pub(crate) fn try_map_legacy( - self, - value_map: impl FnOnce(&mut T) -> Result<&mut V, E>, - ) -> Result, (E, MutableReference)> { - self.emplace_map(|input, emplacer| match value_map(input) { - Ok(output) => { - // SAFETY: Conservative path extension - Ok(unsafe { - emplacer.emplace( - output, - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - }) - } - Err(e) => Err((e, emplacer.revert())), - }) - } - - /// Safe map_optional that uses a placeholder path extension. - /// Bridge method for migration. - #[allow(unused)] - pub(crate) fn map_optional_legacy( - self, - value_map: impl FnOnce(&mut T) -> Option<&mut V>, - ) -> Option> { - self.emplace_map(|input, emplacer| match value_map(input) { - Some(output) => Some(unsafe { - emplacer.emplace( - output, - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - }), - None => { - let _ = emplacer.revert(); - None - } - }) - } - - /// Bridge for the old `replace()` pattern. - pub(crate) fn replace_legacy( - self, - f: impl for<'e> FnOnce(&'e mut T, &mut MutableEmplacerV2<'e, T>) -> O, - ) -> O { - self.emplace_map(f) - } } impl InactiveMutableReference { @@ -232,6 +168,12 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { MutableReference(self.0.revert()) } + /// Returns the current creation span of the tracked reference. + /// Useful for preserving the span during internal type-narrowing operations. + pub(crate) fn current_span(&self) -> SpanRange { + self.0.current_span() + } + /// SAFETY: /// - The caller must ensure that the PathExtension is correct /// (an overly-specific PathExtension may cause safety issues) @@ -264,24 +206,6 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { // - The caller ensures that the PathExtension is correct unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } - - /// Legacy bridge: emplace_unchecked without PathExtension. - /// Uses a conservative default path extension. - /// - /// SAFETY: - /// - The caller must ensure that the value's lifetime is derived from the original content - pub(crate) unsafe fn emplace_unchecked_legacy( - &mut self, - value: &mut V, - ) -> MutableReference { - unsafe { - self.emplace_unchecked( - value, - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - } - } } /// Legacy type alias for backward compatibility diff --git a/src/misc/dynamic_references/reference_core.rs b/src/misc/dynamic_references/reference_core.rs index 52eed74e..bc3e5fef 100644 --- a/src/misc/dynamic_references/reference_core.rs +++ b/src/misc/dynamic_references/reference_core.rs @@ -90,6 +90,12 @@ impl<'e, T: ?Sized> EmplacerCore<'e, T> { } } + /// Returns the current creation span of the tracked reference. + pub(crate) fn current_span(&self) -> SpanRange { + let inner = self.inner.as_ref().expect("Emplacer already consumed"); + inner.core.data().for_reference(inner.id).creation_span + } + /// SAFETY: /// - The caller must ensure that the pointer is derived from the original content /// - The caller must ensure that the PathExtension is correct diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index d1f437bb..349d6b74 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -82,86 +82,16 @@ impl SharedReference { } impl SharedReference { + /// Returns the current creation span of the tracked reference. + pub(crate) fn current_span(&self) -> SpanRange { + self.0.core.data().for_reference(self.0.id).creation_span + } + /// Disables this shared reference (bridge for old `disable()` API). /// Equivalent to `deactivate()` in the new naming. pub(crate) fn disable(self) -> InactiveSharedReference { self.deactivate() } - - /// Safe map that uses a placeholder path extension. - /// This is a bridge method for migration - callers should eventually switch to - /// the unsafe `map()` with proper PathExtension. - pub(crate) fn map_legacy( - self, - value_map: impl FnOnce(&T) -> &V, - ) -> SharedReference { - self.emplace_map(move |input, emplacer| { - // SAFETY: We use Tightened(AnyType::type_kind()) as a conservative path extension - // that says "we're still at the same depth, just changing our view". - // This may be overly conservative but won't cause memory safety issues. - unsafe { - emplacer.emplace( - value_map(input), - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - } - }) - } - - /// Safe try_map that uses a placeholder path extension. - /// Bridge method for migration. - pub(crate) fn try_map_legacy( - self, - value_map: impl FnOnce(&T) -> Result<&V, E>, - ) -> Result, (E, SharedReference)> { - self.emplace_map(|input, emplacer| match value_map(input) { - Ok(output) => { - // SAFETY: Same conservative path extension as map_legacy - Ok(unsafe { - emplacer.emplace( - output, - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - }) - } - Err(e) => Err((e, emplacer.revert())), - }) - } - - /// Safe map_optional that uses a placeholder path extension. - /// Bridge method for migration. - pub(crate) fn map_optional_legacy( - self, - value_map: impl FnOnce(&T) -> Option<&V>, - ) -> Option> { - self.emplace_map(|input, emplacer| match value_map(input) { - Some(output) => { - // SAFETY: Same conservative path extension as map_legacy - Some(unsafe { - emplacer.emplace( - output, - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - }) - } - None => { - let _ = emplacer.revert(); // drop the reverted reference - None - } - }) - } - - /// Bridge for the old `replace()` pattern. - /// Uses the emplace_map internally with a legacy path extension. - pub(crate) fn replace_legacy( - self, - f: impl for<'e> FnOnce(&'e T, &mut SharedEmplacerV2<'e, T>) -> O, - ) -> O { - self.emplace_map(f) - } } impl InactiveSharedReference { @@ -217,6 +147,12 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { SharedReference(self.0.revert()) } + /// Returns the current creation span of the tracked reference. + /// Useful for preserving the span during internal type-narrowing operations. + pub(crate) fn current_span(&self) -> SpanRange { + self.0.current_span() + } + /// SAFETY: /// - The caller must ensure that the PathExtension is correct /// (an overly-specific PathExtension may cause safety issues) @@ -249,24 +185,6 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { // - The caller ensures that the PathExtension is correct unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } - - /// Legacy bridge: emplace_unchecked without PathExtension. - /// Uses a conservative default path extension. - /// - /// SAFETY: - /// - The caller must ensure that the value's lifetime is derived from the original content - pub(crate) unsafe fn emplace_unchecked_legacy( - &mut self, - value: &V, - ) -> SharedReference { - unsafe { - self.emplace_unchecked( - value, - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - } - } } /// Legacy type alias for backward compatibility From 0b255693d7ddccb60b3a122f828ee60bcdb30fc9 Mon Sep 17 00:00:00 2001 From: David Edey Date: Tue, 24 Feb 2026 23:47:50 +0000 Subject: [PATCH 09/25] fix: Partial improvements / fixes --- src/expressions/concepts/content.rs | 15 +- src/expressions/concepts/form.rs | 7 +- src/expressions/concepts/forms/any_mut.rs | 16 +- src/expressions/concepts/forms/any_ref.rs | 16 +- src/expressions/concepts/forms/assignee.rs | 16 +- src/expressions/concepts/forms/mod.rs | 2 - src/expressions/concepts/forms/mutable.rs | 25 +- src/expressions/concepts/forms/owned.rs | 6 +- .../concepts/forms/referenceable.rs | 35 -- src/expressions/concepts/forms/shared.rs | 25 +- src/expressions/concepts/forms/simple_mut.rs | 18 +- src/expressions/concepts/forms/simple_ref.rs | 14 +- src/expressions/concepts/type_traits.rs | 25 +- src/expressions/evaluation/value_frames.rs | 22 +- src/expressions/expression_parsing.rs | 6 +- src/expressions/type_resolution/arguments.rs | 74 +-- src/expressions/type_resolution/type_kinds.rs | 58 ++- src/expressions/values/any_value.rs | 2 +- src/internal_prelude.rs | 2 +- src/interpretation/bindings.rs | 4 +- src/interpretation/refs.rs | 49 +- src/interpretation/variable.rs | 4 +- .../dynamic_references/mutable_reference.rs | 42 +- src/misc/dynamic_references/reference_core.rs | 8 +- src/misc/dynamic_references/referenceable.rs | 76 +-- .../dynamic_references/shared_reference.rs | 42 +- src/misc/mod.rs | 3 - src/misc/mut_rc_ref_cell.rs | 469 ------------------ .../expressions/swap_itself.stderr | 18 +- 29 files changed, 239 insertions(+), 860 deletions(-) delete mode 100644 src/expressions/concepts/forms/referenceable.rs delete mode 100644 src/misc/mut_rc_ref_cell.rs diff --git a/src/expressions/concepts/content.rs b/src/expressions/concepts/content.rs index 641cbcd0..c3502101 100644 --- a/src/expressions/concepts/content.rs +++ b/src/expressions/concepts/content.rs @@ -2,8 +2,7 @@ use super::*; /// Shorthand for representing the form F of a type T with a particular lifetime 'a. pub(crate) type Content<'a, T, F> = ::Content<'a, F>; -pub(crate) type DynContent<'a, D, F> = - ::DynLeaf<'a, ::DynContent>; +pub(crate) type DynContent<'a, D, F> = ::DynLeaf<'a, D>; /// For types which have an associated value (type and form) pub(crate) trait IsValueContent { @@ -69,18 +68,6 @@ where { self.upcast() } - - fn into_referenceable(self) -> Content<'a, Self::Type, BeReferenceable> - where - Self: IsValueContent
, - { - map_via_leaf! { - input: (Content<'a, Self::Type, Self::Form>) = self.into_content(), - fn map_leaf(leaf) -> (Content<'a, T, BeReferenceable>) { - Rc::new(RefCell::new(leaf)) - } - } - } } impl<'a, C> Spanned diff --git a/src/expressions/concepts/form.rs b/src/expressions/concepts/form.rs index 6337ce19..1ebce65f 100644 --- a/src/expressions/concepts/form.rs +++ b/src/expressions/concepts/form.rs @@ -53,18 +53,19 @@ pub(crate) trait IsDynCompatibleForm: IsHierarchicalForm { /// The container for a dyn Trait based type. /// The DynLeaf can be similar to the standard leaf, but must be /// able to support an unsized D. - type DynLeaf<'a, D: 'static + ?Sized>; + type DynLeaf<'a, D: IsDynType>; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn; + T::Leaf: CastDyn; } pub(crate) trait MapFromArgument: IsHierarchicalForm { const ARGUMENT_OWNERSHIP: ArgumentOwnership; + // TODO[references]: Have this take a span so that we can store it into the emplacer fn from_argument_value(value: ArgumentValue) -> FunctionResult>; } diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index e370292f..7fcc4389 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -25,23 +25,21 @@ impl IsHierarchicalForm for BeAnyMut { } impl IsDynCompatibleForm for BeAnyMut { - type DynLeaf<'a, D: 'static + ?Sized> = AnyMut<'a, D>; + type DynLeaf<'a, D: IsDynType> = AnyMut<'a, D::DynContent>; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| { - let span = emplacer.current_span(); + leaf.emplace_map(|content, emplacer| { match ::map_mut(content) { Ok(mapped) => Ok(unsafe { - emplacer.emplace(mapped, PathExtension::Tightened(T::type_kind()), span) - }), - Err(this) => Err(unsafe { - emplacer.emplace(this, PathExtension::Tightened(T::type_kind()), span) + // TODO[references]: Pass a span here by propagating from DynResolveFrom + emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), None) }), + Err(this) => Err(emplacer.revert()), } }) } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index 61e690e1..f7ed377e 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -26,23 +26,21 @@ impl IsHierarchicalForm for BeAnyRef { } impl IsDynCompatibleForm for BeAnyRef { - type DynLeaf<'a, D: 'static + ?Sized> = crate::internal_prelude::AnyRef<'a, D>; + type DynLeaf<'a, D: IsDynType> = crate::internal_prelude::AnyRef<'a, D::DynContent>; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| { - let span = emplacer.current_span(); + leaf.emplace_map(|content, emplacer| { match ::map_ref(content) { Ok(mapped) => Ok(unsafe { - emplacer.emplace(mapped, PathExtension::Tightened(T::type_kind()), span) - }), - Err(this) => Err(unsafe { - emplacer.emplace(this, PathExtension::Tightened(T::type_kind()), span) + // TODO[references]: Pass a span here by propagating from DynResolveFrom + emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), None) }), + Err(this) => Err(emplacer.revert()), } }) } diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index 5a3065e9..ea08247a 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -28,27 +28,25 @@ impl IsHierarchicalForm for BeAssignee { } impl IsDynCompatibleForm for BeAssignee { - type DynLeaf<'a, D: 'static + ?Sized> = QqqAssignee; + type DynLeaf<'a, D: IsDynType> = QqqAssignee; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { leaf.0.emplace_map(|content, emplacer| { - let span = emplacer.current_span(); match ::map_mut(content) { Ok(mapped) => Ok(QqqAssignee(unsafe { + // TODO[references]: Pass a span here by propagating from DynResolveFrom emplacer.emplace_unchecked( mapped, - PathExtension::Tightened(T::type_kind()), - span, + PathExtension::Tightened(D::type_kind()), + None, ) })), - Err(this) => Err(QqqAssignee(unsafe { - emplacer.emplace_unchecked(this, PathExtension::Tightened(T::type_kind()), span) - })), + Err(this) => Err(QqqAssignee(emplacer.revert())), } }) } diff --git a/src/expressions/concepts/forms/mod.rs b/src/expressions/concepts/forms/mod.rs index 3f42e697..17102ad6 100644 --- a/src/expressions/concepts/forms/mod.rs +++ b/src/expressions/concepts/forms/mod.rs @@ -9,7 +9,6 @@ mod copy_on_write; mod late_bound; mod mutable; mod owned; -mod referenceable; mod shared; mod simple_mut; mod simple_ref; @@ -22,7 +21,6 @@ pub(crate) use copy_on_write::*; pub(crate) use late_bound::*; pub(crate) use mutable::*; pub(crate) use owned::*; -pub(crate) use referenceable::*; pub(crate) use shared::*; pub(crate) use simple_mut::*; pub(crate) use simple_ref::*; diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index 4f8ce835..8c3a1feb 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -28,28 +28,19 @@ impl IsHierarchicalForm for BeMutable { } impl IsDynCompatibleForm for BeMutable { - type DynLeaf<'a, D: 'static + ?Sized> = QqqMutable; + type DynLeaf<'a, D: IsDynType> = QqqMutable; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.emplace_map(|content, emplacer| { - let span = emplacer.current_span(); - match ::map_mut(content) { - Ok(mapped) => Ok(unsafe { - emplacer.emplace_unchecked( - mapped, - PathExtension::Tightened(T::type_kind()), - span, - ) - }), - Err(this) => Err(unsafe { - emplacer.emplace_unchecked(this, PathExtension::Tightened(T::type_kind()), span) - }), - } + leaf.emplace_map(|content, emplacer| match ::map_mut(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace_unchecked(mapped, PathExtension::Tightened(D::type_kind()), None) + }), + Err(_) => Err(emplacer.revert()), }) } } diff --git a/src/expressions/concepts/forms/owned.rs b/src/expressions/concepts/forms/owned.rs index 09873161..4fe3e8a9 100644 --- a/src/expressions/concepts/forms/owned.rs +++ b/src/expressions/concepts/forms/owned.rs @@ -21,13 +21,13 @@ impl IsHierarchicalForm for BeOwned { } impl IsDynCompatibleForm for BeOwned { - type DynLeaf<'a, D: 'static + ?Sized> = Box; + type DynLeaf<'a, D: IsDynType> = Box; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { ::map_boxed(Box::new(leaf)).map_err(|boxed| *boxed) } diff --git a/src/expressions/concepts/forms/referenceable.rs b/src/expressions/concepts/forms/referenceable.rs deleted file mode 100644 index 470eb455..00000000 --- a/src/expressions/concepts/forms/referenceable.rs +++ /dev/null @@ -1,35 +0,0 @@ -use super::*; - -// The old `pub(crate) type Referenceable = Rc>;` has been replaced by -// `dynamic_references::Referenceable` which is NOT generic (always wraps AnyValue). -// For the form system, we use Rc> directly as the leaf type. - -impl IsValueContent for Rc> { - type Type = L::Type; - type Form = BeReferenceable; -} - -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for Rc> { - fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self - } -} - -impl<'a, L: IsValueLeaf> FromValueContent<'a> for Rc> { - fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { - content - } -} - -/// Roughly equivalent to an owned, but wrapped so that it can be turned into a Shared/Mutable easily. -/// This is useful for the content of variables. -/// -/// Note that Referenceable form does not support dyn casting, because Rc> cannot be -/// directly cast to Rc>. -#[derive(Copy, Clone)] -pub(crate) struct BeReferenceable; -impl IsForm for BeReferenceable {} - -impl IsHierarchicalForm for BeReferenceable { - type Leaf<'a, T: IsLeafType> = Rc>; -} diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index b402a824..1f6cb40f 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -28,28 +28,19 @@ impl IsHierarchicalForm for BeShared { } impl IsDynCompatibleForm for BeShared { - type DynLeaf<'a, D: 'static + ?Sized> = QqqShared; + type DynLeaf<'a, D: IsDynType> = QqqShared; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.emplace_map(|content, emplacer| { - let span = emplacer.current_span(); - match ::map_ref(content) { - Ok(mapped) => Ok(unsafe { - emplacer.emplace_unchecked( - mapped, - PathExtension::Tightened(T::type_kind()), - span, - ) - }), - Err(this) => Err(unsafe { - emplacer.emplace_unchecked(this, PathExtension::Tightened(T::type_kind()), span) - }), - } + leaf.emplace_map(|content, emplacer| match ::map_ref(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace_unchecked(mapped, PathExtension::Tightened(D::type_kind()), None) + }), + Err(_) => Err(emplacer.revert()), }) } } diff --git a/src/expressions/concepts/forms/simple_mut.rs b/src/expressions/concepts/forms/simple_mut.rs index 3100a8c9..8b82e88b 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -29,13 +29,13 @@ impl IsHierarchicalForm for BeMut { } impl IsDynCompatibleForm for BeMut { - type DynLeaf<'a, D: 'static + ?Sized> = &'a mut D; + type DynLeaf<'a, D: IsDynType> = &'a mut D::DynContent; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { ::map_mut(leaf) } @@ -76,12 +76,12 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - let span = self.emplacer.current_span(); unsafe { self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - span, + // TODO[references]: Pass a span here by propagating from DynResolveFrom + None, ) } } @@ -114,12 +114,12 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - let span = self.emplacer.current_span(); unsafe { QqqAssignee(self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - span, + // TODO[references]: Pass a span here by propagating from DynResolveFrom + None, )) } } @@ -152,12 +152,12 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - let span = self.emplacer.current_span(); let mutable_ref: MutableReference<_> = unsafe { self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - span, + // TODO[references]: Pass a span here by propagating from DynResolveFrom + None, ) }; mutable_ref.into() diff --git a/src/expressions/concepts/forms/simple_ref.rs b/src/expressions/concepts/forms/simple_ref.rs index 072c79ab..5c740cf6 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -29,13 +29,13 @@ impl IsHierarchicalForm for BeRef { } impl IsDynCompatibleForm for BeRef { - type DynLeaf<'a, D: 'static + ?Sized> = &'a D; + type DynLeaf<'a, D: IsDynType> = &'a D::DynContent; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { ::map_ref(leaf) } @@ -76,12 +76,12 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - let span = self.emplacer.current_span(); unsafe { self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - span, + // TODO[references]: Pass a span here by propagating from DynResolveFrom + None, ) } } @@ -114,12 +114,12 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - let span = self.emplacer.current_span(); let shared_ref: SharedReference<_> = unsafe { self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - span, + // TODO[references]: Pass a span here by propagating from DynResolveFrom + None, ) }; shared_ref.into() diff --git a/src/expressions/concepts/type_traits.rs b/src/expressions/concepts/type_traits.rs index d267a660..55b921f5 100644 --- a/src/expressions/concepts/type_traits.rs +++ b/src/expressions/concepts/type_traits.rs @@ -57,7 +57,6 @@ pub(crate) trait IsLeafType: + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> - + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> @@ -104,6 +103,7 @@ pub(crate) trait DowncastFrom: IsHierarchicalType { } pub(crate) trait DynResolveFrom: IsDynType { + // TODO[references]: Have this take a span so it can propagate to the emplacer fn downcast_from<'a, F: IsHierarchicalForm + IsDynCompatibleForm>( content: Content<'a, T, F>, ) -> Result, Content<'a, T, F>>; @@ -472,7 +472,6 @@ macro_rules! define_parent_type { } } - impl<'a, F: IsHierarchicalForm> Copy for $content<'a, F> where $( Content<'a, $variant_type, F>: Copy ),* @@ -712,9 +711,9 @@ macro_rules! define_leaf_type { pub(crate) use define_leaf_type; -pub(crate) struct DynMapper(std::marker::PhantomData); +pub(crate) struct DynMapper(std::marker::PhantomData); -impl DynMapper { +impl DynMapper { pub(crate) const fn new() -> Self { Self(std::marker::PhantomData) } @@ -770,7 +769,7 @@ pub(crate) use impl_value_content_traits; macro_rules! define_dyn_type { ( $type_def_vis:vis $type_def:ident, - content: $dyn_type:ty, + content: $dyn_content:ty, dyn_kind: DynTypeKind::$dyn_kind:ident, type_name: $source_type_name:literal, articled_value_name: $articled_value_name:literal, @@ -802,18 +801,18 @@ macro_rules! define_dyn_type { } impl IsDynType for $type_def { - type DynContent = $dyn_type; + type DynContent = $dyn_content; } impl_type_feature_resolver! { impl TypeFeatureResolver for $type_def: [$type_def] } - impl IsDynLeaf for $dyn_type { + impl IsDynLeaf for $dyn_content { type Type = $type_def; } - impl IsArgument for Box<$dyn_type> { + impl IsArgument for Box<$dyn_content> { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Owned; fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { @@ -822,7 +821,7 @@ macro_rules! define_dyn_type { } } - impl<'a> IsArgument for AnyRef<'a, $dyn_type> { + impl<'a> IsArgument for AnyRef<'a, $dyn_content> { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { @@ -831,7 +830,7 @@ macro_rules! define_dyn_type { } } - impl<'a> IsArgument for AnyMut<'a, $dyn_type> { + impl<'a> IsArgument for AnyMut<'a, $dyn_content> { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { @@ -843,12 +842,12 @@ macro_rules! define_dyn_type { impl DynResolveFrom for $type_def { fn downcast_from<'a, F: IsHierarchicalForm + IsDynCompatibleForm>(content: Content<'a, T, F>) -> Result, Content<'a, T, F>> { - T::map_with::<'a, F, _>(DynMapper::<$dyn_type>::new(), content) + T::map_with::<'a, F, _>(DynMapper::<$type_def>::new(), content) } } - impl LeafMapper for DynMapper<$dyn_type> { - type Output<'a, T: IsHierarchicalType> = Result, Content<'a, T, F>>; + impl LeafMapper for DynMapper<$type_def> { + type Output<'a, T: IsHierarchicalType> = Result, Content<'a, T, F>>; fn to_parent_output<'a, T: IsChildType>( output: Self::Output<'a, T>, diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index 321ad343..5b20f42c 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -452,13 +452,15 @@ impl ArgumentOwnership { ArgumentOwnership::Owned => Ok(ArgumentValue::Owned( copy_on_write.clone_to_owned_transparently(span)?, )), - ArgumentOwnership::Shared => Ok(ArgumentValue::Shared(copy_on_write.into_shared())), + ArgumentOwnership::Shared => Ok(ArgumentValue::Shared(copy_on_write.into_shared(span))), ArgumentOwnership::Mutable => { if copy_on_write.acts_as_shared_reference() { span.ownership_err("A mutable reference is required, but a shared reference was received, this indicates a possible bug as the updated value won't be accessible. To proceed regardless, use `.clone()` to get a mutable reference to a cloned value.") } else { Ok(ArgumentValue::Mutable(Mutable::new_from_owned( copy_on_write.clone_to_owned_transparently(span)?, + None, + span, ))) } } @@ -559,9 +561,9 @@ impl ArgumentOwnership { ArgumentOwnership::CopyOnWrite => { Ok(ArgumentValue::CopyOnWrite(CopyOnWrite::owned(owned))) } - ArgumentOwnership::Mutable => { - Ok(ArgumentValue::Mutable(Mutable::new_from_owned(owned))) - } + ArgumentOwnership::Mutable => Ok(ArgumentValue::Mutable(Mutable::new_from_owned( + owned, None, span, + ))), ArgumentOwnership::Assignee { .. } => { if is_from_last_use { span.ownership_err("The final usage of a variable cannot be assigned to. You can use `let _ = ..` to discard a value.") @@ -569,7 +571,9 @@ impl ArgumentOwnership { span.ownership_err("An owned value cannot be assigned to.") } } - ArgumentOwnership::Shared => Ok(ArgumentValue::Shared(Shared::new_from_owned(owned))), + ArgumentOwnership::Shared => Ok(ArgumentValue::Shared(Shared::new_from_owned( + owned, None, span, + ))), } } } @@ -1065,7 +1069,7 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { ChildSpecifier::ObjectChild(property_name_for_shared), AnyType::type_kind(), ), - result_span, + Some(result_span), ) .map_err(|(e, _)| e) } @@ -1079,7 +1083,7 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { ChildSpecifier::ObjectChild(property_name), AnyType::type_kind(), ), - result_span, + Some(result_span), ) } }, @@ -1183,7 +1187,7 @@ impl EvaluationFrame for ValueIndexAccessBuilder { .try_map( |value| (interface.shared_access)(ctx, value, index), PathExtension::Tightened(AnyType::type_kind()), - result_span, + Some(result_span), ) .map_err(|(e, _)| e) } @@ -1194,7 +1198,7 @@ impl EvaluationFrame for ValueIndexAccessBuilder { mutable.try_map( |value| (interface.mutable_access)(ctx, value, index, auto_create), PathExtension::Tightened(AnyType::type_kind()), - result_span, + Some(result_span), ) } }, diff --git a/src/expressions/expression_parsing.rs b/src/expressions/expression_parsing.rs index 7784557c..4bc53b20 100644 --- a/src/expressions/expression_parsing.rs +++ b/src/expressions/expression_parsing.rs @@ -123,7 +123,7 @@ impl<'a> ExpressionParser<'a> { "true" | "false" => { let bool = input.parse::()?; UnaryAtom::Leaf(Leaf::Value(Spanned( - SharedValue::new_from_owned(bool.value.into_any_value()), + SharedValue::new_from_owned(bool.value.into_any_value(), None, bool.span.span_range()), bool.span.span_range(), ))) } @@ -136,7 +136,7 @@ impl<'a> ExpressionParser<'a> { "None" => { let none_ident = input.parse_any_ident()?; // consume the "None" token UnaryAtom::Leaf(Leaf::Value(Spanned( - SharedValue::new_from_owned(().into_any_value()), + SharedValue::new_from_owned(().into_any_value(), None, none_ident.span().span_range()), none_ident.span().span_range(), ))) } @@ -155,7 +155,7 @@ impl<'a> ExpressionParser<'a> { let lit: syn::Lit = input.parse()?; let span_range = lit.span().span_range(); UnaryAtom::Leaf(Leaf::Value(Spanned( - SharedValue::new_from_owned(AnyValue::for_syn_lit(lit)), + SharedValue::new_from_owned(AnyValue::for_syn_lit(lit), None, span_range), span_range, ))) }, diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index ca523672..54d084ac 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -58,10 +58,7 @@ impl IsArgument for ArgumentValue { } } -// NOTE: IsArgument for SharedReference with leaf T is now provided by the blanket impl -// in content.rs via FromValueContent + MapFromArgument (BeShared). -// For the parent type AnyValue (which is not IsValueLeaf), we provide an explicit impl: -impl IsArgument for SharedReference { +impl IsArgument for Shared { type ValueType = AnyType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; @@ -94,10 +91,7 @@ impl + ResolvableArgumentTarget + ?Sized> IsArgum } } -// NOTE: IsArgument for MutableReference with leaf T is now provided by the blanket impl -// in content.rs via FromValueContent + MapFromArgument (BeMutable). -// For the parent type AnyValue (which is not IsValueLeaf), we provide an explicit impl: -impl IsArgument for MutableReference { +impl IsArgument for Mutable { type ValueType = AnyType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; @@ -223,7 +217,7 @@ pub(crate) trait ResolvableArgumentTarget { type ValueType: TypeData; } -pub(crate) trait ResolvableOwned: Sized { +pub(crate) trait ResolvableOwned: Sized { fn resolve_from_value(value: T, context: ResolutionContext) -> FunctionResult; fn resolve_spanned_from_value( @@ -247,7 +241,7 @@ pub(crate) trait ResolvableOwned: Sized { } } -pub(crate) trait ResolvableShared { +pub(crate) trait ResolvableShared { fn resolve_from_ref<'a>(value: &'a T, context: ResolutionContext) -> FunctionResult<&'a Self>; /// The `resolution_target` should be capitalized, e.g. "This argument" or "The value destructed with an object pattern" @@ -258,8 +252,7 @@ pub(crate) trait ResolvableShared { where Self: 'static, { - // SAFETY: Tightened(AnyType) is conservative - it preserves the current path depth. - // The span is the actual source expression span for accurate error messages. + // SAFETY: Tightened(T::Type) is correct unsafe { value .try_map( @@ -272,8 +265,9 @@ pub(crate) trait ResolvableShared { }, ) }, - PathExtension::Tightened(AnyType::type_kind()), - span, + // TODO[references]: Move unsafe to this constructor so the blocks can be smaller + PathExtension::Tightened(T::Type::type_kind()), + Some(span), ) .map_err(|(err, _)| err) } @@ -308,7 +302,7 @@ pub(crate) trait ResolvableShared { } } -pub(crate) trait ResolvableMutable { +pub(crate) trait ResolvableMutable { fn resolve_from_mut<'a>( value: &'a mut T, context: ResolutionContext, @@ -331,8 +325,7 @@ pub(crate) trait ResolvableMutable { where Self: 'static, { - // SAFETY: Tightened(AnyType) is conservative - it preserves the current path depth. - // The span is the actual source expression span for accurate error messages. + // SAFETY: Tightened(T::Type) is correct unsafe { value .try_map( @@ -345,8 +338,8 @@ pub(crate) trait ResolvableMutable { }, ) }, - PathExtension::Tightened(AnyType::type_kind()), - span, + PathExtension::Tightened(T::Type::type_kind()), + Some(span), ) .map_err(|(err, _)| err) } @@ -443,46 +436,3 @@ macro_rules! impl_resolvable_argument_for { } pub(crate) use impl_resolvable_argument_for; - -macro_rules! impl_delegated_resolvable_argument_for { - (($value:ident: $delegate:ty) -> $type:ty { $expr:expr }) => { - impl ResolvableArgumentTarget for $type { - type ValueType = <$delegate as ResolvableArgumentTarget>::ValueType; - } - - impl ResolvableOwned for $type { - fn resolve_from_value( - input_value: Value, - context: ResolutionContext, - ) -> FunctionResult { - let $value: $delegate = - ResolvableOwned::::resolve_from_value(input_value, context)?; - Ok($expr) - } - } - - impl ResolvableShared for $type { - fn resolve_from_ref<'a>( - input_value: &'a Value, - context: ResolutionContext, - ) -> FunctionResult<&'a Self> { - let $value: &$delegate = - ResolvableShared::::resolve_from_ref(input_value, context)?; - Ok(&$expr) - } - } - - impl ResolvableMutable for $type { - fn resolve_from_mut<'a>( - input_value: &'a mut Value, - context: ResolutionContext, - ) -> FunctionResult<&'a mut Self> { - let $value: &mut $delegate = - ResolvableMutable::::resolve_from_mut(input_value, context)?; - Ok(&mut $expr) - } - } - }; -} - -pub(crate) use impl_delegated_resolvable_argument_for; diff --git a/src/expressions/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index c089e77e..7fe69984 100644 --- a/src/expressions/type_resolution/type_kinds.rs +++ b/src/expressions/type_resolution/type_kinds.rs @@ -94,8 +94,10 @@ impl TypeKind { pub(crate) fn is_tightening_to(&self, other: &Self) -> bool { match self.compare_bindings(other) { TypeBindingComparison::Equal => true, - TypeBindingComparison::RightDerivesFromLeft => true, - TypeBindingComparison::LeftDerivesFromRight => false, + TypeBindingComparison::RightDerivesFromLeftButIsNotSubtype => true, + TypeBindingComparison::RightIsSubtypeOfLeft => true, + TypeBindingComparison::LeftDerivesFromRightButIsNotSubtype => false, + TypeBindingComparison::LeftIsSubtypeOfRight => false, TypeBindingComparison::Incomparable => false, TypeBindingComparison::Incompatible => false, } @@ -117,24 +119,24 @@ impl TypeKind { (TypeKind::Dyn(_), TypeKind::Dyn(_)) => TypeBindingComparison::Incomparable, // Assuming the values are compatible, a dyn can be derived from any leaf/parent, // but not the other way around (at present at least) - (TypeKind::Dyn(_), _) => TypeBindingComparison::LeftDerivesFromRight, - (_, TypeKind::Dyn(_)) => TypeBindingComparison::RightDerivesFromLeft, + (TypeKind::Dyn(_), _) => TypeBindingComparison::LeftDerivesFromRightButIsNotSubtype, + (_, TypeKind::Dyn(_)) => TypeBindingComparison::RightDerivesFromLeftButIsNotSubtype, // All non-any-values are strict subtypes of AnyValue (TypeKind::Parent(ParentTypeKind::Value(_)), _) => { - TypeBindingComparison::RightDerivesFromLeft + TypeBindingComparison::RightIsSubtypeOfLeft } (_, TypeKind::Parent(ParentTypeKind::Value(_))) => { - TypeBindingComparison::LeftDerivesFromRight + TypeBindingComparison::LeftIsSubtypeOfRight } // Integer types ( TypeKind::Parent(ParentTypeKind::Integer(_)), TypeKind::Leaf(AnyValueLeafKind::Integer(_)), - ) => TypeBindingComparison::RightDerivesFromLeft, + ) => TypeBindingComparison::RightIsSubtypeOfLeft, ( TypeKind::Leaf(AnyValueLeafKind::Integer(_)), TypeKind::Parent(ParentTypeKind::Integer(_)), - ) => TypeBindingComparison::LeftDerivesFromRight, + ) => TypeBindingComparison::LeftIsSubtypeOfRight, (TypeKind::Parent(ParentTypeKind::Integer(_)), _) => { TypeBindingComparison::Incompatible } @@ -145,11 +147,11 @@ impl TypeKind { ( TypeKind::Parent(ParentTypeKind::Float(_)), TypeKind::Leaf(AnyValueLeafKind::Float(_)), - ) => TypeBindingComparison::RightDerivesFromLeft, + ) => TypeBindingComparison::RightIsSubtypeOfLeft, ( TypeKind::Leaf(AnyValueLeafKind::Float(_)), TypeKind::Parent(ParentTypeKind::Float(_)), - ) => TypeBindingComparison::LeftDerivesFromRight, + ) => TypeBindingComparison::LeftIsSubtypeOfRight, (TypeKind::Parent(ParentTypeKind::Float(_)), _) => TypeBindingComparison::Incompatible, (_, TypeKind::Parent(ParentTypeKind::Float(_))) => TypeBindingComparison::Incompatible, // Non-equal leaf types are incomparable @@ -163,8 +165,14 @@ impl TypeKind { // is compatible with some other form. pub(crate) enum TypeBindingComparison { Equal, - RightDerivesFromLeft, - LeftDerivesFromRight, + // e.g. U8Value is a subtype of IntegerValue + RightIsSubtypeOfLeft, + // e.g. dyn IterableValue derives from ArrayValue, but isn't a subtype of it. + RightDerivesFromLeftButIsNotSubtype, + // e.g. U8Value is a subtype of IntegerValue + LeftIsSubtypeOfRight, + // e.g. dyn IterableValue derives from ArrayValue, but isn't a subtype of it. + LeftDerivesFromRightButIsNotSubtype, Incomparable, // Indicates that the types are incompatible. // Such a comparison shouldn't arise between valid references. @@ -322,22 +330,34 @@ impl TypeProperty { ) -> FunctionResult> { let resolver = self.source_type.kind.feature_resolver(); // TODO[performance] - lazily initialize properties as Shared - let property_name = &self.property.to_string(); - if let Some(value) = resolver.resolve_type_property(property_name) { + let property_name = self.property.to_string(); + if let Some(value) = resolver.resolve_type_property(&property_name) { return ownership.map_from_shared(Spanned( - SharedValue::new_from_owned(value.into_any_value()), + SharedValue::new_from_owned( + value.into_any_value(), + Some(property_name), + self.span_range(), + ), self.span_range(), )); } - if let Some(method) = resolver.resolve_method(property_name) { + if let Some(method) = resolver.resolve_method(&property_name) { return ownership.map_from_shared(Spanned( - SharedValue::new_from_owned(method.into_any_value()), + SharedValue::new_from_owned( + method.into_any_value(), + Some(property_name.clone()), + self.span_range(), + ), self.span_range(), )); } - if let Some(function) = resolver.resolve_type_function(property_name) { + if let Some(function) = resolver.resolve_type_function(&property_name) { return ownership.map_from_shared(Spanned( - SharedValue::new_from_owned(function.into_any_value()), + SharedValue::new_from_owned( + function.into_any_value(), + Some(property_name.clone()), + self.span_range(), + ), self.span_range(), )); } diff --git a/src/expressions/values/any_value.rs b/src/expressions/values/any_value.rs index d3799c35..ceea4b08 100644 --- a/src/expressions/values/any_value.rs +++ b/src/expressions/values/any_value.rs @@ -50,7 +50,7 @@ define_type_features! { fn as_mut(Spanned(this, span): Spanned) -> FunctionResult { Ok(match this { - ArgumentValue::Owned(owned) => Mutable::new_from_owned(owned), + ArgumentValue::Owned(owned) => Mutable::new_from_owned(owned, None, span), ArgumentValue::CopyOnWrite(copy_on_write) => ArgumentOwnership::Mutable .map_from_copy_on_write(Spanned(copy_on_write, span))? .expect_mutable(), diff --git a/src/internal_prelude.rs b/src/internal_prelude.rs index 7fb94530..93d60f59 100644 --- a/src/internal_prelude.rs +++ b/src/internal_prelude.rs @@ -2,7 +2,7 @@ pub(crate) use proc_macro2::{extra::*, *}; pub(crate) use quote::ToTokens; pub(crate) use std::{ borrow::{Borrow, Cow}, - cell::{Ref, RefCell, RefMut}, + cell::{RefCell, RefMut}, collections::{BTreeMap, HashMap, HashSet}, fmt::Debug, fmt::Write, diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 0afe92a1..e38d45a8 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -675,9 +675,9 @@ impl CopyOnWrite { } /// Converts to shared reference - pub(crate) fn into_shared(self) -> SharedValue { + pub(crate) fn into_shared(self, span: SpanRange) -> SharedValue { match self.inner { - CopyOnWriteInner::Owned(owned) => SharedValue::new_from_owned(owned), + CopyOnWriteInner::Owned(owned) => SharedValue::new_from_owned(owned, None, span), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => shared, CopyOnWriteInner::SharedWithTransparentCloning(shared) => shared, } diff --git a/src/interpretation/refs.rs b/src/interpretation/refs.rs index 07b9ce7d..54c4e9bf 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -15,7 +15,7 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { self, f: impl for<'r> FnOnce(&'r T) -> &'r S, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> AnyRef<'a, S> { match self.inner { AnyRefInner::Direct(value) => AnyRef { @@ -35,7 +35,7 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { self, f: impl for<'r> FnOnce(&'r T) -> Option<&'r S>, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> Option> { Some(match self.inner { AnyRefInner::Direct(value) => AnyRef { @@ -57,7 +57,7 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { }) } - pub(crate) fn replace( + pub(crate) fn emplace_map( self, f: impl for<'e> FnOnce(&'e T, &mut AnyRefEmplacer<'a, 'e, T>) -> O, ) -> O { @@ -81,18 +81,8 @@ pub(crate) struct AnyRefEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { } impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { - /// Returns the current span of the underlying reference (if encapsulated), - /// or a placeholder span (if direct). - pub(crate) fn current_span(&self) -> SpanRange { - match &self - .inner - .as_ref() - .expect("Emplacer already consumed") - .inner - { - AnyRefInner::Direct(_) => SpanRange::new_single(Span::call_site()), - AnyRefInner::Encapsulated(shared) => shared.current_span(), - } + pub(crate) fn revert(&mut self) -> AnyRef<'a, T> { + self.inner.take().expect("Emplacer already consumed") } /// SAFETY: The caller must ensure the PathExtension is correct. @@ -100,7 +90,7 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { &mut self, value: &'e V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> AnyRef<'a, V> { unsafe { // SAFETY: The lifetime 'e is equal to the &'e content argument in replace @@ -116,7 +106,7 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { &mut self, value: &V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> AnyRef<'a, V> { let any_ref = self .inner @@ -219,7 +209,7 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { self, f: impl for<'r> FnOnce(&'r mut T) -> &'r mut S, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> AnyMut<'a, S> { match self.inner { AnyMutInner::Direct(value) => AnyMut { @@ -239,7 +229,7 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { self, f: impl for<'r> FnOnce(&'r mut T) -> Option<&'r mut S>, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> Option> { Some(match self.inner { AnyMutInner::Direct(value) => AnyMut { @@ -261,11 +251,12 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { }) } - pub(crate) fn replace( + pub(crate) fn emplace_map( mut self, f: impl for<'e> FnOnce(&'e mut T, &mut AnyMutEmplacer<'a, 'e, T>) -> O, ) -> O { let copied_mut = self.deref_mut() as *mut T; + // TODO[references]: Change the emplacer to take a *mut T directly, to avoid the duplicate &mut T let mut emplacer = AnyMutEmplacer { inner: Some(self), encapsulation_lifetime: std::marker::PhantomData, @@ -286,18 +277,8 @@ pub(crate) struct AnyMutEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { } impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { - /// Returns the current span of the underlying reference (if encapsulated), - /// or a placeholder span (if direct). - pub(crate) fn current_span(&self) -> SpanRange { - match &self - .inner - .as_ref() - .expect("Emplacer already consumed") - .inner - { - AnyMutInner::Direct(_) => SpanRange::new_single(Span::call_site()), - AnyMutInner::Encapsulated(mutable) => mutable.current_span(), - } + pub(crate) fn revert(&mut self) -> AnyMut<'a, T> { + self.inner.take().expect("Emplacer already consumed") } /// SAFETY: The caller must ensure the PathExtension is correct. @@ -305,7 +286,7 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { &mut self, value: &'e mut V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> AnyMut<'a, V> { unsafe { // SAFETY: The lifetime 'e is equal to the &'e content argument in replace @@ -321,7 +302,7 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { &mut self, value: &mut V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> AnyMut<'a, V> { let any_mut = self .inner diff --git a/src/interpretation/variable.rs b/src/interpretation/variable.rs index b02e06bc..2c05e064 100644 --- a/src/interpretation/variable.rs +++ b/src/interpretation/variable.rs @@ -70,8 +70,8 @@ impl VariableDefinition { self.id, VariableContent::Referenceable(Referenceable::new( value_source.into_any_value(), - "".to_string(), - SpanRange::new_single(Span::call_site()), + Some(self.ident.to_string()), + self.ident.span_range(), )), ); } diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index f1c35220..07427a9e 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -12,7 +12,7 @@ impl MutableReference { /// a structure arbitrarily. pub(crate) fn emplace_map( mut self, - f: impl for<'e> FnOnce(&'e mut T, &mut MutableEmplacerV2<'e, T>) -> O, + f: impl for<'e> FnOnce(&'e mut T, &mut MutableEmplacer<'e, T>) -> O, ) -> O { // SAFETY: The validity + safety invariants are upheld by `ReferenceableCore` // ... assuming this id is marked as a mutable reference for the duration. @@ -20,7 +20,7 @@ impl MutableReference { // - Delegating to the created MutableReference if it is emplaced // - Surviving until Drop at the end of this method if it is not emplaced let copied_mut = unsafe { self.0.pointer.as_mut() }; - let mut emplacer = MutableEmplacerV2(self.0.into_emplacer()); + let mut emplacer = MutableEmplacer(self.0.into_emplacer()); f(copied_mut, &mut emplacer) } @@ -31,7 +31,7 @@ impl MutableReference { self, f: impl FnOnce(&mut T) -> &mut V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> MutableReference { self.emplace_map(move |input, emplacer| { emplacer.emplace(f(input), path_extension, new_span) @@ -45,7 +45,7 @@ impl MutableReference { self, f: impl FnOnce(&mut T) -> Result<&mut V, E>, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> Result, (E, MutableReference)> { self.emplace_map(|input, emplacer| match f(input) { Ok(output) => Ok(emplacer.emplace(output, path_extension, new_span)), @@ -56,12 +56,12 @@ impl MutableReference { impl MutableReference { /// Creates a new MutableReference from an owned value, wrapping it in a Referenceable. - pub(crate) fn new_from_owned(value: AnyValue) -> Self { - let referenceable = Referenceable::new( - value, - "".to_string(), - SpanRange::new_single(Span::call_site()), - ); + pub(crate) fn new_from_owned( + value: AnyValue, + root_name: Option, + span_range: SpanRange, + ) -> Self { + let referenceable = Referenceable::new(value, root_name, span_range); referenceable .new_inactive_mutable() .activate() @@ -70,11 +70,6 @@ impl MutableReference { } impl MutableReference { - /// Returns the current creation span of the tracked reference. - pub(crate) fn current_span(&self) -> SpanRange { - self.0.core.data().for_reference(self.0.id).creation_span - } - /// Disables this mutable reference (bridge for old `disable()` API). /// Equivalent to `deactivate()` in the new naming. pub(crate) fn disable(self) -> InactiveMutableReference { @@ -161,19 +156,13 @@ impl InactiveMutableReference { } } -pub(crate) struct MutableEmplacerV2<'e, T: ?Sized>(EmplacerCore<'e, T>); +pub(crate) struct MutableEmplacer<'e, T: ?Sized>(EmplacerCore<'e, T>); -impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { +impl<'e, T: ?Sized> MutableEmplacer<'e, T> { pub(crate) fn revert(&mut self) -> MutableReference { MutableReference(self.0.revert()) } - /// Returns the current creation span of the tracked reference. - /// Useful for preserving the span during internal type-narrowing operations. - pub(crate) fn current_span(&self) -> SpanRange { - self.0.current_span() - } - /// SAFETY: /// - The caller must ensure that the PathExtension is correct /// (an overly-specific PathExtension may cause safety issues) @@ -181,7 +170,7 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { &mut self, value: &'e mut V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> MutableReference { unsafe { // SAFETY: The lifetime 'e is equal to the &'e content argument in replace @@ -197,7 +186,7 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { &mut self, value: &mut V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> MutableReference { // SAFETY: The pointer is from a reference so non-null let pointer = unsafe { NonNull::new_unchecked(value as *mut V) }; @@ -207,6 +196,3 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } } - -/// Legacy type alias for backward compatibility -pub(crate) type MutableEmplacer<'e, T> = MutableEmplacerV2<'e, T>; diff --git a/src/misc/dynamic_references/reference_core.rs b/src/misc/dynamic_references/reference_core.rs index bc3e5fef..7285f914 100644 --- a/src/misc/dynamic_references/reference_core.rs +++ b/src/misc/dynamic_references/reference_core.rs @@ -90,12 +90,6 @@ impl<'e, T: ?Sized> EmplacerCore<'e, T> { } } - /// Returns the current creation span of the tracked reference. - pub(crate) fn current_span(&self) -> SpanRange { - let inner = self.inner.as_ref().expect("Emplacer already consumed"); - inner.core.data().for_reference(inner.id).creation_span - } - /// SAFETY: /// - The caller must ensure that the pointer is derived from the original content /// - The caller must ensure that the PathExtension is correct @@ -103,7 +97,7 @@ impl<'e, T: ?Sized> EmplacerCore<'e, T> { &mut self, pointer: NonNull, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> ReferenceCore { let emplacer_core = self.take(); let id = emplacer_core.id; diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 6314a3c2..e24c44e4 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -6,7 +6,7 @@ pub(crate) struct Referenceable { } impl Referenceable { - pub(crate) fn new(root: AnyValue, root_name: String, root_span: SpanRange) -> Self { + pub(crate) fn new(root: AnyValue, root_name: Option, root_span: SpanRange) -> Self { Self { core: Rc::new(ReferenceableCore::new(root, root_name, root_span)), } @@ -42,7 +42,7 @@ pub(super) struct ReferenceableCore { } impl ReferenceableCore { - pub(super) fn new(root: T, root_name: String, root_span: SpanRange) -> Self { + pub(super) fn new(root: T, root_name: Option, root_span: SpanRange) -> Self { Self { root: UnsafeCell::new(root), data: RefCell::new(ReferenceableData { @@ -60,10 +60,6 @@ impl ReferenceableCore { } } - pub(super) fn data(&self) -> Ref<'_, ReferenceableData> { - self.data.borrow() - } - pub(super) fn data_mut(&self) -> RefMut<'_, ReferenceableData> { self.data.borrow_mut() } @@ -79,7 +75,7 @@ new_key_type! { pub(super) struct ReferenceableData { // Could be in Referencable Core, but having it here makes the error message API easier - root_name: String, + root_name: Option, // Could be in Referencable Core, but having it here makes the error message API easier root_span: SpanRange, arena: SlotMap, @@ -145,14 +141,14 @@ impl ReferenceableData { Some(reason) => reason, None => continue, }; - let mut error_message = - "Cannot create mutable reference because it clashes with another reference: " - .to_string(); - error_message.push_str(error_reason); - let _ = write!(error_message, "This reference-: "); - self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveMutable)); - let _ = write!(error_message, "Other reference: "); - self.display_path(&mut error_message, other_id, None); + let mut error_message = String::new(); + let _ = write!(error_message, "Cannot create a mutable reference because it clashes with an existing reference:"); + let _ = write!(error_message, "\nThis reference : "); + let _ = + self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveMutable)); + let _ = write!(error_message, "\nOther reference: "); + let _ = self.display_path(&mut error_message, other_id, None); + let _ = write!(error_message, "\nReason : {}\n", error_reason); return data.creation_span.ownership_err(error_message); } } @@ -182,14 +178,14 @@ impl ReferenceableData { Some(reason) => reason, None => continue, }; - let mut error_message = - "Cannot create shared reference because it clashes with a mutable reference: " - .to_string(); - error_message.push_str(error_reason); - let _ = write!(error_message, "This reference-: "); - self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveShared)); - let _ = write!(error_message, "Other reference: "); - self.display_path(&mut error_message, other_id, None); + let mut error_message = String::new(); + let _ = write!(error_message, "Cannot create a shared reference because it clashes with an existing mutable reference:"); + let _ = write!(error_message, "\nThis reference : "); + let _ = + self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveShared)); + let _ = write!(error_message, "\nOther reference: "); + let _ = self.display_path(&mut error_message, other_id, None); + let _ = write!(error_message, "\nReason : {}\n", error_reason); return data.creation_span.ownership_err(error_message); } } @@ -207,10 +203,12 @@ impl ReferenceableData { &mut self, id: LocalReferenceId, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) { let data = self.for_reference_mut(id); - data.creation_span = new_span; + if let Some(span) = new_span { + data.creation_span = span; + } let last_path_part = data.path.parts.last_mut().expect("path is non-empty"); match (last_path_part, path_extension) { (last_path_part, PathExtension::Child(specifier, child_bound_as)) => { @@ -266,7 +264,10 @@ impl ReferenceableData { ReferenceKind::ActiveShared => f.write_str("[*active*] &")?, ReferenceKind::ActiveMutable => f.write_str("[*active*] &mut ")?, } - f.write_str(&self.root_name)?; + match self.root_name.as_ref() { + Some(name) => f.write_str(name)?, + None => f.write_str("[root]")?, + } for part in data.path.parts.iter() { match part { PathPart::Value { bound_as } => { @@ -299,6 +300,8 @@ impl ReferenceableData { pub(super) struct TrackedReference { pub(super) path: ReferencePath, pub(super) reference_kind: ReferenceKind, + /// Storing this span isn't strictly necessary for now... + /// But it's added for a future world where the diagnostic API is stabilized and we can add a diagnostic onto the clashing reference. pub(super) creation_span: SpanRange, } @@ -363,23 +366,27 @@ impl PathComparison { Some(match self { PathComparison::Divergent => return None, PathComparison::Overlapping => { - "they overlap, so mutation may invalidate the other reference" + "mutation may invalidate the other overlapping reference" } PathComparison::RightIsDescendent => { "mutation may invalidate the other descendent reference" } - PathComparison::ReferencesEqual(TypeBindingComparison::RightDerivesFromLeft) => { - "mutation may invalidate the other reference with more specific type" - } - PathComparison::ReferencesEqual(TypeBindingComparison::Incomparable) => { + PathComparison::ReferencesEqual(TypeBindingComparison::RightIsSubtypeOfLeft) + | PathComparison::ReferencesEqual( + TypeBindingComparison::RightDerivesFromLeftButIsNotSubtype, + ) + | PathComparison::ReferencesEqual( + TypeBindingComparison::LeftDerivesFromRightButIsNotSubtype, + ) + | PathComparison::ReferencesEqual(TypeBindingComparison::Incomparable) => { "mutation may invalidate the other reference with an incompatible type" } - // Activated reference is descendent of existing reference + // Mutable reference is a descendent of the other reference PathComparison::ReferencesEqual(TypeBindingComparison::Equal) - | PathComparison::ReferencesEqual(TypeBindingComparison::LeftDerivesFromRight) + | PathComparison::ReferencesEqual(TypeBindingComparison::LeftIsSubtypeOfRight) | PathComparison::LeftIsDescendent => { if other_is_active { - "the mutable reference is observable from the other reference, which breaks aliasing rules" + "mutation may be observed from the other active reference, which breaks aliasing rules" } else { return None; } @@ -434,6 +441,7 @@ enum PathPart { #[derive(PartialEq, Eq, Clone)] pub(crate) enum ChildSpecifier { + #[allow(unused)] // TODO[references]: Use this correctly ArrayChild(usize), ObjectChild(String), } diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 349d6b74..4807fb8f 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -18,7 +18,7 @@ impl SharedReference { /// a structure arbitrarily. pub(crate) fn emplace_map( self, - f: impl for<'e> FnOnce(&'e T, &mut SharedEmplacerV2<'e, T>) -> O, + f: impl for<'e> FnOnce(&'e T, &mut SharedEmplacer<'e, T>) -> O, ) -> O { // SAFETY: The validity + safety invariants are upheld by `ReferenceableCore` // ... assuming this id is marked as a shared reference for the duration. @@ -26,7 +26,7 @@ impl SharedReference { // - Delegating to the created SharedReference if it is emplaced // - Surviving until Drop at the end of this method if it is not emplaced let copied_ref = unsafe { self.0.pointer.as_ref() }; - let mut emplacer = SharedEmplacerV2(self.0.into_emplacer()); + let mut emplacer = SharedEmplacer(self.0.into_emplacer()); f(copied_ref, &mut emplacer) } @@ -37,7 +37,7 @@ impl SharedReference { self, f: impl FnOnce(&T) -> &V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> SharedReference { self.emplace_map(move |input, emplacer| { emplacer.emplace(f(input), path_extension, new_span) @@ -51,7 +51,7 @@ impl SharedReference { self, f: impl FnOnce(&T) -> Result<&V, E>, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> Result, (E, SharedReference)> { self.emplace_map(|input, emplacer| match f(input) { Ok(output) => Ok(emplacer.emplace(output, path_extension, new_span)), @@ -63,12 +63,12 @@ impl SharedReference { impl SharedReference { /// Creates a new SharedReference from an owned value, wrapping it in a Referenceable. /// Uses a placeholder name and span for the Referenceable root. - pub(crate) fn new_from_owned(value: AnyValue) -> Self { - let referenceable = Referenceable::new( - value, - "".to_string(), - SpanRange::new_single(Span::call_site()), - ); + pub(crate) fn new_from_owned( + value: AnyValue, + root_name: Option, + span_range: SpanRange, + ) -> Self { + let referenceable = Referenceable::new(value, root_name, span_range); referenceable .new_inactive_shared() .activate() @@ -82,11 +82,6 @@ impl SharedReference { } impl SharedReference { - /// Returns the current creation span of the tracked reference. - pub(crate) fn current_span(&self) -> SpanRange { - self.0.core.data().for_reference(self.0.id).creation_span - } - /// Disables this shared reference (bridge for old `disable()` API). /// Equivalent to `deactivate()` in the new naming. pub(crate) fn disable(self) -> InactiveSharedReference { @@ -140,19 +135,13 @@ impl InactiveSharedReference { } } -pub(crate) struct SharedEmplacerV2<'e, T: ?Sized>(EmplacerCore<'e, T>); +pub(crate) struct SharedEmplacer<'e, T: ?Sized>(EmplacerCore<'e, T>); -impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { +impl<'e, T: ?Sized> SharedEmplacer<'e, T> { pub(crate) fn revert(&mut self) -> SharedReference { SharedReference(self.0.revert()) } - /// Returns the current creation span of the tracked reference. - /// Useful for preserving the span during internal type-narrowing operations. - pub(crate) fn current_span(&self) -> SpanRange { - self.0.current_span() - } - /// SAFETY: /// - The caller must ensure that the PathExtension is correct /// (an overly-specific PathExtension may cause safety issues) @@ -160,7 +149,7 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { &mut self, value: &'e V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> SharedReference { unsafe { // SAFETY: The lifetime 'e is equal to the &'e content argument in replace @@ -176,7 +165,7 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { &mut self, value: &V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> SharedReference { // SAFETY: The pointer is from a reference so non-null let pointer = unsafe { NonNull::new_unchecked(value as *const V as *mut V) }; @@ -186,6 +175,3 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } } - -/// Legacy type alias for backward compatibility -pub(crate) type SharedEmplacer<'e, T> = SharedEmplacerV2<'e, T>; diff --git a/src/misc/mod.rs b/src/misc/mod.rs index 11b389c5..2ce22658 100644 --- a/src/misc/mod.rs +++ b/src/misc/mod.rs @@ -4,7 +4,6 @@ mod errors; mod field_inputs; mod iterators; mod keywords; -mod mut_rc_ref_cell; mod parse_traits; pub(crate) mod string_conversion; @@ -14,8 +13,6 @@ pub(crate) use errors::*; pub(crate) use field_inputs::*; pub(crate) use iterators::*; pub(crate) use keywords::*; -// Old RefCell-based abstractions - no longer glob-exported; replaced by dynamic_references. -// pub(crate) use mut_rc_ref_cell::*; pub(crate) use parse_traits::*; use crate::internal_prelude::*; diff --git a/src/misc/mut_rc_ref_cell.rs b/src/misc/mut_rc_ref_cell.rs deleted file mode 100644 index 2ff7912a..00000000 --- a/src/misc/mut_rc_ref_cell.rs +++ /dev/null @@ -1,469 +0,0 @@ -#![allow(unused, dead_code)] -// This module is no longer exported - kept for reference during migration. -// All types have been replaced by dynamic_references equivalents. - -use crate::internal_prelude::*; -use std::cell::{BorrowError, BorrowMutError}; -use std::rc::Rc; - -// Local alias to avoid conflict with dynamic_references::Referenceable -type Referenceable = Rc>; - -/// A mutable reference to a sub-value `U` inside a [`Rc>`]. -/// Only one [`MutableSubRcRefCell`] can exist at a time for a given [`Rc>`]. -pub(crate) struct MutableSubRcRefCell { - /// This is actually a reference to the contents of the RefCell - /// but we store it using `unsafe` as `'static`, and use unsafe blocks - /// to ensure it's dropped first. - /// - /// SAFETY: This *must* appear before `pointed_at` so that it's dropped first. - ref_mut: RefMut<'static, U>, - pointed_at: Rc>, -} - -impl MutableSubRcRefCell { - pub(crate) fn new(pointed_at: Rc>) -> Result>> { - let ref_mut = match pointed_at.try_borrow_mut() { - Ok(ref_mut) => { - // SAFETY: We must ensure that this lifetime lives as long as the - // reference to pointed_at (i.e. the RefCell). - // This is guaranteed by the fact that the only time we drop the RefCell - // is when we drop the MutRcRefCell, and we ensure that the RefMut is dropped first. - unsafe { - Some(less_buggy_transmute::< - std::cell::RefMut<'_, T>, - std::cell::RefMut<'static, T>, - >(ref_mut)) - } - } - Err(_) => None, - }; - match ref_mut { - Some(ref_mut) => Ok(Self { - ref_mut, - pointed_at, - }), - None => Err(pointed_at), - } - } - - pub(crate) fn new_from_owned(owned: T) -> Self - where - T: Sized, - { - let rc = Rc::new(RefCell::new(owned)); - Self::new(rc).unwrap_or_else(|_| unreachable!("New refcell must be mut borrowable")) - } -} - -impl MutableSubRcRefCell { - pub(crate) fn into_shared(self) -> SharedSubRcRefCell { - let ptr = self.ref_mut.deref() as *const U; - drop(self.ref_mut); - // SAFETY: - // - The pointer was previously a reference, so it is safe to deference it here - // (the pointer is pointing into the Rc> which hasn't moved) - // - All our invariants for SharedSubRcRefCell / MutableSubRcRefCell are maintained - unsafe { - // The unwrap cannot panic because we just held a mutable borrow, we're not in Sync land, so no-one else can have a borrow. - SharedSubRcRefCell::new(self.pointed_at) - .unwrap_or_else(|_| { - unreachable!("Must be able to create shared after holding mutable") - }) - .map(|_| &*ptr) - } - } - - /// Disables this mutable reference, releasing the borrow on the RefCell. - /// Returns a `DisabledMutableSubRcRefCell` which can be cloned and later re-enabled. - pub(crate) fn disable(self) -> DisabledMutableSubRcRefCell { - let sub_ptr = self.ref_mut.deref() as *const U as *mut U; - // Drop the RefMut to release the borrow - drop(self.ref_mut); - DisabledMutableSubRcRefCell { - pointed_at: self.pointed_at, - sub_ptr, - } - } - - pub(crate) fn map( - self, - f: impl FnOnce(&mut U) -> &mut V, - ) -> MutableSubRcRefCell { - MutableSubRcRefCell { - ref_mut: RefMut::map(self.ref_mut, f), - pointed_at: self.pointed_at, - } - } - - pub(crate) fn map_optional( - self, - f: impl FnOnce(&mut U) -> Option<&mut V>, - ) -> Option> { - Some(MutableSubRcRefCell { - ref_mut: RefMut::filter_map(self.ref_mut, f).ok()?, - pointed_at: self.pointed_at, - }) - } - - pub(crate) fn try_map( - self, - f: impl FnOnce(&mut U) -> Result<&mut V, E>, - ) -> Result, (E, MutableSubRcRefCell)> { - let mut error = None; - let outcome = RefMut::filter_map(self.ref_mut, |inner| match f(inner) { - Ok(value) => Some(value), - Err(e) => { - error = Some(e); - None - } - }); - match outcome { - Ok(ref_mut) => Ok(MutableSubRcRefCell { - ref_mut, - pointed_at: self.pointed_at, - }), - Err(original_ref_mut) => Err(( - error.unwrap(), - MutableSubRcRefCell { - ref_mut: original_ref_mut, - pointed_at: self.pointed_at, - }, - )), - } - } - - pub(crate) fn replace( - mut self, - f: impl for<'a> FnOnce(&'a mut U, &mut MutableSubEmplacer<'a, T, U>) -> O, - ) -> O { - let ref_mut = self.ref_mut.deref_mut() as *mut U; - let mut emplacer = MutableSubEmplacer { - inner: Some(self), - encapsulation_lifetime: std::marker::PhantomData, - }; - f( - // SAFETY: We are cloning a mutable reference here, but it is safe because: - // - What it's pointing at still lives, as RefMut still lives inside emplacer.inner - // - No other "mutable reference" is created from the RefMut except at encapsulation time - unsafe { &mut *ref_mut }, - &mut emplacer, - ) - } -} - -#[allow(unused)] -pub(crate) type MutableEmplacer<'e, U> = MutableSubEmplacer<'e, AnyValue, U>; - -pub(crate) struct MutableSubEmplacer<'e, T: 'static + ?Sized, U: 'static + ?Sized> { - inner: Option>, - encapsulation_lifetime: std::marker::PhantomData<&'e ()>, -} - -impl<'e, T: 'static + ?Sized, U: 'static + ?Sized> MutableSubEmplacer<'e, T, U> { - pub(crate) fn emplace( - &mut self, - value: &'e mut V, - ) -> MutableSubRcRefCell { - unsafe { - // SAFETY: The lifetime 'e is equal to the &'e content argument in replace - // So this guarantees that the returned reference is valid as long as the MutableSubRcRefCell exists - self.emplace_unchecked(value) - } - } - - // SAFETY: - // * The caller must ensure that the value's lifetime is derived from the original content - pub(crate) unsafe fn emplace_unchecked( - &mut self, - value: &mut V, - ) -> MutableSubRcRefCell { - self.inner - .take() - .expect("You can only emplace to create a new Mutable value once") - .map(|_| - // SAFETY: As defined in the rustdoc above - unsafe { less_buggy_transmute::<&mut V, &'static mut V>(value) }) - } -} - -impl DerefMut for MutableSubRcRefCell { - fn deref_mut(&mut self) -> &mut U { - &mut self.ref_mut - } -} - -impl Deref for MutableSubRcRefCell { - type Target = U; - fn deref(&self) -> &U { - &self.ref_mut - } -} - -/// A disabled mutable reference that can be safely cloned and dropped. -/// -/// This type holds just the `Rc` and a raw pointer to the sub-value, -/// without an active borrow on the RefCell. This means: -/// - Dropping is safe (no borrow count to decrement) -/// - Cloning is safe (just clones the Rc and copies the pointer) -/// - The value cannot be accessed until re-enabled -pub(crate) struct DisabledMutableSubRcRefCell { - pointed_at: Rc>, - sub_ptr: *mut U, -} - -impl Clone for DisabledMutableSubRcRefCell { - fn clone(&self) -> Self { - Self { - pointed_at: Rc::clone(&self.pointed_at), - sub_ptr: self.sub_ptr, - } - } -} - -impl DisabledMutableSubRcRefCell { - /// Re-enables this disabled mutable reference by re-acquiring the borrow. - /// - /// Returns an error if the RefCell is currently borrowed. - pub(crate) fn enable(self) -> Result, BorrowMutError> { - let ref_mut = self.pointed_at.try_borrow_mut()?; - // SAFETY: - // - sub_ptr was derived from a valid &mut U inside the RefCell - // - The Rc is still alive, so the RefCell contents haven't moved - // - We just acquired a mutable borrow, so we have exclusive access - let ref_mut = RefMut::map(ref_mut, |_| unsafe { &mut *self.sub_ptr }); - Ok(MutableSubRcRefCell { - ref_mut: unsafe { less_buggy_transmute::, RefMut<'static, U>>(ref_mut) }, - pointed_at: self.pointed_at, - }) - } - - pub(crate) fn into_shared(self) -> DisabledSharedSubRcRefCell { - DisabledSharedSubRcRefCell { - pointed_at: self.pointed_at, - sub_ptr: self.sub_ptr as *const U, - } - } -} - -/// A shared (immutable) reference to a sub-value `U` inside a [`Rc>`]. -/// Many [`SharedSubRcRefCell`] can exist at the same time for a given [`Rc>`], -/// but if any exist, then no [`MutableSubRcRefCell`] can exist. -pub(crate) struct SharedSubRcRefCell { - /// This is actually a reference to the contents of the RefCell - /// but we store it using `unsafe` as `'static`, and use unsafe blocks - /// to ensure it's dropped first. - /// - /// SAFETY: This *must* appear before `pointed_at` so that it's dropped first. - shared_ref: Ref<'static, U>, - pointed_at: Rc>, -} - -impl SharedSubRcRefCell { - pub(crate) fn new(pointed_at: Referenceable) -> Result> { - let shared_ref = match pointed_at.try_borrow() { - Ok(shared_ref) => { - // SAFETY: We must ensure that this lifetime lives as long as the - // reference to pointed_at (i.e. the RefCell). - // This is guaranteed by the fact that the only time we drop the RefCell - // is when we drop the SharedSubRcRefCell, and we ensure that the Ref is dropped first. - unsafe { - Some(less_buggy_transmute::, Ref<'static, T>>( - shared_ref, - )) - } - } - Err(_) => None, - }; - match shared_ref { - Some(shared_ref) => Ok(Self { - shared_ref, - pointed_at, - }), - None => Err(pointed_at), - } - } - - pub(crate) fn new_from_owned(owned: T) -> Self - where - T: Sized, - { - let rc = Rc::new(RefCell::new(owned)); - Self::new(rc).unwrap_or_else(|_| unreachable!("New refcell must be borrowable")) - } -} - -impl SharedSubRcRefCell { - pub(crate) fn clone(this: &SharedSubRcRefCell) -> Self { - Self { - shared_ref: Ref::clone(&this.shared_ref), - pointed_at: Rc::clone(&this.pointed_at), - } - } - - pub(crate) fn map( - self, - f: impl for<'a> FnOnce(&'a U) -> &'a V, - ) -> SharedSubRcRefCell { - SharedSubRcRefCell { - shared_ref: Ref::map(self.shared_ref, f), - pointed_at: self.pointed_at, - } - } - - pub(crate) fn map_optional( - self, - f: impl FnOnce(&U) -> Option<&V>, - ) -> Option> { - Some(SharedSubRcRefCell { - shared_ref: Ref::filter_map(self.shared_ref, f).ok()?, - pointed_at: self.pointed_at, - }) - } - - pub(crate) fn try_map( - self, - f: impl FnOnce(&U) -> Result<&V, E>, - ) -> Result, (E, SharedSubRcRefCell)> { - let mut error = None; - let outcome = Ref::filter_map(self.shared_ref, |inner| match f(inner) { - Ok(value) => Some(value), - Err(e) => { - error = Some(e); - None - } - }); - match outcome { - Ok(shared_ref) => Ok(SharedSubRcRefCell { - shared_ref, - pointed_at: self.pointed_at, - }), - Err(original_shared_ref) => Err(( - error.unwrap(), - SharedSubRcRefCell { - shared_ref: original_shared_ref, - pointed_at: self.pointed_at, - }, - )), - } - } - - pub(crate) fn replace( - self, - f: impl for<'e> FnOnce(&'e U, &mut SharedSubEmplacer<'e, T, U>) -> O, - ) -> O { - let copied_ref = Ref::clone(&self.shared_ref); - let mut emplacer = SharedSubEmplacer { - inner: Some(self), - encapsulation_lifetime: std::marker::PhantomData, - }; - f(&*copied_ref, &mut emplacer) - } - - /// Disables this shared reference, releasing the borrow on the RefCell. - /// Returns a `DisabledSharedSubRcRefCell` which can be cloned and later re-enabled. - pub(crate) fn disable(self) -> DisabledSharedSubRcRefCell { - let sub_ptr = self.shared_ref.deref() as *const U; - // Drop the Ref to release the borrow - drop(self.shared_ref); - DisabledSharedSubRcRefCell { - pointed_at: self.pointed_at, - sub_ptr, - } - } -} - -pub(crate) type SharedEmplacer<'e, U> = SharedSubEmplacer<'e, AnyValue, U>; - -pub(crate) struct SharedSubEmplacer<'e, T: ?Sized, U: 'static + ?Sized> { - inner: Option>, - encapsulation_lifetime: std::marker::PhantomData<&'e ()>, -} - -impl<'e, T: 'static + ?Sized, U: 'static + ?Sized> SharedSubEmplacer<'e, T, U> { - pub(crate) fn emplace( - &mut self, - value: &'e V, - ) -> SharedSubRcRefCell { - unsafe { - // SAFETY: The lifetime 'e is equal to the &'e content argument in replace - // So this guarantees that the returned reference is valid as long as the SharedSubRcRefCell exists - self.emplace_unchecked(value) - } - } - - // SAFETY: - // * The caller must ensure that the value's lifetime is derived from the original content - pub(crate) unsafe fn emplace_unchecked( - &mut self, - value: &V, - ) -> SharedSubRcRefCell { - self.inner - .take() - .expect("You can only emplace to create a new shared value once") - .map(|_| - // SAFETY: As defined in the rustdoc above - unsafe { less_buggy_transmute::<&V, &'static V>(value) }) - } -} - -impl Deref for SharedSubRcRefCell { - type Target = U; - - fn deref(&self) -> &U { - &self.shared_ref - } -} - -/// A disabled shared reference that can be safely cloned and dropped. -/// -/// This type holds just the `Rc` and a raw pointer to the sub-value, -/// without an active borrow on the RefCell. This means: -/// - Dropping is safe (no borrow count to decrement) -/// - Cloning is safe (just clones the Rc and copies the pointer) -/// - The value cannot be accessed until re-enabled -pub(crate) struct DisabledSharedSubRcRefCell { - pointed_at: Rc>, - sub_ptr: *const U, -} - -impl Clone for DisabledSharedSubRcRefCell { - fn clone(&self) -> Self { - Self { - pointed_at: Rc::clone(&self.pointed_at), - sub_ptr: self.sub_ptr, - } - } -} - -impl DisabledSharedSubRcRefCell { - /// Re-enables this disabled shared reference by re-acquiring the borrow. - /// - /// Returns an error if the RefCell is currently mutably borrowed. - pub(crate) fn enable(self) -> Result, BorrowError> { - let shared_ref = self.pointed_at.try_borrow()?; - // SAFETY: - // - sub_ptr was derived from a valid &U inside the RefCell - // - The Rc is still alive, so the RefCell contents haven't moved - // - We just acquired a shared borrow, so the data is valid - let shared_ref = Ref::map(shared_ref, |_| unsafe { &*self.sub_ptr }); - Ok(SharedSubRcRefCell { - shared_ref: unsafe { less_buggy_transmute::, Ref<'static, U>>(shared_ref) }, - pointed_at: self.pointed_at, - }) - } -} - -/// SAFETY: The user must ensure that the two types are transmutable, -/// and in particular are the same size -unsafe fn less_buggy_transmute(t: T) -> U { - // std::mem::transmute::, Ref<'static, T>> for T: ?Sized - // Is fine on latest Rust, but on MSRV only incorrectly flags: - // > error[E0512]: cannot transmute between types of different sizes, or dependently-sized types - // Likely on old versions of Rust, it assumes that Ref is therefore ?Sized (it's not). - // To workaround this, we use a recommendation from https://users.rust-lang.org/t/transmute-doesnt-work-on-generic-types/87272 - // using transmute_copy and manual forgetting - use std::mem::ManuallyDrop; - debug_assert!(std::mem::size_of::() == std::mem::size_of::()); - std::mem::transmute_copy::, U>(&ManuallyDrop::new(t)) -} diff --git a/tests/compilation_failures/expressions/swap_itself.stderr b/tests/compilation_failures/expressions/swap_itself.stderr index c8fab41e..97a8b675 100644 --- a/tests/compilation_failures/expressions/swap_itself.stderr +++ b/tests/compilation_failures/expressions/swap_itself.stderr @@ -1,12 +1,8 @@ -error: Cannot create mutable reference because it clashes with another reference: the mutable reference is observable from the other reference, which breaks aliasing rulesThis reference-: [*active*] &mut (of type any)Other reference: [*active*] &mut (of type any) - --> tests/compilation_failures/expressions/swap_itself.rs:4:13 +error: Cannot create a mutable reference because it clashes with an existing reference: + This reference : [*active*] &mut a (of type any) + Other reference: [*active*] &mut a (of type any) + Reason : mutation may be observed from the other active reference, which breaks aliasing rules + --> tests/compilation_failures/expressions/swap_itself.rs:5:13 | -4 | let _ = run!{ - | _____________^ -5 | | let a = "a"; -6 | | a.swap(a); -7 | | a -8 | | }; - | |_____^ - | - = note: this error originates in the macro `run` (in Nightly builds, run with -Z macro-backtrace for more info) +5 | let a = "a"; + | ^ From 8d5c52b34746434ad52bea52df209a9f84bd0260 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Feb 2026 00:35:54 +0000 Subject: [PATCH 10/25] refactor: Improve shared/mutable reference safety and span threading - Introduce MappedRef and MappedMut types with unsafe constructors to confine unsafety to construction rather than use sites - Update SharedReference/MutableReference map/try_map to accept closures returning MappedRef/MappedMut, making callers safe - Fix &mut aliasing UB in AnyMut::emplace_map by storing *mut T in emplacer state instead of duplicate &mut T - Have PropertyAccessInterface/IndexAccessInterface return MappedRef/MappedMut with proper ChildSpecifier (ObjectChild/ArrayChild) - Add output_span_range to PropertyAccessCallContext/IndexAccessCallContext - Thread SpanRange through DynResolveFrom, leaf_to_dyn, and from_argument_value to eliminate None span arguments - Remove all TODO[references] comments https://claude.ai/code/session_01XaCsWwXFYntkKqZNf8kizU --- src/expressions/concepts/content.rs | 2 +- src/expressions/concepts/form.rs | 8 +- src/expressions/concepts/forms/any_mut.rs | 23 +-- src/expressions/concepts/forms/any_ref.rs | 23 +-- src/expressions/concepts/forms/argument.rs | 2 +- src/expressions/concepts/forms/assignee.rs | 16 +- .../concepts/forms/copy_on_write.rs | 12 +- src/expressions/concepts/forms/mutable.rs | 12 +- src/expressions/concepts/forms/owned.rs | 4 +- src/expressions/concepts/forms/shared.rs | 12 +- src/expressions/concepts/forms/simple_mut.rs | 23 ++- src/expressions/concepts/forms/simple_ref.rs | 16 +- src/expressions/concepts/type_traits.rs | 28 ++- src/expressions/evaluation/value_frames.rs | 60 ++---- src/expressions/type_resolution/arguments.rs | 71 +++---- .../type_resolution/interface_macros.rs | 29 +-- src/expressions/type_resolution/type_data.rs | 28 ++- src/expressions/values/array.rs | 84 ++++---- src/expressions/values/object.rs | 80 +++++--- src/interpretation/bindings.rs | 21 +- src/interpretation/refs.rs | 190 +++++++++++------- .../dynamic_references/mutable_reference.rs | 58 +++--- src/misc/dynamic_references/referenceable.rs | 53 ++++- .../dynamic_references/shared_reference.rs | 58 +++--- .../expressions/swap_itself.stderr | 1 + 25 files changed, 520 insertions(+), 394 deletions(-) diff --git a/src/expressions/concepts/content.rs b/src/expressions/concepts/content.rs index c3502101..16ae7a21 100644 --- a/src/expressions/concepts/content.rs +++ b/src/expressions/concepts/content.rs @@ -213,7 +213,7 @@ impl< type ValueType = T; const OWNERSHIP: ArgumentOwnership = F::ARGUMENT_OWNERSHIP; fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { - let ownership_mapped = F::from_argument_value(value)?; + let ownership_mapped = F::from_argument_value(Spanned(value, span_range))?; let type_mapped = T::resolve(ownership_mapped, span_range, "This argument")?; Ok(X::from_content(type_mapped)) } diff --git a/src/expressions/concepts/form.rs b/src/expressions/concepts/form.rs index 1ebce65f..8a06301d 100644 --- a/src/expressions/concepts/form.rs +++ b/src/expressions/concepts/form.rs @@ -56,7 +56,7 @@ pub(crate) trait IsDynCompatibleForm: IsHierarchicalForm { type DynLeaf<'a, D: IsDynType>; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + leaf: Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn; @@ -65,9 +65,9 @@ pub(crate) trait IsDynCompatibleForm: IsHierarchicalForm { pub(crate) trait MapFromArgument: IsHierarchicalForm { const ARGUMENT_OWNERSHIP: ArgumentOwnership; - // TODO[references]: Have this take a span so that we can store it into the emplacer - fn from_argument_value(value: ArgumentValue) - -> FunctionResult>; + fn from_argument_value( + value: Spanned, + ) -> FunctionResult>; } pub(crate) trait MapIntoReturned: IsHierarchicalForm { diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index 7fcc4389..d6af2ad3 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -28,19 +28,16 @@ impl IsDynCompatibleForm for BeAnyMut { type DynLeaf<'a, D: IsDynType> = AnyMut<'a, D::DynContent>; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, { - leaf.emplace_map(|content, emplacer| { - match ::map_mut(content) { - Ok(mapped) => Ok(unsafe { - // TODO[references]: Pass a span here by propagating from DynResolveFrom - emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), None) - }), - Err(this) => Err(emplacer.revert()), - } + leaf.emplace_map(|content, emplacer| match ::map_mut(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) + }), + Err(_this) => Err(emplacer.revert()), }) } } @@ -61,10 +58,10 @@ impl MapFromArgument for BeAnyMut { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; fn from_argument_value( - value: ArgumentValue, + Spanned(value, span): Spanned, ) -> FunctionResult> { - Ok(value - .expect_mutable() - .emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable_any_mut(emplacer))) + Ok(value.expect_mutable().emplace_map(|inner, emplacer| { + inner.as_mut_value().into_mutable_any_mut(emplacer, span) + })) } } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index f7ed377e..2b82e77d 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -29,19 +29,16 @@ impl IsDynCompatibleForm for BeAnyRef { type DynLeaf<'a, D: IsDynType> = crate::internal_prelude::AnyRef<'a, D::DynContent>; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, { - leaf.emplace_map(|content, emplacer| { - match ::map_ref(content) { - Ok(mapped) => Ok(unsafe { - // TODO[references]: Pass a span here by propagating from DynResolveFrom - emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), None) - }), - Err(this) => Err(emplacer.revert()), - } + leaf.emplace_map(|content, emplacer| match ::map_ref(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) + }), + Err(_this) => Err(emplacer.revert()), }) } } @@ -56,10 +53,10 @@ impl MapFromArgument for BeAnyRef { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; fn from_argument_value( - value: ArgumentValue, + Spanned(value, span): Spanned, ) -> FunctionResult> { - Ok(value - .expect_shared() - .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared_any_ref(emplacer))) + Ok(value.expect_shared().emplace_map(|inner, emplacer| { + inner.as_ref_value().into_shared_any_ref(emplacer, span) + })) } } diff --git a/src/expressions/concepts/forms/argument.rs b/src/expressions/concepts/forms/argument.rs index f7cafdfc..bbe3fda5 100644 --- a/src/expressions/concepts/forms/argument.rs +++ b/src/expressions/concepts/forms/argument.rs @@ -37,7 +37,7 @@ impl MapFromArgument for BeArgument { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::AsIs; fn from_argument_value( - value: ArgumentValue, + Spanned(value, _span): Spanned, ) -> FunctionResult> { todo!("Argument") } diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index ea08247a..08ea7f52 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -31,24 +31,22 @@ impl IsDynCompatibleForm for BeAssignee { type DynLeaf<'a, D: IsDynType> = QqqAssignee; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, { - leaf.0.emplace_map(|content, emplacer| { - match ::map_mut(content) { + leaf.0 + .emplace_map(|content, emplacer| match ::map_mut(content) { Ok(mapped) => Ok(QqqAssignee(unsafe { - // TODO[references]: Pass a span here by propagating from DynResolveFrom emplacer.emplace_unchecked( mapped, PathExtension::Tightened(D::type_kind()), - None, + Some(span), ) })), - Err(this) => Err(QqqAssignee(emplacer.revert())), - } - }) + Err(_this) => Err(QqqAssignee(emplacer.revert())), + }) } } @@ -69,7 +67,7 @@ impl MapFromArgument for BeAssignee { ArgumentOwnership::Assignee { auto_create: false }; fn from_argument_value( - value: ArgumentValue, + Spanned(value, _span): Spanned, ) -> FunctionResult> { Ok(value.expect_assignee().into_content()) } diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index 0cf03e50..34d51f09 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -75,18 +75,20 @@ impl MapFromArgument for BeCopyOnWrite { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::CopyOnWrite; fn from_argument_value( - value: ArgumentValue, + Spanned(value, span): Spanned, ) -> FunctionResult> { match value.expect_copy_on_write().inner { CopyOnWriteInner::Owned(owned) => Ok(BeCopyOnWrite::new_owned(owned)), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - let content = shared - .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + let content = shared.emplace_map(|inner, emplacer| { + inner.as_ref_value().into_shared(emplacer, span) + }); Ok(BeCopyOnWrite::new_shared_in_place_of_owned(content)) } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - let content = shared - .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + let content = shared.emplace_map(|inner, emplacer| { + inner.as_ref_value().into_shared(emplacer, span) + }); Ok(BeCopyOnWrite::new_shared_in_place_of_shared(content)) } } diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index 8c3a1feb..89437c9c 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -31,14 +31,18 @@ impl IsDynCompatibleForm for BeMutable { type DynLeaf<'a, D: IsDynType> = QqqMutable; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, { leaf.emplace_map(|content, emplacer| match ::map_mut(content) { Ok(mapped) => Ok(unsafe { - emplacer.emplace_unchecked(mapped, PathExtension::Tightened(D::type_kind()), None) + emplacer.emplace_unchecked( + mapped, + PathExtension::Tightened(D::type_kind()), + Some(span), + ) }), Err(_) => Err(emplacer.revert()), }) @@ -61,11 +65,11 @@ impl MapFromArgument for BeMutable { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; fn from_argument_value( - value: ArgumentValue, + Spanned(value, span): Spanned, ) -> FunctionResult> { Ok(value .expect_mutable() - .emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer))) + .emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer, span))) } } diff --git a/src/expressions/concepts/forms/owned.rs b/src/expressions/concepts/forms/owned.rs index 4fe3e8a9..b572c53a 100644 --- a/src/expressions/concepts/forms/owned.rs +++ b/src/expressions/concepts/forms/owned.rs @@ -24,7 +24,7 @@ impl IsDynCompatibleForm for BeOwned { type DynLeaf<'a, D: IsDynType> = Box; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, _span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, @@ -49,7 +49,7 @@ impl MapFromArgument for BeOwned { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Owned; fn from_argument_value( - value: ArgumentValue, + Spanned(value, _span): Spanned, ) -> FunctionResult> { Ok(value.expect_owned()) } diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index 1f6cb40f..e3d6f474 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -31,14 +31,18 @@ impl IsDynCompatibleForm for BeShared { type DynLeaf<'a, D: IsDynType> = QqqShared; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, { leaf.emplace_map(|content, emplacer| match ::map_ref(content) { Ok(mapped) => Ok(unsafe { - emplacer.emplace_unchecked(mapped, PathExtension::Tightened(D::type_kind()), None) + emplacer.emplace_unchecked( + mapped, + PathExtension::Tightened(D::type_kind()), + Some(span), + ) }), Err(_) => Err(emplacer.revert()), }) @@ -55,11 +59,11 @@ impl MapFromArgument for BeShared { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; fn from_argument_value( - value: ArgumentValue, + Spanned(value, span): Spanned, ) -> FunctionResult> { Ok(value .expect_shared() - .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer))) + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer, span))) } } diff --git a/src/expressions/concepts/forms/simple_mut.rs b/src/expressions/concepts/forms/simple_mut.rs index 8b82e88b..6eeb78a2 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -32,7 +32,7 @@ impl IsDynCompatibleForm for BeMut { type DynLeaf<'a, D: IsDynType> = &'a mut D::DynContent; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, _span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, @@ -55,12 +55,14 @@ where fn into_mutable<'b, T: 'static>( self, emplacer: &'b mut MutableEmplacer<'a, T>, + span: SpanRange, ) -> Content<'static, Self::Type, BeMutable> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut MutableEmplacer<'e2, X>, + span: SpanRange, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeMutable>; @@ -80,25 +82,26 @@ where self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - // TODO[references]: Pass a span here by propagating from DynResolveFrom - None, + Some(self.span), ) } } }; - let __mapper = __InlineMapper { emplacer }; + let __mapper = __InlineMapper { emplacer, span }; ::map_with::(__mapper, self) } fn into_assignee<'b, T: 'static>( self, emplacer: &'b mut MutableEmplacer<'a, T>, + span: SpanRange, ) -> Content<'static, Self::Type, BeAssignee> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut MutableEmplacer<'e2, X>, + span: SpanRange, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAssignee>; @@ -118,25 +121,26 @@ where QqqAssignee(self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - // TODO[references]: Pass a span here by propagating from DynResolveFrom - None, + Some(self.span), )) } } }; - let __mapper = __InlineMapper { emplacer }; + let __mapper = __InlineMapper { emplacer, span }; ::map_with::(__mapper, self) } fn into_mutable_any_mut<'b, T: 'static>( self, emplacer: &'b mut MutableEmplacer<'a, T>, + span: SpanRange, ) -> Content<'static, Self::Type, BeAnyMut> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut MutableEmplacer<'e2, X>, + span: SpanRange, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAnyMut>; @@ -156,14 +160,13 @@ where self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - // TODO[references]: Pass a span here by propagating from DynResolveFrom - None, + Some(self.span), ) }; mutable_ref.into() } }; - let __mapper = __InlineMapper { emplacer }; + let __mapper = __InlineMapper { emplacer, span }; ::map_with::(__mapper, self) } } diff --git a/src/expressions/concepts/forms/simple_ref.rs b/src/expressions/concepts/forms/simple_ref.rs index 5c740cf6..4047f55b 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -32,7 +32,7 @@ impl IsDynCompatibleForm for BeRef { type DynLeaf<'a, D: IsDynType> = &'a D::DynContent; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, _span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, @@ -55,12 +55,14 @@ where fn into_shared<'b, T: 'static>( self, emplacer: &'b mut SharedEmplacer<'a, T>, + span: SpanRange, ) -> Content<'static, Self::Type, BeShared> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut SharedEmplacer<'e2, X>, + span: SpanRange, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeShared>; @@ -80,25 +82,26 @@ where self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - // TODO[references]: Pass a span here by propagating from DynResolveFrom - None, + Some(self.span), ) } } }; - let __mapper = __InlineMapper { emplacer }; + let __mapper = __InlineMapper { emplacer, span }; ::map_with::(__mapper, self) } fn into_shared_any_ref<'b, T: 'static>( self, emplacer: &'b mut SharedEmplacer<'a, T>, + span: SpanRange, ) -> Content<'static, Self::Type, BeAnyRef> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut SharedEmplacer<'e2, X>, + span: SpanRange, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAnyRef>; @@ -118,14 +121,13 @@ where self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - // TODO[references]: Pass a span here by propagating from DynResolveFrom - None, + Some(self.span), ) }; shared_ref.into() } }; - let __mapper = __InlineMapper { emplacer }; + let __mapper = __InlineMapper { emplacer, span }; ::map_with::(__mapper, self) } } diff --git a/src/expressions/concepts/type_traits.rs b/src/expressions/concepts/type_traits.rs index 55b921f5..52c6f4b9 100644 --- a/src/expressions/concepts/type_traits.rs +++ b/src/expressions/concepts/type_traits.rs @@ -103,9 +103,9 @@ pub(crate) trait DowncastFrom: IsHierarchicalType { } pub(crate) trait DynResolveFrom: IsDynType { - // TODO[references]: Have this take a span so it can propagate to the emplacer fn downcast_from<'a, F: IsHierarchicalForm + IsDynCompatibleForm>( content: Content<'a, T, F>, + span: SpanRange, ) -> Result, Content<'a, T, F>>; fn resolve<'a, F: IsHierarchicalForm + IsDynCompatibleForm>( @@ -113,7 +113,7 @@ pub(crate) trait DynResolveFrom: IsDynType { span_range: SpanRange, resolution_target: &str, ) -> FunctionResult> { - let content = match Self::downcast_from(content) { + let content = match Self::downcast_from(content, span_range) { Ok(c) => c, Err(existing) => { let leaf_kind = T::content_to_leaf_kind::(&existing); @@ -711,11 +711,17 @@ macro_rules! define_leaf_type { pub(crate) use define_leaf_type; -pub(crate) struct DynMapper(std::marker::PhantomData); +pub(crate) struct DynMapper { + _phantom: std::marker::PhantomData, + pub(crate) span: SpanRange, +} impl DynMapper { - pub(crate) const fn new() -> Self { - Self(std::marker::PhantomData) + pub(crate) fn new(span: SpanRange) -> Self { + Self { + _phantom: std::marker::PhantomData, + span, + } } } @@ -816,7 +822,7 @@ macro_rules! define_dyn_type { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Owned; fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { - let form_mapped = BeOwned::from_argument_value(value)?; + let form_mapped = BeOwned::from_argument_value(Spanned(value, span_range))?; <$type_def as DynResolveFrom>::resolve(form_mapped, span_range, "This argument") } } @@ -825,7 +831,7 @@ macro_rules! define_dyn_type { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { - let form_mapped = BeAnyRef::from_argument_value(value)?; + let form_mapped = BeAnyRef::from_argument_value(Spanned(value, span_range))?; <$type_def as DynResolveFrom>::resolve(form_mapped, span_range, "This argument") } } @@ -834,15 +840,15 @@ macro_rules! define_dyn_type { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { - let form_mapped = BeAnyMut::from_argument_value(value)?; + let form_mapped = BeAnyMut::from_argument_value(Spanned(value, span_range))?; <$type_def as DynResolveFrom>::resolve(form_mapped, span_range, "This argument") } } impl DynResolveFrom for $type_def { - fn downcast_from<'a, F: IsHierarchicalForm + IsDynCompatibleForm>(content: Content<'a, T, F>) -> Result, Content<'a, T, F>> { - T::map_with::<'a, F, _>(DynMapper::<$type_def>::new(), content) + fn downcast_from<'a, F: IsHierarchicalForm + IsDynCompatibleForm>(content: Content<'a, T, F>, span: SpanRange) -> Result, Content<'a, T, F>> { + T::map_with::<'a, F, _>(DynMapper::<$type_def>::new(span), content) } } @@ -862,7 +868,7 @@ macro_rules! define_dyn_type { self, leaf: F::Leaf<'a, T>, ) -> Self::Output<'a, T> { - F::leaf_to_dyn(leaf) + F::leaf_to_dyn(Spanned(leaf, self.span)) } } }; diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index 5b20f42c..9ed95f01 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -1054,39 +1054,17 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { let ctx = PropertyAccessCallContext { property: &self.access, + output_span_range: result_span, }; let auto_create = context.requested_ownership().requests_auto_create(); - let property_name_for_shared = property_name.clone(); let mapped = value.expect_any_value_and_map( |shared| { - // SAFETY: Property access navigates to a named child of the source value. - unsafe { - shared - .try_map( - |value| (interface.shared_access)(ctx, value), - PathExtension::Child( - ChildSpecifier::ObjectChild(property_name_for_shared), - AnyType::type_kind(), - ), - Some(result_span), - ) - .map_err(|(e, _)| e) - } - }, - |mutable| { - // SAFETY: Property access navigates to a named child of the source value. - unsafe { - mutable.try_map( - |value| (interface.mutable_access)(ctx, value, auto_create), - PathExtension::Child( - ChildSpecifier::ObjectChild(property_name), - AnyType::type_kind(), - ), - Some(result_span), - ) - } + shared + .try_map(|value| (interface.shared_access)(ctx, value)) + .map_err(|(e, _)| e) }, + |mutable| mutable.try_map(|value| (interface.mutable_access)(ctx, value, auto_create)), |owned| (interface.owned_access)(ctx, owned), )?; @@ -1172,35 +1150,23 @@ impl EvaluationFrame for ValueIndexAccessBuilder { let index = value.expect_shared(); let index = index.as_ref_value().spanned(span); + let result_span = SpanRange::new_between(source_span, self.access.span_range()); let ctx = IndexAccessCallContext { access: &self.access, + output_span_range: result_span, }; let auto_create = context.requested_ownership().requests_auto_create(); - let result_span = SpanRange::new_between(source_span, self.access.span_range()); let result = source.expect_any_value_and_map( |shared| { - // SAFETY: Tightened(AnyType) is conservative for index access. - // A future improvement could derive ChildSpecifier from the index value. - unsafe { - shared - .try_map( - |value| (interface.shared_access)(ctx, value, index), - PathExtension::Tightened(AnyType::type_kind()), - Some(result_span), - ) - .map_err(|(e, _)| e) - } + shared + .try_map(|value| (interface.shared_access)(ctx, value, index)) + .map_err(|(e, _)| e) }, |mutable| { - // SAFETY: Tightened(AnyType) is conservative for index access. - unsafe { - mutable.try_map( - |value| (interface.mutable_access)(ctx, value, index, auto_create), - PathExtension::Tightened(AnyType::type_kind()), - Some(result_span), - ) - } + mutable.try_map(|value| { + (interface.mutable_access)(ctx, value, index, auto_create) + }) }, |owned| (interface.owned_access)(ctx, owned, index), )?; diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index 54d084ac..be59dc96 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -252,25 +252,25 @@ pub(crate) trait ResolvableShared { where Self: 'static, { - // SAFETY: Tightened(T::Type) is correct - unsafe { - value - .try_map( - |v| { - Self::resolve_from_ref( - v, - ResolutionContext { - span_range: &span, - resolution_target, - }, - ) + value + .try_map(|v| { + let resolved = Self::resolve_from_ref( + v, + ResolutionContext { + span_range: &span, + resolution_target, }, - // TODO[references]: Move unsafe to this constructor so the blocks can be smaller - PathExtension::Tightened(T::Type::type_kind()), - Some(span), - ) - .map_err(|(err, _)| err) - } + )?; + // SAFETY: Tightened(T::Type) correctly describes type narrowing + Ok(unsafe { + MappedRef::new( + resolved, + PathExtension::Tightened(T::Type::type_kind()), + span, + ) + }) + }) + .map_err(|(err, _)| err) } fn resolve_ref<'a>( @@ -325,24 +325,25 @@ pub(crate) trait ResolvableMutable { where Self: 'static, { - // SAFETY: Tightened(T::Type) is correct - unsafe { - value - .try_map( - |v| { - Self::resolve_from_mut( - v, - ResolutionContext { - span_range: &span, - resolution_target, - }, - ) + value + .try_map(|v| { + let resolved = Self::resolve_from_mut( + v, + ResolutionContext { + span_range: &span, + resolution_target, }, - PathExtension::Tightened(T::Type::type_kind()), - Some(span), - ) - .map_err(|(err, _)| err) - } + )?; + // SAFETY: Tightened(T::Type) correctly describes type narrowing + Ok(unsafe { + MappedMut::new( + resolved, + PathExtension::Tightened(T::Type::type_kind()), + span, + ) + }) + }) + .map_err(|(err, _)| err) } fn resolve_ref_mut<'a>( diff --git a/src/expressions/type_resolution/interface_macros.rs b/src/expressions/type_resolution/interface_macros.rs index 245b0bb0..f8b1c46f 100644 --- a/src/expressions/type_resolution/interface_macros.rs +++ b/src/expressions/type_resolution/interface_macros.rs @@ -313,10 +313,10 @@ where // ============================================================================ pub(crate) fn apply_property_shared<'a, S: ResolvableShared + ?Sized + 'a>( - f: for<'b> fn(PropertyAccessCallContext, &'b S) -> FunctionResult<&'b AnyValue>, + f: for<'b> fn(PropertyAccessCallContext, &'b S) -> FunctionResult>, ctx: PropertyAccessCallContext, source: &'a AnyValue, -) -> FunctionResult<&'a AnyValue> { +) -> FunctionResult> { let source = S::resolve_from_ref( source, ResolutionContext::new(&ctx.property.span_range(), "The property access source"), @@ -325,11 +325,15 @@ pub(crate) fn apply_property_shared<'a, S: ResolvableShared + ?Sized + } pub(crate) fn apply_property_mutable<'a, S: ResolvableMutable + ?Sized + 'a>( - f: for<'b> fn(PropertyAccessCallContext, &'b mut S, bool) -> FunctionResult<&'b mut AnyValue>, + f: for<'b> fn( + PropertyAccessCallContext, + &'b mut S, + bool, + ) -> FunctionResult>, ctx: PropertyAccessCallContext, source: &'a mut AnyValue, auto_create: bool, -) -> FunctionResult<&'a mut AnyValue> { +) -> FunctionResult> { let source = S::resolve_from_mut( source, ResolutionContext::new(&ctx.property.span_range(), "The property access source"), @@ -358,11 +362,11 @@ pub(crate) fn apply_index_shared<'a, S: ResolvableShared + ?Sized + 'a IndexAccessCallContext, &'b S, Spanned, - ) -> FunctionResult<&'b AnyValue>, + ) -> FunctionResult>, ctx: IndexAccessCallContext, source: &'a AnyValue, index: Spanned, -) -> FunctionResult<&'a AnyValue> { +) -> FunctionResult> { let source = S::resolve_from_ref( source, ResolutionContext::new(&ctx.access.span_range(), "The index access source"), @@ -370,18 +374,19 @@ pub(crate) fn apply_index_shared<'a, S: ResolvableShared + ?Sized + 'a f(ctx, source, index) } +#[allow(clippy::type_complexity)] pub(crate) fn apply_index_mutable<'a, S: ResolvableMutable + ?Sized + 'a>( f: for<'b> fn( IndexAccessCallContext, &'b mut S, Spanned, bool, - ) -> FunctionResult<&'b mut AnyValue>, + ) -> FunctionResult>, ctx: IndexAccessCallContext, source: &'a mut AnyValue, index: Spanned, auto_create: bool, -) -> FunctionResult<&'a mut AnyValue> { +) -> FunctionResult> { let source = S::resolve_from_mut( source, ResolutionContext::new(&ctx.access.span_range(), "The index access source"), @@ -601,9 +606,9 @@ macro_rules! define_type_features { #[allow(unused)] use super::*; - pub(crate) fn shared<'a>(if_empty!([$($property_shared_context)?][_ctx]): PropertyAccessCallContext, $($property_shared_args)*) -> FunctionResult<&'a AnyValue> $property_shared_body + pub(crate) fn shared<'a>(if_empty!([$($property_shared_context)?][_ctx]): PropertyAccessCallContext, $($property_shared_args)*) -> FunctionResult> $property_shared_body - pub(crate) fn mutable<'a>(if_empty!([$($property_mutable_context)?][_ctx]): PropertyAccessCallContext, $($property_mutable_args)*) -> FunctionResult<&'a mut AnyValue> $property_mutable_body + pub(crate) fn mutable<'a>(if_empty!([$($property_mutable_context)?][_ctx]): PropertyAccessCallContext, $($property_mutable_args)*) -> FunctionResult> $property_mutable_body pub(crate) fn owned(if_empty!([$($property_owned_context)?][_ctx]): PropertyAccessCallContext, $($property_owned_args)*) -> FunctionResult $property_owned_body } @@ -622,9 +627,9 @@ macro_rules! define_type_features { #[allow(unused)] use super::*; - pub(crate) fn shared<'a>(if_empty!([$($index_shared_context)?][_ctx]): IndexAccessCallContext, $($index_shared_args)*) -> FunctionResult<&'a AnyValue> $index_shared_body + pub(crate) fn shared<'a>(if_empty!([$($index_shared_context)?][_ctx]): IndexAccessCallContext, $($index_shared_args)*) -> FunctionResult> $index_shared_body - pub(crate) fn mutable<'a>(if_empty!([$($index_mutable_context)?][_ctx]): IndexAccessCallContext, $($index_mutable_args)*) -> FunctionResult<&'a mut AnyValue> $index_mutable_body + pub(crate) fn mutable<'a>(if_empty!([$($index_mutable_context)?][_ctx]): IndexAccessCallContext, $($index_mutable_args)*) -> FunctionResult> $index_mutable_body pub(crate) fn owned(if_empty!([$($index_owned_context)?][_ctx]): IndexAccessCallContext, $($index_owned_args)*) -> FunctionResult $index_owned_body } diff --git a/src/expressions/type_resolution/type_data.rs b/src/expressions/type_resolution/type_data.rs index c31cd275..c678c584 100644 --- a/src/expressions/type_resolution/type_data.rs +++ b/src/expressions/type_resolution/type_data.rs @@ -338,23 +338,28 @@ impl BinaryOperationInterface { #[derive(Clone, Copy)] pub(crate) struct PropertyAccessCallContext<'a> { pub property: &'a PropertyAccess, + pub output_span_range: SpanRange, } /// Interface for property access on a type (e.g., `obj.field`). /// /// Unlike unary/binary operations which return owned values, property access -/// returns references into the source value. This requires three separate -/// access methods for shared, mutable, and owned access patterns. +/// returns references into the source value. The shared and mutable access methods +/// return [`MappedRef`]/[`MappedMut`] which bundle the result with a [`PathExtension`] +/// describing the navigation. pub(crate) struct PropertyAccessInterface { - /// Access a property by shared reference. - pub shared_access: - for<'a> fn(PropertyAccessCallContext, &'a AnyValue) -> FunctionResult<&'a AnyValue>, + /// Access a property by shared reference, returning a [`MappedRef`] with path info. + pub shared_access: for<'a> fn( + PropertyAccessCallContext, + &'a AnyValue, + ) -> FunctionResult>, /// Access a property by mutable reference, optionally auto-creating if missing. + /// Returns a [`MappedMut`] with path info. pub mutable_access: for<'a> fn( PropertyAccessCallContext, &'a mut AnyValue, bool, - ) -> FunctionResult<&'a mut AnyValue>, + ) -> FunctionResult>, /// Extract a property from an owned value. pub owned_access: fn(PropertyAccessCallContext, AnyValue) -> FunctionResult, } @@ -367,28 +372,31 @@ pub(crate) struct PropertyAccessInterface { #[derive(Clone, Copy)] pub(crate) struct IndexAccessCallContext<'a> { pub access: &'a IndexAccess, + pub output_span_range: SpanRange, } /// Interface for index access on a type (e.g., `arr[0]` or `obj["key"]`). /// /// Similar to property access, but the index is an evaluated expression -/// rather than a static identifier. +/// rather than a static identifier. The shared and mutable access methods +/// return [`MappedRef`]/[`MappedMut`] which bundle the result with a [`PathExtension`]. pub(crate) struct IndexAccessInterface { /// The ownership requirement for the index value. pub index_ownership: ArgumentOwnership, - /// Access an element by shared reference. + /// Access an element by shared reference, returning a [`MappedRef`] with path info. pub shared_access: for<'a> fn( IndexAccessCallContext, &'a AnyValue, Spanned, - ) -> FunctionResult<&'a AnyValue>, + ) -> FunctionResult>, /// Access an element by mutable reference, optionally auto-creating if missing. + /// Returns a [`MappedMut`] with path info. pub mutable_access: for<'a> fn( IndexAccessCallContext, &'a mut AnyValue, Spanned, bool, - ) -> FunctionResult<&'a mut AnyValue>, + ) -> FunctionResult>, /// Extract an element from an owned value. pub owned_access: fn(IndexAccessCallContext, AnyValue, Spanned) -> FunctionResult, diff --git a/src/expressions/values/array.rs b/src/expressions/values/array.rs index daf4b5ea..d40ade4c 100644 --- a/src/expressions/values/array.rs +++ b/src/expressions/values/array.rs @@ -59,42 +59,6 @@ impl ArrayValue { }) } - pub(super) fn index_mut( - &mut self, - Spanned(index, span_range): Spanned, - ) -> FunctionResult<&mut AnyValue> { - Ok(match index { - AnyValueContent::Integer(integer) => { - let index = - self.resolve_valid_index_from_integer(Spanned(integer, span_range), false)?; - &mut self.items[index] - } - AnyValueContent::Range(..) => { - // TODO[slice-support] Temporary until we add slice types - we error here - return span_range.ownership_err("Currently, a range-indexed array must be owned. Use `.take()` or `.clone()` before indexing [..]"); - } - _ => return span_range.type_err("The index must be an integer or a range"), - }) - } - - pub(super) fn index_ref( - &self, - Spanned(index, span_range): Spanned, - ) -> FunctionResult<&AnyValue> { - Ok(match index { - AnyValueContent::Integer(integer) => { - let index = - self.resolve_valid_index_from_integer(Spanned(integer, span_range), false)?; - &self.items[index] - } - AnyValueContent::Range(..) => { - // TODO[slice-support] Temporary until we add slice types - we error here - return span_range.ownership_err("Currently, a range-indexed array must be owned. Use `.take()` or `.clone()` before indexing [..]"); - } - _ => return span_range.type_err("The index must be an integer or a range"), - }) - } - pub(super) fn resolve_valid_index( &self, Spanned(index, span_range): Spanned, @@ -233,11 +197,51 @@ define_type_features! { } } index_access(ArrayValue) { - fn shared(source: &'a ArrayValue, index: Spanned) { - source.index_ref(index) + [ctx] fn shared(source: &'a ArrayValue, Spanned(index, span_range): Spanned) { + match index { + AnyValueContent::Integer(integer) => { + let idx = source.resolve_valid_index_from_integer(Spanned(integer, span_range), false)?; + // SAFETY: ArrayChild correctly describes navigating to an array element + Ok(unsafe { + MappedRef::new( + &source.items[idx], + PathExtension::Child( + ChildSpecifier::ArrayChild(idx), + AnyType::type_kind(), + ), + ctx.output_span_range, + ) + }) + } + AnyValueContent::Range(..) => { + // TODO[slice-support] Temporary until we add slice types - we error here + span_range.ownership_err("Currently, a range-indexed array must be owned. Use `.take()` or `.clone()` before indexing [..]") + } + _ => span_range.type_err("The index must be an integer or a range"), + } } - fn mutable(source: &'a mut ArrayValue, index: Spanned, _auto_create: bool) { - source.index_mut(index) + [ctx] fn mutable(source: &'a mut ArrayValue, Spanned(index, span_range): Spanned, _auto_create: bool) { + match index { + AnyValueContent::Integer(integer) => { + let idx = source.resolve_valid_index_from_integer(Spanned(integer, span_range), false)?; + // SAFETY: ArrayChild correctly describes navigating to an array element + Ok(unsafe { + MappedMut::new( + &mut source.items[idx], + PathExtension::Child( + ChildSpecifier::ArrayChild(idx), + AnyType::type_kind(), + ), + ctx.output_span_range, + ) + }) + } + AnyValueContent::Range(..) => { + // TODO[slice-support] Temporary until we add slice types - we error here + span_range.ownership_err("Currently, a range-indexed array must be owned. Use `.take()` or `.clone()` before indexing [..]") + } + _ => span_range.type_err("The index must be an integer or a range"), + } } fn owned(source: ArrayValue, index: Spanned) { source.into_indexed(index) diff --git a/src/expressions/values/object.rs b/src/expressions/values/object.rs index aa8dd59b..e927826f 100644 --- a/src/expressions/values/object.rs +++ b/src/expressions/values/object.rs @@ -72,23 +72,6 @@ impl ObjectValue { } } - pub(super) fn index_mut( - &mut self, - index: Spanned, - auto_create: bool, - ) -> FunctionResult<&mut AnyValue> { - let index: Spanned<&str> = index.downcast_resolve("An object key")?; - self.mut_entry(index.map(|s| s.to_string()), auto_create) - } - - pub(super) fn index_ref(&self, index: Spanned) -> FunctionResult<&AnyValue> { - let key: Spanned<&str> = index.downcast_resolve("An object key")?; - match self.entries.get(*key) { - Some(entry) => Ok(&entry.value), - None => Ok(static_none_ref()), - } - } - pub(super) fn property_mut( &mut self, access: &PropertyAccess, @@ -286,21 +269,72 @@ define_type_features! { } property_access(ObjectValue) { [ctx] fn shared(source: &'a ObjectValue) { - source.property_ref(ctx.property) + let value = source.property_ref(ctx.property)?; + // SAFETY: ObjectChild correctly describes navigating to a named property + Ok(unsafe { + MappedRef::new( + value, + PathExtension::Child( + ChildSpecifier::ObjectChild(ctx.property.property.to_string()), + AnyType::type_kind(), + ), + ctx.output_span_range, + ) + }) } [ctx] fn mutable(source: &'a mut ObjectValue, auto_create: bool) { - source.property_mut(ctx.property, auto_create) + let value = source.property_mut(ctx.property, auto_create)?; + // SAFETY: ObjectChild correctly describes navigating to a named property + Ok(unsafe { + MappedMut::new( + value, + PathExtension::Child( + ChildSpecifier::ObjectChild(ctx.property.property.to_string()), + AnyType::type_kind(), + ), + ctx.output_span_range, + ) + }) } [ctx] fn owned(source: ObjectValue) { source.into_property(ctx.property) } } index_access(ObjectValue) { - fn shared(source: &'a ObjectValue, index: Spanned) { - source.index_ref(index) + [ctx] fn shared(source: &'a ObjectValue, index: Spanned) { + let key: Spanned<&str> = index.downcast_resolve("An object key")?; + let key_string = key.to_string(); + let value = match source.entries.get(*key) { + Some(entry) => &entry.value, + None => static_none_ref(), + }; + // SAFETY: ObjectChild correctly describes navigating to a named key + Ok(unsafe { + MappedRef::new( + value, + PathExtension::Child( + ChildSpecifier::ObjectChild(key_string), + AnyType::type_kind(), + ), + ctx.output_span_range, + ) + }) } - fn mutable(source: &'a mut ObjectValue, index: Spanned, auto_create: bool) { - source.index_mut(index, auto_create) + [ctx] fn mutable(source: &'a mut ObjectValue, index: Spanned, auto_create: bool) { + let key: Spanned<&str> = index.downcast_resolve("An object key")?; + let key_string = key.to_string(); + let value = source.mut_entry(key.map(|s| s.to_string()), auto_create)?; + // SAFETY: ObjectChild correctly describes navigating to a named key + Ok(unsafe { + MappedMut::new( + value, + PathExtension::Child( + ChildSpecifier::ObjectChild(key_string), + AnyType::type_kind(), + ), + ctx.output_span_range, + ) + }) } fn owned(source: ObjectValue, index: Spanned) { source.into_indexed(index) diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index e38d45a8..6678efb9 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -397,8 +397,11 @@ where X::Form: LeafAsMutForm, { fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - self.0 - .emplace_map(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer)) + self.0.emplace_map(|inner, emplacer| { + inner + .as_mut_value() + .into_assignee(emplacer, Span::call_site().span_range()) + }) } } @@ -616,14 +619,20 @@ where AnyLevelCopyOnWrite::::Owned(owned).into_copy_on_write() } CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - let content = shared - .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + let content = shared.emplace_map(|inner, emplacer| { + inner + .as_ref_value() + .into_shared(emplacer, Span::call_site().span_range()) + }); AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(content) .into_copy_on_write() } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - let content = shared - .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + let content = shared.emplace_map(|inner, emplacer| { + inner + .as_ref_value() + .into_shared(emplacer, Span::call_site().span_range()) + }); AnyLevelCopyOnWrite::::SharedWithTransparentCloning(content) .into_copy_on_write() } diff --git a/src/interpretation/refs.rs b/src/interpretation/refs.rs index 54c4e9bf..66817a0f 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -9,45 +9,53 @@ pub(crate) struct AnyRef<'a, T: ?Sized + 'static> { } impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { - /// SAFETY: The caller must ensure the PathExtension is correct. + /// Maps this `AnyRef` using a closure that returns a [`MappedRef`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedRef::new`] constructor. #[allow(unused)] - pub(crate) unsafe fn map( + pub(crate) fn map( self, - f: impl for<'r> FnOnce(&'r T) -> &'r S, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r T) -> MappedRef<'r, S>, ) -> AnyRef<'a, S> { match self.inner { - AnyRefInner::Direct(value) => AnyRef { - inner: AnyRefInner::Direct(f(value)), - }, + AnyRefInner::Direct(value) => { + let mapped = f(value); + AnyRef { + inner: AnyRefInner::Direct(mapped.value), + } + } AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(unsafe { - shared.map(|x| f(x), path_extension, new_span) - }), + inner: AnyRefInner::Encapsulated(shared.map(f)), }, } } - /// SAFETY: The caller must ensure the PathExtension is correct. + /// Maps this `AnyRef` using a closure that returns an optional [`MappedRef`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedRef::new`] constructor. #[allow(unused)] - pub(crate) unsafe fn map_optional( + pub(crate) fn map_optional( self, - f: impl for<'r> FnOnce(&'r T) -> Option<&'r S>, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r T) -> Option>, ) -> Option> { Some(match self.inner { - AnyRefInner::Direct(value) => AnyRef { - inner: AnyRefInner::Direct(f(value)?), - }, + AnyRefInner::Direct(value) => { + let mapped = f(value)?; + AnyRef { + inner: AnyRefInner::Direct(mapped.value), + } + } AnyRefInner::Encapsulated(shared) => AnyRef { inner: AnyRefInner::Encapsulated(shared.emplace_map(|input, emplacer| match f( input, ) { - Some(output) => { - Some(unsafe { emplacer.emplace(output, path_extension, new_span) }) - } + Some(mapped) => Some(unsafe { + emplacer.emplace_unchecked( + mapped.value, + mapped.path_extension, + Some(mapped.span), + ) + }), None => { let _ = emplacer.revert(); None @@ -118,13 +126,13 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { inner: AnyRefInner::Direct(unsafe { transmute::<&V, &'static V>(value) }), }, AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(unsafe { - shared.map( - |_| transmute::<&V, &'static V>(value), + inner: AnyRefInner::Encapsulated(shared.emplace_map(|_, emplacer| unsafe { + emplacer.emplace_unchecked( + transmute::<&V, &'static V>(value), path_extension, new_span, ) - }), + })), }, } } @@ -203,44 +211,52 @@ impl<'a, T: ?Sized> From<&'a mut T> for AnyMut<'a, T> { } impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { - /// SAFETY: The caller must ensure the PathExtension is correct. + /// Maps this `AnyMut` using a closure that returns a [`MappedMut`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedMut::new`] constructor. #[allow(unused)] - pub(crate) unsafe fn map( + pub(crate) fn map( self, - f: impl for<'r> FnOnce(&'r mut T) -> &'r mut S, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r mut T) -> MappedMut<'r, S>, ) -> AnyMut<'a, S> { match self.inner { - AnyMutInner::Direct(value) => AnyMut { - inner: AnyMutInner::Direct(f(value)), - }, + AnyMutInner::Direct(value) => { + let mapped = f(value); + AnyMut { + inner: AnyMutInner::Direct(mapped.value), + } + } AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(unsafe { - mutable.map(|x| f(x), path_extension, new_span) - }), + inner: AnyMutInner::Encapsulated(mutable.map(f)), }, } } - /// SAFETY: The caller must ensure the PathExtension is correct. + /// Maps this `AnyMut` using a closure that returns an optional [`MappedMut`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedMut::new`] constructor. #[allow(unused)] - pub(crate) unsafe fn map_optional( + pub(crate) fn map_optional( self, - f: impl for<'r> FnOnce(&'r mut T) -> Option<&'r mut S>, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r mut T) -> Option>, ) -> Option> { Some(match self.inner { - AnyMutInner::Direct(value) => AnyMut { - inner: AnyMutInner::Direct(f(value)?), - }, + AnyMutInner::Direct(value) => { + let mapped = f(value)?; + AnyMut { + inner: AnyMutInner::Direct(mapped.value), + } + } AnyMutInner::Encapsulated(mutable) => AnyMut { inner: AnyMutInner::Encapsulated(mutable.emplace_map( |input, emplacer| match f(input) { - Some(output) => { - Some(unsafe { emplacer.emplace(output, path_extension, new_span) }) - } + Some(mapped) => Some(unsafe { + emplacer.emplace_unchecked( + mapped.value, + mapped.path_extension, + Some(mapped.span), + ) + }), None => { let _ = emplacer.revert(); None @@ -252,33 +268,63 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { } pub(crate) fn emplace_map( - mut self, + self, f: impl for<'e> FnOnce(&'e mut T, &mut AnyMutEmplacer<'a, 'e, T>) -> O, ) -> O { - let copied_mut = self.deref_mut() as *mut T; - // TODO[references]: Change the emplacer to take a *mut T directly, to avoid the duplicate &mut T - let mut emplacer = AnyMutEmplacer { - inner: Some(self), - encapsulation_lifetime: std::marker::PhantomData, - }; - f( - // SAFETY: We are cloning a mutable reference here, but it is safe because: - // - What it's pointing at still lives, inside emplacer.inner - // - No other "mutable reference" is created except at encapsulation time - unsafe { &mut *copied_mut }, - &mut emplacer, - ) + match self.inner { + AnyMutInner::Direct(direct_mut) => { + // Convert to raw pointer to avoid &mut aliasing: the emplacer + // stores the raw pointer, so no second &mut T exists. + let raw_ptr = direct_mut as *mut T; + let mut emplacer = AnyMutEmplacer { + inner: Some(AnyMutEmplacerState::Direct(raw_ptr, PhantomData)), + encapsulation_lifetime: std::marker::PhantomData, + }; + // SAFETY: raw_ptr was derived from a valid &'a mut T, and the original + // reference was consumed by converting to *mut T. + f(unsafe { &mut *raw_ptr }, &mut emplacer) + } + AnyMutInner::Encapsulated(mut mutable) => { + // MutableReference internally stores NonNull (a raw pointer), + // not &mut T, so there is no aliasing issue. + let raw_ptr = (&mut *mutable) as *mut T; + let mut emplacer = AnyMutEmplacer { + inner: Some(AnyMutEmplacerState::Encapsulated(mutable)), + encapsulation_lifetime: std::marker::PhantomData, + }; + // SAFETY: raw_ptr was derived from the MutableReference which + // uses NonNull internally (not &mut), so no aliasing occurs. + f(unsafe { &mut *raw_ptr }, &mut emplacer) + } + } } } pub(crate) struct AnyMutEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { - inner: Option>, + inner: Option>, encapsulation_lifetime: std::marker::PhantomData<&'e ()>, } +/// Stores either a raw pointer (for Direct) or a MutableReference (for Encapsulated), +/// avoiding &mut aliasing that would occur if we stored the full AnyMut. +enum AnyMutEmplacerState<'a, T: 'static + ?Sized> { + Direct(*mut T, PhantomData<&'a mut T>), + Encapsulated(MutableReference), +} + impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { pub(crate) fn revert(&mut self) -> AnyMut<'a, T> { - self.inner.take().expect("Emplacer already consumed") + let state = self.inner.take().expect("Emplacer already consumed"); + match state { + AnyMutEmplacerState::Direct(ptr, _) => AnyMut { + // SAFETY: ptr was derived from a valid &'a mut T and no other + // &mut T currently exists (the one passed to the closure has ended). + inner: AnyMutInner::Direct(unsafe { &mut *ptr }), + }, + AnyMutEmplacerState::Encapsulated(mutable) => AnyMut { + inner: AnyMutInner::Encapsulated(mutable), + }, + } } /// SAFETY: The caller must ensure the PathExtension is correct. @@ -304,23 +350,23 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { path_extension: PathExtension, new_span: Option, ) -> AnyMut<'a, V> { - let any_mut = self + let state = self .inner .take() .expect("You can only emplace to create a new AnyMut value once"); - match any_mut.inner { - AnyMutInner::Direct(_) => AnyMut { + match state { + AnyMutEmplacerState::Direct(_, _) => AnyMut { // SAFETY: As defined in the rustdoc above inner: AnyMutInner::Direct(unsafe { transmute::<&mut V, &'static mut V>(value) }), }, - AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(unsafe { - mutable.map( - |_| transmute::<&mut V, &'static mut V>(value), + AnyMutEmplacerState::Encapsulated(mutable) => AnyMut { + inner: AnyMutInner::Encapsulated(mutable.emplace_map(|_, emplacer| unsafe { + emplacer.emplace_unchecked( + transmute::<&mut V, &'static mut V>(value), path_extension, new_span, ) - }), + })), }, } } diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index 07427a9e..84158a61 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -24,31 +24,41 @@ impl MutableReference { f(copied_mut, &mut emplacer) } - /// SAFETY: - /// - The caller must ensure that the PathExtension is correct - /// (an overly-specific PathExtension may cause safety issues) - pub(crate) unsafe fn map( + /// Maps this mutable reference using a closure that returns a [`MappedMut`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedMut::new`] constructor, + /// making this method itself safe. + pub(crate) fn map( self, - f: impl FnOnce(&mut T) -> &mut V, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r mut T) -> MappedMut<'r, V>, ) -> MutableReference { self.emplace_map(move |input, emplacer| { - emplacer.emplace(f(input), path_extension, new_span) + let mapped = f(input); + // SAFETY: MappedMut constructor already validated the PathExtension + unsafe { + emplacer.emplace_unchecked(mapped.value, mapped.path_extension, Some(mapped.span)) + } }) } - /// SAFETY: - /// - The caller must ensure that the PathExtension is correct - /// (an overly-specific PathExtension may cause safety issues) - pub(crate) unsafe fn try_map( + /// Fallible version of [`map`](Self::map) that returns the original reference on error. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedMut::new`] constructor. + pub(crate) fn try_map( self, - f: impl FnOnce(&mut T) -> Result<&mut V, E>, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r mut T) -> Result, E>, ) -> Result, (E, MutableReference)> { self.emplace_map(|input, emplacer| match f(input) { - Ok(output) => Ok(emplacer.emplace(output, path_extension, new_span)), + Ok(mapped) => { + // SAFETY: MappedMut constructor already validated the PathExtension + Ok(unsafe { + emplacer.emplace_unchecked( + mapped.value, + mapped.path_extension, + Some(mapped.span), + ) + }) + } Err(e) => Err((e, emplacer.revert())), }) } @@ -163,22 +173,6 @@ impl<'e, T: ?Sized> MutableEmplacer<'e, T> { MutableReference(self.0.revert()) } - /// SAFETY: - /// - The caller must ensure that the PathExtension is correct - /// (an overly-specific PathExtension may cause safety issues) - pub(crate) unsafe fn emplace( - &mut self, - value: &'e mut V, - path_extension: PathExtension, - new_span: Option, - ) -> MutableReference { - unsafe { - // SAFETY: The lifetime 'e is equal to the &'e content argument in replace - // So this guarantees that the returned reference is valid as long as the SharedSubRcRefCell exists - self.emplace_unchecked(value, path_extension, new_span) - } - } - /// SAFETY: /// - The caller must ensure that the value's lifetime is derived from the original content /// - The caller must ensure that the PathExtension is correct diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index e24c44e4..86f1256e 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -433,6 +433,58 @@ pub(crate) enum PathExtension { Tightened(TypeKind), } +/// A mapped shared reference bundled with its [`PathExtension`] and span. +/// +/// Constructing this is unsafe because the caller must ensure the +/// [`PathExtension`] correctly describes the relationship between the +/// source and mapped reference. +pub(crate) struct MappedRef<'a, V: ?Sized> { + pub(crate) value: &'a V, + pub(crate) path_extension: PathExtension, + pub(crate) span: SpanRange, +} + +impl<'a, V: ?Sized> MappedRef<'a, V> { + /// SAFETY: The caller must ensure that the PathExtension correctly describes + /// the navigation from the source reference to this mapped reference. + /// An overly-specific PathExtension may cause safety issues. + pub(crate) unsafe fn new(value: &'a V, path_extension: PathExtension, span: SpanRange) -> Self { + Self { + value, + path_extension, + span, + } + } +} + +/// A mapped mutable reference bundled with its [`PathExtension`] and span. +/// +/// Constructing this is unsafe because the caller must ensure the +/// [`PathExtension`] correctly describes the relationship between the +/// source and mapped reference. +pub(crate) struct MappedMut<'a, V: ?Sized> { + pub(crate) value: &'a mut V, + pub(crate) path_extension: PathExtension, + pub(crate) span: SpanRange, +} + +impl<'a, V: ?Sized> MappedMut<'a, V> { + /// SAFETY: The caller must ensure that the PathExtension correctly describes + /// the navigation from the source reference to this mapped reference. + /// An overly-specific PathExtension may cause safety issues. + pub(crate) unsafe fn new( + value: &'a mut V, + path_extension: PathExtension, + span: SpanRange, + ) -> Self { + Self { + value, + path_extension, + span, + } + } +} + #[derive(PartialEq, Eq, Clone)] enum PathPart { Value { bound_as: TypeKind }, @@ -441,7 +493,6 @@ enum PathPart { #[derive(PartialEq, Eq, Clone)] pub(crate) enum ChildSpecifier { - #[allow(unused)] // TODO[references]: Use this correctly ArrayChild(usize), ObjectChild(String), } diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 4807fb8f..30f813c6 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -30,31 +30,41 @@ impl SharedReference { f(copied_ref, &mut emplacer) } - /// SAFETY: - /// - The caller must ensure that the PathExtension is correct - /// (an overly-specific PathExtension may cause safety issues) - pub(crate) unsafe fn map( + /// Maps this shared reference using a closure that returns a [`MappedRef`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedRef::new`] constructor, + /// making this method itself safe. + pub(crate) fn map( self, - f: impl FnOnce(&T) -> &V, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r T) -> MappedRef<'r, V>, ) -> SharedReference { self.emplace_map(move |input, emplacer| { - emplacer.emplace(f(input), path_extension, new_span) + let mapped = f(input); + // SAFETY: MappedRef constructor already validated the PathExtension + unsafe { + emplacer.emplace_unchecked(mapped.value, mapped.path_extension, Some(mapped.span)) + } }) } - /// SAFETY: - /// - The caller must ensure that the PathExtension is correct - /// (an overly-specific PathExtension may cause safety issues) - pub(crate) unsafe fn try_map( + /// Fallible version of [`map`](Self::map) that returns the original reference on error. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedRef::new`] constructor. + pub(crate) fn try_map( self, - f: impl FnOnce(&T) -> Result<&V, E>, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r T) -> Result, E>, ) -> Result, (E, SharedReference)> { self.emplace_map(|input, emplacer| match f(input) { - Ok(output) => Ok(emplacer.emplace(output, path_extension, new_span)), + Ok(mapped) => { + // SAFETY: MappedRef constructor already validated the PathExtension + Ok(unsafe { + emplacer.emplace_unchecked( + mapped.value, + mapped.path_extension, + Some(mapped.span), + ) + }) + } Err(e) => Err((e, emplacer.revert())), }) } @@ -142,22 +152,6 @@ impl<'e, T: ?Sized> SharedEmplacer<'e, T> { SharedReference(self.0.revert()) } - /// SAFETY: - /// - The caller must ensure that the PathExtension is correct - /// (an overly-specific PathExtension may cause safety issues) - pub(crate) unsafe fn emplace( - &mut self, - value: &'e V, - path_extension: PathExtension, - new_span: Option, - ) -> SharedReference { - unsafe { - // SAFETY: The lifetime 'e is equal to the &'e content argument in replace - // So this guarantees that the returned reference is valid as long as the SharedSubRcRefCell exists - self.emplace_unchecked(value, path_extension, new_span) - } - } - /// SAFETY: /// - The caller must ensure that the value's lifetime is derived from the original content /// - The caller must ensure that the PathExtension is correct diff --git a/tests/compilation_failures/expressions/swap_itself.stderr b/tests/compilation_failures/expressions/swap_itself.stderr index 97a8b675..b9335319 100644 --- a/tests/compilation_failures/expressions/swap_itself.stderr +++ b/tests/compilation_failures/expressions/swap_itself.stderr @@ -2,6 +2,7 @@ error: Cannot create a mutable reference because it clashes with an existing ref This reference : [*active*] &mut a (of type any) Other reference: [*active*] &mut a (of type any) Reason : mutation may be observed from the other active reference, which breaks aliasing rules + --> tests/compilation_failures/expressions/swap_itself.rs:5:13 | 5 | let a = "a"; From d4ab592a6bca909436068e2ba9fd88e6651dd8a3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Feb 2026 01:32:27 +0000 Subject: [PATCH 11/25] refactor: Restore emplace on emplacers and make MappedRef/MappedMut fields private - Make MappedRef/MappedMut fields private, add into_parts() for decomposition. This preserves the invariant that construction must go through the unsafe new() constructor. - Add emplace() method back to SharedEmplacer/MutableEmplacer with lifetime-checked &'e V parameter, keeping emplace_unchecked for cases where the compiler can't prove the lifetime (e.g. __InlineMapper). - Switch callers from emplace_unchecked to emplace where the lifetime is available: SharedReference::map/try_map, MutableReference::map/ try_map, AnyRef::map_optional, AnyMut::map_optional, and leaf_to_dyn implementations in shared.rs, mutable.rs, and assignee.rs. https://claude.ai/code/session_01XaCsWwXFYntkKqZNf8kizU --- src/expressions/concepts/forms/assignee.rs | 6 +-- src/expressions/concepts/forms/mutable.rs | 6 +-- src/expressions/concepts/forms/shared.rs | 6 +-- src/interpretation/refs.rs | 42 +++++++++---------- .../dynamic_references/mutable_reference.rs | 34 +++++++++------ src/misc/dynamic_references/referenceable.rs | 20 ++++++--- .../dynamic_references/shared_reference.rs | 34 +++++++++------ 7 files changed, 79 insertions(+), 69 deletions(-) diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index 08ea7f52..f85a84f6 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -39,11 +39,7 @@ impl IsDynCompatibleForm for BeAssignee { leaf.0 .emplace_map(|content, emplacer| match ::map_mut(content) { Ok(mapped) => Ok(QqqAssignee(unsafe { - emplacer.emplace_unchecked( - mapped, - PathExtension::Tightened(D::type_kind()), - Some(span), - ) + emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) })), Err(_this) => Err(QqqAssignee(emplacer.revert())), }) diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index 89437c9c..5c81337f 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -38,11 +38,7 @@ impl IsDynCompatibleForm for BeMutable { { leaf.emplace_map(|content, emplacer| match ::map_mut(content) { Ok(mapped) => Ok(unsafe { - emplacer.emplace_unchecked( - mapped, - PathExtension::Tightened(D::type_kind()), - Some(span), - ) + emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) }), Err(_) => Err(emplacer.revert()), }) diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index e3d6f474..bbc93072 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -38,11 +38,7 @@ impl IsDynCompatibleForm for BeShared { { leaf.emplace_map(|content, emplacer| match ::map_ref(content) { Ok(mapped) => Ok(unsafe { - emplacer.emplace_unchecked( - mapped, - PathExtension::Tightened(D::type_kind()), - Some(span), - ) + emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) }), Err(_) => Err(emplacer.revert()), }) diff --git a/src/interpretation/refs.rs b/src/interpretation/refs.rs index 66817a0f..2f7c0f98 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -19,9 +19,9 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { ) -> AnyRef<'a, S> { match self.inner { AnyRefInner::Direct(value) => { - let mapped = f(value); + let (value, _path_extension, _span) = f(value).into_parts(); AnyRef { - inner: AnyRefInner::Direct(mapped.value), + inner: AnyRefInner::Direct(value), } } AnyRefInner::Encapsulated(shared) => AnyRef { @@ -40,22 +40,21 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { ) -> Option> { Some(match self.inner { AnyRefInner::Direct(value) => { - let mapped = f(value)?; + let (value, _path_extension, _span) = f(value)?.into_parts(); AnyRef { - inner: AnyRefInner::Direct(mapped.value), + inner: AnyRefInner::Direct(value), } } AnyRefInner::Encapsulated(shared) => AnyRef { inner: AnyRefInner::Encapsulated(shared.emplace_map(|input, emplacer| match f( input, ) { - Some(mapped) => Some(unsafe { - emplacer.emplace_unchecked( - mapped.value, - mapped.path_extension, - Some(mapped.span), - ) - }), + Some(mapped) => { + let (value, path_extension, span) = mapped.into_parts(); + // SAFETY: PathExtension validated by MappedRef constructor. + // Lifetime checked by emplace (value: &'e S). + Some(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) + } None => { let _ = emplacer.revert(); None @@ -221,9 +220,9 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { ) -> AnyMut<'a, S> { match self.inner { AnyMutInner::Direct(value) => { - let mapped = f(value); + let (value, _path_extension, _span) = f(value).into_parts(); AnyMut { - inner: AnyMutInner::Direct(mapped.value), + inner: AnyMutInner::Direct(value), } } AnyMutInner::Encapsulated(mutable) => AnyMut { @@ -242,21 +241,20 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { ) -> Option> { Some(match self.inner { AnyMutInner::Direct(value) => { - let mapped = f(value)?; + let (value, _path_extension, _span) = f(value)?.into_parts(); AnyMut { - inner: AnyMutInner::Direct(mapped.value), + inner: AnyMutInner::Direct(value), } } AnyMutInner::Encapsulated(mutable) => AnyMut { inner: AnyMutInner::Encapsulated(mutable.emplace_map( |input, emplacer| match f(input) { - Some(mapped) => Some(unsafe { - emplacer.emplace_unchecked( - mapped.value, - mapped.path_extension, - Some(mapped.span), - ) - }), + Some(mapped) => { + let (value, path_extension, span) = mapped.into_parts(); + // SAFETY: PathExtension validated by MappedMut constructor. + // Lifetime checked by emplace (value: &'e mut S). + Some(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) + } None => { let _ = emplacer.revert(); None diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index 84158a61..8350cd26 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -33,11 +33,10 @@ impl MutableReference { f: impl for<'r> FnOnce(&'r mut T) -> MappedMut<'r, V>, ) -> MutableReference { self.emplace_map(move |input, emplacer| { - let mapped = f(input); - // SAFETY: MappedMut constructor already validated the PathExtension - unsafe { - emplacer.emplace_unchecked(mapped.value, mapped.path_extension, Some(mapped.span)) - } + let (value, path_extension, span) = f(input).into_parts(); + // SAFETY: MappedMut constructor already validated the PathExtension. + // The lifetime is checked by emplace (value: &'e mut V). + unsafe { emplacer.emplace(value, path_extension, Some(span)) } }) } @@ -50,14 +49,10 @@ impl MutableReference { ) -> Result, (E, MutableReference)> { self.emplace_map(|input, emplacer| match f(input) { Ok(mapped) => { - // SAFETY: MappedMut constructor already validated the PathExtension - Ok(unsafe { - emplacer.emplace_unchecked( - mapped.value, - mapped.path_extension, - Some(mapped.span), - ) - }) + let (value, path_extension, span) = mapped.into_parts(); + // SAFETY: MappedMut constructor already validated the PathExtension. + // The lifetime is checked by emplace (value: &'e mut V). + Ok(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) } Err(e) => Err((e, emplacer.revert())), }) @@ -173,6 +168,19 @@ impl<'e, T: ?Sized> MutableEmplacer<'e, T> { MutableReference(self.0.revert()) } + /// SAFETY: The caller must ensure that the PathExtension is correct. + /// + /// The lifetime `'e` is checked by the compiler, ensuring the value is derived + /// from the original content. + pub(crate) unsafe fn emplace( + &mut self, + value: &'e mut V, + path_extension: PathExtension, + new_span: Option, + ) -> MutableReference { + unsafe { self.emplace_unchecked(value, path_extension, new_span) } + } + /// SAFETY: /// - The caller must ensure that the value's lifetime is derived from the original content /// - The caller must ensure that the PathExtension is correct diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 86f1256e..b680a2a1 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -439,9 +439,9 @@ pub(crate) enum PathExtension { /// [`PathExtension`] correctly describes the relationship between the /// source and mapped reference. pub(crate) struct MappedRef<'a, V: ?Sized> { - pub(crate) value: &'a V, - pub(crate) path_extension: PathExtension, - pub(crate) span: SpanRange, + value: &'a V, + path_extension: PathExtension, + span: SpanRange, } impl<'a, V: ?Sized> MappedRef<'a, V> { @@ -455,6 +455,10 @@ impl<'a, V: ?Sized> MappedRef<'a, V> { span, } } + + pub(crate) fn into_parts(self) -> (&'a V, PathExtension, SpanRange) { + (self.value, self.path_extension, self.span) + } } /// A mapped mutable reference bundled with its [`PathExtension`] and span. @@ -463,9 +467,9 @@ impl<'a, V: ?Sized> MappedRef<'a, V> { /// [`PathExtension`] correctly describes the relationship between the /// source and mapped reference. pub(crate) struct MappedMut<'a, V: ?Sized> { - pub(crate) value: &'a mut V, - pub(crate) path_extension: PathExtension, - pub(crate) span: SpanRange, + value: &'a mut V, + path_extension: PathExtension, + span: SpanRange, } impl<'a, V: ?Sized> MappedMut<'a, V> { @@ -483,6 +487,10 @@ impl<'a, V: ?Sized> MappedMut<'a, V> { span, } } + + pub(crate) fn into_parts(self) -> (&'a mut V, PathExtension, SpanRange) { + (self.value, self.path_extension, self.span) + } } #[derive(PartialEq, Eq, Clone)] diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 30f813c6..918ffe33 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -39,11 +39,10 @@ impl SharedReference { f: impl for<'r> FnOnce(&'r T) -> MappedRef<'r, V>, ) -> SharedReference { self.emplace_map(move |input, emplacer| { - let mapped = f(input); - // SAFETY: MappedRef constructor already validated the PathExtension - unsafe { - emplacer.emplace_unchecked(mapped.value, mapped.path_extension, Some(mapped.span)) - } + let (value, path_extension, span) = f(input).into_parts(); + // SAFETY: MappedRef constructor already validated the PathExtension. + // The lifetime is checked by emplace (value: &'e V). + unsafe { emplacer.emplace(value, path_extension, Some(span)) } }) } @@ -56,14 +55,10 @@ impl SharedReference { ) -> Result, (E, SharedReference)> { self.emplace_map(|input, emplacer| match f(input) { Ok(mapped) => { - // SAFETY: MappedRef constructor already validated the PathExtension - Ok(unsafe { - emplacer.emplace_unchecked( - mapped.value, - mapped.path_extension, - Some(mapped.span), - ) - }) + let (value, path_extension, span) = mapped.into_parts(); + // SAFETY: MappedRef constructor already validated the PathExtension. + // The lifetime is checked by emplace (value: &'e V). + Ok(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) } Err(e) => Err((e, emplacer.revert())), }) @@ -152,6 +147,19 @@ impl<'e, T: ?Sized> SharedEmplacer<'e, T> { SharedReference(self.0.revert()) } + /// SAFETY: The caller must ensure that the PathExtension is correct. + /// + /// The lifetime `'e` is checked by the compiler, ensuring the value is derived + /// from the original content. + pub(crate) unsafe fn emplace( + &mut self, + value: &'e V, + path_extension: PathExtension, + new_span: Option, + ) -> SharedReference { + unsafe { self.emplace_unchecked(value, path_extension, new_span) } + } + /// SAFETY: /// - The caller must ensure that the value's lifetime is derived from the original content /// - The caller must ensure that the PathExtension is correct From 7f03b272c9da45724e9f6508c4105493b9867fca Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Feb 2026 01:59:22 +0000 Subject: [PATCH 12/25] refactor: Make emplace methods safe by taking MappedRef/MappedMut directly - Move MappedRef to shared_reference.rs and MappedMut to mutable_reference.rs - Add new_unchecked constructors for lifetime transmute cases (__InlineMapper) - Change all emplacer emplace methods to take MappedRef/MappedMut, making them safe - Drop emplace_unchecked from all emplacers (SharedEmplacer, MutableEmplacer, AnyRefEmplacer, AnyMutEmplacer) - All unsafe is now confined to MappedRef/MappedMut constructors https://claude.ai/code/session_01XaCsWwXFYntkKqZNf8kizU --- src/expressions/concepts/forms/any_mut.rs | 10 +- src/expressions/concepts/forms/any_ref.rs | 10 +- src/expressions/concepts/forms/assignee.rs | 10 +- src/expressions/concepts/forms/mutable.rs | 10 +- src/expressions/concepts/forms/shared.rs | 10 +- src/expressions/concepts/forms/simple_mut.rs | 36 ++++--- src/expressions/concepts/forms/simple_ref.rs | 22 ++-- src/interpretation/refs.rs | 102 ++++++------------ .../dynamic_references/mutable_reference.rs | 102 +++++++++++------- src/misc/dynamic_references/referenceable.rs | 60 ----------- .../dynamic_references/shared_reference.rs | 98 ++++++++++------- 11 files changed, 227 insertions(+), 243 deletions(-) diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index d6af2ad3..5a867a9c 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -34,9 +34,13 @@ impl IsDynCompatibleForm for BeAnyMut { T::Leaf: CastDyn, { leaf.emplace_map(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(unsafe { - emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) - }), + Ok(mapped) => { + // SAFETY: PathExtension::Tightened correctly describes type narrowing + let mapped_mut = unsafe { + MappedMut::new(mapped, PathExtension::Tightened(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_mut)) + } Err(_this) => Err(emplacer.revert()), }) } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index 2b82e77d..e67f3704 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -35,9 +35,13 @@ impl IsDynCompatibleForm for BeAnyRef { T::Leaf: CastDyn, { leaf.emplace_map(|content, emplacer| match ::map_ref(content) { - Ok(mapped) => Ok(unsafe { - emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) - }), + Ok(mapped) => { + // SAFETY: PathExtension::Tightened correctly describes type narrowing + let mapped_ref = unsafe { + MappedRef::new(mapped, PathExtension::Tightened(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_ref)) + } Err(_this) => Err(emplacer.revert()), }) } diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index f85a84f6..1c02fc80 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -38,9 +38,13 @@ impl IsDynCompatibleForm for BeAssignee { { leaf.0 .emplace_map(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(QqqAssignee(unsafe { - emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) - })), + Ok(mapped) => { + // SAFETY: PathExtension::Tightened correctly describes type narrowing + let mapped_mut = unsafe { + MappedMut::new(mapped, PathExtension::Tightened(D::type_kind()), span) + }; + Ok(QqqAssignee(emplacer.emplace(mapped_mut))) + } Err(_this) => Err(QqqAssignee(emplacer.revert())), }) } diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index 5c81337f..ac40ba80 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -37,9 +37,13 @@ impl IsDynCompatibleForm for BeMutable { T::Leaf: CastDyn, { leaf.emplace_map(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(unsafe { - emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) - }), + Ok(mapped) => { + // SAFETY: PathExtension::Tightened correctly describes type narrowing + let mapped_mut = unsafe { + MappedMut::new(mapped, PathExtension::Tightened(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_mut)) + } Err(_) => Err(emplacer.revert()), }) } diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index bbc93072..baf3cc1e 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -37,9 +37,13 @@ impl IsDynCompatibleForm for BeShared { T::Leaf: CastDyn, { leaf.emplace_map(|content, emplacer| match ::map_ref(content) { - Ok(mapped) => Ok(unsafe { - emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) - }), + Ok(mapped) => { + // SAFETY: PathExtension::Tightened correctly describes type narrowing + let mapped_ref = unsafe { + MappedRef::new(mapped, PathExtension::Tightened(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_ref)) + } Err(_) => Err(emplacer.revert()), }) } diff --git a/src/expressions/concepts/forms/simple_mut.rs b/src/expressions/concepts/forms/simple_mut.rs index 6eeb78a2..f87fe41d 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -77,14 +77,16 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { - self.emplacer.emplace_unchecked( + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension::Tightened correctly describes type narrowing + let mapped = unsafe { + MappedMut::new_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - Some(self.span), + self.span, ) - } + }; + self.emplacer.emplace(mapped) } }; let __mapper = __InlineMapper { emplacer, span }; @@ -116,14 +118,16 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { - QqqAssignee(self.emplacer.emplace_unchecked( + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension::Tightened correctly describes type narrowing + let mapped = unsafe { + MappedMut::new_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - Some(self.span), - )) - } + self.span, + ) + }; + QqqAssignee(self.emplacer.emplace(mapped)) } }; let __mapper = __InlineMapper { emplacer, span }; @@ -155,14 +159,16 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - let mutable_ref: MutableReference<_> = unsafe { - self.emplacer.emplace_unchecked( + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension::Tightened correctly describes type narrowing + let mapped = unsafe { + MappedMut::new_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - Some(self.span), + self.span, ) }; + let mutable_ref: MutableReference<_> = self.emplacer.emplace(mapped); mutable_ref.into() } }; diff --git a/src/expressions/concepts/forms/simple_ref.rs b/src/expressions/concepts/forms/simple_ref.rs index 4047f55b..00e7ff7e 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -77,14 +77,16 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { - self.emplacer.emplace_unchecked( + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension::Tightened correctly describes type narrowing + let mapped = unsafe { + MappedRef::new_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - Some(self.span), + self.span, ) - } + }; + self.emplacer.emplace(mapped) } }; let __mapper = __InlineMapper { emplacer, span }; @@ -116,14 +118,16 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - let shared_ref: SharedReference<_> = unsafe { - self.emplacer.emplace_unchecked( + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension::Tightened correctly describes type narrowing + let mapped = unsafe { + MappedRef::new_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - Some(self.span), + self.span, ) }; + let shared_ref: SharedReference<_> = self.emplacer.emplace(mapped); shared_ref.into() } }; diff --git a/src/interpretation/refs.rs b/src/interpretation/refs.rs index 2f7c0f98..bf8faa57 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -1,5 +1,3 @@ -use std::mem::transmute; - use super::*; /// A flexible type which can either be a reference to a value of type `T`, @@ -49,12 +47,7 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { inner: AnyRefInner::Encapsulated(shared.emplace_map(|input, emplacer| match f( input, ) { - Some(mapped) => { - let (value, path_extension, span) = mapped.into_parts(); - // SAFETY: PathExtension validated by MappedRef constructor. - // Lifetime checked by emplace (value: &'e S). - Some(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) - } + Some(mapped) => Some(emplacer.emplace(mapped)), None => { let _ = emplacer.revert(); None @@ -92,45 +85,30 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { self.inner.take().expect("Emplacer already consumed") } - /// SAFETY: The caller must ensure the PathExtension is correct. - pub(crate) unsafe fn emplace( - &mut self, - value: &'e V, - path_extension: PathExtension, - new_span: Option, - ) -> AnyRef<'a, V> { - unsafe { - // SAFETY: The lifetime 'e is equal to the &'e content argument in replace - // So this guarantees that the returned reference is valid as long as the AnyRef exists - self.emplace_unchecked(value, path_extension, new_span) - } - } - - /// SAFETY: - /// - The caller must ensure that the value's lifetime is derived from the original content - /// - The caller must ensure the PathExtension is correct - pub(crate) unsafe fn emplace_unchecked( + /// Emplaces a mapped shared reference, consuming the emplacer's reference tracking. + /// + /// This is safe because all preconditions (correct PathExtension and valid reference + /// derivation) are validated by the [`MappedRef`] constructor. + pub(crate) fn emplace( &mut self, - value: &V, - path_extension: PathExtension, - new_span: Option, + mapped: MappedRef<'e, V>, ) -> AnyRef<'a, V> { let any_ref = self .inner .take() .expect("You can only emplace to create a new AnyRef value once"); + let (value, path_extension, span) = mapped.into_parts(); match any_ref.inner { AnyRefInner::Direct(_) => AnyRef { - // SAFETY: As defined in the rustdoc above - inner: AnyRefInner::Direct(unsafe { transmute::<&V, &'static V>(value) }), + // 'e: 'a is guaranteed by the struct constraint, so coercion is valid + inner: AnyRefInner::Direct(value), }, AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.emplace_map(|_, emplacer| unsafe { - emplacer.emplace_unchecked( - transmute::<&V, &'static V>(value), - path_extension, - new_span, - ) + inner: AnyRefInner::Encapsulated(shared.emplace_map(|_, emplacer| { + // SAFETY: The value's lifetime ('e) matches the SharedEmplacer's lifetime + // since both are derived from the same underlying data + let remapped = unsafe { MappedRef::new_unchecked(value, path_extension, span) }; + emplacer.emplace(remapped) })), }, } @@ -249,12 +227,7 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { AnyMutInner::Encapsulated(mutable) => AnyMut { inner: AnyMutInner::Encapsulated(mutable.emplace_map( |input, emplacer| match f(input) { - Some(mapped) => { - let (value, path_extension, span) = mapped.into_parts(); - // SAFETY: PathExtension validated by MappedMut constructor. - // Lifetime checked by emplace (value: &'e mut S). - Some(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) - } + Some(mapped) => Some(emplacer.emplace(mapped)), None => { let _ = emplacer.revert(); None @@ -325,45 +298,30 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { } } - /// SAFETY: The caller must ensure the PathExtension is correct. - pub(crate) unsafe fn emplace( - &mut self, - value: &'e mut V, - path_extension: PathExtension, - new_span: Option, - ) -> AnyMut<'a, V> { - unsafe { - // SAFETY: The lifetime 'e is equal to the &'e content argument in replace - // So this guarantees that the returned reference is valid as long as the AnyMut exists - self.emplace_unchecked(value, path_extension, new_span) - } - } - - /// SAFETY: - /// - The caller must ensure that the value's lifetime is derived from the original content - /// - The caller must ensure the PathExtension is correct - pub(crate) unsafe fn emplace_unchecked( + /// Emplaces a mapped mutable reference, consuming the emplacer's reference tracking. + /// + /// This is safe because all preconditions (correct PathExtension and valid reference + /// derivation) are validated by the [`MappedMut`] constructor. + pub(crate) fn emplace( &mut self, - value: &mut V, - path_extension: PathExtension, - new_span: Option, + mapped: MappedMut<'e, V>, ) -> AnyMut<'a, V> { let state = self .inner .take() .expect("You can only emplace to create a new AnyMut value once"); + let (value, path_extension, span) = mapped.into_parts(); match state { AnyMutEmplacerState::Direct(_, _) => AnyMut { - // SAFETY: As defined in the rustdoc above - inner: AnyMutInner::Direct(unsafe { transmute::<&mut V, &'static mut V>(value) }), + // 'e: 'a is guaranteed by the struct constraint, so coercion is valid + inner: AnyMutInner::Direct(value), }, AnyMutEmplacerState::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.emplace_map(|_, emplacer| unsafe { - emplacer.emplace_unchecked( - transmute::<&mut V, &'static mut V>(value), - path_extension, - new_span, - ) + inner: AnyMutInner::Encapsulated(mutable.emplace_map(|_, emplacer| { + // SAFETY: The value's lifetime ('e) matches the MutableEmplacer's lifetime + // since both are derived from the same underlying data + let remapped = unsafe { MappedMut::new_unchecked(value, path_extension, span) }; + emplacer.emplace(remapped) })), }, } diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index 8350cd26..0ce3bcef 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -1,3 +1,5 @@ +use std::mem::transmute; + use super::*; pub(crate) struct MutableReference(pub(super) ReferenceCore); @@ -32,12 +34,7 @@ impl MutableReference { self, f: impl for<'r> FnOnce(&'r mut T) -> MappedMut<'r, V>, ) -> MutableReference { - self.emplace_map(move |input, emplacer| { - let (value, path_extension, span) = f(input).into_parts(); - // SAFETY: MappedMut constructor already validated the PathExtension. - // The lifetime is checked by emplace (value: &'e mut V). - unsafe { emplacer.emplace(value, path_extension, Some(span)) } - }) + self.emplace_map(move |input, emplacer| emplacer.emplace(f(input))) } /// Fallible version of [`map`](Self::map) that returns the original reference on error. @@ -48,12 +45,7 @@ impl MutableReference { f: impl for<'r> FnOnce(&'r mut T) -> Result, E>, ) -> Result, (E, MutableReference)> { self.emplace_map(|input, emplacer| match f(input) { - Ok(mapped) => { - let (value, path_extension, span) = mapped.into_parts(); - // SAFETY: MappedMut constructor already validated the PathExtension. - // The lifetime is checked by emplace (value: &'e mut V). - Ok(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) - } + Ok(mapped) => Ok(emplacer.emplace(mapped)), Err(e) => Err((e, emplacer.revert())), }) } @@ -161,6 +153,54 @@ impl InactiveMutableReference { } } +/// A mapped mutable reference bundled with its [`PathExtension`] and span. +/// +/// Constructing this is unsafe because the caller must ensure the +/// [`PathExtension`] correctly describes the relationship between the +/// source and mapped reference. +pub(crate) struct MappedMut<'a, V: ?Sized> { + value: &'a mut V, + path_extension: PathExtension, + span: SpanRange, +} + +impl<'a, V: ?Sized> MappedMut<'a, V> { + /// SAFETY: The caller must ensure that the PathExtension correctly describes + /// the navigation from the source reference to this mapped reference. + /// An overly-specific PathExtension may cause safety issues. + pub(crate) unsafe fn new( + value: &'a mut V, + path_extension: PathExtension, + span: SpanRange, + ) -> Self { + Self { + value, + path_extension, + span, + } + } + + /// SAFETY: In addition to the safety requirements of [`new`](Self::new), the caller + /// must ensure that the reference lifetime is valid for the target lifetime `'a`. + /// This is needed when the compiler cannot prove the lifetime relationship + /// (e.g. in LeafMapper implementations where `'l` and `'e` cannot be unified). + pub(crate) unsafe fn new_unchecked<'any>( + value: &'any mut V, + path_extension: PathExtension, + span: SpanRange, + ) -> Self { + Self { + value: unsafe { transmute::<&'any mut V, &'a mut V>(value) }, + path_extension, + span, + } + } + + pub(crate) fn into_parts(self) -> (&'a mut V, PathExtension, SpanRange) { + (self.value, self.path_extension, self.span) + } +} + pub(crate) struct MutableEmplacer<'e, T: ?Sized>(EmplacerCore<'e, T>); impl<'e, T: ?Sized> MutableEmplacer<'e, T> { @@ -168,33 +208,23 @@ impl<'e, T: ?Sized> MutableEmplacer<'e, T> { MutableReference(self.0.revert()) } - /// SAFETY: The caller must ensure that the PathExtension is correct. + /// Emplaces a mapped mutable reference, consuming the emplacer's reference tracking. /// - /// The lifetime `'e` is checked by the compiler, ensuring the value is derived - /// from the original content. - pub(crate) unsafe fn emplace( + /// This is safe because all preconditions (correct PathExtension and valid reference + /// derivation) are validated by the [`MappedMut`] constructor. + pub(crate) fn emplace( &mut self, - value: &'e mut V, - path_extension: PathExtension, - new_span: Option, - ) -> MutableReference { - unsafe { self.emplace_unchecked(value, path_extension, new_span) } - } - - /// SAFETY: - /// - The caller must ensure that the value's lifetime is derived from the original content - /// - The caller must ensure that the PathExtension is correct - pub(crate) unsafe fn emplace_unchecked( - &mut self, - value: &mut V, - path_extension: PathExtension, - new_span: Option, + mapped: MappedMut<'e, V>, ) -> MutableReference { - // SAFETY: The pointer is from a reference so non-null + let (value, path_extension, span) = mapped.into_parts(); + // SAFETY: The pointer is from a valid reference (guaranteed by MappedMut constructor), + // and the PathExtension was validated by the MappedMut constructor. let pointer = unsafe { NonNull::new_unchecked(value as *mut V) }; - // SAFETY: - // - The caller ensures that the reference is derived from the original content - // - The caller ensures that the PathExtension is correct - unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } + unsafe { + MutableReference( + self.0 + .emplace_unchecked(pointer, path_extension, Some(span)), + ) + } } } diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index b680a2a1..2cb40216 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -433,66 +433,6 @@ pub(crate) enum PathExtension { Tightened(TypeKind), } -/// A mapped shared reference bundled with its [`PathExtension`] and span. -/// -/// Constructing this is unsafe because the caller must ensure the -/// [`PathExtension`] correctly describes the relationship between the -/// source and mapped reference. -pub(crate) struct MappedRef<'a, V: ?Sized> { - value: &'a V, - path_extension: PathExtension, - span: SpanRange, -} - -impl<'a, V: ?Sized> MappedRef<'a, V> { - /// SAFETY: The caller must ensure that the PathExtension correctly describes - /// the navigation from the source reference to this mapped reference. - /// An overly-specific PathExtension may cause safety issues. - pub(crate) unsafe fn new(value: &'a V, path_extension: PathExtension, span: SpanRange) -> Self { - Self { - value, - path_extension, - span, - } - } - - pub(crate) fn into_parts(self) -> (&'a V, PathExtension, SpanRange) { - (self.value, self.path_extension, self.span) - } -} - -/// A mapped mutable reference bundled with its [`PathExtension`] and span. -/// -/// Constructing this is unsafe because the caller must ensure the -/// [`PathExtension`] correctly describes the relationship between the -/// source and mapped reference. -pub(crate) struct MappedMut<'a, V: ?Sized> { - value: &'a mut V, - path_extension: PathExtension, - span: SpanRange, -} - -impl<'a, V: ?Sized> MappedMut<'a, V> { - /// SAFETY: The caller must ensure that the PathExtension correctly describes - /// the navigation from the source reference to this mapped reference. - /// An overly-specific PathExtension may cause safety issues. - pub(crate) unsafe fn new( - value: &'a mut V, - path_extension: PathExtension, - span: SpanRange, - ) -> Self { - Self { - value, - path_extension, - span, - } - } - - pub(crate) fn into_parts(self) -> (&'a mut V, PathExtension, SpanRange) { - (self.value, self.path_extension, self.span) - } -} - #[derive(PartialEq, Eq, Clone)] enum PathPart { Value { bound_as: TypeKind }, diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 918ffe33..742ec1af 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -1,3 +1,5 @@ +use std::mem::transmute; + use super::*; pub(crate) struct SharedReference(pub(super) ReferenceCore); @@ -38,12 +40,7 @@ impl SharedReference { self, f: impl for<'r> FnOnce(&'r T) -> MappedRef<'r, V>, ) -> SharedReference { - self.emplace_map(move |input, emplacer| { - let (value, path_extension, span) = f(input).into_parts(); - // SAFETY: MappedRef constructor already validated the PathExtension. - // The lifetime is checked by emplace (value: &'e V). - unsafe { emplacer.emplace(value, path_extension, Some(span)) } - }) + self.emplace_map(move |input, emplacer| emplacer.emplace(f(input))) } /// Fallible version of [`map`](Self::map) that returns the original reference on error. @@ -54,12 +51,7 @@ impl SharedReference { f: impl for<'r> FnOnce(&'r T) -> Result, E>, ) -> Result, (E, SharedReference)> { self.emplace_map(|input, emplacer| match f(input) { - Ok(mapped) => { - let (value, path_extension, span) = mapped.into_parts(); - // SAFETY: MappedRef constructor already validated the PathExtension. - // The lifetime is checked by emplace (value: &'e V). - Ok(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) - } + Ok(mapped) => Ok(emplacer.emplace(mapped)), Err(e) => Err((e, emplacer.revert())), }) } @@ -140,6 +132,50 @@ impl InactiveSharedReference { } } +/// A mapped shared reference bundled with its [`PathExtension`] and span. +/// +/// Constructing this is unsafe because the caller must ensure the +/// [`PathExtension`] correctly describes the relationship between the +/// source and mapped reference. +pub(crate) struct MappedRef<'a, V: ?Sized> { + value: &'a V, + path_extension: PathExtension, + span: SpanRange, +} + +impl<'a, V: ?Sized> MappedRef<'a, V> { + /// SAFETY: The caller must ensure that the PathExtension correctly describes + /// the navigation from the source reference to this mapped reference. + /// An overly-specific PathExtension may cause safety issues. + pub(crate) unsafe fn new(value: &'a V, path_extension: PathExtension, span: SpanRange) -> Self { + Self { + value, + path_extension, + span, + } + } + + /// SAFETY: In addition to the safety requirements of [`new`](Self::new), the caller + /// must ensure that the reference lifetime is valid for the target lifetime `'a`. + /// This is needed when the compiler cannot prove the lifetime relationship + /// (e.g. in LeafMapper implementations where `'l` and `'e` cannot be unified). + pub(crate) unsafe fn new_unchecked<'any>( + value: &'any V, + path_extension: PathExtension, + span: SpanRange, + ) -> Self { + Self { + value: unsafe { transmute::<&'any V, &'a V>(value) }, + path_extension, + span, + } + } + + pub(crate) fn into_parts(self) -> (&'a V, PathExtension, SpanRange) { + (self.value, self.path_extension, self.span) + } +} + pub(crate) struct SharedEmplacer<'e, T: ?Sized>(EmplacerCore<'e, T>); impl<'e, T: ?Sized> SharedEmplacer<'e, T> { @@ -147,33 +183,23 @@ impl<'e, T: ?Sized> SharedEmplacer<'e, T> { SharedReference(self.0.revert()) } - /// SAFETY: The caller must ensure that the PathExtension is correct. + /// Emplaces a mapped shared reference, consuming the emplacer's reference tracking. /// - /// The lifetime `'e` is checked by the compiler, ensuring the value is derived - /// from the original content. - pub(crate) unsafe fn emplace( - &mut self, - value: &'e V, - path_extension: PathExtension, - new_span: Option, - ) -> SharedReference { - unsafe { self.emplace_unchecked(value, path_extension, new_span) } - } - - /// SAFETY: - /// - The caller must ensure that the value's lifetime is derived from the original content - /// - The caller must ensure that the PathExtension is correct - pub(crate) unsafe fn emplace_unchecked( + /// This is safe because all preconditions (correct PathExtension and valid reference + /// derivation) are validated by the [`MappedRef`] constructor. + pub(crate) fn emplace( &mut self, - value: &V, - path_extension: PathExtension, - new_span: Option, + mapped: MappedRef<'e, V>, ) -> SharedReference { - // SAFETY: The pointer is from a reference so non-null + let (value, path_extension, span) = mapped.into_parts(); + // SAFETY: The pointer is from a valid reference (guaranteed by MappedRef constructor), + // and the PathExtension was validated by the MappedRef constructor. let pointer = unsafe { NonNull::new_unchecked(value as *const V as *mut V) }; - // SAFETY: - // - The caller ensures that the reference is derived from the original content - // - The caller ensures that the PathExtension is correct - unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } + unsafe { + SharedReference( + self.0 + .emplace_unchecked(pointer, path_extension, Some(span)), + ) + } } } From aa0c9d5767b2e70aeed2ee921af549d34976be50 Mon Sep 17 00:00:00 2001 From: David Edey Date: Wed, 25 Feb 2026 01:49:01 +0000 Subject: [PATCH 13/25] feat: Fix IntoValueContent for Shared/Mutable --- src/expressions/concepts/form.rs | 7 ++++ src/expressions/concepts/forms/any_mut.rs | 12 +++++- src/expressions/concepts/forms/any_ref.rs | 12 +++++- src/expressions/concepts/forms/argument.rs | 8 ++++ src/expressions/concepts/forms/assignee.rs | 8 ++++ .../concepts/forms/copy_on_write.rs | 18 ++++---- src/expressions/concepts/forms/late_bound.rs | 8 ++++ src/expressions/concepts/forms/mutable.rs | 27 +++++++++--- src/expressions/concepts/forms/owned.rs | 8 ++++ src/expressions/concepts/forms/shared.rs | 27 +++++++++--- src/expressions/concepts/forms/simple_mut.rs | 41 ++++++++++++------- src/expressions/concepts/forms/simple_ref.rs | 36 ++++++++++------ src/expressions/concepts/type_traits.rs | 33 +++++++++++++++ src/interpretation/bindings.rs | 15 ++----- .../dynamic_references/mutable_reference.rs | 15 +++---- .../dynamic_references/shared_reference.rs | 15 +++---- 16 files changed, 211 insertions(+), 79 deletions(-) diff --git a/src/expressions/concepts/form.rs b/src/expressions/concepts/form.rs index 8a06301d..a7113215 100644 --- a/src/expressions/concepts/form.rs +++ b/src/expressions/concepts/form.rs @@ -18,6 +18,13 @@ pub(crate) trait IsHierarchicalForm: IsForm { type Leaf<'a, T: IsLeafType>: IsValueContent + IntoValueContent<'a> + FromValueContent<'a>; + + /// Proof of covariance: shrinks the lifetime of a leaf from `'a` to `'b`. + /// Implementors should use the identity function `{ leaf }` as the body. + /// This will only compile if the leaf type is genuinely covariant in `'a`. + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b; } pub(crate) trait LeafAsRefForm: IsHierarchicalForm { diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index 5a867a9c..a4761ec8 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -22,6 +22,14 @@ pub(crate) struct BeAnyMut; impl IsForm for BeAnyMut {} impl IsHierarchicalForm for BeAnyMut { type Leaf<'a, T: IsLeafType> = AnyMut<'a, T::Leaf>; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeAnyMut { @@ -65,7 +73,9 @@ impl MapFromArgument for BeAnyMut { Spanned(value, span): Spanned, ) -> FunctionResult> { Ok(value.expect_mutable().emplace_map(|inner, emplacer| { - inner.as_mut_value().into_mutable_any_mut(emplacer, span) + inner + .as_mut_value() + .into_mutable_any_mut(emplacer, Some(span)) })) } } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index e67f3704..51407b86 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -23,6 +23,14 @@ impl IsForm for BeAnyRef {} impl IsHierarchicalForm for BeAnyRef { type Leaf<'a, T: IsLeafType> = crate::internal_prelude::AnyRef<'a, T::Leaf>; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeAnyRef { @@ -60,7 +68,9 @@ impl MapFromArgument for BeAnyRef { Spanned(value, span): Spanned, ) -> FunctionResult> { Ok(value.expect_shared().emplace_map(|inner, emplacer| { - inner.as_ref_value().into_shared_any_ref(emplacer, span) + inner + .as_ref_value() + .into_shared_any_ref(emplacer, Some(span)) })) } } diff --git a/src/expressions/concepts/forms/argument.rs b/src/expressions/concepts/forms/argument.rs index bbe3fda5..ce455b06 100644 --- a/src/expressions/concepts/forms/argument.rs +++ b/src/expressions/concepts/forms/argument.rs @@ -31,6 +31,14 @@ impl IsForm for BeArgument {} impl IsHierarchicalForm for BeArgument { type Leaf<'a, T: IsLeafType> = Argument; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl MapFromArgument for BeArgument { diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index 1c02fc80..37a8744c 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -25,6 +25,14 @@ impl IsForm for BeAssignee {} impl IsHierarchicalForm for BeAssignee { type Leaf<'a, T: IsLeafType> = QqqAssignee; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeAssignee { diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index 34d51f09..742d01bc 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -34,6 +34,14 @@ impl IsForm for BeCopyOnWrite {} impl IsHierarchicalForm for BeCopyOnWrite { type Leaf<'a, T: IsLeafType> = QqqCopyOnWrite; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl BeCopyOnWrite { @@ -80,16 +88,10 @@ impl MapFromArgument for BeCopyOnWrite { match value.expect_copy_on_write().inner { CopyOnWriteInner::Owned(owned) => Ok(BeCopyOnWrite::new_owned(owned)), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - let content = shared.emplace_map(|inner, emplacer| { - inner.as_ref_value().into_shared(emplacer, span) - }); - Ok(BeCopyOnWrite::new_shared_in_place_of_owned(content)) + Ok(BeCopyOnWrite::new_shared_in_place_of_owned(shared)) } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - let content = shared.emplace_map(|inner, emplacer| { - inner.as_ref_value().into_shared(emplacer, span) - }); - Ok(BeCopyOnWrite::new_shared_in_place_of_shared(content)) + Ok(BeCopyOnWrite::new_shared_in_place_of_shared(shared)) } } } diff --git a/src/expressions/concepts/forms/late_bound.rs b/src/expressions/concepts/forms/late_bound.rs index 0395e1ca..67ba3bc1 100644 --- a/src/expressions/concepts/forms/late_bound.rs +++ b/src/expressions/concepts/forms/late_bound.rs @@ -44,6 +44,14 @@ impl IsForm for BeLateBound {} impl IsHierarchicalForm for BeLateBound { type Leaf<'a, T: IsLeafType> = QqqLateBound; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } pub(crate) struct QqqLateBoundOwned { diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index ac40ba80..40e57ca5 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -2,17 +2,26 @@ use super::*; pub(crate) type QqqMutable = MutableReference; -impl IsValueContent for QqqMutable { - type Type = L::Type; +impl IsValueContent for QqqMutable { + type Type = X::Type; type Form = BeMutable; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqMutable { +impl<'a, X: IsValueContent> IntoValueContent<'a> for QqqMutable +where + X: 'static, + X::Type: IsHierarchicalType = X>, + X::Form: IsHierarchicalForm, + X::Form: LeafAsMutForm, +{ fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self + self.emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer, None)) } } +// Note we can't implement this more widely than leaves. +// This is because e.g. Content has Mutable() in its leaves, +// This can't be mapped back to having Mutable(Content). impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqMutable { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content @@ -25,6 +34,14 @@ impl IsForm for BeMutable {} impl IsHierarchicalForm for BeMutable { type Leaf<'a, T: IsLeafType> = QqqMutable; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeMutable { @@ -69,7 +86,7 @@ impl MapFromArgument for BeMutable { ) -> FunctionResult> { Ok(value .expect_mutable() - .emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer, span))) + .emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer, Some(span)))) } } diff --git a/src/expressions/concepts/forms/owned.rs b/src/expressions/concepts/forms/owned.rs index b572c53a..5a575a03 100644 --- a/src/expressions/concepts/forms/owned.rs +++ b/src/expressions/concepts/forms/owned.rs @@ -18,6 +18,14 @@ impl IsForm for BeOwned {} impl IsHierarchicalForm for BeOwned { type Leaf<'a, T: IsLeafType> = T::Leaf; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeOwned { diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index baf3cc1e..5015adf5 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -2,17 +2,26 @@ use super::*; pub(crate) type QqqShared = SharedReference; -impl IsValueContent for QqqShared { - type Type = L::Type; +impl IsValueContent for QqqShared { + type Type = X::Type; type Form = BeShared; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqShared { +impl<'a, X: IsValueContent> IntoValueContent<'a> for QqqShared +where + X: 'static, + X::Type: IsHierarchicalType = X>, + X::Form: IsHierarchicalForm, + X::Form: LeafAsRefForm, +{ fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self + self.emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer, None)) } } +// Note we can't implement this more widely than leaves. +// This is because e.g. Content has Shared() in its leaves, +// This can't be mapped back to having Shared(Content). impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqShared { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content @@ -25,6 +34,14 @@ impl IsForm for BeShared {} impl IsHierarchicalForm for BeShared { type Leaf<'a, T: IsLeafType> = QqqShared; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeShared { @@ -63,7 +80,7 @@ impl MapFromArgument for BeShared { ) -> FunctionResult> { Ok(value .expect_shared() - .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer, span))) + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer, Some(span)))) } } diff --git a/src/expressions/concepts/forms/simple_mut.rs b/src/expressions/concepts/forms/simple_mut.rs index f87fe41d..ff447af5 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -26,6 +26,14 @@ impl IsForm for BeMut {} impl IsHierarchicalForm for BeMut { type Leaf<'a, T: IsLeafType> = &'a mut T::Leaf; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeMut { @@ -52,17 +60,17 @@ where Self: IsValueContent, Self::Type: IsHierarchicalType = Self>, { - fn into_mutable<'b, T: 'static>( + fn into_mutable<'o, 'b, T: 'static>( self, emplacer: &'b mut MutableEmplacer<'a, T>, - span: SpanRange, - ) -> Content<'static, Self::Type, BeMutable> + span: Option, + ) -> Content<'o, Self::Type, BeMutable> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut MutableEmplacer<'e2, X>, - span: SpanRange, + span: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeMutable>; @@ -90,20 +98,21 @@ where } }; let __mapper = __InlineMapper { emplacer, span }; - ::map_with::(__mapper, self) + let content = ::map_with::(__mapper, self); + ::covariant::(content) } - fn into_assignee<'b, T: 'static>( + fn into_assignee<'o, 'b, T: 'static>( self, emplacer: &'b mut MutableEmplacer<'a, T>, - span: SpanRange, - ) -> Content<'static, Self::Type, BeAssignee> + span: Option, + ) -> Content<'o, Self::Type, BeAssignee> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut MutableEmplacer<'e2, X>, - span: SpanRange, + span: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAssignee>; @@ -131,20 +140,21 @@ where } }; let __mapper = __InlineMapper { emplacer, span }; - ::map_with::(__mapper, self) + let content = ::map_with::(__mapper, self); + ::covariant::(content) } - fn into_mutable_any_mut<'b, T: 'static>( + fn into_mutable_any_mut<'o, 'b, T: 'static>( self, emplacer: &'b mut MutableEmplacer<'a, T>, - span: SpanRange, - ) -> Content<'static, Self::Type, BeAnyMut> + span: Option, + ) -> Content<'o, Self::Type, BeAnyMut> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut MutableEmplacer<'e2, X>, - span: SpanRange, + span: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAnyMut>; @@ -173,7 +183,8 @@ where } }; let __mapper = __InlineMapper { emplacer, span }; - ::map_with::(__mapper, self) + let content = ::map_with::(__mapper, self); + ::covariant::(content) } } diff --git a/src/expressions/concepts/forms/simple_ref.rs b/src/expressions/concepts/forms/simple_ref.rs index 00e7ff7e..c9086171 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -26,6 +26,14 @@ impl IsForm for BeRef {} impl IsHierarchicalForm for BeRef { type Leaf<'a, T: IsLeafType> = &'a T::Leaf; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeRef { @@ -52,19 +60,19 @@ where Self: IsValueContent, Self::Type: IsHierarchicalType = Self>, { - fn into_shared<'b, T: 'static>( + fn into_shared<'o, 'b, T: 'static>( self, emplacer: &'b mut SharedEmplacer<'a, T>, - span: SpanRange, - ) -> Content<'static, Self::Type, BeShared> + span: Option, + ) -> Content<'o, Self::Type, BeShared> where Self: Sized, { - struct __InlineMapper<'b, 'e2, X: 'static> { - emplacer: &'b mut SharedEmplacer<'e2, X>, - span: SpanRange, + struct __InlineMapper<'b, 'e, X: 'static> { + emplacer: &'b mut SharedEmplacer<'e, X>, + span: Option, } - impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { + impl<'b, 'e, X> LeafMapper for __InlineMapper<'b, 'e, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeShared>; fn to_parent_output<'a, T: IsChildType>( @@ -90,20 +98,21 @@ where } }; let __mapper = __InlineMapper { emplacer, span }; - ::map_with::(__mapper, self) + let content = ::map_with::(__mapper, self); + ::covariant::(content) } - fn into_shared_any_ref<'b, T: 'static>( + fn into_shared_any_ref<'o, 'b, T: 'static>( self, emplacer: &'b mut SharedEmplacer<'a, T>, - span: SpanRange, - ) -> Content<'static, Self::Type, BeAnyRef> + span: Option, + ) -> Content<'o, Self::Type, BeAnyRef> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut SharedEmplacer<'e2, X>, - span: SpanRange, + span: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAnyRef>; @@ -132,7 +141,8 @@ where } }; let __mapper = __InlineMapper { emplacer, span }; - ::map_with::(__mapper, self) + let content = ::map_with::(__mapper, self); + ::covariant::(content) } } diff --git a/src/expressions/concepts/type_traits.rs b/src/expressions/concepts/type_traits.rs index 52c6f4b9..02b09942 100644 --- a/src/expressions/concepts/type_traits.rs +++ b/src/expressions/concepts/type_traits.rs @@ -42,6 +42,15 @@ pub(crate) trait IsHierarchicalType: IsType { fn content_to_leaf_kind( content: &Self::Content<'_, F>, ) -> Self::LeafKind; + + /// Shrinks the lifetime of a content value from `'a` to `'b`. + /// This is safe because all forms' leaf types are covariant in `'a`, + /// as enforced by [`IsHierarchicalForm::covariant_leaf`]. + fn covariant<'a, 'b, F: IsHierarchicalForm>( + content: Self::Content<'a, F>, + ) -> Self::Content<'b, F> + where + 'a: 'b; } pub(crate) trait IsLeafType: @@ -455,6 +464,20 @@ macro_rules! define_parent_type { ) -> Self::LeafKind { content.kind() } + + #[inline] + fn covariant<'a, 'b, F: IsHierarchicalForm>( + content: Self::Content<'a, F>, + ) -> Self::Content<'b, F> + where + 'a: 'b, + { + match content { + $( $content::$variant(x) => $content::$variant( + <$variant_type>::covariant::(x) + ), )* + } + } } $content_vis enum $content<'a, F: IsHierarchicalForm> { @@ -633,6 +656,16 @@ macro_rules! define_leaf_type { ) -> Self::LeafKind { $kind } + + #[inline] + fn covariant<'a, 'b, F: IsHierarchicalForm>( + content: Self::Content<'a, F>, + ) -> Self::Content<'b, F> + where + 'a: 'b, + { + F::covariant_leaf::(content) + } } impl IsLeafType for $type_def { diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 6678efb9..7d789a91 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -397,11 +397,8 @@ where X::Form: LeafAsMutForm, { fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - self.0.emplace_map(|inner, emplacer| { - inner - .as_mut_value() - .into_assignee(emplacer, Span::call_site().span_range()) - }) + self.0 + .emplace_map(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer, None)) } } @@ -620,18 +617,14 @@ where } CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { let content = shared.emplace_map(|inner, emplacer| { - inner - .as_ref_value() - .into_shared(emplacer, Span::call_site().span_range()) + inner.as_ref_value().into_shared(emplacer, None) }); AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(content) .into_copy_on_write() } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { let content = shared.emplace_map(|inner, emplacer| { - inner - .as_ref_value() - .into_shared(emplacer, Span::call_site().span_range()) + inner.as_ref_value().into_shared(emplacer, None) }); AnyLevelCopyOnWrite::::SharedWithTransparentCloning(content) .into_copy_on_write() diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index 0ce3bcef..b4fafec4 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -161,7 +161,7 @@ impl InactiveMutableReference { pub(crate) struct MappedMut<'a, V: ?Sized> { value: &'a mut V, path_extension: PathExtension, - span: SpanRange, + span: Option, } impl<'a, V: ?Sized> MappedMut<'a, V> { @@ -176,7 +176,7 @@ impl<'a, V: ?Sized> MappedMut<'a, V> { Self { value, path_extension, - span, + span: Some(span), } } @@ -187,7 +187,7 @@ impl<'a, V: ?Sized> MappedMut<'a, V> { pub(crate) unsafe fn new_unchecked<'any>( value: &'any mut V, path_extension: PathExtension, - span: SpanRange, + span: Option, ) -> Self { Self { value: unsafe { transmute::<&'any mut V, &'a mut V>(value) }, @@ -196,7 +196,7 @@ impl<'a, V: ?Sized> MappedMut<'a, V> { } } - pub(crate) fn into_parts(self) -> (&'a mut V, PathExtension, SpanRange) { + pub(crate) fn into_parts(self) -> (&'a mut V, PathExtension, Option) { (self.value, self.path_extension, self.span) } } @@ -220,11 +220,6 @@ impl<'e, T: ?Sized> MutableEmplacer<'e, T> { // SAFETY: The pointer is from a valid reference (guaranteed by MappedMut constructor), // and the PathExtension was validated by the MappedMut constructor. let pointer = unsafe { NonNull::new_unchecked(value as *mut V) }; - unsafe { - MutableReference( - self.0 - .emplace_unchecked(pointer, path_extension, Some(span)), - ) - } + unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, span)) } } } diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 742ec1af..1bc62f91 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -140,7 +140,7 @@ impl InactiveSharedReference { pub(crate) struct MappedRef<'a, V: ?Sized> { value: &'a V, path_extension: PathExtension, - span: SpanRange, + span: Option, } impl<'a, V: ?Sized> MappedRef<'a, V> { @@ -151,7 +151,7 @@ impl<'a, V: ?Sized> MappedRef<'a, V> { Self { value, path_extension, - span, + span: Some(span), } } @@ -162,7 +162,7 @@ impl<'a, V: ?Sized> MappedRef<'a, V> { pub(crate) unsafe fn new_unchecked<'any>( value: &'any V, path_extension: PathExtension, - span: SpanRange, + span: Option, ) -> Self { Self { value: unsafe { transmute::<&'any V, &'a V>(value) }, @@ -171,7 +171,7 @@ impl<'a, V: ?Sized> MappedRef<'a, V> { } } - pub(crate) fn into_parts(self) -> (&'a V, PathExtension, SpanRange) { + pub(crate) fn into_parts(self) -> (&'a V, PathExtension, Option) { (self.value, self.path_extension, self.span) } } @@ -195,11 +195,6 @@ impl<'e, T: ?Sized> SharedEmplacer<'e, T> { // SAFETY: The pointer is from a valid reference (guaranteed by MappedRef constructor), // and the PathExtension was validated by the MappedRef constructor. let pointer = unsafe { NonNull::new_unchecked(value as *const V as *mut V) }; - unsafe { - SharedReference( - self.0 - .emplace_unchecked(pointer, path_extension, Some(span)), - ) - } + unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, span)) } } } From ee0472ed511fd15ef6ba2aae285f880e5cf61113 Mon Sep 17 00:00:00 2001 From: David Edey Date: Wed, 25 Feb 2026 02:10:43 +0000 Subject: [PATCH 14/25] fix: Fix swap_itself whitespace --- tests/compilation_failures/expressions/swap_itself.stderr | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/compilation_failures/expressions/swap_itself.stderr b/tests/compilation_failures/expressions/swap_itself.stderr index b9335319..97a8b675 100644 --- a/tests/compilation_failures/expressions/swap_itself.stderr +++ b/tests/compilation_failures/expressions/swap_itself.stderr @@ -2,7 +2,6 @@ error: Cannot create a mutable reference because it clashes with an existing ref This reference : [*active*] &mut a (of type any) Other reference: [*active*] &mut a (of type any) Reason : mutation may be observed from the other active reference, which breaks aliasing rules - --> tests/compilation_failures/expressions/swap_itself.rs:5:13 | 5 | let a = "a"; From 930c6bbac510e09f573f78095a5aad7861d4177f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Feb 2026 02:29:35 +0000 Subject: [PATCH 15/25] fix: Add set_tracked_span to fix borrow-conflict error spans The swap_itself.stderr error was pointing to `let a = "a"` instead of the second `a` in `a.swap(a)` because enable() ignored its span parameter and creation_span was never updated from the root span. - Add set_tracked_span to SharedReference, MutableReference, and their inactive variants - Update enable() to call set_tracked_span before activate(), so borrow-conflict errors point to the usage site - Update VariableBinding::into_mut/into_shared/into_late_bound to set the tracked span before initial activation too https://claude.ai/code/session_01XaCsWwXFYntkKqZNf8kizU --- src/interpretation/bindings.rs | 34 +++++++++++-------- .../dynamic_references/mutable_reference.rs | 20 +++++++++-- src/misc/dynamic_references/referenceable.rs | 4 +++ .../dynamic_references/shared_reference.rs | 22 +++++++++--- .../expressions/swap_itself.stderr | 7 ++-- 5 files changed, 63 insertions(+), 24 deletions(-) diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 7d789a91..7fbd91c0 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -180,15 +180,17 @@ impl VariableBinding { } fn into_mut(self) -> FunctionResult { + let span = self.variable_span.span_range(); match self.content { - VariableContent::Referenceable(referenceable) => referenceable - .new_inactive_mutable() - .activate() - .map_err(|_| { + VariableContent::Referenceable(referenceable) => { + let inactive = referenceable.new_inactive_mutable(); + inactive.set_tracked_span(span); + inactive.activate().map_err(|_| { self.variable_span .ownership_error::(MUTABLE_ERROR_MESSAGE) - }), - VariableContent::Mutable(disabled) => disabled.enable(self.variable_span.span_range()), + }) + } + VariableContent::Mutable(disabled) => disabled.enable(span), VariableContent::Shared(shared) => self .variable_span .ownership_err(SHARED_TO_MUTABLE_ERROR_MESSAGE), @@ -196,29 +198,33 @@ impl VariableBinding { } fn into_shared(self) -> FunctionResult { + let span = self.variable_span.span_range(); match self.content { VariableContent::Referenceable(referenceable) => { - referenceable.new_inactive_shared().activate().map_err(|_| { + let inactive = referenceable.new_inactive_shared(); + inactive.set_tracked_span(span); + inactive.activate().map_err(|_| { self.variable_span .ownership_error::(SHARED_ERROR_MESSAGE) }) } - VariableContent::Mutable(mutable) => mutable - .into_shared() - .enable(self.variable_span.span_range()), - VariableContent::Shared(shared) => shared.enable(self.variable_span.span_range()), + VariableContent::Mutable(mutable) => mutable.into_shared().enable(span), + VariableContent::Shared(shared) => shared.enable(span), } } fn into_late_bound(self) -> FunctionResult { + let span = self.variable_span.span_range(); match self.content { VariableContent::Referenceable(referenceable) => { let inactive_mut = referenceable.new_inactive_mutable(); + inactive_mut.set_tracked_span(span); match inactive_mut.activate() { Ok(mutable) => Ok(LateBoundValue::Mutable(mutable)), Err(_) => { // Mutable failed, try shared let inactive_shared = referenceable.new_inactive_shared(); + inactive_shared.set_tracked_span(span); let shared = inactive_shared.activate().map_err(|_| { self.variable_span .ownership_error::(SHARED_ERROR_MESSAGE) @@ -230,12 +236,10 @@ impl VariableBinding { } } } - VariableContent::Mutable(mutable) => Ok(LateBoundValue::Mutable( - mutable.enable(self.variable_span.span_range())?, - )), + VariableContent::Mutable(mutable) => Ok(LateBoundValue::Mutable(mutable.enable(span)?)), VariableContent::Shared(shared) => { Ok(LateBoundValue::Shared(LateBoundSharedValue::new( - shared.enable(self.variable_span.span_range())?, + shared.enable(span)?, self.variable_span .syn_error(SHARED_TO_MUTABLE_ERROR_MESSAGE), ))) diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index b4fafec4..b148213d 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -82,13 +82,29 @@ impl MutableReference { .activate() .expect("Converting mutable to shared should always succeed since we just released the mutable borrow") } + + /// Updates the tracked span for this reference, which is used in + /// borrow-conflict error messages to point to the usage site. + #[allow(unused)] + pub(crate) fn set_tracked_span(&self, span: SpanRange) { + self.0.core.data_mut().set_creation_span(self.0.id, span); + } } impl InactiveMutableReference { - /// Re-enables this inactive mutable reference (bridge for old `enable()` API). - pub(crate) fn enable(self, _span: SpanRange) -> FunctionResult> { + /// Re-enables this inactive mutable reference. + /// Updates the tracked span to the given usage-site span before activating, + /// so that borrow-conflict errors point to the correct location. + pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { + self.set_tracked_span(span); self.activate() } + + /// Updates the tracked span for this reference, which is used in + /// borrow-conflict error messages to point to the usage site. + pub(crate) fn set_tracked_span(&self, span: SpanRange) { + self.0.core.data_mut().set_creation_span(self.0.id, span); + } } impl Deref for MutableReference { diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 2cb40216..7b6c0eb0 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -104,6 +104,10 @@ impl ReferenceableData { self.arena.remove(id); } + pub(super) fn set_creation_span(&mut self, id: LocalReferenceId, span: SpanRange) { + self.for_reference_mut(id).creation_span = span; + } + pub(super) fn deactivate_reference(&mut self, id: LocalReferenceId) { let data = self.for_reference_mut(id); data.reference_kind = match data.reference_kind { diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 1bc62f91..5e4c19d2 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -84,15 +84,29 @@ impl SharedReference { pub(crate) fn disable(self) -> InactiveSharedReference { self.deactivate() } + + /// Updates the tracked span for this reference, which is used in + /// borrow-conflict error messages to point to the usage site. + #[allow(unused)] + pub(crate) fn set_tracked_span(&self, span: SpanRange) { + self.0.core.data_mut().set_creation_span(self.0.id, span); + } } impl InactiveSharedReference { - /// Re-enables this inactive shared reference (bridge for old `enable()` API). - /// The span parameter is kept for API compatibility but activation errors - /// now include span information from the reference itself. - pub(crate) fn enable(self, _span: SpanRange) -> FunctionResult> { + /// Re-enables this inactive shared reference. + /// Updates the tracked span to the given usage-site span before activating, + /// so that borrow-conflict errors point to the correct location. + pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { + self.set_tracked_span(span); self.activate() } + + /// Updates the tracked span for this reference, which is used in + /// borrow-conflict error messages to point to the usage site. + pub(crate) fn set_tracked_span(&self, span: SpanRange) { + self.0.core.data_mut().set_creation_span(self.0.id, span); + } } impl Deref for SharedReference { diff --git a/tests/compilation_failures/expressions/swap_itself.stderr b/tests/compilation_failures/expressions/swap_itself.stderr index 97a8b675..de6412af 100644 --- a/tests/compilation_failures/expressions/swap_itself.stderr +++ b/tests/compilation_failures/expressions/swap_itself.stderr @@ -2,7 +2,8 @@ error: Cannot create a mutable reference because it clashes with an existing ref This reference : [*active*] &mut a (of type any) Other reference: [*active*] &mut a (of type any) Reason : mutation may be observed from the other active reference, which breaks aliasing rules - --> tests/compilation_failures/expressions/swap_itself.rs:5:13 + + --> tests/compilation_failures/expressions/swap_itself.rs:6:16 | -5 | let a = "a"; - | ^ +6 | a.swap(a); + | ^ From 09d0c3a749c3bfa2a40b884d5bac18dc014ca3e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Feb 2026 02:50:21 +0000 Subject: [PATCH 16/25] refactor: Make activate() take SpanRange, remove enable/set_tracked_span Move span-setting into activate() so callers don't need a separate set_tracked_span step. Remove enable() and set_tracked_span() from Inactive*Reference types. Add new_active_shared/new_active_mutable convenience methods to Referenceable. https://claude.ai/code/session_01XaCsWwXFYntkKqZNf8kizU --- src/expressions/evaluation/value_frames.rs | 12 +++--- src/interpretation/bindings.rs | 38 ++++++++----------- .../dynamic_references/mutable_reference.rs | 38 +++++-------------- src/misc/dynamic_references/referenceable.rs | 27 ++++++++++--- .../dynamic_references/shared_reference.rs | 30 ++------------- 5 files changed, 57 insertions(+), 88 deletions(-) diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index 9ed95f01..d88224a3 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -101,14 +101,14 @@ impl DisabledArgumentValue { DisabledArgumentValue::CopyOnWrite(copy_on_write) => { Ok(ArgumentValue::CopyOnWrite(copy_on_write.enable(span)?)) } - DisabledArgumentValue::Mutable(mutable) => { - Ok(ArgumentValue::Mutable(mutable.enable(span)?)) + DisabledArgumentValue::Mutable(inactive) => { + Ok(ArgumentValue::Mutable(inactive.activate(span)?)) } - DisabledArgumentValue::Assignee(assignee) => { - Ok(ArgumentValue::Assignee(Assignee(assignee.enable(span)?))) + DisabledArgumentValue::Assignee(inactive) => { + Ok(ArgumentValue::Assignee(Assignee(inactive.activate(span)?))) } - DisabledArgumentValue::Shared(shared) => { - Ok(ArgumentValue::Shared(shared.enable(span)?)) + DisabledArgumentValue::Shared(inactive) => { + Ok(ArgumentValue::Shared(inactive.activate(span)?)) } } } diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 7fbd91c0..9fc8f7b7 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -183,14 +183,12 @@ impl VariableBinding { let span = self.variable_span.span_range(); match self.content { VariableContent::Referenceable(referenceable) => { - let inactive = referenceable.new_inactive_mutable(); - inactive.set_tracked_span(span); - inactive.activate().map_err(|_| { + referenceable.new_active_mutable(span).map_err(|_| { self.variable_span .ownership_error::(MUTABLE_ERROR_MESSAGE) }) } - VariableContent::Mutable(disabled) => disabled.enable(span), + VariableContent::Mutable(inactive) => inactive.activate(span), VariableContent::Shared(shared) => self .variable_span .ownership_err(SHARED_TO_MUTABLE_ERROR_MESSAGE), @@ -201,15 +199,13 @@ impl VariableBinding { let span = self.variable_span.span_range(); match self.content { VariableContent::Referenceable(referenceable) => { - let inactive = referenceable.new_inactive_shared(); - inactive.set_tracked_span(span); - inactive.activate().map_err(|_| { + referenceable.new_active_shared(span).map_err(|_| { self.variable_span .ownership_error::(SHARED_ERROR_MESSAGE) }) } - VariableContent::Mutable(mutable) => mutable.into_shared().enable(span), - VariableContent::Shared(shared) => shared.enable(span), + VariableContent::Mutable(inactive) => inactive.into_shared().activate(span), + VariableContent::Shared(inactive) => inactive.activate(span), } } @@ -217,15 +213,11 @@ impl VariableBinding { let span = self.variable_span.span_range(); match self.content { VariableContent::Referenceable(referenceable) => { - let inactive_mut = referenceable.new_inactive_mutable(); - inactive_mut.set_tracked_span(span); - match inactive_mut.activate() { + match referenceable.new_active_mutable(span) { Ok(mutable) => Ok(LateBoundValue::Mutable(mutable)), Err(_) => { // Mutable failed, try shared - let inactive_shared = referenceable.new_inactive_shared(); - inactive_shared.set_tracked_span(span); - let shared = inactive_shared.activate().map_err(|_| { + let shared = referenceable.new_active_shared(span).map_err(|_| { self.variable_span .ownership_error::(SHARED_ERROR_MESSAGE) })?; @@ -236,10 +228,12 @@ impl VariableBinding { } } } - VariableContent::Mutable(mutable) => Ok(LateBoundValue::Mutable(mutable.enable(span)?)), - VariableContent::Shared(shared) => { + VariableContent::Mutable(inactive) => { + Ok(LateBoundValue::Mutable(inactive.activate(span)?)) + } + VariableContent::Shared(inactive) => { Ok(LateBoundValue::Shared(LateBoundSharedValue::new( - shared.enable(span)?, + inactive.activate(span)?, self.variable_span .syn_error(SHARED_TO_MUTABLE_ERROR_MESSAGE), ))) @@ -593,11 +587,11 @@ impl DisabledCopyOnWrite { pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { let inner = match self.inner { DisabledCopyOnWriteInner::Owned(owned) => CopyOnWriteInner::Owned(owned), - DisabledCopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - CopyOnWriteInner::SharedWithInfallibleCloning(shared.enable(span)?) + DisabledCopyOnWriteInner::SharedWithInfallibleCloning(inactive) => { + CopyOnWriteInner::SharedWithInfallibleCloning(inactive.activate(span)?) } - DisabledCopyOnWriteInner::SharedWithTransparentCloning(shared) => { - CopyOnWriteInner::SharedWithTransparentCloning(shared.enable(span)?) + DisabledCopyOnWriteInner::SharedWithTransparentCloning(inactive) => { + CopyOnWriteInner::SharedWithTransparentCloning(inactive.activate(span)?) } }; Ok(CopyOnWrite { inner }) diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index b148213d..c3d17e28 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -60,8 +60,7 @@ impl MutableReference { ) -> Self { let referenceable = Referenceable::new(value, root_name, span_range); referenceable - .new_inactive_mutable() - .activate() + .new_active_mutable(span_range) .expect("Freshly created referenceable must be borrowable as mutable") } } @@ -76,35 +75,18 @@ impl MutableReference { /// Converts this mutable reference into a shared reference. /// Bridge for old `into_shared()` API. pub(crate) fn into_shared(self) -> SharedReference { + let span = self + .0 + .core + .data_mut() + .for_reference(self.0.id) + .creation_span; let inactive = self.deactivate(); let inactive_shared = inactive.into_shared(); inactive_shared - .activate() + .activate(span) .expect("Converting mutable to shared should always succeed since we just released the mutable borrow") } - - /// Updates the tracked span for this reference, which is used in - /// borrow-conflict error messages to point to the usage site. - #[allow(unused)] - pub(crate) fn set_tracked_span(&self, span: SpanRange) { - self.0.core.data_mut().set_creation_span(self.0.id, span); - } -} - -impl InactiveMutableReference { - /// Re-enables this inactive mutable reference. - /// Updates the tracked span to the given usage-site span before activating, - /// so that borrow-conflict errors point to the correct location. - pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { - self.set_tracked_span(span); - self.activate() - } - - /// Updates the tracked span for this reference, which is used in - /// borrow-conflict error messages to point to the usage site. - pub(crate) fn set_tracked_span(&self, span: SpanRange) { - self.0.core.data_mut().set_creation_span(self.0.id, span); - } } impl Deref for MutableReference { @@ -152,11 +134,11 @@ impl Clone for InactiveMutableReference { } impl InactiveMutableReference { - pub(crate) fn activate(self) -> FunctionResult> { + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { self.0 .core .data_mut() - .activate_mutable_reference(self.0.id)?; + .activate_mutable_reference(self.0.id, span)?; Ok(MutableReference(self.0)) } diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 7b6c0eb0..31de0f01 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -26,6 +26,20 @@ impl Referenceable { )) } + pub(crate) fn new_active_shared( + &self, + span: SpanRange, + ) -> FunctionResult> { + self.new_inactive_shared().activate(span) + } + + pub(crate) fn new_active_mutable( + &self, + span: SpanRange, + ) -> FunctionResult> { + self.new_inactive_mutable().activate(span) + } + /// Attempts to unwrap the inner value if this is the sole owner (no outstanding references). pub(crate) fn try_into_inner(self) -> Result { match Rc::try_unwrap(self.core) { @@ -104,10 +118,6 @@ impl ReferenceableData { self.arena.remove(id); } - pub(super) fn set_creation_span(&mut self, id: LocalReferenceId, span: SpanRange) { - self.for_reference_mut(id).creation_span = span; - } - pub(super) fn deactivate_reference(&mut self, id: LocalReferenceId) { let data = self.for_reference_mut(id); data.reference_kind = match data.reference_kind { @@ -132,7 +142,9 @@ impl ReferenceableData { pub(super) fn activate_mutable_reference( &mut self, id: LocalReferenceId, + span: SpanRange, ) -> FunctionResult<()> { + self.for_reference_mut(id).creation_span = span; let data = self.for_reference(id); // Perform checks as per the module doc on `dynamic_references` for (other_id, other_data) in self.arena.iter() { @@ -169,7 +181,12 @@ impl ReferenceableData { Ok(()) } - pub(super) fn activate_shared_reference(&mut self, id: LocalReferenceId) -> FunctionResult<()> { + pub(super) fn activate_shared_reference( + &mut self, + id: LocalReferenceId, + span: SpanRange, + ) -> FunctionResult<()> { + self.for_reference_mut(id).creation_span = span; let data = self.for_reference(id); // Perform checks as per the module doc on `dynamic_references` for (other_id, other_data) in self.arena.iter() { diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 5e4c19d2..245ba601 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -67,8 +67,7 @@ impl SharedReference { ) -> Self { let referenceable = Referenceable::new(value, root_name, span_range); referenceable - .new_inactive_shared() - .activate() + .new_active_shared(span_range) .expect("Freshly created referenceable must be borrowable as shared") } @@ -84,29 +83,6 @@ impl SharedReference { pub(crate) fn disable(self) -> InactiveSharedReference { self.deactivate() } - - /// Updates the tracked span for this reference, which is used in - /// borrow-conflict error messages to point to the usage site. - #[allow(unused)] - pub(crate) fn set_tracked_span(&self, span: SpanRange) { - self.0.core.data_mut().set_creation_span(self.0.id, span); - } -} - -impl InactiveSharedReference { - /// Re-enables this inactive shared reference. - /// Updates the tracked span to the given usage-site span before activating, - /// so that borrow-conflict errors point to the correct location. - pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { - self.set_tracked_span(span); - self.activate() - } - - /// Updates the tracked span for this reference, which is used in - /// borrow-conflict error messages to point to the usage site. - pub(crate) fn set_tracked_span(&self, span: SpanRange) { - self.0.core.data_mut().set_creation_span(self.0.id, span); - } } impl Deref for SharedReference { @@ -137,11 +113,11 @@ impl Clone for InactiveSharedReference { } impl InactiveSharedReference { - pub(crate) fn activate(self) -> FunctionResult> { + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { self.0 .core .data_mut() - .activate_shared_reference(self.0.id)?; + .activate_shared_reference(self.0.id, span)?; Ok(SharedReference(self.0)) } } From 48ef9efda6f9841b7bac12ee7752cc425ed19013 Mon Sep 17 00:00:00 2001 From: David Edey Date: Wed, 25 Feb 2026 02:52:16 +0000 Subject: [PATCH 17/25] docs: Update TODOs --- plans/TODO.md | 26 +++++++++++++++++++------- src/interpretation/bindings.rs | 10 ++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index 7776dff5..ba9b359a 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -305,13 +305,25 @@ The issue is that we get a variable stored as `arr := DisabledMutable` and then - [x] Emplacing - [x] Map, Try map - [x] Ability to map deeper. Should take a `PathExtension` and a new span. -- [ ] Try replacing existing RefCell based abstractions with new custom dynamic references, - and report on what breaks: - - `pub(crate) type Referenceable = Rc>;` becomes `pub(crate) type Referenceable = dynamic_references::Referenceable` - - `Shared` / `SharedSubRcRefCell` - both become type aliases for `SharedReference` - - `DisabledShared` becomes `InactiveSharedReference` - - `Mutable` / `MutableSubRcRefCell` - becomes type alias for `MutableReference` - - `DisabledMutable` becomes `InactiveMutableReference` +- [x] Try replacing existing RefCell based abstractions with new custom dynamic references, and report on what breaks: + - `pub(crate) type Referenceable = Rc>;` becomes `pub(crate) type Referenceable = dynamic_references::Referenceable` + - `Shared` / `SharedSubRcRefCell` - both become type aliases for `SharedReference` + - `DisabledShared` becomes `InactiveSharedReference` + - `Mutable` / `MutableSubRcRefCell` - becomes type alias for `MutableReference` + - `DisabledMutable` becomes `InactiveMutableReference` +- [ ] Replace all the aliases: + - Remove `QqqShared`, `QqqMutable`, `SharedValue`, `AssigneeValue` + - Move `type Shared` and `type Mutable` alongside `SharedReference` / `MutableReference` + - Remove references to `MutableReference` and `SharedReference` outside these aliases + - Rename `SharedReference -> Shared` and `MutableReference -> Mutable` + - Rename `DisabledShared` -> `InactiveShared` and same for `Mutable` + - Remove `type AssigneeValue` and `type SharedValue` + - Move `Assignee` out of bindings. To e.g. `dynamic_references` + - See what else can be deleted from `bindings.rs` + - Rename `PathExtension::Tightened` to `PathExtension::TypeNarrowed` + - Rename `DisabledArgumentValue` and `DisabledCopyOnWrite` to + `Inactive__` and their method from `enable` to `activate` and ditto + with `disable -> deactivate` ### Other ideas diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 9fc8f7b7..fca3309b 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -614,17 +614,11 @@ where AnyLevelCopyOnWrite::::Owned(owned).into_copy_on_write() } CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - let content = shared.emplace_map(|inner, emplacer| { - inner.as_ref_value().into_shared(emplacer, None) - }); - AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(content) + AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(shared.into_content()) .into_copy_on_write() } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - let content = shared.emplace_map(|inner, emplacer| { - inner.as_ref_value().into_shared(emplacer, None) - }); - AnyLevelCopyOnWrite::::SharedWithTransparentCloning(content) + AnyLevelCopyOnWrite::::SharedWithTransparentCloning(shared.into_content()) .into_copy_on_write() } } From b2459b4b94915ae2e1994aba809d71898942f190 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 28 Feb 2026 08:38:20 +0000 Subject: [PATCH 18/25] refactor: Simplify aliases for forms --- plans/TODO.md | 55 ++-- src/expressions/closures.rs | 4 +- src/expressions/concepts/forms/any_mut.rs | 4 +- src/expressions/concepts/forms/any_ref.rs | 4 +- src/expressions/concepts/forms/argument.rs | 6 +- src/expressions/concepts/forms/assignee.rs | 55 +++- .../concepts/forms/copy_on_write.rs | 6 +- src/expressions/concepts/forms/late_bound.rs | 4 +- src/expressions/concepts/forms/mutable.rs | 16 +- src/expressions/concepts/forms/shared.rs | 16 +- src/expressions/concepts/forms/simple_mut.rs | 17 +- src/expressions/concepts/forms/simple_ref.rs | 11 +- src/expressions/evaluation/evaluator.rs | 6 +- src/expressions/evaluation/node_conversion.rs | 2 +- src/expressions/evaluation/value_frames.rs | 103 ++++---- src/expressions/expression.rs | 4 +- src/expressions/expression_parsing.rs | 6 +- src/expressions/type_resolution/arguments.rs | 57 +++-- src/expressions/type_resolution/outputs.rs | 2 +- src/expressions/type_resolution/type_kinds.rs | 8 +- src/expressions/values/any_value.rs | 23 +- src/expressions/values/function.rs | 2 +- src/interpretation/bindings.rs | 235 +++++------------- src/interpretation/refs.rs | 207 ++++++++------- src/interpretation/variable.rs | 2 +- .../dynamic_references/mutable_reference.rs | 88 +++---- src/misc/dynamic_references/reference_core.rs | 3 +- src/misc/dynamic_references/referenceable.rs | 30 +-- .../dynamic_references/shared_reference.rs | 67 +++-- src/misc/errors.rs | 9 + 30 files changed, 490 insertions(+), 562 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index ba9b359a..eba29e01 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -272,36 +272,13 @@ Suddenly dawned on me - my Disabled arguments might not be safe. * Imagine if I get `my_arr = [[]]` a Shared `my_arr[0]` then do `my_arr.pop()` * Then we enable `my_arr[0]` and get a use after free(!!). e.g. `my_arr[0].push(my_arr.pop())`. -Instead, as per Rust two-phase borrows https://rustc-dev-guide.rust-lang.org/borrow-check/two-phase-borrows.html - -- We shouldn't be able to disable a `Shared` -- A `Disabled` becomes a `Shared`... - -I have a branch `spike/fix-disabled-abstraction` to explore this... Which converts a Shared back to a Mutable (which I think is technically UB and can't be done without it). Regardless, it works for a spike, and we get a few failures: -* `stream_append_can_use_self_in_appender` - these are OK to fail! -* This test is *not* OK to fail: -```rust -#[test] -fn can_pass_owned_to_mutable_argument() { - run! { - let push_twice_and_return_mut = |arr: &mut any| { - arr.push(0); - arr.push(0); - arr - }; - %[_].assert_eq(push_twice_and_return_mut([1, 2, 3]).len(), 5); - } -} -``` - -The issue is that we get a variable stored as `arr := DisabledMutable` and then when we do `arr.push()` we clone the DisabledMutable, and then enable it... - ### Task list - [x] Write up model in `dynamic_references/mod.rs` - [x] Add structure for `dynamic_references` - [x] Create Referenceable, and Reference types - [x] Create error messages for rule breaks in `referenceable.rs` -- [ ] Add the following to `ReferenceCore` and maybe others: +- [x] Add the following to `ReferenceCore` and maybe others: - [x] Emplacing - [x] Map, Try map - [x] Ability to map deeper. Should take a `PathExtension` and a new span. @@ -311,19 +288,23 @@ The issue is that we get a variable stored as `arr := DisabledMutable` and then - `DisabledShared` becomes `InactiveSharedReference` - `Mutable` / `MutableSubRcRefCell` - becomes type alias for `MutableReference` - `DisabledMutable` becomes `InactiveMutableReference` -- [ ] Replace all the aliases: - - Remove `QqqShared`, `QqqMutable`, `SharedValue`, `AssigneeValue` - - Move `type Shared` and `type Mutable` alongside `SharedReference` / `MutableReference` - - Remove references to `MutableReference` and `SharedReference` outside these aliases - - Rename `SharedReference -> Shared` and `MutableReference -> Mutable` - - Rename `DisabledShared` -> `InactiveShared` and same for `Mutable` - - Remove `type AssigneeValue` and `type SharedValue` - - Move `Assignee` out of bindings. To e.g. `dynamic_references` - - See what else can be deleted from `bindings.rs` - - Rename `PathExtension::Tightened` to `PathExtension::TypeNarrowed` - - Rename `DisabledArgumentValue` and `DisabledCopyOnWrite` to - `Inactive__` and their method from `enable` to `activate` and ditto - with `disable -> deactivate` +- [x] Replace all the aliases: + - [x] Remove `Shared`, `Mutable`, `SharedValue`, `AssigneeValue` + - [x] Move `type Shared` and `type Mutable` alongside `SharedReference` / `MutableReference` + - [x] Remove references to `MutableReference` and `SharedReference` outside these aliases + - [x] Rename `SharedReference -> Shared` and `MutableReference -> Mutable` + - [x] Rename `DisabledShared` -> `InactiveShared` and same for `Mutable` + - [x] Remove `type AssigneeValue` and `type SharedValue` + - [x] Move `Assignee` out of bindings. To e.g. `dynamic_references` + - [x] Rename `PathExtension::Tightening` to `PathExtension::TypeNarrowing` + - [x] Rename `DisabledArgumentValue` and `DisabledCopyOnWrite` to `Inactive__` and their method from `enable` to `activate` and ditto with `disable -> deactivate` +- [ ] See what else can be deleted from `bindings.rs` + - [ ] Unify LateBound into `QqqLateBound` + - [ ] Unify CopyOnWrite into `QqqCopyOnWrite` +- [ ] Add various tests: + - [ ] Stretching different error messages + - [ ] Showing I can do e.g. `x.a += x.b` + - [ ] Show that `let my_arr = [[]]; my_arr[0].push(my_arr.pop())` gives a suitable error ### Other ideas diff --git a/src/expressions/closures.rs b/src/expressions/closures.rs index 5c1da71a..1bff2d1c 100644 --- a/src/expressions/closures.rs +++ b/src/expressions/closures.rs @@ -89,7 +89,7 @@ impl ClosureValue { (Pattern::Variable(variable), ArgumentValue::Shared(shared)) => { context.interpreter.define_variable( variable.definition.id, - VariableContent::Shared(shared.disable()), + VariableContent::Shared(shared.deactivate()), ); } (_, ArgumentValue::Shared(_)) => { @@ -100,7 +100,7 @@ impl ClosureValue { (Pattern::Variable(variable), ArgumentValue::Mutable(mutable)) => { context.interpreter.define_variable( variable.definition.id, - VariableContent::Mutable(mutable.disable()), + VariableContent::Mutable(mutable.deactivate()), ); } (_, ArgumentValue::Mutable(_)) => { diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index a4761ec8..bf08c3ce 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -43,9 +43,9 @@ impl IsDynCompatibleForm for BeAnyMut { { leaf.emplace_map(|content, emplacer| match ::map_mut(content) { Ok(mapped) => { - // SAFETY: PathExtension::Tightened correctly describes type narrowing + // SAFETY: PathExtension is correct for mapping to a dyn type let mapped_mut = unsafe { - MappedMut::new(mapped, PathExtension::Tightened(D::type_kind()), span) + MappedMut::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) }; Ok(emplacer.emplace(mapped_mut)) } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index 51407b86..e073e31a 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -44,9 +44,9 @@ impl IsDynCompatibleForm for BeAnyRef { { leaf.emplace_map(|content, emplacer| match ::map_ref(content) { Ok(mapped) => { - // SAFETY: PathExtension::Tightened correctly describes type narrowing + // SAFETY: PathExtension is correct for mapping to a dyn type let mapped_ref = unsafe { - MappedRef::new(mapped, PathExtension::Tightened(D::type_kind()), span) + MappedRef::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) }; Ok(emplacer.emplace(mapped_ref)) } diff --git a/src/expressions/concepts/forms/argument.rs b/src/expressions/concepts/forms/argument.rs index ce455b06..5c260ced 100644 --- a/src/expressions/concepts/forms/argument.rs +++ b/src/expressions/concepts/forms/argument.rs @@ -3,9 +3,9 @@ use super::*; pub(crate) enum Argument { Owned(L), CopyOnWrite(QqqCopyOnWrite), - Mutable(QqqMutable), - Assignee(QqqAssignee), - Shared(QqqShared), + Mutable(Mutable), + Assignee(Assignee), + Shared(Shared), } impl IsValueContent for Argument { diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index 37a8744c..9973b56c 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -1,30 +1,61 @@ use super::*; -pub(crate) struct QqqAssignee(pub(crate) MutableReference); +/// A binding of a unique (mutable) reference to a value. +/// See [`ArgumentOwnership::Assignee`] for more details. +/// +/// If you need span information, wrap with `Spanned>`. +pub(crate) struct Assignee(pub(crate) Mutable); -impl IsValueContent for QqqAssignee { - type Type = L::Type; +impl AnyValueAssignee { + pub(crate) fn set(&mut self, content: impl IntoAnyValue) { + *self.0 = content.into_any_value(); + } +} + +impl IsValueContent for Assignee { + type Type = X::Type; type Form = BeAssignee; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqAssignee { +impl<'a, X: IsValueContent> IntoValueContent<'a> for Assignee +where + X: 'static, + X::Type: IsHierarchicalType = X>, + X::Form: IsHierarchicalForm, + X::Form: LeafAsMutForm, +{ fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self + self.0 + .emplace_map(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer, None)) } } -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqAssignee { +impl<'a, L: IsValueLeaf> FromValueContent<'a> for Assignee { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } } +impl Deref for Assignee { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Assignee { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + #[derive(Copy, Clone)] pub(crate) struct BeAssignee; impl IsForm for BeAssignee {} impl IsHierarchicalForm for BeAssignee { - type Leaf<'a, T: IsLeafType> = QqqAssignee; + type Leaf<'a, T: IsLeafType> = Assignee; #[inline] fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> @@ -36,7 +67,7 @@ impl IsHierarchicalForm for BeAssignee { } impl IsDynCompatibleForm for BeAssignee { - type DynLeaf<'a, D: IsDynType> = QqqAssignee; + type DynLeaf<'a, D: IsDynType> = Assignee; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( Spanned(leaf, span): Spanned>, @@ -47,13 +78,13 @@ impl IsDynCompatibleForm for BeAssignee { leaf.0 .emplace_map(|content, emplacer| match ::map_mut(content) { Ok(mapped) => { - // SAFETY: PathExtension::Tightened correctly describes type narrowing + // SAFETY: PathExtension is correct for mapping to a dyn type let mapped_mut = unsafe { - MappedMut::new(mapped, PathExtension::Tightened(D::type_kind()), span) + MappedMut::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) }; - Ok(QqqAssignee(emplacer.emplace(mapped_mut))) + Ok(Assignee(emplacer.emplace(mapped_mut))) } - Err(_this) => Err(QqqAssignee(emplacer.revert())), + Err(_this) => Err(Assignee(emplacer.revert())), }) } } diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index 742d01bc..7bec58b9 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -5,10 +5,10 @@ pub(crate) enum QqqCopyOnWrite { Owned(Owned), /// For use when the CopyOnWrite value effectively represents the owned value (post-clone). /// In this case, returning a Cow is just an optimization and we can always clone infallibly. - SharedWithInfallibleCloning(QqqShared), + SharedWithInfallibleCloning(Shared), /// For use when the CopyOnWrite value represents a pre-cloned read-only value. /// A transparent clone may fail in this case at use time. - SharedWithTransparentCloning(QqqShared), + SharedWithTransparentCloning(Shared), } impl IsValueContent for QqqCopyOnWrite { @@ -221,7 +221,7 @@ where // // Apply map, get Shared> // let shared = map_via_leaf! { // input: (Content<'a, Self::Type, BeShared>) = shared, - // fn map_leaf(leaf) -> (ExecutionResult>>) { + // fn map_leaf(leaf) -> (ExecutionResult>>) { // leaf.try_map(|value_ref| { // let from_ref = value_ref.into_any(); // inner_map_ref(from_ref) // &Content diff --git a/src/expressions/concepts/forms/late_bound.rs b/src/expressions/concepts/forms/late_bound.rs index 67ba3bc1..22f45a18 100644 --- a/src/expressions/concepts/forms/late_bound.rs +++ b/src/expressions/concepts/forms/late_bound.rs @@ -6,7 +6,7 @@ pub(crate) enum QqqLateBound { /// A copy-on-write value that can be converted to an owned value CopyOnWrite(QqqCopyOnWrite), /// A mutable reference - Mutable(QqqMutable), + Mutable(Mutable), /// A shared reference where mutable access failed for a specific reason Shared(QqqLateBoundShared), } @@ -61,6 +61,6 @@ pub(crate) struct QqqLateBoundOwned { /// A shared value where mutable access failed for a specific reason pub(crate) struct QqqLateBoundShared { - pub(crate) shared: QqqShared, + pub(crate) shared: Shared, pub(crate) reason_not_mutable: syn::Error, } diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index 40e57ca5..e26fcf36 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -1,13 +1,11 @@ use super::*; -pub(crate) type QqqMutable = MutableReference; - -impl IsValueContent for QqqMutable { +impl IsValueContent for Mutable { type Type = X::Type; type Form = BeMutable; } -impl<'a, X: IsValueContent> IntoValueContent<'a> for QqqMutable +impl<'a, X: IsValueContent> IntoValueContent<'a> for Mutable where X: 'static, X::Type: IsHierarchicalType = X>, @@ -22,7 +20,7 @@ where // Note we can't implement this more widely than leaves. // This is because e.g. Content has Mutable() in its leaves, // This can't be mapped back to having Mutable(Content). -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqMutable { +impl<'a, L: IsValueLeaf> FromValueContent<'a> for Mutable { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } @@ -33,7 +31,7 @@ pub(crate) struct BeMutable; impl IsForm for BeMutable {} impl IsHierarchicalForm for BeMutable { - type Leaf<'a, T: IsLeafType> = QqqMutable; + type Leaf<'a, T: IsLeafType> = Mutable; #[inline] fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> @@ -45,7 +43,7 @@ impl IsHierarchicalForm for BeMutable { } impl IsDynCompatibleForm for BeMutable { - type DynLeaf<'a, D: IsDynType> = QqqMutable; + type DynLeaf<'a, D: IsDynType> = Mutable; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( Spanned(leaf, span): Spanned>, @@ -55,9 +53,9 @@ impl IsDynCompatibleForm for BeMutable { { leaf.emplace_map(|content, emplacer| match ::map_mut(content) { Ok(mapped) => { - // SAFETY: PathExtension::Tightened correctly describes type narrowing + // SAFETY: PathExtension is correct for mapping to a dyn type let mapped_mut = unsafe { - MappedMut::new(mapped, PathExtension::Tightened(D::type_kind()), span) + MappedMut::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) }; Ok(emplacer.emplace(mapped_mut)) } diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index 5015adf5..bd22f6bc 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -1,13 +1,11 @@ use super::*; -pub(crate) type QqqShared = SharedReference; - -impl IsValueContent for QqqShared { +impl IsValueContent for Shared { type Type = X::Type; type Form = BeShared; } -impl<'a, X: IsValueContent> IntoValueContent<'a> for QqqShared +impl<'a, X: IsValueContent> IntoValueContent<'a> for Shared where X: 'static, X::Type: IsHierarchicalType = X>, @@ -22,7 +20,7 @@ where // Note we can't implement this more widely than leaves. // This is because e.g. Content has Shared() in its leaves, // This can't be mapped back to having Shared(Content). -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqShared { +impl<'a, L: IsValueLeaf> FromValueContent<'a> for Shared { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } @@ -33,7 +31,7 @@ pub(crate) struct BeShared; impl IsForm for BeShared {} impl IsHierarchicalForm for BeShared { - type Leaf<'a, T: IsLeafType> = QqqShared; + type Leaf<'a, T: IsLeafType> = Shared; #[inline] fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> @@ -45,7 +43,7 @@ impl IsHierarchicalForm for BeShared { } impl IsDynCompatibleForm for BeShared { - type DynLeaf<'a, D: IsDynType> = QqqShared; + type DynLeaf<'a, D: IsDynType> = Shared; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( Spanned(leaf, span): Spanned>, @@ -55,9 +53,9 @@ impl IsDynCompatibleForm for BeShared { { leaf.emplace_map(|content, emplacer| match ::map_ref(content) { Ok(mapped) => { - // SAFETY: PathExtension::Tightened correctly describes type narrowing + // SAFETY: PathExtension is correct for mapping to a dyn type let mapped_ref = unsafe { - MappedRef::new(mapped, PathExtension::Tightened(D::type_kind()), span) + MappedRef::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) }; Ok(emplacer.emplace(mapped_ref)) } diff --git a/src/expressions/concepts/forms/simple_mut.rs b/src/expressions/concepts/forms/simple_mut.rs index ff447af5..e7175dd5 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -86,11 +86,11 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and - // PathExtension::Tightened correctly describes type narrowing + // PathExtension correctly describes mapping to a leaf type T let mapped = unsafe { MappedMut::new_unchecked( leaf, - PathExtension::Tightened(T::type_kind()), + PathExtension::TypeNarrowing(T::type_kind()), self.span, ) }; @@ -128,15 +128,15 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and - // PathExtension::Tightened correctly describes type narrowing + // PathExtension correctly describes mapping to a leaf type T let mapped = unsafe { MappedMut::new_unchecked( leaf, - PathExtension::Tightened(T::type_kind()), + PathExtension::TypeNarrowing(T::type_kind()), self.span, ) }; - QqqAssignee(self.emplacer.emplace(mapped)) + Assignee(self.emplacer.emplace(mapped)) } }; let __mapper = __InlineMapper { emplacer, span }; @@ -170,16 +170,15 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and - // PathExtension::Tightened correctly describes type narrowing + // PathExtension correctly describes mapping to a leaf type T let mapped = unsafe { MappedMut::new_unchecked( leaf, - PathExtension::Tightened(T::type_kind()), + PathExtension::TypeNarrowing(T::type_kind()), self.span, ) }; - let mutable_ref: MutableReference<_> = self.emplacer.emplace(mapped); - mutable_ref.into() + self.emplacer.emplace(mapped).into() } }; let __mapper = __InlineMapper { emplacer, span }; diff --git a/src/expressions/concepts/forms/simple_ref.rs b/src/expressions/concepts/forms/simple_ref.rs index c9086171..1d36b72a 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -86,11 +86,11 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and - // PathExtension::Tightened correctly describes type narrowing + // PathExtension correctly describes mapping to a leaf type T let mapped = unsafe { MappedRef::new_unchecked( leaf, - PathExtension::Tightened(T::type_kind()), + PathExtension::TypeNarrowing(T::type_kind()), self.span, ) }; @@ -128,16 +128,15 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and - // PathExtension::Tightened correctly describes type narrowing + // PathExtension correctly describes mapping to a leaf type T let mapped = unsafe { MappedRef::new_unchecked( leaf, - PathExtension::Tightened(T::type_kind()), + PathExtension::TypeNarrowing(T::type_kind()), self.span, ) }; - let shared_ref: SharedReference<_> = self.emplacer.emplace(mapped); - shared_ref.into() + self.emplacer.emplace(mapped).into() } }; let __mapper = __InlineMapper { emplacer, span }; diff --git a/src/expressions/evaluation/evaluator.rs b/src/expressions/evaluation/evaluator.rs index 1576b1ad..2c80252b 100644 --- a/src/expressions/evaluation/evaluator.rs +++ b/src/expressions/evaluation/evaluator.rs @@ -149,8 +149,8 @@ pub(crate) enum RequestedValue { Owned(AnyValueOwned), Shared(AnyValueShared), Mutable(AnyValueMutable), - CopyOnWrite(CopyOnWriteValue), - Assignee(AssigneeValue), + CopyOnWrite(AnyValueCopyOnWrite), + Assignee(AnyValueAssignee), // RequestedOwnership::LateBound // ------------------------------- @@ -177,7 +177,7 @@ impl RequestedValue { } #[allow(dead_code)] - pub(crate) fn expect_copy_on_write(self) -> CopyOnWriteValue { + pub(crate) fn expect_copy_on_write(self) -> AnyValueCopyOnWrite { match self { RequestedValue::CopyOnWrite(copy_on_write) => copy_on_write, _ => panic!("expect_copy_on_write() called on non-copy-on-write RequestedValue"), diff --git a/src/expressions/evaluation/node_conversion.rs b/src/expressions/evaluation/node_conversion.rs index 68333d37..ca19336f 100644 --- a/src/expressions/evaluation/node_conversion.rs +++ b/src/expressions/evaluation/node_conversion.rs @@ -32,7 +32,7 @@ impl ExpressionNode { // We return a freely clonable CopyOnWrite in order to delay the clone of the literal if it's not necessary // This allows something like e.g. x[0][5][2] to only clone the innermost value instead of the full multi-dimensional array let shared_cloned = Shared::clone(value); - let cow = CopyOnWriteValue::shared_in_place_of_owned(shared_cloned); + let cow = AnyValueCopyOnWrite::shared_in_place_of_owned(shared_cloned); context.return_returned_value(Spanned( ReturnedValue::CopyOnWrite(cow), *span_range, diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index d88224a3..ae160ee0 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -7,7 +7,7 @@ use super::*; /// which indicates what ownership type to resolve to. pub(crate) enum ArgumentValue { Owned(AnyValueOwned), - CopyOnWrite(CopyOnWriteValue), + CopyOnWrite(AnyValueCopyOnWrite), Mutable(AnyValueMutable), Assignee(AnyValueAssignee), Shared(AnyValueShared), @@ -21,7 +21,7 @@ impl ArgumentValue { } } - pub(crate) fn expect_copy_on_write(self) -> CopyOnWriteValue { + pub(crate) fn expect_copy_on_write(self) -> AnyValueCopyOnWrite { match self { ArgumentValue::CopyOnWrite(value) => value, _ => panic!("expect_copy_on_write() called on a non-copy-on-write ArgumentValue"), @@ -49,65 +49,65 @@ impl ArgumentValue { } } - /// Disables this argument value, releasing any borrow on the RefCell. - /// Returns a `DisabledArgumentValue` which can be cloned and later re-enabled. - pub(crate) fn disable(self) -> DisabledArgumentValue { + /// Deactivates this argument value, releasing any borrow on the RefCell. + /// Returns a `InactiveArgumentValue` which can be cloned and later re-enabled. + pub(crate) fn deactivate(self) -> InactiveArgumentValue { match self { - ArgumentValue::Owned(owned) => DisabledArgumentValue::Owned(owned), + ArgumentValue::Owned(owned) => InactiveArgumentValue::Owned(owned), ArgumentValue::CopyOnWrite(copy_on_write) => { - DisabledArgumentValue::CopyOnWrite(copy_on_write.disable()) + InactiveArgumentValue::CopyOnWrite(copy_on_write.deactivate()) } - ArgumentValue::Mutable(mutable) => DisabledArgumentValue::Mutable(mutable.disable()), + ArgumentValue::Mutable(mutable) => InactiveArgumentValue::Mutable(mutable.deactivate()), ArgumentValue::Assignee(Assignee(mutable)) => { - DisabledArgumentValue::Assignee(mutable.disable()) + InactiveArgumentValue::Assignee(mutable.deactivate()) } - ArgumentValue::Shared(shared) => DisabledArgumentValue::Shared(shared.disable()), + ArgumentValue::Shared(shared) => InactiveArgumentValue::Shared(shared.deactivate()), } } } -/// A disabled argument value that can be safely cloned and dropped. -pub(crate) enum DisabledArgumentValue { +/// An inactive argument value that can be safely cloned and dropped. +pub(crate) enum InactiveArgumentValue { Owned(AnyValueOwned), - CopyOnWrite(DisabledCopyOnWrite), - Mutable(DisabledMutable), - Assignee(DisabledMutable), - Shared(DisabledShared), + CopyOnWrite(InactiveCopyOnWrite), + Mutable(InactiveMutable), + Assignee(InactiveMutable), + Shared(InactiveShared), } -impl Clone for DisabledArgumentValue { +impl Clone for InactiveArgumentValue { fn clone(&self) -> Self { match self { - DisabledArgumentValue::Owned(owned) => DisabledArgumentValue::Owned(owned.clone()), - DisabledArgumentValue::CopyOnWrite(copy_on_write) => { - DisabledArgumentValue::CopyOnWrite(copy_on_write.clone()) + InactiveArgumentValue::Owned(owned) => InactiveArgumentValue::Owned(owned.clone()), + InactiveArgumentValue::CopyOnWrite(copy_on_write) => { + InactiveArgumentValue::CopyOnWrite(copy_on_write.clone()) } - DisabledArgumentValue::Mutable(mutable) => { - DisabledArgumentValue::Mutable(mutable.clone()) + InactiveArgumentValue::Mutable(mutable) => { + InactiveArgumentValue::Mutable(mutable.clone()) } - DisabledArgumentValue::Assignee(assignee) => { - DisabledArgumentValue::Assignee(assignee.clone()) + InactiveArgumentValue::Assignee(assignee) => { + InactiveArgumentValue::Assignee(assignee.clone()) } - DisabledArgumentValue::Shared(shared) => DisabledArgumentValue::Shared(shared.clone()), + InactiveArgumentValue::Shared(shared) => InactiveArgumentValue::Shared(shared.clone()), } } } -impl DisabledArgumentValue { +impl InactiveArgumentValue { /// Re-enables this disabled argument value by re-acquiring any borrow. pub(crate) fn enable(self, span: SpanRange) -> FunctionResult { match self { - DisabledArgumentValue::Owned(owned) => Ok(ArgumentValue::Owned(owned)), - DisabledArgumentValue::CopyOnWrite(copy_on_write) => { - Ok(ArgumentValue::CopyOnWrite(copy_on_write.enable(span)?)) + InactiveArgumentValue::Owned(owned) => Ok(ArgumentValue::Owned(owned)), + InactiveArgumentValue::CopyOnWrite(copy_on_write) => { + Ok(ArgumentValue::CopyOnWrite(copy_on_write.activate(span)?)) } - DisabledArgumentValue::Mutable(inactive) => { + InactiveArgumentValue::Mutable(inactive) => { Ok(ArgumentValue::Mutable(inactive.activate(span)?)) } - DisabledArgumentValue::Assignee(inactive) => { + InactiveArgumentValue::Assignee(inactive) => { Ok(ArgumentValue::Assignee(Assignee(inactive.activate(span)?))) } - DisabledArgumentValue::Shared(inactive) => { + InactiveArgumentValue::Shared(inactive) => { Ok(ArgumentValue::Shared(inactive.activate(span)?)) } } @@ -164,8 +164,7 @@ impl AsRef for ArgumentValue { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum RequestedOwnership { - /// Receives any of Owned, SharedReference or MutableReference, depending on what - /// is available. + /// Receives any of Owned, Shared or Mutable, depending on what is available. /// This can then be used to resolve the value kind, and use the correct one. LateBound, /// A concrete value of the correct type. @@ -297,7 +296,7 @@ impl RequestedOwnership { pub(crate) fn map_from_copy_on_write( &self, - Spanned(cow, span): Spanned, + Spanned(cow, span): Spanned, ) -> FunctionResult> { Ok(Spanned( match self { @@ -331,7 +330,7 @@ impl RequestedOwnership { pub(crate) fn map_from_assignee( &self, - Spanned(assignee, span): Spanned, + Spanned(assignee, span): Spanned, ) -> FunctionResult> { Ok(Spanned( match self { @@ -348,7 +347,7 @@ impl RequestedOwnership { pub(crate) fn map_from_shared( &self, - Spanned(shared, span): Spanned, + Spanned(shared, span): Spanned, ) -> FunctionResult> { Ok(Spanned( match self { @@ -446,7 +445,7 @@ impl ArgumentOwnership { pub(crate) fn map_from_copy_on_write( &self, - Spanned(copy_on_write, span): Spanned, + Spanned(copy_on_write, span): Spanned, ) -> FunctionResult { match self { ArgumentOwnership::Owned => Ok(ArgumentValue::Owned( @@ -479,7 +478,7 @@ impl ArgumentOwnership { pub(crate) fn map_from_shared( &self, - shared: Spanned, + shared: Spanned, ) -> FunctionResult { self.map_from_shared_with_error_reason( shared, @@ -489,13 +488,13 @@ impl ArgumentOwnership { fn map_from_shared_with_error_reason( &self, - Spanned(shared, span): Spanned, + Spanned(shared, span): Spanned, mutable_error: impl FnOnce(SpanRange) -> FunctionError, ) -> FunctionResult { match self { - ArgumentOwnership::Owned => Ok(ArgumentValue::Owned( - Spanned(shared, span).transparent_clone()?, - )), + ArgumentOwnership::Owned => { + Ok(ArgumentValue::Owned(shared.try_transparent_clone(span)?)) + } ArgumentOwnership::CopyOnWrite => Ok(ArgumentValue::CopyOnWrite( CopyOnWrite::shared_in_place_of_shared(shared), )), @@ -516,7 +515,7 @@ impl ArgumentOwnership { pub(crate) fn map_from_assignee( &self, - Spanned(assignee, span): Spanned, + Spanned(assignee, span): Spanned, ) -> FunctionResult { self.map_from_mutable_inner(Spanned(assignee.0, span), false) } @@ -529,9 +528,7 @@ impl ArgumentOwnership { match self { ArgumentOwnership::Owned => { if is_late_bound { - Ok(ArgumentValue::Owned( - Spanned(mutable, span).transparent_clone()?, - )) + Ok(ArgumentValue::Owned(mutable.try_transparent_clone(span)?)) } else { span.ownership_err("An owned value is required, but a mutable reference was received. This indicates a possible bug. If this was intended, use `.clone()` to get an owned value.") } @@ -874,7 +871,7 @@ enum BinaryPath { right: ExpressionNodeId, }, OnRightBranch { - left: Spanned, + left: Spanned, interface: BinaryOperationInterface, }, } @@ -935,7 +932,7 @@ impl EvaluationFrame for BinaryOperationBuilder { let left = Spanned(left, left_span); // Disable left so we can evaluate right without borrow conflicts - let left = left.map(|v| v.disable()); + let left = left.map(|v| v.deactivate()); self.state = BinaryPath::OnRightBranch { left, interface }; context.request_argument_value(self, right, rhs_ownership) @@ -960,7 +957,7 @@ impl EvaluationFrame for BinaryOperationBuilder { // If left and right clash, then the error message should be on the right, not the left // Disable right, then re-enable left first (for intuitive error messages) - let right = right.map(|v| v.disable()); + let right = right.map(|v| v.deactivate()); let left_span = left.1; let right_span = right.1; let left = left.try_map(|v| v.enable(left_span))?; @@ -1034,7 +1031,7 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { .map_from_late_bound(receiver.spanned(source_span))? .spanned(source_span); // Disable receiver so it can be stored in the FunctionValue - let receiver = receiver.map(|v| v.disable()); + let receiver = receiver.map(|v| v.deactivate()); let function_value = FunctionValue { invokable: InvokableFunction::Native(method), disabled_bound_arguments: vec![receiver], @@ -1348,7 +1345,7 @@ enum InvocationPath { ArgumentsPath { function_span: SpanRange, invokable: InvokableFunction, - disabled_evaluated_arguments: Vec>, + disabled_evaluated_arguments: Vec>, }, } @@ -1471,7 +1468,7 @@ impl EvaluationFrame for InvocationBuilder { let argument = value.expect_argument_value(); let argument = Spanned(argument, span); // Disable argument so we can evaluate remaining arguments without borrow conflicts - let argument = argument.map(|v| v.disable()); + let argument = argument.map(|v| v.deactivate()); disabled_evaluated_arguments.push(argument); } }; diff --git a/src/expressions/expression.rs b/src/expressions/expression.rs index 6c3a4829..e9f8a561 100644 --- a/src/expressions/expression.rs +++ b/src/expressions/expression.rs @@ -50,7 +50,7 @@ impl Expression { pub(crate) fn evaluate_shared( &self, interpreter: &mut Interpreter, - ) -> ExecutionResult> { + ) -> ExecutionResult> { Ok(self .evaluate(interpreter, RequestedOwnership::shared())? .expect_shared()) @@ -154,7 +154,7 @@ pub(super) enum Leaf { Variable(VariableReference), TypeProperty(TypeProperty), Discarded(Token![_]), - Value(Spanned), + Value(Spanned), StreamLiteral(StreamLiteral), ParseTemplateLiteral(ParseTemplateLiteral), IfExpression(Box), diff --git a/src/expressions/expression_parsing.rs b/src/expressions/expression_parsing.rs index 4bc53b20..9779be1e 100644 --- a/src/expressions/expression_parsing.rs +++ b/src/expressions/expression_parsing.rs @@ -123,7 +123,7 @@ impl<'a> ExpressionParser<'a> { "true" | "false" => { let bool = input.parse::()?; UnaryAtom::Leaf(Leaf::Value(Spanned( - SharedValue::new_from_owned(bool.value.into_any_value(), None, bool.span.span_range()), + AnyValueShared::new_from_owned(bool.value.into_any_value(), None, bool.span.span_range()), bool.span.span_range(), ))) } @@ -136,7 +136,7 @@ impl<'a> ExpressionParser<'a> { "None" => { let none_ident = input.parse_any_ident()?; // consume the "None" token UnaryAtom::Leaf(Leaf::Value(Spanned( - SharedValue::new_from_owned(().into_any_value(), None, none_ident.span().span_range()), + AnyValueShared::new_from_owned(().into_any_value(), None, none_ident.span().span_range()), none_ident.span().span_range(), ))) } @@ -155,7 +155,7 @@ impl<'a> ExpressionParser<'a> { let lit: syn::Lit = input.parse()?; let span_range = lit.span().span_range(); UnaryAtom::Leaf(Leaf::Value(Spanned( - SharedValue::new_from_owned(AnyValue::for_syn_lit(lit), None, span_range), + AnyValueShared::new_from_owned(AnyValue::for_syn_lit(lit), None, span_range), span_range, ))) }, diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index be59dc96..82c6b99e 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -80,14 +80,45 @@ impl IsArgument for Shared { // } // } -impl + ResolvableArgumentTarget + ?Sized> IsArgument - for Assignee -{ - type ValueType = T::ValueType; +// We have some friction: +// - Assignee wants to take whole parent types e.g. AnyValue or FloatValue, so it +// can replace them +// - The blanket impl of IsArgument for X: FromValueContent<'static, Type = T, Form = F> +// breaks down, because this only works for LeafContent based types (which e.g. have +// Assignee in each leaf, which isn't very useful) +// - But FromValueContent for leaves is required by the Form system +// - So for now, we're stuck implementing IsArgument manually for Assignee for each parent +// X that we need. +// +// TODO[non-leaf-form]: Future work - +// - Rename the Content type alias/associated type to LeafContent or something +// - Make IsValueContent / FromValueContent more flexible, so it can capture the group level +// somehow, and take values with the content at any given level above the input level. + +impl IsArgument for Assignee { + type ValueType = AnyType; + const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Assignee { auto_create: false }; + + fn from_argument(argument: Spanned) -> FunctionResult { + Ok(argument.expect_assignee().0) + } +} + +impl IsArgument for Assignee { + type ValueType = FloatType; + const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Assignee { auto_create: false }; + + fn from_argument(argument: Spanned) -> FunctionResult { + FloatValue::resolve_assignee(argument.expect_assignee(), "This argument") + } +} + +impl IsArgument for Assignee { + type ValueType = IntegerType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Assignee { auto_create: false }; fn from_argument(argument: Spanned) -> FunctionResult { - T::resolve_assignee(argument.expect_assignee(), "This argument") + IntegerValue::resolve_assignee(argument.expect_assignee(), "This argument") } } @@ -122,10 +153,9 @@ impl IsArgument for Mutable { // } // } -impl + ResolvableArgumentTarget + ToOwned> IsArgument - for CopyOnWrite +impl + ResolvableArgumentTarget> IsArgument for CopyOnWrite where - T::Owned: ResolvableOwned, + T: ResolvableOwned, { type ValueType = T::ValueType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::CopyOnWrite; @@ -133,12 +163,7 @@ where fn from_argument(Spanned(value, span): Spanned) -> FunctionResult { value.expect_copy_on_write().map( |v| T::resolve_shared(v.spanned(span), "This argument"), - |v| { - >::resolve_value( - v.spanned(span), - "This argument", - ) - }, + |v| T::resolve_value(v.spanned(span), "This argument"), ) } } @@ -265,7 +290,7 @@ pub(crate) trait ResolvableShared { Ok(unsafe { MappedRef::new( resolved, - PathExtension::Tightened(T::Type::type_kind()), + PathExtension::TypeNarrowing(T::Type::type_kind()), span, ) }) @@ -338,7 +363,7 @@ pub(crate) trait ResolvableMutable { Ok(unsafe { MappedMut::new( resolved, - PathExtension::Tightened(T::Type::type_kind()), + PathExtension::TypeNarrowing(T::Type::type_kind()), span, ) }) diff --git a/src/expressions/type_resolution/outputs.rs b/src/expressions/type_resolution/outputs.rs index 74a66561..b62593c8 100644 --- a/src/expressions/type_resolution/outputs.rs +++ b/src/expressions/type_resolution/outputs.rs @@ -6,7 +6,7 @@ use super::*; /// See also [`RequestedValue`] for values that have been fully evaluated. pub(crate) enum ReturnedValue { Owned(AnyValueOwned), - CopyOnWrite(CopyOnWriteValue), + CopyOnWrite(AnyValueCopyOnWrite), Mutable(AnyValueMutable), Shared(AnyValueShared), } diff --git a/src/expressions/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index 7fe69984..14a75a14 100644 --- a/src/expressions/type_resolution/type_kinds.rs +++ b/src/expressions/type_resolution/type_kinds.rs @@ -91,7 +91,7 @@ impl TypeKind { } } - pub(crate) fn is_tightening_to(&self, other: &Self) -> bool { + pub(crate) fn is_narrowing_to(&self, other: &Self) -> bool { match self.compare_bindings(other) { TypeBindingComparison::Equal => true, TypeBindingComparison::RightDerivesFromLeftButIsNotSubtype => true, @@ -333,7 +333,7 @@ impl TypeProperty { let property_name = self.property.to_string(); if let Some(value) = resolver.resolve_type_property(&property_name) { return ownership.map_from_shared(Spanned( - SharedValue::new_from_owned( + AnyValueShared::new_from_owned( value.into_any_value(), Some(property_name), self.span_range(), @@ -343,7 +343,7 @@ impl TypeProperty { } if let Some(method) = resolver.resolve_method(&property_name) { return ownership.map_from_shared(Spanned( - SharedValue::new_from_owned( + AnyValueShared::new_from_owned( method.into_any_value(), Some(property_name.clone()), self.span_range(), @@ -353,7 +353,7 @@ impl TypeProperty { } if let Some(function) = resolver.resolve_type_function(&property_name) { return ownership.map_from_shared(Spanned( - SharedValue::new_from_owned( + AnyValueShared::new_from_owned( function.into_any_value(), Some(property_name.clone()), self.span_range(), diff --git a/src/expressions/values/any_value.rs b/src/expressions/values/any_value.rs index ceea4b08..880b990a 100644 --- a/src/expressions/values/any_value.rs +++ b/src/expressions/values/any_value.rs @@ -8,6 +8,7 @@ pub(crate) type AnyValueAnyRef<'a> = AnyValueContent<'a, BeAnyRef>; pub(crate) type AnyValueShared = Shared; pub(crate) type AnyValueMutable = Mutable; pub(crate) type AnyValueAssignee = Assignee; +pub(crate) type AnyValueCopyOnWrite = CopyOnWrite; // pub(crate) type AnyValueShared = AnyValueContent<'static, BeShared>; // pub(crate) type AnyValueMutable = AnyValueContent<'static, BeMutable>; // pub(crate) type AnyValueAssignee = AnyValueContent<'static, BeAssignee>; @@ -44,7 +45,7 @@ define_type_features! { impl AnyType, pub(crate) mod value_interface { methods { - fn clone(this: CopyOnWriteValue) -> AnyValue { + fn clone(this: AnyValueCopyOnWrite) -> AnyValue { this.clone_to_owned_infallible() } @@ -64,28 +65,28 @@ define_type_features! { // NOTE: // All value types can be coerced into SharedValue as an input, so this method does actually do something - fn as_ref(this: SharedValue) -> SharedValue { + fn as_ref(this: AnyValueShared) -> AnyValueShared { this } - fn swap(mut a: AssigneeValue, mut b: AssigneeValue) -> () { + fn swap(mut a: AnyValueAssignee, mut b: AnyValueAssignee) -> () { core::mem::swap(a.0.deref_mut(), b.0.deref_mut()); } - fn replace(mut a: AssigneeValue, b: AnyValue) -> AnyValue { + fn replace(mut a: AnyValueAssignee, b: AnyValue) -> AnyValue { core::mem::replace(a.0.deref_mut(), b) } - [context] fn debug(Spanned(this, span_range): Spanned) -> FunctionResult<()> { + [context] fn debug(Spanned(this, span_range): Spanned) -> FunctionResult<()> { let message = this.as_ref_value().concat_recursive(&ConcatBehaviour::debug(span_range), context.interpreter)?; span_range.debug_err(message) } - [context] fn to_debug_string(Spanned(this, span_range): Spanned) -> FunctionResult { + [context] fn to_debug_string(Spanned(this, span_range): Spanned) -> FunctionResult { this.as_ref_value().concat_recursive(&ConcatBehaviour::debug(span_range), context.interpreter) } - [context] fn to_stream(Spanned(input, span_range): Spanned) -> FunctionResult { + [context] fn to_stream(Spanned(input, span_range): Spanned) -> FunctionResult { let interpreter_ptr = context.interpreter as *mut Interpreter; input.map_into( // SAFETY: map_into only calls one of these two closures, @@ -95,7 +96,7 @@ define_type_features! { ) } - [context] fn to_group(Spanned(input, span_range): Spanned) -> FunctionResult { + [context] fn to_group(Spanned(input, span_range): Spanned) -> FunctionResult { let interpreter_ptr = context.interpreter as *mut Interpreter; input.map_into( // SAFETY: map_into only calls one of these two closures, @@ -105,11 +106,11 @@ define_type_features! { ) } - [context] fn to_string(Spanned(input, span_range): Spanned) -> FunctionResult { + [context] fn to_string(Spanned(input, span_range): Spanned) -> FunctionResult { input.as_ref_value().concat_recursive(&ConcatBehaviour::standard(span_range), context.interpreter) } - [context] fn with_span(value: Spanned, spans: AnyRef) -> FunctionResult { + [context] fn with_span(value: Spanned, spans: AnyRef) -> FunctionResult { let mut this = to_stream(context, value)?; let span_to_use = match spans.resolve_content_span_range() { Some(span_range) => span_range.span_from_join_else_start(), @@ -121,7 +122,7 @@ define_type_features! { // TYPE CHECKING // =============================== - fn is_none(this: SharedValue) -> bool { + fn is_none(this: AnyValueShared) -> bool { this.is_none() } diff --git a/src/expressions/values/function.rs b/src/expressions/values/function.rs index ce37e42e..bcaad322 100644 --- a/src/expressions/values/function.rs +++ b/src/expressions/values/function.rs @@ -16,7 +16,7 @@ define_type_features! { #[derive(Clone)] pub(crate) struct FunctionValue { - pub(crate) disabled_bound_arguments: Vec>, + pub(crate) disabled_bound_arguments: Vec>, pub(crate) invokable: InvokableFunction, } diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index fca3309b..70e31b20 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -1,6 +1,6 @@ use super::*; -use std::borrow::{Borrow, ToOwned}; +use std::borrow::Borrow; pub(super) enum VariableState { Uninitialized, @@ -8,16 +8,16 @@ pub(super) enum VariableState { Finished, } -/// Cheaply clonable, non-active content of a variable. -/// By non-active, we mean that the shared/mutable invariants are not required to hold. +/// Cheaply clonable, inactive content of a variable. +/// By inactive, we mean that the shared/mutable aliasing invariants are not required to hold. /// Before use, they have to be activated. This allows more flexibility over how references /// can be used and held, without compromising safety. #[derive(Clone)] pub(crate) enum VariableContent { // Owned, but possibly with pre-existing references Referenceable(Referenceable), - Shared(DisabledShared), - Mutable(DisabledMutable), + Shared(InactiveShared), + Mutable(InactiveMutable), } impl VariableContent { @@ -168,7 +168,6 @@ pub(crate) struct VariableBinding { variable_span: Span, } -#[allow(unused)] impl VariableBinding { /// Gets the cloned expression value /// This only works if the value can be transparently cloned @@ -182,14 +181,9 @@ impl VariableBinding { fn into_mut(self) -> FunctionResult { let span = self.variable_span.span_range(); match self.content { - VariableContent::Referenceable(referenceable) => { - referenceable.new_active_mutable(span).map_err(|_| { - self.variable_span - .ownership_error::(MUTABLE_ERROR_MESSAGE) - }) - } + VariableContent::Referenceable(referenceable) => referenceable.new_active_mutable(span), VariableContent::Mutable(inactive) => inactive.activate(span), - VariableContent::Shared(shared) => self + VariableContent::Shared(_) => self .variable_span .ownership_err(SHARED_TO_MUTABLE_ERROR_MESSAGE), } @@ -198,12 +192,7 @@ impl VariableBinding { fn into_shared(self) -> FunctionResult { let span = self.variable_span.span_range(); match self.content { - VariableContent::Referenceable(referenceable) => { - referenceable.new_active_shared(span).map_err(|_| { - self.variable_span - .ownership_error::(SHARED_ERROR_MESSAGE) - }) - } + VariableContent::Referenceable(referenceable) => referenceable.new_active_shared(span), VariableContent::Mutable(inactive) => inactive.into_shared().activate(span), VariableContent::Shared(inactive) => inactive.activate(span), } @@ -215,15 +204,12 @@ impl VariableBinding { VariableContent::Referenceable(referenceable) => { match referenceable.new_active_mutable(span) { Ok(mutable) => Ok(LateBoundValue::Mutable(mutable)), - Err(_) => { + Err(err) => { // Mutable failed, try shared - let shared = referenceable.new_active_shared(span).map_err(|_| { - self.variable_span - .ownership_error::(SHARED_ERROR_MESSAGE) - })?; + let shared = referenceable.new_active_shared(span)?; Ok(LateBoundValue::Shared(LateBoundSharedValue::new( shared, - self.variable_span.syn_error(SHARED_ERROR_MESSAGE), + err.expect_ownership_error()?, ))) } } @@ -242,6 +228,8 @@ impl VariableBinding { } } +static SHARED_TO_MUTABLE_ERROR_MESSAGE: &str = "Cannot mutate a non-mutable reference"; + pub(crate) struct LateBoundOwnedValue { pub(crate) owned: AnyValueOwned, pub(crate) is_from_last_use: bool, @@ -249,12 +237,12 @@ pub(crate) struct LateBoundOwnedValue { /// A shared value where mutable access failed for a specific reason pub(crate) struct LateBoundSharedValue { - pub(crate) shared: SharedValue, + pub(crate) shared: AnyValueShared, pub(crate) reason_not_mutable: syn::Error, } impl LateBoundSharedValue { - pub(crate) fn new(shared: SharedValue, reason_not_mutable: syn::Error) -> Self { + pub(crate) fn new(shared: AnyValueShared, reason_not_mutable: syn::Error) -> Self { Self { shared, reason_not_mutable, @@ -276,7 +264,7 @@ pub(crate) enum LateBoundValue { /// An owned value that can be converted to any ownership type Owned(LateBoundOwnedValue), /// A copy-on-write value that can be converted to an owned value - CopyOnWrite(CopyOnWriteValue), + CopyOnWrite(AnyValueCopyOnWrite), /// A mutable reference Mutable(AnyValueMutable), /// A shared reference where mutable access failed for a specific reason @@ -352,141 +340,47 @@ impl Deref for LateBoundValue { } } -// ============================================================================ -// Type aliases: Old names → New dynamic reference types -// ============================================================================ - -/// Type alias: `Shared` is now `SharedReference`. -pub(crate) type Shared = SharedReference; - -/// Type alias: `Mutable` is now `MutableReference`. -pub(crate) type Mutable = MutableReference; - -/// Type alias: `DisabledShared` is now `InactiveSharedReference`. -pub(crate) type DisabledShared = InactiveSharedReference; - -/// Type alias: `DisabledMutable` is now `InactiveMutableReference`. -pub(crate) type DisabledMutable = InactiveMutableReference; - -pub(crate) type AssigneeValue = AnyValueAssignee; -pub(crate) type SharedValue = AnyValueShared; - -/// A binding of a unique (mutable) reference to a value. -/// See [`ArgumentOwnership::Assignee`] for more details. -/// -/// If you need span information, wrap with `Spanned>`. -pub(crate) struct Assignee(pub MutableReference); - -impl AssigneeValue { - pub(crate) fn set(&mut self, content: impl IntoAnyValue) { - *self.0 = content.into_any_value(); - } -} - -impl IsValueContent for Assignee { - type Type = X::Type; - type Form = BeAssignee; -} - -impl> IntoValueContent<'static> for Assignee -where - X::Type: IsHierarchicalType = X>, - X::Form: IsHierarchicalForm, - X::Form: LeafAsMutForm, -{ - fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - self.0 - .emplace_map(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer, None)) - } -} - -impl Deref for Assignee { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Assignee { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -pub(crate) static MUTABLE_ERROR_MESSAGE: &str = - "The variable cannot be modified as it is already being modified"; -pub(crate) static SHARED_TO_MUTABLE_ERROR_MESSAGE: &str = - "The variable cannot be modified as it is a shared reference"; - -impl Spanned { - pub(crate) fn transparent_clone(&self) -> FunctionResult { - let value = self.0.as_ref().try_transparent_clone(self.1)?; - Ok(value) - } -} - -impl Spanned { - pub(crate) fn transparent_clone(&self) -> FunctionResult { - let value = self.0.as_ref().try_transparent_clone(self.1)?; - Ok(value) - } -} - -pub(crate) static SHARED_ERROR_MESSAGE: &str = - "The variable cannot be read as it is already being modified"; - -// ============================================================================ -// IntoValueContent impls for the new types (formerly on Shared / Mutable) -// ============================================================================ - -// Note: IsValueContent and IntoValueContent are implemented via QqqShared/QqqMutable -// in the forms system, since SharedReference IS the leaf type now. - // ============================================================================ // CopyOnWrite // ============================================================================ /// Copy-on-write value that can be either owned or shared -pub(crate) struct CopyOnWrite { +pub(crate) struct CopyOnWrite { pub(crate) inner: CopyOnWriteInner, } -pub(crate) enum CopyOnWriteInner { +pub(crate) enum CopyOnWriteInner { /// An owned value that can be used directly - Owned(Owned), + Owned(Owned), /// For use when the CopyOnWrite value effectively represents the owned value (post-clone). /// In this case, returning a Cow is just an optimization and we can always clone infallibly. - SharedWithInfallibleCloning(SharedReference), + SharedWithInfallibleCloning(Shared), /// For use when the CopyOnWrite value represents a pre-cloned read-only value. /// A transparent clone may fail in this case at use time. - SharedWithTransparentCloning(SharedReference), + SharedWithTransparentCloning(Shared), } -impl CopyOnWrite { - pub(crate) fn shared_in_place_of_owned(shared: SharedReference) -> Self { +impl CopyOnWrite { + pub(crate) fn shared_in_place_of_owned(shared: Shared) -> Self { Self { inner: CopyOnWriteInner::SharedWithInfallibleCloning(shared), } } - pub(crate) fn shared_in_place_of_shared(shared: SharedReference) -> Self { + pub(crate) fn shared_in_place_of_shared(shared: Shared) -> Self { Self { inner: CopyOnWriteInner::SharedWithTransparentCloning(shared), } } - pub(crate) fn owned(owned: Owned) -> Self { + pub(crate) fn owned(owned: Owned) -> Self { Self { inner: CopyOnWriteInner::Owned(owned), } } #[allow(unused)] - pub(crate) fn extract_owned( - self, - map: impl FnOnce(T::Owned) -> Result, - ) -> Result { + pub(crate) fn extract_owned(self, map: impl FnOnce(T) -> Result) -> Result { match self.inner { CopyOnWriteInner::Owned(value) => match map(value) { Ok(mapped) => Ok(mapped), @@ -506,11 +400,11 @@ impl CopyOnWrite { } } - pub(crate) fn map( + pub(crate) fn map( self, - map_shared: impl FnOnce(SharedReference) -> FunctionResult>, - map_owned: impl FnOnce(Owned) -> FunctionResult>, - ) -> FunctionResult> { + map_shared: impl FnOnce(Shared) -> FunctionResult>, + map_owned: impl FnOnce(Owned) -> FunctionResult>, + ) -> FunctionResult> { let inner = match self.inner { CopyOnWriteInner::Owned(owned) => CopyOnWriteInner::Owned(map_owned(owned)?), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { @@ -525,8 +419,8 @@ impl CopyOnWrite { pub(crate) fn map_into( self, - map_shared: impl FnOnce(SharedReference) -> U, - map_owned: impl FnOnce(Owned) -> U, + map_shared: impl FnOnce(Shared) -> U, + map_owned: impl FnOnce(Owned) -> U, ) -> U { match self.inner { CopyOnWriteInner::Owned(owned) => map_owned(owned), @@ -535,62 +429,59 @@ impl CopyOnWrite { } } - /// Disables this copy-on-write value, releasing any borrow. - /// Returns a `DisabledCopyOnWrite` which can be cloned and later re-enabled. - pub(crate) fn disable(self) -> DisabledCopyOnWrite { + /// Deactivates this copy-on-write value, releasing any borrow. + /// Returns a `InactiveCopyOnWrite` which can be cloned and later re-activated. + pub(crate) fn deactivate(self) -> InactiveCopyOnWrite { let inner = match self.inner { - CopyOnWriteInner::Owned(owned) => DisabledCopyOnWriteInner::Owned(owned), + CopyOnWriteInner::Owned(owned) => InactiveCopyOnWriteInner::Owned(owned), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - DisabledCopyOnWriteInner::SharedWithInfallibleCloning(shared.disable()) + InactiveCopyOnWriteInner::SharedWithInfallibleCloning(shared.deactivate()) } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - DisabledCopyOnWriteInner::SharedWithTransparentCloning(shared.disable()) + InactiveCopyOnWriteInner::SharedWithTransparentCloning(shared.deactivate()) } }; - DisabledCopyOnWrite { inner } + InactiveCopyOnWrite { inner } } } /// A disabled copy-on-write value that can be safely cloned and dropped. -pub(crate) struct DisabledCopyOnWrite { - inner: DisabledCopyOnWriteInner, +pub(crate) struct InactiveCopyOnWrite { + inner: InactiveCopyOnWriteInner, } -enum DisabledCopyOnWriteInner { - Owned(Owned), - SharedWithInfallibleCloning(InactiveSharedReference), - SharedWithTransparentCloning(InactiveSharedReference), +enum InactiveCopyOnWriteInner { + Owned(Owned), + SharedWithInfallibleCloning(InactiveShared), + SharedWithTransparentCloning(InactiveShared), } -impl Clone for DisabledCopyOnWrite -where - T::Owned: Clone, -{ +impl Clone for InactiveCopyOnWrite { fn clone(&self) -> Self { let inner = match &self.inner { - DisabledCopyOnWriteInner::Owned(owned) => { - DisabledCopyOnWriteInner::Owned(owned.clone()) + InactiveCopyOnWriteInner::Owned(owned) => { + InactiveCopyOnWriteInner::Owned(owned.clone()) } - DisabledCopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - DisabledCopyOnWriteInner::SharedWithInfallibleCloning(shared.clone()) + InactiveCopyOnWriteInner::SharedWithInfallibleCloning(shared) => { + InactiveCopyOnWriteInner::SharedWithInfallibleCloning(shared.clone()) } - DisabledCopyOnWriteInner::SharedWithTransparentCloning(shared) => { - DisabledCopyOnWriteInner::SharedWithTransparentCloning(shared.clone()) + InactiveCopyOnWriteInner::SharedWithTransparentCloning(shared) => { + InactiveCopyOnWriteInner::SharedWithTransparentCloning(shared.clone()) } }; Self { inner } } } -impl DisabledCopyOnWrite { - /// Re-enables this disabled copy-on-write value by re-acquiring any borrow. - pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { +impl InactiveCopyOnWrite { + /// Re-activates this inactive copy-on-write value by re-acquiring any borrow. + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { let inner = match self.inner { - DisabledCopyOnWriteInner::Owned(owned) => CopyOnWriteInner::Owned(owned), - DisabledCopyOnWriteInner::SharedWithInfallibleCloning(inactive) => { + InactiveCopyOnWriteInner::Owned(owned) => CopyOnWriteInner::Owned(owned), + InactiveCopyOnWriteInner::SharedWithInfallibleCloning(inactive) => { CopyOnWriteInner::SharedWithInfallibleCloning(inactive.activate(span)?) } - DisabledCopyOnWriteInner::SharedWithTransparentCloning(inactive) => { + InactiveCopyOnWriteInner::SharedWithTransparentCloning(inactive) => { CopyOnWriteInner::SharedWithTransparentCloning(inactive.activate(span)?) } }; @@ -625,13 +516,13 @@ where } } -impl AsRef for CopyOnWrite { +impl AsRef for CopyOnWrite { fn as_ref(&self) -> &T { self } } -impl Deref for CopyOnWrite { +impl Deref for CopyOnWrite { type Target = T; fn deref(&self) -> &T { @@ -669,13 +560,11 @@ impl CopyOnWrite { } /// Converts to shared reference - pub(crate) fn into_shared(self, span: SpanRange) -> SharedValue { + pub(crate) fn into_shared(self, span: SpanRange) -> AnyValueShared { match self.inner { - CopyOnWriteInner::Owned(owned) => SharedValue::new_from_owned(owned, None, span), + CopyOnWriteInner::Owned(owned) => AnyValueShared::new_from_owned(owned, None, span), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => shared, CopyOnWriteInner::SharedWithTransparentCloning(shared) => shared, } } } - -pub(crate) type CopyOnWriteValue = CopyOnWrite; diff --git a/src/interpretation/refs.rs b/src/interpretation/refs.rs index bf8faa57..38efaccc 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -1,7 +1,9 @@ +use std::mem::transmute; + use super::*; /// A flexible type which can either be a reference to a value of type `T`, -/// or an emplaced reference from a [`SharedReference`]. +/// or an emplaced reference from a [`Shared`]. pub(crate) struct AnyRef<'a, T: ?Sized + 'static> { inner: AnyRefInner<'a, T>, } @@ -59,30 +61,60 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { pub(crate) fn emplace_map( self, - f: impl for<'e> FnOnce(&'e T, &mut AnyRefEmplacer<'a, 'e, T>) -> O, + f: impl for<'m> FnOnce(&'m T, &mut AnyRefEmplacer<'_, 'a, 'm, T>) -> O, ) -> O { - let copied_ref = self.deref() as *const T; - let mut emplacer = AnyRefEmplacer { - inner: Some(self), - encapsulation_lifetime: std::marker::PhantomData, - }; - f( - // SAFETY: The underlying reference is valid for the lifetime of self - // So we can copy it fine - unsafe { &*copied_ref }, - &mut emplacer, - ) + match self.inner { + AnyRefInner::Direct(direct_ref) => { + let raw_ptr = direct_ref as *const T; + let mut emplacer = AnyRefEmplacer { + inner: AnyRefEmplacerState::Direct(DirectRefEmplacerState { + ptr: Some(raw_ptr), + original_lifetime: PhantomData, + }), + }; + // SAFETY: raw_ptr was derived from a valid &'a T, and the original + // reference was consumed by converting to *const T. + f(unsafe { &*raw_ptr }, &mut emplacer) + } + AnyRefInner::Encapsulated(shared) => shared.emplace_map(|x, shared_emplacer| { + let mut emplacer = AnyRefEmplacer { + inner: AnyRefEmplacerState::Encapsulated(shared_emplacer), + }; + f(x, &mut emplacer) + }), + } } } -pub(crate) struct AnyRefEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { - inner: Option>, - encapsulation_lifetime: std::marker::PhantomData<&'e ()>, +pub(crate) struct AnyRefEmplacer<'x, 'a, 'm: 'x, T: 'static + ?Sized> { + inner: AnyRefEmplacerState<'x, 'a, 'm, T>, +} + +enum AnyRefEmplacerState<'x, 'a, 'm: 'x, T: 'static + ?Sized> { + Direct(DirectRefEmplacerState<'a, T>), + Encapsulated(&'x mut SharedEmplacer<'m, T>), +} + +struct DirectRefEmplacerState<'a, T: 'static + ?Sized> { + ptr: Option<*const T>, + original_lifetime: PhantomData<&'a T>, } -impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { +impl<'x, 'a, 'm, T: 'static + ?Sized> AnyRefEmplacer<'x, 'a, 'm, T> { pub(crate) fn revert(&mut self) -> AnyRef<'a, T> { - self.inner.take().expect("Emplacer already consumed") + match &mut self.inner { + AnyRefEmplacerState::Direct(DirectRefEmplacerState { ptr, .. }) => { + let ptr = ptr.take().expect("Emplacer already consumed"); + AnyRef { + // SAFETY: ptr was derived from a valid &'a T and the original + // reference was consumed when creating the emplacer. + inner: AnyRefInner::Direct(unsafe { &*ptr }), + } + } + AnyRefEmplacerState::Encapsulated(emplacer) => AnyRef { + inner: AnyRefInner::Encapsulated(emplacer.revert()), + }, + } } /// Emplaces a mapped shared reference, consuming the emplacer's reference tracking. @@ -91,25 +123,19 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { /// derivation) are validated by the [`MappedRef`] constructor. pub(crate) fn emplace( &mut self, - mapped: MappedRef<'e, V>, + mapped: MappedRef<'m, V>, ) -> AnyRef<'a, V> { - let any_ref = self - .inner - .take() - .expect("You can only emplace to create a new AnyRef value once"); - let (value, path_extension, span) = mapped.into_parts(); - match any_ref.inner { - AnyRefInner::Direct(_) => AnyRef { - // 'e: 'a is guaranteed by the struct constraint, so coercion is valid - inner: AnyRefInner::Direct(value), - }, - AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.emplace_map(|_, emplacer| { - // SAFETY: The value's lifetime ('e) matches the SharedEmplacer's lifetime - // since both are derived from the same underlying data - let remapped = unsafe { MappedRef::new_unchecked(value, path_extension, span) }; - emplacer.emplace(remapped) - })), + match &mut self.inner { + AnyRefEmplacerState::Direct(_) => { + let (value, _, _) = mapped.into_parts(); + // SAFETY: In Direct case, 'm == 'a, so the lifetime of the value is valid for 'a + let value = unsafe { transmute::<&'m V, &'a V>(value) }; + AnyRef { + inner: AnyRefInner::Direct(value), + } + } + AnyRefEmplacerState::Encapsulated(emplacer) => AnyRef { + inner: AnyRefInner::Encapsulated(emplacer.emplace(mapped)), }, } } @@ -139,15 +165,15 @@ impl<'a, T: ?Sized> ToSpannedRef<'a> for &'a T { } } -impl<'a, T: ?Sized> From> for AnyRef<'a, T> { - fn from(value: SharedReference) -> Self { +impl<'a, T: ?Sized> From> for AnyRef<'a, T> { + fn from(value: Shared) -> Self { Self { inner: AnyRefInner::Encapsulated(value), } } } -impl ToSpannedRef<'static> for SharedReference { +impl ToSpannedRef<'static> for Shared { type Target = T; fn into_ref(self) -> AnyRef<'static, Self::Target> { self.into() @@ -159,7 +185,7 @@ impl ToSpannedRef<'static> for SharedReference { enum AnyRefInner<'a, T: 'static + ?Sized> { Direct(&'a T), - Encapsulated(SharedReference), + Encapsulated(Shared), } impl<'a, T: 'static + ?Sized> Deref for AnyRef<'a, T> { @@ -174,7 +200,7 @@ impl<'a, T: 'static + ?Sized> Deref for AnyRef<'a, T> { } /// A flexible type which can either be a mutable reference to a value of type `T`, -/// or an emplaced reference from a [`MutableReference`]. +/// or an emplaced reference from a [`Mutable`]. pub(crate) struct AnyMut<'a, T: 'static + ?Sized> { inner: AnyMutInner<'a, T>, } @@ -240,7 +266,7 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { pub(crate) fn emplace_map( self, - f: impl for<'e> FnOnce(&'e mut T, &mut AnyMutEmplacer<'a, 'e, T>) -> O, + f: impl for<'m> FnOnce(&'m mut T, &mut AnyMutEmplacer<'_, 'a, 'm, T>) -> O, ) -> O { match self.inner { AnyMutInner::Direct(direct_mut) => { @@ -248,52 +274,53 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { // stores the raw pointer, so no second &mut T exists. let raw_ptr = direct_mut as *mut T; let mut emplacer = AnyMutEmplacer { - inner: Some(AnyMutEmplacerState::Direct(raw_ptr, PhantomData)), - encapsulation_lifetime: std::marker::PhantomData, + inner: AnyMutEmplacerState::Direct(DirectMutEmplacerState { + ptr: Some(raw_ptr), + original_lifetime: PhantomData, + }), }; // SAFETY: raw_ptr was derived from a valid &'a mut T, and the original // reference was consumed by converting to *mut T. f(unsafe { &mut *raw_ptr }, &mut emplacer) } - AnyMutInner::Encapsulated(mut mutable) => { - // MutableReference internally stores NonNull (a raw pointer), - // not &mut T, so there is no aliasing issue. - let raw_ptr = (&mut *mutable) as *mut T; + AnyMutInner::Encapsulated(mutable) => mutable.emplace_map(|x, mut_emplacer| { let mut emplacer = AnyMutEmplacer { - inner: Some(AnyMutEmplacerState::Encapsulated(mutable)), - encapsulation_lifetime: std::marker::PhantomData, + inner: AnyMutEmplacerState::Encapsulated(mut_emplacer), }; - // SAFETY: raw_ptr was derived from the MutableReference which - // uses NonNull internally (not &mut), so no aliasing occurs. - f(unsafe { &mut *raw_ptr }, &mut emplacer) - } + + f(x, &mut emplacer) + }), } } } -pub(crate) struct AnyMutEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { - inner: Option>, - encapsulation_lifetime: std::marker::PhantomData<&'e ()>, +pub(crate) struct AnyMutEmplacer<'x, 'a, 'm: 'x, T: 'static + ?Sized> { + inner: AnyMutEmplacerState<'x, 'a, 'm, T>, +} + +enum AnyMutEmplacerState<'x, 'a, 'm: 'x, T: 'static + ?Sized> { + Direct(DirectMutEmplacerState<'a, T>), + Encapsulated(&'x mut MutableEmplacer<'m, T>), } -/// Stores either a raw pointer (for Direct) or a MutableReference (for Encapsulated), -/// avoiding &mut aliasing that would occur if we stored the full AnyMut. -enum AnyMutEmplacerState<'a, T: 'static + ?Sized> { - Direct(*mut T, PhantomData<&'a mut T>), - Encapsulated(MutableReference), +struct DirectMutEmplacerState<'a, T: 'static + ?Sized> { + ptr: Option<*mut T>, + original_lifetime: PhantomData<&'a mut T>, } -impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { +impl<'x, 'a, 'm, T: 'static + ?Sized> AnyMutEmplacer<'x, 'a, 'm, T> { pub(crate) fn revert(&mut self) -> AnyMut<'a, T> { - let state = self.inner.take().expect("Emplacer already consumed"); - match state { - AnyMutEmplacerState::Direct(ptr, _) => AnyMut { - // SAFETY: ptr was derived from a valid &'a mut T and no other - // &mut T currently exists (the one passed to the closure has ended). - inner: AnyMutInner::Direct(unsafe { &mut *ptr }), - }, - AnyMutEmplacerState::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable), + match &mut self.inner { + AnyMutEmplacerState::Direct(DirectMutEmplacerState { ptr, .. }) => { + let ptr = ptr.take().expect("Emplacer already consumed"); + AnyMut { + // SAFETY: ptr was derived from a valid &'a mut T and no other + // &mut T currently exists (the one passed to the closure has ended). + inner: AnyMutInner::Direct(unsafe { &mut *ptr }), + } + } + AnyMutEmplacerState::Encapsulated(emplacer) => AnyMut { + inner: AnyMutInner::Encapsulated(emplacer.revert()), }, } } @@ -304,25 +331,19 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { /// derivation) are validated by the [`MappedMut`] constructor. pub(crate) fn emplace( &mut self, - mapped: MappedMut<'e, V>, + mapped: MappedMut<'m, V>, ) -> AnyMut<'a, V> { - let state = self - .inner - .take() - .expect("You can only emplace to create a new AnyMut value once"); - let (value, path_extension, span) = mapped.into_parts(); - match state { - AnyMutEmplacerState::Direct(_, _) => AnyMut { - // 'e: 'a is guaranteed by the struct constraint, so coercion is valid - inner: AnyMutInner::Direct(value), - }, - AnyMutEmplacerState::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.emplace_map(|_, emplacer| { - // SAFETY: The value's lifetime ('e) matches the MutableEmplacer's lifetime - // since both are derived from the same underlying data - let remapped = unsafe { MappedMut::new_unchecked(value, path_extension, span) }; - emplacer.emplace(remapped) - })), + match &mut self.inner { + AnyMutEmplacerState::Direct(_) => { + let (value, _, _) = mapped.into_parts(); + // SAFETY: In Direct case, 'm == 'a, so the lifetime of the value is valid for 'a + let value = unsafe { transmute::<&'m mut V, &'a mut V>(value) }; + AnyMut { + inner: AnyMutInner::Direct(value), + } + } + AnyMutEmplacerState::Encapsulated(emplacer) => AnyMut { + inner: AnyMutInner::Encapsulated(emplacer.emplace(mapped)), }, } } @@ -342,8 +363,8 @@ impl<'a, T: ?Sized + 'static> IntoAnyMut<'a> for &'a mut T { } } -impl<'a, T: ?Sized> From> for AnyMut<'a, T> { - fn from(value: MutableReference) -> Self { +impl<'a, T: ?Sized> From> for AnyMut<'a, T> { + fn from(value: Mutable) -> Self { Self { inner: AnyMutInner::Encapsulated(value), } @@ -352,7 +373,7 @@ impl<'a, T: ?Sized> From> for AnyMut<'a, T> { enum AnyMutInner<'a, T: 'static + ?Sized> { Direct(&'a mut T), - Encapsulated(MutableReference), + Encapsulated(Mutable), } impl<'a, T: 'static + ?Sized> Deref for AnyMut<'a, T> { diff --git a/src/interpretation/variable.rs b/src/interpretation/variable.rs index 2c05e064..93600c40 100644 --- a/src/interpretation/variable.rs +++ b/src/interpretation/variable.rs @@ -161,7 +161,7 @@ impl VariableReference { pub(crate) fn resolve_shared( &self, interpreter: &mut Interpreter, - ) -> FunctionResult { + ) -> FunctionResult { Ok(self .resolve_concrete(interpreter, ArgumentOwnership::Shared)? .expect_shared()) diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index c3d17e28..68af8048 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -2,19 +2,20 @@ use std::mem::transmute; use super::*; -pub(crate) struct MutableReference(pub(super) ReferenceCore); +/// Represents an active `&mut T` reference, to something derived from a Referenceable root. +pub(crate) struct Mutable(pub(super) ReferenceCore); -impl MutableReference { - pub(crate) fn deactivate(self) -> InactiveMutableReference { +impl Mutable { + pub(crate) fn deactivate(self) -> InactiveMutable { self.0.core.data_mut().deactivate_reference(self.0.id); - InactiveMutableReference(self.0) + InactiveMutable(self.0) } /// A powerful map method which lets you place the resultant mapped reference inside /// a structure arbitrarily. pub(crate) fn emplace_map( mut self, - f: impl for<'e> FnOnce(&'e mut T, &mut MutableEmplacer<'e, T>) -> O, + f: impl for<'a> FnOnce(&'a mut T, &mut MutableEmplacer<'a, T>) -> O, ) -> O { // SAFETY: The validity + safety invariants are upheld by `ReferenceableCore` // ... assuming this id is marked as a mutable reference for the duration. @@ -32,8 +33,8 @@ impl MutableReference { /// making this method itself safe. pub(crate) fn map( self, - f: impl for<'r> FnOnce(&'r mut T) -> MappedMut<'r, V>, - ) -> MutableReference { + f: impl for<'a> FnOnce(&'a mut T) -> MappedMut<'a, V>, + ) -> Mutable { self.emplace_map(move |input, emplacer| emplacer.emplace(f(input))) } @@ -42,8 +43,8 @@ impl MutableReference { /// The unsafe PathExtension assertion is confined to the [`MappedMut::new`] constructor. pub(crate) fn try_map( self, - f: impl for<'r> FnOnce(&'r mut T) -> Result, E>, - ) -> Result, (E, MutableReference)> { + f: impl for<'a> FnOnce(&'a mut T) -> Result, E>, + ) -> Result, (E, Mutable)> { self.emplace_map(|input, emplacer| match f(input) { Ok(mapped) => Ok(emplacer.emplace(mapped)), Err(e) => Err((e, emplacer.revert())), @@ -51,7 +52,7 @@ impl MutableReference { } } -impl MutableReference { +impl Mutable { /// Creates a new MutableReference from an owned value, wrapping it in a Referenceable. pub(crate) fn new_from_owned( value: AnyValue, @@ -65,31 +66,14 @@ impl MutableReference { } } -impl MutableReference { - /// Disables this mutable reference (bridge for old `disable()` API). - /// Equivalent to `deactivate()` in the new naming. - pub(crate) fn disable(self) -> InactiveMutableReference { - self.deactivate() - } - - /// Converts this mutable reference into a shared reference. - /// Bridge for old `into_shared()` API. - pub(crate) fn into_shared(self) -> SharedReference { - let span = self - .0 - .core - .data_mut() - .for_reference(self.0.id) - .creation_span; - let inactive = self.deactivate(); - let inactive_shared = inactive.into_shared(); - inactive_shared - .activate(span) - .expect("Converting mutable to shared should always succeed since we just released the mutable borrow") +impl Mutable { + pub(crate) fn into_shared(self) -> Shared { + self.0.core.data_mut().make_shared(self.0.id); + Shared(self.0) } } -impl Deref for MutableReference { +impl Deref for Mutable { type Target = T; fn deref(&self) -> &Self::Target { @@ -102,19 +86,19 @@ impl Deref for MutableReference { } } -impl AsRef for MutableReference { +impl AsRef for Mutable { fn as_ref(&self) -> &T { self } } -impl AsMut for MutableReference { +impl AsMut for Mutable { fn as_mut(&mut self) -> &mut T { self } } -impl DerefMut for MutableReference { +impl DerefMut for Mutable { fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: See the rustdoc on dynamic_references/mod.rs for full details. // To summarize: @@ -125,29 +109,33 @@ impl DerefMut for MutableReference { } } -pub(crate) struct InactiveMutableReference(pub(super) ReferenceCore); +/// Represents an inactive but *valid* mutable reference. +/// +/// It can't currently deref into a `&mut T`, but can after being activated, when +/// the aliasing rules get checked and enforced. +pub(crate) struct InactiveMutable(pub(super) ReferenceCore); -impl Clone for InactiveMutableReference { +impl Clone for InactiveMutable { fn clone(&self) -> Self { - InactiveMutableReference(self.0.clone()) + InactiveMutable(self.0.clone()) } } -impl InactiveMutableReference { - pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { +impl InactiveMutable { + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { self.0 .core .data_mut() .activate_mutable_reference(self.0.id, span)?; - Ok(MutableReference(self.0)) + Ok(Mutable(self.0)) } - pub(crate) fn into_shared(self) -> InactiveSharedReference { + pub(crate) fn into_shared(self) -> InactiveShared { // As an inactive reference, we are free to map between them // ... we could even enable the other way around, but that'd likely allow breaking // application invariants which we want to respect. self.0.core.data_mut().make_shared(self.0.id); - InactiveSharedReference(self.0) + InactiveShared(self.0) } } @@ -199,25 +187,25 @@ impl<'a, V: ?Sized> MappedMut<'a, V> { } } -pub(crate) struct MutableEmplacer<'e, T: ?Sized>(EmplacerCore<'e, T>); +pub(crate) struct MutableEmplacer<'a, T: ?Sized>(EmplacerCore<'a, T>); -impl<'e, T: ?Sized> MutableEmplacer<'e, T> { - pub(crate) fn revert(&mut self) -> MutableReference { - MutableReference(self.0.revert()) +impl<'a, T: ?Sized> MutableEmplacer<'a, T> { + pub(crate) fn revert(&mut self) -> Mutable { + Mutable(self.0.revert()) } /// Emplaces a mapped mutable reference, consuming the emplacer's reference tracking. /// /// This is safe because all preconditions (correct PathExtension and valid reference /// derivation) are validated by the [`MappedMut`] constructor. - pub(crate) fn emplace( + pub(crate) fn emplace<'e: 'a, V: 'static + ?Sized>( &mut self, mapped: MappedMut<'e, V>, - ) -> MutableReference { + ) -> Mutable { let (value, path_extension, span) = mapped.into_parts(); // SAFETY: The pointer is from a valid reference (guaranteed by MappedMut constructor), // and the PathExtension was validated by the MappedMut constructor. let pointer = unsafe { NonNull::new_unchecked(value as *mut V) }; - unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, span)) } + unsafe { Mutable(self.0.emplace_unchecked(pointer, path_extension, span)) } } } diff --git a/src/misc/dynamic_references/reference_core.rs b/src/misc/dynamic_references/reference_core.rs index 7285f914..ed30dea9 100644 --- a/src/misc/dynamic_references/reference_core.rs +++ b/src/misc/dynamic_references/reference_core.rs @@ -91,7 +91,8 @@ impl<'e, T: ?Sized> EmplacerCore<'e, T> { } /// SAFETY: - /// - The caller must ensure that the pointer is derived from the original content + /// - The caller must ensure that the pointer is derived from the original content, + /// or more specifically, it lives at least as long as that content /// - The caller must ensure that the PathExtension is correct pub(crate) unsafe fn emplace_unchecked( &mut self, diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 31de0f01..6af5c6bc 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -12,31 +12,25 @@ impl Referenceable { } } - pub(crate) fn new_inactive_shared(&self) -> InactiveSharedReference { - InactiveSharedReference(ReferenceCore::new_root( + pub(crate) fn new_inactive_shared(&self) -> InactiveShared { + InactiveShared(ReferenceCore::new_root( self.core.clone(), ReferenceKind::InactiveShared, )) } - pub(crate) fn new_inactive_mutable(&self) -> InactiveMutableReference { - InactiveMutableReference(ReferenceCore::new_root( + pub(crate) fn new_inactive_mutable(&self) -> InactiveMutable { + InactiveMutable(ReferenceCore::new_root( self.core.clone(), ReferenceKind::InactiveMutable, )) } - pub(crate) fn new_active_shared( - &self, - span: SpanRange, - ) -> FunctionResult> { + pub(crate) fn new_active_shared(&self, span: SpanRange) -> FunctionResult> { self.new_inactive_shared().activate(span) } - pub(crate) fn new_active_mutable( - &self, - span: SpanRange, - ) -> FunctionResult> { + pub(crate) fn new_active_mutable(&self, span: SpanRange) -> FunctionResult> { self.new_inactive_mutable().activate(span) } @@ -236,7 +230,7 @@ impl ReferenceableData { let parent_type = specifier.bound_type_kind(); match last_path_part { PathPart::Value { bound_as } => { - if !bound_as.is_tightening_to(&parent_type) { + if !bound_as.is_narrowing_to(&parent_type) { panic!( "Invalid path extension: cannot derive {} from {}", parent_type.source_name(), @@ -253,12 +247,12 @@ impl ReferenceableData { bound_as: child_bound_as, }); } - (PathPart::Value { bound_as }, PathExtension::Tightened(new_bound_as)) => { - if bound_as.is_tightening_to(&new_bound_as) { + (PathPart::Value { bound_as }, PathExtension::TypeNarrowing(new_bound_as)) => { + if bound_as.is_narrowing_to(&new_bound_as) { *bound_as = new_bound_as; } else { panic!( - "Invalid path extension: cannot derive {} from {}", + "Invalid path extension: cannot narrow to {} from {}", new_bound_as.source_name(), bound_as.source_name() ); @@ -450,8 +444,8 @@ impl ReferencePath { pub(crate) enum PathExtension { /// Extends the path with a child reference (e.g. .x or [0]) Child(ChildSpecifier, TypeKind), - /// Extends the path with a value of a certain type (e.g. dereferencing a pointer) - Tightened(TypeKind), + /// Extends the path with a value of a certain type + TypeNarrowing(TypeKind), } #[derive(PartialEq, Eq, Clone)] diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 245ba601..fc1d842e 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -2,25 +2,26 @@ use std::mem::transmute; use super::*; -pub(crate) struct SharedReference(pub(super) ReferenceCore); +/// Represents an active `&T` reference, to something derived from a Referenceable root. +pub(crate) struct Shared(pub(super) ReferenceCore); -impl Clone for SharedReference { +impl Clone for Shared { fn clone(&self) -> Self { - SharedReference(self.0.clone()) + Shared(self.0.clone()) } } -impl SharedReference { - pub(crate) fn deactivate(self) -> InactiveSharedReference { +impl Shared { + pub(crate) fn deactivate(self) -> InactiveShared { self.0.core.data_mut().deactivate_reference(self.0.id); - InactiveSharedReference(self.0) + InactiveShared(self.0) } /// A powerful map method which lets you place the resultant mapped reference inside /// a structure arbitrarily. pub(crate) fn emplace_map( self, - f: impl for<'e> FnOnce(&'e T, &mut SharedEmplacer<'e, T>) -> O, + f: impl for<'a> FnOnce(&'a T, &mut SharedEmplacer<'a, T>) -> O, ) -> O { // SAFETY: The validity + safety invariants are upheld by `ReferenceableCore` // ... assuming this id is marked as a shared reference for the duration. @@ -38,8 +39,8 @@ impl SharedReference { /// making this method itself safe. pub(crate) fn map( self, - f: impl for<'r> FnOnce(&'r T) -> MappedRef<'r, V>, - ) -> SharedReference { + f: impl for<'a> FnOnce(&'a T) -> MappedRef<'a, V>, + ) -> Shared { self.emplace_map(move |input, emplacer| emplacer.emplace(f(input))) } @@ -48,8 +49,8 @@ impl SharedReference { /// The unsafe PathExtension assertion is confined to the [`MappedRef::new`] constructor. pub(crate) fn try_map( self, - f: impl for<'r> FnOnce(&'r T) -> Result, E>, - ) -> Result, (E, SharedReference)> { + f: impl for<'a> FnOnce(&'a T) -> Result, E>, + ) -> Result, (E, Shared)> { self.emplace_map(|input, emplacer| match f(input) { Ok(mapped) => Ok(emplacer.emplace(mapped)), Err(e) => Err((e, emplacer.revert())), @@ -57,7 +58,7 @@ impl SharedReference { } } -impl SharedReference { +impl Shared { /// Creates a new SharedReference from an owned value, wrapping it in a Referenceable. /// Uses a placeholder name and span for the Referenceable root. pub(crate) fn new_from_owned( @@ -77,15 +78,7 @@ impl SharedReference { } } -impl SharedReference { - /// Disables this shared reference (bridge for old `disable()` API). - /// Equivalent to `deactivate()` in the new naming. - pub(crate) fn disable(self) -> InactiveSharedReference { - self.deactivate() - } -} - -impl Deref for SharedReference { +impl Deref for Shared { type Target = T; fn deref(&self) -> &Self::Target { @@ -98,27 +91,31 @@ impl Deref for SharedReference { } } -impl AsRef for SharedReference { +impl AsRef for Shared { fn as_ref(&self) -> &T { self } } -pub(crate) struct InactiveSharedReference(pub(super) ReferenceCore); +/// Represents an inactive but *valid* shared reference. +/// +/// It can't currently deref into a `&T`, but can after being activated, when +/// the aliasing rules get checked and enforced. +pub(crate) struct InactiveShared(pub(super) ReferenceCore); -impl Clone for InactiveSharedReference { +impl Clone for InactiveShared { fn clone(&self) -> Self { - InactiveSharedReference(self.0.clone()) + InactiveShared(self.0.clone()) } } -impl InactiveSharedReference { - pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { +impl InactiveShared { + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { self.0 .core .data_mut() .activate_shared_reference(self.0.id, span)?; - Ok(SharedReference(self.0)) + Ok(Shared(self.0)) } } @@ -166,25 +163,25 @@ impl<'a, V: ?Sized> MappedRef<'a, V> { } } -pub(crate) struct SharedEmplacer<'e, T: ?Sized>(EmplacerCore<'e, T>); +pub(crate) struct SharedEmplacer<'a, T: ?Sized>(EmplacerCore<'a, T>); -impl<'e, T: ?Sized> SharedEmplacer<'e, T> { - pub(crate) fn revert(&mut self) -> SharedReference { - SharedReference(self.0.revert()) +impl<'a, T: ?Sized> SharedEmplacer<'a, T> { + pub(crate) fn revert(&mut self) -> Shared { + Shared(self.0.revert()) } /// Emplaces a mapped shared reference, consuming the emplacer's reference tracking. /// /// This is safe because all preconditions (correct PathExtension and valid reference /// derivation) are validated by the [`MappedRef`] constructor. - pub(crate) fn emplace( + pub(crate) fn emplace<'e: 'a, V: 'static + ?Sized>( &mut self, mapped: MappedRef<'e, V>, - ) -> SharedReference { + ) -> Shared { let (value, path_extension, span) = mapped.into_parts(); // SAFETY: The pointer is from a valid reference (guaranteed by MappedRef constructor), // and the PathExtension was validated by the MappedRef constructor. let pointer = unsafe { NonNull::new_unchecked(value as *const V as *mut V) }; - unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, span)) } + unsafe { Shared(self.0.emplace_unchecked(pointer, path_extension, span)) } } } diff --git a/src/misc/errors.rs b/src/misc/errors.rs index 27c04977..2a373e8b 100644 --- a/src/misc/errors.rs +++ b/src/misc/errors.rs @@ -143,6 +143,15 @@ impl FunctionError { _ => Err(self), } } + + pub(crate) fn expect_ownership_error(self) -> Result { + match self.0.inner.as_ref() { + ExecutionInterruptInner::Error(ExecutionError(ErrorKind::Ownership, _)) => { + Ok(self.0.expect_error().convert_to_syn_error()) + } + _ => panic!("Expected ownership error"), + } + } } pub(crate) trait FunctionResultExt { From e53715edaf004343e5aff73e9b57476e41e38e1d Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 28 Feb 2026 16:59:56 +0000 Subject: [PATCH 19/25] tests: Update error output --- .../expressions/array_place_destructure_multiple_muts.stderr | 5 ++++- tests/compilation_failures/expressions/swap_itself.stderr | 1 - .../functions/ref_argument_cannot_be_mutated.stderr | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/compilation_failures/expressions/array_place_destructure_multiple_muts.stderr b/tests/compilation_failures/expressions/array_place_destructure_multiple_muts.stderr index 58d88c8c..21e440fd 100644 --- a/tests/compilation_failures/expressions/array_place_destructure_multiple_muts.stderr +++ b/tests/compilation_failures/expressions/array_place_destructure_multiple_muts.stderr @@ -1,4 +1,7 @@ -error: The variable cannot be read as it is already being modified +error: Cannot create a shared reference because it clashes with an existing mutable reference: + This reference : [*active*] &arr (of type any) + Other reference: [*active*] &mut arr (of type any) + Reason : mutation may be observed from the other active reference, which breaks aliasing rules --> tests/compilation_failures/expressions/array_place_destructure_multiple_muts.rs:6:13 | 6 | arr[arr[1]] = 3; diff --git a/tests/compilation_failures/expressions/swap_itself.stderr b/tests/compilation_failures/expressions/swap_itself.stderr index de6412af..a15b6faf 100644 --- a/tests/compilation_failures/expressions/swap_itself.stderr +++ b/tests/compilation_failures/expressions/swap_itself.stderr @@ -2,7 +2,6 @@ error: Cannot create a mutable reference because it clashes with an existing ref This reference : [*active*] &mut a (of type any) Other reference: [*active*] &mut a (of type any) Reason : mutation may be observed from the other active reference, which breaks aliasing rules - --> tests/compilation_failures/expressions/swap_itself.rs:6:16 | 6 | a.swap(a); diff --git a/tests/compilation_failures/functions/ref_argument_cannot_be_mutated.stderr b/tests/compilation_failures/functions/ref_argument_cannot_be_mutated.stderr index 185a9ee0..8b72a038 100644 --- a/tests/compilation_failures/functions/ref_argument_cannot_be_mutated.stderr +++ b/tests/compilation_failures/functions/ref_argument_cannot_be_mutated.stderr @@ -1,4 +1,4 @@ -error: The variable cannot be modified as it is a shared reference +error: Cannot mutate a non-mutable reference --> tests/compilation_failures/functions/ref_argument_cannot_be_mutated.rs:5:48 | 5 | let my_array_push = |arr: &any, value| arr.push(value); From 8b6178283f2cd523be42ab523fa5df1917b5867c Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 28 Feb 2026 18:32:55 +0000 Subject: [PATCH 20/25] refactor: Unify `CopyOnWrite` --- plans/TODO.md | 2 +- src/expressions/concepts/content.rs | 4 +- src/expressions/concepts/form.rs | 2 +- src/expressions/concepts/forms/argument.rs | 2 +- .../concepts/forms/copy_on_write.rs | 359 +++++++++++++----- src/expressions/concepts/forms/late_bound.rs | 2 +- src/expressions/concepts/forms/shared.rs | 1 + src/expressions/concepts/mapping.rs | 3 + src/expressions/type_resolution/arguments.rs | 21 +- src/interpretation/bindings.rs | 233 +----------- 10 files changed, 284 insertions(+), 345 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index eba29e01..0d7cd05d 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -300,7 +300,7 @@ Suddenly dawned on me - my Disabled arguments might not be safe. - [x] Rename `DisabledArgumentValue` and `DisabledCopyOnWrite` to `Inactive__` and their method from `enable` to `activate` and ditto with `disable -> deactivate` - [ ] See what else can be deleted from `bindings.rs` - [ ] Unify LateBound into `QqqLateBound` - - [ ] Unify CopyOnWrite into `QqqCopyOnWrite` + - [x] Unify CopyOnWrite into `QqqCopyOnWrite` - [ ] Add various tests: - [ ] Stretching different error messages - [ ] Showing I can do e.g. `x.a += x.b` diff --git a/src/expressions/concepts/content.rs b/src/expressions/concepts/content.rs index 16ae7a21..4b42b108 100644 --- a/src/expressions/concepts/content.rs +++ b/src/expressions/concepts/content.rs @@ -180,7 +180,7 @@ where fn clone_to_owned_transparently<'r>( &'r self, span_range: SpanRange, - ) -> ExecutionResult> + ) -> FunctionResult> where 'a: 'r, Self::Form: LeafAsRefForm, @@ -189,7 +189,7 @@ where map_via_leaf! { input: &'r (Content<'a, Self::Type, Self::Form>) = self, state: SpanRange | let span_range = span_range, - fn map_leaf(leaf) -> (ExecutionResult>) { + fn map_leaf(leaf) -> (FunctionResult>) { F::leaf_clone_to_owned_transparently(leaf, span_range) } } diff --git a/src/expressions/concepts/form.rs b/src/expressions/concepts/form.rs index a7113215..bda41811 100644 --- a/src/expressions/concepts/form.rs +++ b/src/expressions/concepts/form.rs @@ -39,7 +39,7 @@ pub(crate) trait LeafAsRefForm: IsHierarchicalForm { fn leaf_clone_to_owned_transparently<'r, 'a: 'r, T: IsLeafType>( leaf: &'r Self::Leaf<'a, T>, error_span: SpanRange, - ) -> ExecutionResult { + ) -> FunctionResult { let type_kind = T::type_kind(); if type_kind.supports_transparent_cloning() { Ok(Self::leaf_clone_to_owned_infallible(leaf)) diff --git a/src/expressions/concepts/forms/argument.rs b/src/expressions/concepts/forms/argument.rs index 5c260ced..85b1dcb4 100644 --- a/src/expressions/concepts/forms/argument.rs +++ b/src/expressions/concepts/forms/argument.rs @@ -2,7 +2,7 @@ use super::*; pub(crate) enum Argument { Owned(L), - CopyOnWrite(QqqCopyOnWrite), + CopyOnWrite(CopyOnWrite), Mutable(Mutable), Assignee(Assignee), Shared(Shared), diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index 7bec58b9..92fbc418 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -1,6 +1,6 @@ use super::*; -pub(crate) enum QqqCopyOnWrite { +pub(crate) enum CopyOnWrite { /// An owned value that can be used directly Owned(Owned), /// For use when the CopyOnWrite value effectively represents the owned value (post-clone). @@ -10,30 +10,190 @@ pub(crate) enum QqqCopyOnWrite { /// A transparent clone may fail in this case at use time. SharedWithTransparentCloning(Shared), } - -impl IsValueContent for QqqCopyOnWrite { - type Type = L::Type; +impl IsValueContent for CopyOnWrite { + type Type = X::Type; type Form = BeCopyOnWrite; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqCopyOnWrite { +impl<'a, X: Clone + IsValueContent> IntoValueContent<'a> for CopyOnWrite +where + X: 'static, + X::Type: IsHierarchicalType = X>, + X: IsValueContent + IsSelfValueContent<'static> + for<'b> IntoValueContent<'b>, +{ fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self + match self { + CopyOnWrite::Owned(owned) => { + BeCopyOnWrite::new_owned(owned) + } + CopyOnWrite::SharedWithInfallibleCloning(shared) => { + BeCopyOnWrite::new_shared_in_place_of_owned(shared) + } + CopyOnWrite::SharedWithTransparentCloning(shared) => { + BeCopyOnWrite::new_shared_in_place_of_shared(shared) + } + } } } -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqCopyOnWrite { +impl<'a, L: IsValueLeaf> FromValueContent<'a> for CopyOnWrite { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } } +impl CopyOnWrite { + pub(crate) fn shared_in_place_of_owned(shared: Shared) -> Self { + CopyOnWrite::SharedWithInfallibleCloning(shared) + } + + pub(crate) fn shared_in_place_of_shared(shared: Shared) -> Self { + CopyOnWrite::SharedWithTransparentCloning(shared) + } + + pub(crate) fn owned(owned: Owned) -> Self { + CopyOnWrite::Owned(owned) + } + + pub(crate) fn acts_as_shared_reference(&self) -> bool { + match &self { + CopyOnWrite::Owned { .. } => false, + CopyOnWrite::SharedWithInfallibleCloning { .. } => false, + CopyOnWrite::SharedWithTransparentCloning { .. } => true, + } + } + + pub(crate) fn map( + self, + map_shared: impl FnOnce(Shared) -> FunctionResult>, + map_owned: impl FnOnce(Owned) -> FunctionResult>, + ) -> FunctionResult> { + Ok(match self { + CopyOnWrite::Owned(owned) => CopyOnWrite::Owned(map_owned(owned)?), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { + CopyOnWrite::SharedWithInfallibleCloning(map_shared(shared)?) + } + CopyOnWrite::SharedWithTransparentCloning(shared) => { + CopyOnWrite::SharedWithTransparentCloning(map_shared(shared)?) + } + }) + } + + pub(crate) fn map_into( + self, + map_shared: impl FnOnce(Shared) -> U, + map_owned: impl FnOnce(Owned) -> U, + ) -> U { + match self { + CopyOnWrite::Owned(owned) => map_owned(owned), + CopyOnWrite::SharedWithInfallibleCloning(shared) => map_shared(shared), + CopyOnWrite::SharedWithTransparentCloning(shared) => map_shared(shared), + } + } + + /// Deactivates this copy-on-write value, releasing any borrow. + /// Returns a `InactiveCopyOnWrite` which can be cloned and later re-activated. + pub(crate) fn deactivate(self) -> InactiveCopyOnWrite { + match self { + CopyOnWrite::Owned(owned) => InactiveCopyOnWrite::Owned(owned), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { + InactiveCopyOnWrite::SharedWithInfallibleCloning(shared.deactivate()) + } + CopyOnWrite::SharedWithTransparentCloning(shared) => { + InactiveCopyOnWrite::SharedWithTransparentCloning(shared.deactivate()) + } + } + } +} + +impl CopyOnWrite { + /// Converts to shared reference + pub(crate) fn into_shared(self, span: SpanRange) -> Shared { + match self { + CopyOnWrite::Owned(owned) => Shared::new_from_owned(owned, None, span), + CopyOnWrite::SharedWithInfallibleCloning(shared) => shared, + CopyOnWrite::SharedWithTransparentCloning(shared) => shared, + } + } + + /// Converts to owned, using transparent clone for shared values where cloning was not requested + /// Note - there's a more general `IsSelfValueContent::clone_to_owned_transparently` + /// but this is re-implemented here as a specialization for efficiency + pub(crate) fn clone_to_owned_transparently( + self, + span: SpanRange, + ) -> FunctionResult { + match self { + CopyOnWrite::Owned(owned) => Ok(owned), + CopyOnWrite::SharedWithInfallibleCloning(shared) => Ok(shared.infallible_clone()), + CopyOnWrite::SharedWithTransparentCloning(shared) => { + let value = shared.as_ref().try_transparent_clone(span)?; + Ok(value) + } + } + } +} + +/// A disabled copy-on-write value that can be safely cloned and dropped. +pub(crate) enum InactiveCopyOnWrite { + Owned(Owned), + SharedWithInfallibleCloning(InactiveShared), + SharedWithTransparentCloning(InactiveShared), +} + +impl Clone for InactiveCopyOnWrite { + fn clone(&self) -> Self { + match self { + InactiveCopyOnWrite::Owned(owned) => InactiveCopyOnWrite::Owned(owned.clone()), + InactiveCopyOnWrite::SharedWithInfallibleCloning(shared) => { + InactiveCopyOnWrite::SharedWithInfallibleCloning(shared.clone()) + } + InactiveCopyOnWrite::SharedWithTransparentCloning(shared) => { + InactiveCopyOnWrite::SharedWithTransparentCloning(shared.clone()) + } + } + } +} + +impl InactiveCopyOnWrite { + /// Re-activates this inactive copy-on-write value by re-acquiring any borrow. + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { + Ok(match self { + InactiveCopyOnWrite::Owned(owned) => CopyOnWrite::Owned(owned), + InactiveCopyOnWrite::SharedWithInfallibleCloning(inactive) => { + CopyOnWrite::SharedWithInfallibleCloning(inactive.activate(span)?) + } + InactiveCopyOnWrite::SharedWithTransparentCloning(inactive) => { + CopyOnWrite::SharedWithTransparentCloning(inactive.activate(span)?) + } + }) + } +} + +impl AsRef for CopyOnWrite { + fn as_ref(&self) -> &T { + self + } +} + +impl Deref for CopyOnWrite { + type Target = T; + + fn deref(&self) -> &T { + match self { + CopyOnWrite::Owned(owned) => owned, + CopyOnWrite::SharedWithInfallibleCloning(shared) => shared.as_ref(), + CopyOnWrite::SharedWithTransparentCloning(shared) => shared.as_ref(), + } + } +} + #[derive(Copy, Clone)] pub(crate) struct BeCopyOnWrite; impl IsForm for BeCopyOnWrite {} impl IsHierarchicalForm for BeCopyOnWrite { - type Leaf<'a, T: IsLeafType> = QqqCopyOnWrite; + type Leaf<'a, T: IsLeafType> = CopyOnWrite; #[inline] fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> @@ -51,7 +211,7 @@ impl BeCopyOnWrite { map_via_leaf! { input: (Content<'a, C::Type, BeOwned>) = owned.into_content(), fn map_leaf(leaf) -> (Content<'a, T, BeCopyOnWrite>) { - QqqCopyOnWrite::Owned(leaf) + CopyOnWrite::Owned(leaf) } } } @@ -62,7 +222,7 @@ impl BeCopyOnWrite { map_via_leaf! { input: (Content<'a, C::Type, BeShared>) = shared.into_content(), fn map_leaf(leaf) -> (Content<'a, T, BeCopyOnWrite>) { - QqqCopyOnWrite::SharedWithInfallibleCloning(leaf) + CopyOnWrite::SharedWithInfallibleCloning(leaf) } } } @@ -73,7 +233,7 @@ impl BeCopyOnWrite { map_via_leaf! { input: (Content<'a, C::Type, BeShared>) = shared.into_content(), fn map_leaf(leaf) -> (Content<'a, T, BeCopyOnWrite>) { - QqqCopyOnWrite::SharedWithTransparentCloning(leaf) + CopyOnWrite::SharedWithTransparentCloning(leaf) } } } @@ -85,12 +245,12 @@ impl MapFromArgument for BeCopyOnWrite { fn from_argument_value( Spanned(value, span): Spanned, ) -> FunctionResult> { - match value.expect_copy_on_write().inner { - CopyOnWriteInner::Owned(owned) => Ok(BeCopyOnWrite::new_owned(owned)), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { + match value.expect_copy_on_write() { + CopyOnWrite::Owned(owned) => Ok(BeCopyOnWrite::new_owned(owned)), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { Ok(BeCopyOnWrite::new_shared_in_place_of_owned(shared)) } - CopyOnWriteInner::SharedWithTransparentCloning(shared) => { + CopyOnWrite::SharedWithTransparentCloning(shared) => { Ok(BeCopyOnWrite::new_shared_in_place_of_shared(shared)) } } @@ -100,9 +260,9 @@ impl MapFromArgument for BeCopyOnWrite { impl LeafAsRefForm for BeCopyOnWrite { fn leaf_as_ref<'r, 'a: 'r, T: IsLeafType>(leaf: &'r Self::Leaf<'a, T>) -> &'r T::Leaf { match leaf { - QqqCopyOnWrite::Owned(owned) => owned, - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => shared, - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => shared, + CopyOnWrite::Owned(owned) => owned, + CopyOnWrite::SharedWithInfallibleCloning(shared) => shared, + CopyOnWrite::SharedWithTransparentCloning(shared) => shared, } } @@ -110,11 +270,11 @@ impl LeafAsRefForm for BeCopyOnWrite { leaf: &'r Self::Leaf<'a, T>, ) -> T::Leaf { match leaf { - QqqCopyOnWrite::Owned(owned) => owned.clone(), - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => { + CopyOnWrite::Owned(owned) => owned.clone(), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { BeShared::leaf_clone_to_owned_infallible::(shared) } - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => { + CopyOnWrite::SharedWithTransparentCloning(shared) => { BeShared::leaf_clone_to_owned_infallible::(shared) } } @@ -123,13 +283,13 @@ impl LeafAsRefForm for BeCopyOnWrite { fn leaf_clone_to_owned_transparently<'r, 'a: 'r, T: IsLeafType>( leaf: &'r Self::Leaf<'a, T>, error_span: SpanRange, - ) -> ExecutionResult { + ) -> FunctionResult { match leaf { - QqqCopyOnWrite::Owned(owned) => Ok(owned.clone()), - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => { + CopyOnWrite::Owned(owned) => Ok(owned.clone()), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { Ok(BeShared::leaf_clone_to_owned_infallible::(shared)) } - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => { + CopyOnWrite::SharedWithTransparentCloning(shared) => { BeShared::leaf_clone_to_owned_transparently::(shared, error_span) } } @@ -149,9 +309,9 @@ where input: (Content<'a, Self::Type, Self::Form>) = self, fn map_leaf(leaf) -> (AnyLevelCopyOnWrite<'a, T>) { match leaf { - QqqCopyOnWrite::Owned(owned) => AnyLevelCopyOnWrite::Owned(owned), - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => AnyLevelCopyOnWrite::SharedWithInfallibleCloning(shared), - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => AnyLevelCopyOnWrite::SharedWithTransparentCloning(shared), + CopyOnWrite::Owned(owned) => AnyLevelCopyOnWrite::Owned(owned), + CopyOnWrite::SharedWithInfallibleCloning(shared) => AnyLevelCopyOnWrite::SharedWithInfallibleCloning(shared), + CopyOnWrite::SharedWithTransparentCloning(shared) => AnyLevelCopyOnWrite::SharedWithTransparentCloning(shared), } } } @@ -165,9 +325,9 @@ where input: (Content<'a, Self::Type, Self::Form>) = self, fn map_leaf(leaf) -> (Content<'static, T, BeOwned>) { match leaf { - QqqCopyOnWrite::Owned(owned) => owned, - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), + CopyOnWrite::Owned(owned) => owned, + CopyOnWrite::SharedWithInfallibleCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), + CopyOnWrite::SharedWithTransparentCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), } } } @@ -185,9 +345,9 @@ where state: SpanRange | let error_span = error_span, fn map_leaf(leaf) -> (ExecutionResult>) { Ok(match leaf { - QqqCopyOnWrite::Owned(owned) => owned, - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => BeShared::leaf_clone_to_owned_transparently::(&shared, error_span)?, + CopyOnWrite::Owned(owned) => owned, + CopyOnWrite::SharedWithInfallibleCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), + CopyOnWrite::SharedWithTransparentCloning(shared) => BeShared::leaf_clone_to_owned_transparently::(&shared, error_span)?, }) } } @@ -198,59 +358,58 @@ where input: &'r (Content<'a, Self::Type, Self::Form>) = self, fn map_leaf(leaf) -> (bool) { match leaf { - QqqCopyOnWrite::Owned(_) => false, - QqqCopyOnWrite::SharedWithInfallibleCloning(_) => false, - QqqCopyOnWrite::SharedWithTransparentCloning(_) => true, + CopyOnWrite::Owned(_) => false, + CopyOnWrite::SharedWithInfallibleCloning(_) => false, + CopyOnWrite::SharedWithTransparentCloning(_) => true, } } } } + // // TODO - Find alternative implementation or replace + // fn map(self, mapper: M) -> ExecutionResult> + // where + // 'a: 'static, + // M: OwnedTypeMapper + RefTypeMapper, + // M: TypeMapper, // u32 is temporary to see if we can make it work without adding generics to `map_via_leaf!` + // Self: Sized, + // Self: IsValueContent, // Temporary to see if we can make it work without adding generics to `map_via_leaf!` + // { + // Ok(match self.into_any_level_copy_on_write() { + // AnyLevelCopyOnWrite::Owned(owned) => BeCopyOnWrite::new_owned(mapper.map_owned(owned)?), + // AnyLevelCopyOnWrite::SharedWithInfallibleCloning(shared) => { + // // // Apply map, get Shared> + // // let shared = map_via_leaf! { + // // input: (Content<'a, Self::Type, BeShared>) = shared, + // // fn map_leaf(leaf) -> (ExecutionResult>>) { + // // leaf.try_map(|value_ref| { + // // let from_ref = value_ref.into_any(); + // // inner_map_ref(from_ref) // &Content - // TODO - Find alternative implementation or replace - fn map(self, mapper: M) -> ExecutionResult> - where - 'a: 'static, - M: OwnedTypeMapper + RefTypeMapper, - M: TypeMapper, // u32 is temporary to see if we can make it work without adding generics to `map_via_leaf!` - Self: Sized, - Self: IsValueContent, // Temporary to see if we can make it work without adding generics to `map_via_leaf!` - { - Ok(match self.into_any_level_copy_on_write() { - AnyLevelCopyOnWrite::Owned(owned) => BeCopyOnWrite::new_owned(mapper.map_owned(owned)?), - AnyLevelCopyOnWrite::SharedWithInfallibleCloning(shared) => { - // // Apply map, get Shared> - // let shared = map_via_leaf! { - // input: (Content<'a, Self::Type, BeShared>) = shared, - // fn map_leaf(leaf) -> (ExecutionResult>>) { - // leaf.try_map(|value_ref| { - // let from_ref = value_ref.into_any(); - // inner_map_ref(from_ref) // &Content - - // // If inner_map_ref returned a Content<'a, M::TTo, BeRef> - // // then we'd need to move the leaf map inside here, but it'd work the same - // }) - // } - // }?; - // // Migrate Shared into leaf - // let shared_content = shared.replace(|content, emplacer| { - // // TODO - use two lifetimes here - // // ---- - // map_via_leaf! { - // input: &'r (Content<'a, AnyType, BeOwned>) = content, - // state: | <'e> SharedEmplacer<'e, AnyValue, AnyValue> | let emplacer = emplacer, - // fn map_leaf(leaf) -> (Content<'static, T, BeShared>) { - // emplacer.emplace(leaf) - // } - // } - // }); - // BeCopyOnWrite::new_shared_in_place_of_owned::(shared_content) - todo!() - } - AnyLevelCopyOnWrite::SharedWithTransparentCloning(shared) => { - todo!() - } - }) - } + // // // If inner_map_ref returned a Content<'a, M::TTo, BeRef> + // // // then we'd need to move the leaf map inside here, but it'd work the same + // // }) + // // } + // // }?; + // // // Migrate Shared into leaf + // // let shared_content = shared.replace(|content, emplacer| { + // // // TODO - use two lifetimes here + // // // ---- + // // map_via_leaf! { + // // input: &'r (Content<'a, AnyType, BeOwned>) = content, + // // state: | <'e> SharedEmplacer<'e, AnyValue, AnyValue> | let emplacer = emplacer, + // // fn map_leaf(leaf) -> (Content<'static, T, BeShared>) { + // // emplacer.emplace(leaf) + // // } + // // } + // // }); + // // BeCopyOnWrite::new_shared_in_place_of_owned::(shared_content) + // todo!() + // } + // AnyLevelCopyOnWrite::SharedWithTransparentCloning(shared) => { + // todo!() + // } + // }) + // } } fn inner_map_ref<'a>( @@ -297,26 +456,26 @@ pub(crate) trait TypeMapper { type TFrom: IsHierarchicalType; type TTo: IsHierarchicalType; } -pub(crate) trait OwnedTypeMapper: TypeMapper { - fn map_owned<'a>( - self, - owned_value: Content<'a, Self::TFrom, BeOwned>, - ) -> ExecutionResult>; -} +// pub(crate) trait OwnedTypeMapper: TypeMapper { +// fn map_owned<'a>( +// self, +// owned_value: Content<'a, Self::TFrom, BeOwned>, +// ) -> ExecutionResult>; +// } -pub(crate) trait RefTypeMapper: TypeMapper { - fn map_ref<'a>( - self, - ref_value: Content<'a, Self::TFrom, BeRef>, - ) -> ExecutionResult<&'a Content<'a, Self::TTo, BeOwned>>; -} +// pub(crate) trait RefTypeMapper: TypeMapper { +// fn map_ref<'a>( +// self, +// ref_value: Content<'a, Self::TFrom, BeRef>, +// ) -> ExecutionResult<&'a Content<'a, Self::TTo, BeOwned>>; +// } -pub(crate) trait MutTypeMapper: TypeMapper { - fn map_mut<'a>( - self, - mut_value: Content<'a, Self::TFrom, BeMut>, - ) -> ExecutionResult<&'a mut Content<'a, Self::TTo, BeOwned>>; -} +// pub(crate) trait MutTypeMapper: TypeMapper { +// fn map_mut<'a>( +// self, +// mut_value: Content<'a, Self::TFrom, BeMut>, +// ) -> ExecutionResult<&'a mut Content<'a, Self::TTo, BeOwned>>; +// } impl<'a, C: IsSelfValueContent<'a>> IsSelfCopyOnWriteContent<'a> for C where diff --git a/src/expressions/concepts/forms/late_bound.rs b/src/expressions/concepts/forms/late_bound.rs index 22f45a18..9236e02a 100644 --- a/src/expressions/concepts/forms/late_bound.rs +++ b/src/expressions/concepts/forms/late_bound.rs @@ -4,7 +4,7 @@ pub(crate) enum QqqLateBound { /// An owned value that can be converted to any ownership type Owned(QqqLateBoundOwned), /// A copy-on-write value that can be converted to an owned value - CopyOnWrite(QqqCopyOnWrite), + CopyOnWrite(CopyOnWrite), /// A mutable reference Mutable(Mutable), /// A shared reference where mutable access failed for a specific reason diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index bd22f6bc..330d2065 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -8,6 +8,7 @@ impl IsValueContent for Shared { impl<'a, X: IsValueContent> IntoValueContent<'a> for Shared where X: 'static, + X: IsSelfValueContent<'static>, X::Type: IsHierarchicalType = X>, X::Form: IsHierarchicalForm, X::Form: LeafAsRefForm, diff --git a/src/expressions/concepts/mapping.rs b/src/expressions/concepts/mapping.rs index 6b58a307..a7ea5a14 100644 --- a/src/expressions/concepts/mapping.rs +++ b/src/expressions/concepts/mapping.rs @@ -146,6 +146,9 @@ macro_rules! __map_via_leaf_to_parent { ($output:ident -> ExecutionResult>) => { Ok($t::into_parent($output?)) }; + ($output:ident -> FunctionResult>) => { + Ok($t::into_parent($output?)) + }; ($output:ident -> Result, $err:ty>) => { Ok($t::into_parent($output?)) }; diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index 82c6b99e..12ef60b1 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -153,17 +153,24 @@ impl IsArgument for Mutable { // } // } -impl + ResolvableArgumentTarget> IsArgument for CopyOnWrite -where - T: ResolvableOwned, -{ - type ValueType = T::ValueType; +// See the "We have some friction" post above +impl IsArgument for CopyOnWrite { + type ValueType = AnyType; + const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::CopyOnWrite; + + fn from_argument(Spanned(value, _): Spanned) -> FunctionResult { + Ok(value.expect_copy_on_write()) + } +} + +impl IsArgument for CopyOnWrite { + type ValueType = AnyType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::CopyOnWrite; fn from_argument(Spanned(value, span): Spanned) -> FunctionResult { value.expect_copy_on_write().map( - |v| T::resolve_shared(v.spanned(span), "This argument"), - |v| T::resolve_value(v.spanned(span), "This argument"), + |v| FloatValue::resolve_shared(v.spanned(span), "This argument"), + |v| FloatValue::resolve_value(v.spanned(span), "This argument"), ) } } diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 70e31b20..c62a24f7 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -1,7 +1,5 @@ use super::*; -use std::borrow::Borrow; - pub(super) enum VariableState { Uninitialized, Value(VariableContent), @@ -163,7 +161,7 @@ impl VariableState { } #[derive(Clone)] -pub(crate) struct VariableBinding { +struct VariableBinding { content: VariableContent, variable_span: Span, } @@ -339,232 +337,3 @@ impl Deref for LateBoundValue { self.as_value() } } - -// ============================================================================ -// CopyOnWrite -// ============================================================================ - -/// Copy-on-write value that can be either owned or shared -pub(crate) struct CopyOnWrite { - pub(crate) inner: CopyOnWriteInner, -} - -pub(crate) enum CopyOnWriteInner { - /// An owned value that can be used directly - Owned(Owned), - /// For use when the CopyOnWrite value effectively represents the owned value (post-clone). - /// In this case, returning a Cow is just an optimization and we can always clone infallibly. - SharedWithInfallibleCloning(Shared), - /// For use when the CopyOnWrite value represents a pre-cloned read-only value. - /// A transparent clone may fail in this case at use time. - SharedWithTransparentCloning(Shared), -} - -impl CopyOnWrite { - pub(crate) fn shared_in_place_of_owned(shared: Shared) -> Self { - Self { - inner: CopyOnWriteInner::SharedWithInfallibleCloning(shared), - } - } - - pub(crate) fn shared_in_place_of_shared(shared: Shared) -> Self { - Self { - inner: CopyOnWriteInner::SharedWithTransparentCloning(shared), - } - } - - pub(crate) fn owned(owned: Owned) -> Self { - Self { - inner: CopyOnWriteInner::Owned(owned), - } - } - - #[allow(unused)] - pub(crate) fn extract_owned(self, map: impl FnOnce(T) -> Result) -> Result { - match self.inner { - CopyOnWriteInner::Owned(value) => match map(value) { - Ok(mapped) => Ok(mapped), - Err(other) => Err(Self { - inner: CopyOnWriteInner::Owned(other), - }), - }, - other => Err(Self { inner: other }), - } - } - - pub(crate) fn acts_as_shared_reference(&self) -> bool { - match &self.inner { - CopyOnWriteInner::Owned { .. } => false, - CopyOnWriteInner::SharedWithInfallibleCloning { .. } => false, - CopyOnWriteInner::SharedWithTransparentCloning { .. } => true, - } - } - - pub(crate) fn map( - self, - map_shared: impl FnOnce(Shared) -> FunctionResult>, - map_owned: impl FnOnce(Owned) -> FunctionResult>, - ) -> FunctionResult> { - let inner = match self.inner { - CopyOnWriteInner::Owned(owned) => CopyOnWriteInner::Owned(map_owned(owned)?), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - CopyOnWriteInner::SharedWithInfallibleCloning(map_shared(shared)?) - } - CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - CopyOnWriteInner::SharedWithTransparentCloning(map_shared(shared)?) - } - }; - Ok(CopyOnWrite { inner }) - } - - pub(crate) fn map_into( - self, - map_shared: impl FnOnce(Shared) -> U, - map_owned: impl FnOnce(Owned) -> U, - ) -> U { - match self.inner { - CopyOnWriteInner::Owned(owned) => map_owned(owned), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => map_shared(shared), - CopyOnWriteInner::SharedWithTransparentCloning(shared) => map_shared(shared), - } - } - - /// Deactivates this copy-on-write value, releasing any borrow. - /// Returns a `InactiveCopyOnWrite` which can be cloned and later re-activated. - pub(crate) fn deactivate(self) -> InactiveCopyOnWrite { - let inner = match self.inner { - CopyOnWriteInner::Owned(owned) => InactiveCopyOnWriteInner::Owned(owned), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - InactiveCopyOnWriteInner::SharedWithInfallibleCloning(shared.deactivate()) - } - CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - InactiveCopyOnWriteInner::SharedWithTransparentCloning(shared.deactivate()) - } - }; - InactiveCopyOnWrite { inner } - } -} - -/// A disabled copy-on-write value that can be safely cloned and dropped. -pub(crate) struct InactiveCopyOnWrite { - inner: InactiveCopyOnWriteInner, -} - -enum InactiveCopyOnWriteInner { - Owned(Owned), - SharedWithInfallibleCloning(InactiveShared), - SharedWithTransparentCloning(InactiveShared), -} - -impl Clone for InactiveCopyOnWrite { - fn clone(&self) -> Self { - let inner = match &self.inner { - InactiveCopyOnWriteInner::Owned(owned) => { - InactiveCopyOnWriteInner::Owned(owned.clone()) - } - InactiveCopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - InactiveCopyOnWriteInner::SharedWithInfallibleCloning(shared.clone()) - } - InactiveCopyOnWriteInner::SharedWithTransparentCloning(shared) => { - InactiveCopyOnWriteInner::SharedWithTransparentCloning(shared.clone()) - } - }; - Self { inner } - } -} - -impl InactiveCopyOnWrite { - /// Re-activates this inactive copy-on-write value by re-acquiring any borrow. - pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { - let inner = match self.inner { - InactiveCopyOnWriteInner::Owned(owned) => CopyOnWriteInner::Owned(owned), - InactiveCopyOnWriteInner::SharedWithInfallibleCloning(inactive) => { - CopyOnWriteInner::SharedWithInfallibleCloning(inactive.activate(span)?) - } - InactiveCopyOnWriteInner::SharedWithTransparentCloning(inactive) => { - CopyOnWriteInner::SharedWithTransparentCloning(inactive.activate(span)?) - } - }; - Ok(CopyOnWrite { inner }) - } -} - -impl IsValueContent for CopyOnWrite { - type Type = X::Type; - type Form = BeCopyOnWrite; -} - -impl IntoValueContent<'static> for CopyOnWrite -where - X: IsValueContent + IsSelfValueContent<'static> + IntoValueContent<'static>, - X::Type: IsHierarchicalType = X>, -{ - fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - match self.inner { - CopyOnWriteInner::Owned(owned) => { - AnyLevelCopyOnWrite::::Owned(owned).into_copy_on_write() - } - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(shared.into_content()) - .into_copy_on_write() - } - CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - AnyLevelCopyOnWrite::::SharedWithTransparentCloning(shared.into_content()) - .into_copy_on_write() - } - } - } -} - -impl AsRef for CopyOnWrite { - fn as_ref(&self) -> &T { - self - } -} - -impl Deref for CopyOnWrite { - type Target = T; - - fn deref(&self) -> &T { - match self.inner { - CopyOnWriteInner::Owned(ref owned) => (*owned).borrow(), - CopyOnWriteInner::SharedWithInfallibleCloning(ref shared) => shared.as_ref(), - CopyOnWriteInner::SharedWithTransparentCloning(ref shared) => shared.as_ref(), - } - } -} - -impl CopyOnWrite { - /// Converts to owned, cloning if necessary - pub(crate) fn clone_to_owned_infallible(self) -> AnyValueOwned { - match self.inner { - CopyOnWriteInner::Owned(owned) => owned, - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => shared.infallible_clone(), - CopyOnWriteInner::SharedWithTransparentCloning(shared) => shared.infallible_clone(), - } - } - - /// Converts to owned, using transparent clone for shared values where cloning was not requested - pub(crate) fn clone_to_owned_transparently( - self, - span: SpanRange, - ) -> FunctionResult { - match self.inner { - CopyOnWriteInner::Owned(owned) => Ok(owned), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => Ok(shared.infallible_clone()), - CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - let value = shared.as_ref().try_transparent_clone(span)?; - Ok(value) - } - } - } - - /// Converts to shared reference - pub(crate) fn into_shared(self, span: SpanRange) -> AnyValueShared { - match self.inner { - CopyOnWriteInner::Owned(owned) => AnyValueShared::new_from_owned(owned, None, span), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => shared, - CopyOnWriteInner::SharedWithTransparentCloning(shared) => shared, - } - } -} From eec576cd799829be6380479609711142b4cc165b Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 1 Mar 2026 20:23:04 +0000 Subject: [PATCH 21/25] refactor: Unify `LateBound` --- plans/TODO.md | 4 +- .../concepts/forms/copy_on_write.rs | 6 +- src/expressions/concepts/forms/late_bound.rs | 104 ++++++++++-- src/expressions/evaluation/evaluator.rs | 8 +- src/expressions/evaluation/value_frames.rs | 24 +-- src/expressions/values/any_value.rs | 1 + src/interpretation/interpreter.rs | 4 +- src/interpretation/mod.rs | 4 +- src/interpretation/variable.rs | 2 +- .../{bindings.rs => variable_state.rs} | 155 +++--------------- 10 files changed, 131 insertions(+), 181 deletions(-) rename src/interpretation/{bindings.rs => variable_state.rs} (60%) diff --git a/plans/TODO.md b/plans/TODO.md index 0d7cd05d..9d62ea27 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -298,8 +298,8 @@ Suddenly dawned on me - my Disabled arguments might not be safe. - [x] Move `Assignee` out of bindings. To e.g. `dynamic_references` - [x] Rename `PathExtension::Tightening` to `PathExtension::TypeNarrowing` - [x] Rename `DisabledArgumentValue` and `DisabledCopyOnWrite` to `Inactive__` and their method from `enable` to `activate` and ditto with `disable -> deactivate` -- [ ] See what else can be deleted from `bindings.rs` - - [ ] Unify LateBound into `QqqLateBound` +- [x] See what else can be deleted from `bindings.rs` + - [x] Unify LateBound into `LateBound` - [x] Unify CopyOnWrite into `QqqCopyOnWrite` - [ ] Add various tests: - [ ] Stretching different error messages diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index 92fbc418..0b80e81f 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -23,9 +23,7 @@ where { fn into_content(self) -> Content<'a, Self::Type, Self::Form> { match self { - CopyOnWrite::Owned(owned) => { - BeCopyOnWrite::new_owned(owned) - } + CopyOnWrite::Owned(owned) => BeCopyOnWrite::new_owned(owned), CopyOnWrite::SharedWithInfallibleCloning(shared) => { BeCopyOnWrite::new_shared_in_place_of_owned(shared) } @@ -158,7 +156,7 @@ impl Clone for InactiveCopyOnWrite { impl InactiveCopyOnWrite { /// Re-activates this inactive copy-on-write value by re-acquiring any borrow. pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { - Ok(match self { + Ok(match self { InactiveCopyOnWrite::Owned(owned) => CopyOnWrite::Owned(owned), InactiveCopyOnWrite::SharedWithInfallibleCloning(inactive) => { CopyOnWrite::SharedWithInfallibleCloning(inactive.activate(span)?) diff --git a/src/expressions/concepts/forms/late_bound.rs b/src/expressions/concepts/forms/late_bound.rs index 9236e02a..2628b357 100644 --- a/src/expressions/concepts/forms/late_bound.rs +++ b/src/expressions/concepts/forms/late_bound.rs @@ -1,49 +1,117 @@ use super::*; -pub(crate) enum QqqLateBound { +/// A temporary, flexible form that can resolve to any concrete ownership type. +/// +/// Sometimes, a value can be accessed, but we don't yet know *how* we need to access it. +/// In this case, we attempt to load it with the most powerful access we can have, and convert it later. +/// +/// ## Example of requirement +/// For example, if we have `x[a].method()`, we first need to resolve the type of `x[a]` to know +/// whether the method needs `x[a]` to be a shared reference, mutable reference or an owned value. +/// +/// So instead, we take the most powerful access we can have for `x[a]`, and convert it later. +pub(crate) enum LateBound { /// An owned value that can be converted to any ownership type - Owned(QqqLateBoundOwned), + Owned(LateBoundOwned), /// A copy-on-write value that can be converted to an owned value CopyOnWrite(CopyOnWrite), /// A mutable reference Mutable(Mutable), /// A shared reference where mutable access failed for a specific reason - Shared(QqqLateBoundShared), + Shared(LateBoundShared), } -impl IsValueContent for QqqLateBound { +impl LateBound { + pub(crate) fn new_shared(shared: Shared, reason_not_mutable: syn::Error) -> Self { + LateBound::Shared(LateBoundShared { + shared, + reason_not_mutable, + }) + } + + /// Maps the late-bound value through the appropriate accessor function. + /// + /// If the mutable mapping fails with a retryable reason, we fall back to `map_shared`. + /// This allows operations that don't need mutable access (like reading a non-existent + /// key from an object) to still work, with the mutable error preserved as `reason_not_mutable`. + pub(crate) fn map_any( + self, + map_shared: impl FnOnce(Shared) -> FunctionResult>, + map_mutable: impl FnOnce(Mutable) -> Result, (FunctionError, Mutable)>, + map_owned: impl FnOnce(T) -> FunctionResult, + ) -> FunctionResult { + Ok(match self { + LateBound::Owned(owned) => LateBound::Owned(LateBoundOwned { + owned: map_owned(owned.owned)?, + is_from_last_use: owned.is_from_last_use, + }), + LateBound::CopyOnWrite(copy_on_write) => { + LateBound::CopyOnWrite(copy_on_write.map(map_shared, map_owned)?) + } + LateBound::Mutable(mutable) => match map_mutable(mutable) { + Ok(mapped) => LateBound::Mutable(mapped), + Err((error, recovered_mutable)) => { + // Check if this error can be caught for fallback to shared access + let reason_not_mutable = error.into_caught_mutable_map_attempt_error()?; + LateBound::new_shared( + map_shared(recovered_mutable.into_shared())?, + reason_not_mutable, + ) + } + }, + LateBound::Shared(LateBoundShared { + shared, + reason_not_mutable, + }) => LateBound::new_shared(map_shared(shared)?, reason_not_mutable), + }) + } +} + +impl Spanned { + pub(crate) fn resolve(self, ownership: ArgumentOwnership) -> FunctionResult { + ownership.map_from_late_bound(self) + } +} + +impl Deref for LateBound { + type Target = T; + + fn deref(&self) -> &Self::Target { + match self { + LateBound::Owned(owned) => &owned.owned, + LateBound::CopyOnWrite(cow) => cow.as_ref(), + LateBound::Mutable(mutable) => mutable.as_ref(), + LateBound::Shared(shared) => shared.shared.as_ref(), + } + } +} + +impl IsValueContent for LateBound { type Type = L::Type; type Form = BeLateBound; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqLateBound { +impl<'a, L: IsValueLeaf> IntoValueContent<'a> for LateBound { fn into_content(self) -> Content<'a, Self::Type, Self::Form> { self } } -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqLateBound { +impl<'a, L: IsValueLeaf> FromValueContent<'a> for LateBound { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } } -/// Universal value type that can resolve to any concrete ownership type. -/// -/// Sometimes, a value can be accessed, but we don't yet know *how* we need to access it. -/// In this case, we attempt to load it with the most powerful access we can have, and convert it later. +/// A temporary, flexible form that can resolve to any concrete ownership type. /// -/// ## Example of requirement -/// For example, if we have `x[a].method()`, we first need to resolve the type of `x[a]` to know -/// whether the method needs `x[a]` to be a shared reference, mutable reference or an owned value. -/// -/// So instead, we take the most powerful access we can have for `x[a]`, and convert it later. +/// See [`LateBound`] for more details. #[derive(Copy, Clone)] pub(crate) struct BeLateBound; impl IsForm for BeLateBound {} impl IsHierarchicalForm for BeLateBound { - type Leaf<'a, T: IsLeafType> = QqqLateBound; + type Leaf<'a, T: IsLeafType> = LateBound; #[inline] fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> @@ -54,13 +122,13 @@ impl IsHierarchicalForm for BeLateBound { } } -pub(crate) struct QqqLateBoundOwned { +pub(crate) struct LateBoundOwned { pub(crate) owned: O, pub(crate) is_from_last_use: bool, } /// A shared value where mutable access failed for a specific reason -pub(crate) struct QqqLateBoundShared { +pub(crate) struct LateBoundShared { pub(crate) shared: Shared, pub(crate) reason_not_mutable: syn::Error, } diff --git a/src/expressions/evaluation/evaluator.rs b/src/expressions/evaluation/evaluator.rs index 2c80252b..0f7b8dba 100644 --- a/src/expressions/evaluation/evaluator.rs +++ b/src/expressions/evaluation/evaluator.rs @@ -154,7 +154,7 @@ pub(crate) enum RequestedValue { // RequestedOwnership::LateBound // ------------------------------- - LateBound(LateBoundValue), + LateBound(AnyValueLateBound), // Marks completion of an assignment frame // --------------------------------------- @@ -191,7 +191,7 @@ impl RequestedValue { } } - pub(crate) fn expect_late_bound(self) -> LateBoundValue { + pub(crate) fn expect_late_bound(self) -> AnyValueLateBound { match self { RequestedValue::LateBound(late_bound) => late_bound, _ => panic!("expect_late_bound() called on non-late-bound RequestedValue"), @@ -287,7 +287,7 @@ impl Spanned { } #[inline] - pub(crate) fn expect_late_bound(self) -> Spanned { + pub(crate) fn expect_late_bound(self) -> Spanned { self.map(|v| v.expect_late_bound()) } @@ -476,7 +476,7 @@ impl<'a> Context<'a, ReturnsValue> { pub(super) fn return_late_bound( self, - late_bound: Spanned, + late_bound: Spanned, ) -> ExecutionResult { let value = self.request.map_from_late_bound(late_bound)?; Ok(NextAction::return_requested(value)) diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index ae160ee0..10ffd94a 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -211,7 +211,7 @@ impl RequestedOwnership { pub(crate) fn map_from_late_bound( &self, - Spanned(late_bound, span): Spanned, + Spanned(late_bound, span): Spanned, ) -> FunctionResult> { Ok(match self { RequestedOwnership::LateBound => RequestedValue::LateBound(late_bound), @@ -281,7 +281,7 @@ impl RequestedOwnership { Ok(Spanned( match self { RequestedOwnership::LateBound => { - RequestedValue::LateBound(LateBoundValue::Owned(LateBoundOwnedValue { + RequestedValue::LateBound(LateBound::Owned(LateBoundOwned { owned: value, is_from_last_use: false, })) @@ -301,7 +301,7 @@ impl RequestedOwnership { Ok(Spanned( match self { RequestedOwnership::LateBound => { - RequestedValue::LateBound(LateBoundValue::CopyOnWrite(cow)) + RequestedValue::LateBound(AnyValueLateBound::CopyOnWrite(cow)) } RequestedOwnership::Concrete(requested) => { Self::item_from_argument(requested.map_from_copy_on_write(Spanned(cow, span))?) @@ -318,7 +318,7 @@ impl RequestedOwnership { Ok(Spanned( match self { RequestedOwnership::LateBound => { - RequestedValue::LateBound(LateBoundValue::Mutable(mutable)) + RequestedValue::LateBound(AnyValueLateBound::Mutable(mutable)) } RequestedOwnership::Concrete(requested) => { Self::item_from_argument(requested.map_from_mutable(Spanned(mutable, span))?) @@ -335,7 +335,7 @@ impl RequestedOwnership { Ok(Spanned( match self { RequestedOwnership::LateBound => { - RequestedValue::LateBound(LateBoundValue::Mutable(assignee.0)) + RequestedValue::LateBound(AnyValueLateBound::Mutable(assignee.0)) } RequestedOwnership::Concrete(requested) => { Self::item_from_argument(requested.map_from_assignee(Spanned(assignee, span))?) @@ -352,7 +352,7 @@ impl RequestedOwnership { Ok(Spanned( match self { RequestedOwnership::LateBound => RequestedValue::LateBound( - LateBoundValue::CopyOnWrite(CopyOnWrite::shared_in_place_of_shared(shared)), + AnyValueLateBound::CopyOnWrite(CopyOnWrite::shared_in_place_of_shared(shared)), ), RequestedOwnership::Concrete(requested) => { Self::item_from_argument(requested.map_from_shared(Spanned(shared, span))?) @@ -419,20 +419,20 @@ pub(crate) enum ArgumentOwnership { impl ArgumentOwnership { pub(crate) fn map_from_late_bound( &self, - Spanned(late_bound, span): Spanned, + Spanned(late_bound, span): Spanned, ) -> FunctionResult { match late_bound { - LateBoundValue::Owned(owned) => self.map_from_owned_with_is_last_use( + LateBound::Owned(owned) => self.map_from_owned_with_is_last_use( Spanned(owned.owned, span), owned.is_from_last_use, ), - LateBoundValue::CopyOnWrite(copy_on_write) => { + LateBound::CopyOnWrite(copy_on_write) => { self.map_from_copy_on_write(Spanned(copy_on_write, span)) } - LateBoundValue::Mutable(mutable) => { + LateBound::Mutable(mutable) => { self.map_from_mutable_inner(Spanned(mutable, span), true) } - LateBoundValue::Shared(late_bound_shared) => self.map_from_shared_with_error_reason( + LateBound::Shared(late_bound_shared) => self.map_from_shared_with_error_reason( Spanned(late_bound_shared.shared, span), |_| { FunctionError::new(ExecutionInterrupt::ownership_error( @@ -910,7 +910,7 @@ impl EvaluationFrame for BinaryOperationBuilder { // Check for lazy evaluation first (short-circuit operators) // Use operator span for type errors since the error is about the operation's requirements - let left_value = Spanned(left.as_value(), left_span); + let left_value = Spanned(&*left, left_span); if let Some(result) = self.operation.lazy_evaluate(left_value)? { // For short-circuit, the result span is just the left operand's span // (the right operand was never evaluated) diff --git a/src/expressions/values/any_value.rs b/src/expressions/values/any_value.rs index 880b990a..7dd20b02 100644 --- a/src/expressions/values/any_value.rs +++ b/src/expressions/values/any_value.rs @@ -9,6 +9,7 @@ pub(crate) type AnyValueShared = Shared; pub(crate) type AnyValueMutable = Mutable; pub(crate) type AnyValueAssignee = Assignee; pub(crate) type AnyValueCopyOnWrite = CopyOnWrite; +pub(crate) type AnyValueLateBound = LateBound; // pub(crate) type AnyValueShared = AnyValueContent<'static, BeShared>; // pub(crate) type AnyValueMutable = AnyValueContent<'static, BeMutable>; // pub(crate) type AnyValueAssignee = AnyValueContent<'static, BeAssignee>; diff --git a/src/interpretation/interpreter.rs b/src/interpretation/interpreter.rs index 19868f66..a6ef8239 100644 --- a/src/interpretation/interpreter.rs +++ b/src/interpretation/interpreter.rs @@ -251,7 +251,7 @@ impl Interpreter { &mut self, variable: &VariableReference, ownership: RequestedOwnership, - ) -> FunctionResult> { + ) -> FunctionResult> { let reference = self.scope_definitions.references.get(variable.id); let (definition, span, is_final) = ( reference.definition, @@ -512,7 +512,7 @@ impl RuntimeScope { is_final: bool, ownership: RequestedOwnership, blocked_from_mutation: Option, - ) -> FunctionResult> { + ) -> FunctionResult> { self.variables .get_mut(&definition_id) .expect("Variable data not found in scope") diff --git a/src/interpretation/mod.rs b/src/interpretation/mod.rs index 23deb67d..ce92f88f 100644 --- a/src/interpretation/mod.rs +++ b/src/interpretation/mod.rs @@ -1,4 +1,3 @@ -mod bindings; mod input_handler; mod interpret_traits; mod interpreter; @@ -9,10 +8,10 @@ mod parse_template_stream; mod refs; mod source_stream; mod variable; +mod variable_state; // Marked as use for expression sub-modules to use with a `use super::*` statement use crate::internal_prelude::*; -pub(crate) use bindings::*; pub(crate) use input_handler::*; pub(crate) use interpret_traits::*; pub(crate) use interpreter::*; @@ -23,3 +22,4 @@ pub(crate) use parse_template_stream::*; pub(crate) use refs::*; pub(crate) use source_stream::*; pub(crate) use variable::*; +pub(crate) use variable_state::*; diff --git a/src/interpretation/variable.rs b/src/interpretation/variable.rs index 93600c40..d4004b92 100644 --- a/src/interpretation/variable.rs +++ b/src/interpretation/variable.rs @@ -144,7 +144,7 @@ impl VariableReference { pub(crate) fn resolve_late_bound( &self, interpreter: &mut Interpreter, - ) -> FunctionResult> { + ) -> FunctionResult> { interpreter.resolve(self, RequestedOwnership::LateBound) } diff --git a/src/interpretation/bindings.rs b/src/interpretation/variable_state.rs similarity index 60% rename from src/interpretation/bindings.rs rename to src/interpretation/variable_state.rs index c62a24f7..fdbb9dd4 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/variable_state.rs @@ -71,7 +71,7 @@ impl VariableState { is_final: bool, ownership: RequestedOwnership, blocked_from_mutation: Option, - ) -> FunctionResult> { + ) -> FunctionResult> { let span_range = variable_span.span_range(); // If blocked from mutation, we technically could allow is_final to work and @@ -90,7 +90,7 @@ impl VariableState { return variable_span.control_flow_err("The final usage of a variable cannot be assigned to. You can use `let _ = ..` to discard a value."); } return Ok(Spanned( - LateBoundValue::Owned(LateBoundOwnedValue { + LateBound::Owned(LateBoundOwned { owned, is_from_last_use: true, }), @@ -122,7 +122,7 @@ impl VariableState { RequestedOwnership::LateBound => binding.into_late_bound(), RequestedOwnership::Concrete(ownership) => match ownership { ArgumentOwnership::Owned => binding.into_transparently_cloned().map(|owned| { - LateBoundValue::Owned(LateBoundOwnedValue { + LateBound::Owned(LateBoundOwned { owned, is_from_last_use: false, }) @@ -130,26 +130,26 @@ impl VariableState { ArgumentOwnership::Shared => binding .into_shared() .map(CopyOnWrite::shared_in_place_of_shared) - .map(LateBoundValue::CopyOnWrite), + .map(AnyValueLateBound::CopyOnWrite), ArgumentOwnership::Assignee { .. } => { - binding.into_mut().map(LateBoundValue::Mutable) + binding.into_mut().map(AnyValueLateBound::Mutable) } - ArgumentOwnership::Mutable => binding.into_mut().map(LateBoundValue::Mutable), + ArgumentOwnership::Mutable => binding.into_mut().map(AnyValueLateBound::Mutable), ArgumentOwnership::CopyOnWrite | ArgumentOwnership::AsIs => binding .into_shared() .map(CopyOnWrite::shared_in_place_of_shared) - .map(LateBoundValue::CopyOnWrite), + .map(AnyValueLateBound::CopyOnWrite), }, }; let late_bound = if let Some(mutation_block_reason) = blocked_from_mutation { match resolved { - Ok(LateBoundValue::Mutable(mutable)) => { + Ok(AnyValueLateBound::Mutable(mutable)) => { let reason_not_mutable = variable_span .syn_error(mutation_block_reason.error_message("mutate this variable")); - Ok(LateBoundValue::Shared(LateBoundSharedValue::new( + Ok(LateBound::new_shared( mutable.into_shared(), reason_not_mutable, - ))) + )) } x => x, } @@ -196,144 +196,27 @@ impl VariableBinding { } } - fn into_late_bound(self) -> FunctionResult { + fn into_late_bound(self) -> FunctionResult { let span = self.variable_span.span_range(); match self.content { VariableContent::Referenceable(referenceable) => { match referenceable.new_active_mutable(span) { - Ok(mutable) => Ok(LateBoundValue::Mutable(mutable)), + Ok(mutable) => Ok(AnyValueLateBound::Mutable(mutable)), Err(err) => { // Mutable failed, try shared let shared = referenceable.new_active_shared(span)?; - Ok(LateBoundValue::Shared(LateBoundSharedValue::new( - shared, - err.expect_ownership_error()?, - ))) + Ok(LateBound::new_shared(shared, err.expect_ownership_error()?)) } } } - VariableContent::Mutable(inactive) => { - Ok(LateBoundValue::Mutable(inactive.activate(span)?)) - } - VariableContent::Shared(inactive) => { - Ok(LateBoundValue::Shared(LateBoundSharedValue::new( - inactive.activate(span)?, - self.variable_span - .syn_error(SHARED_TO_MUTABLE_ERROR_MESSAGE), - ))) - } - } - } -} - -static SHARED_TO_MUTABLE_ERROR_MESSAGE: &str = "Cannot mutate a non-mutable reference"; - -pub(crate) struct LateBoundOwnedValue { - pub(crate) owned: AnyValueOwned, - pub(crate) is_from_last_use: bool, -} - -/// A shared value where mutable access failed for a specific reason -pub(crate) struct LateBoundSharedValue { - pub(crate) shared: AnyValueShared, - pub(crate) reason_not_mutable: syn::Error, -} - -impl LateBoundSharedValue { - pub(crate) fn new(shared: AnyValueShared, reason_not_mutable: syn::Error) -> Self { - Self { - shared, - reason_not_mutable, - } - } -} - -/// Universal value type that can resolve to any concrete ownership type. -/// -/// Sometimes, a value can be accessed, but we don't yet know *how* we need to access it. -/// In this case, we attempt to load it with the most powerful access we can have, and convert it later. -/// -/// ## Example of requirement -/// For example, if we have `x[a].method()`, we first need to resolve the type of `x[a]` to know -/// whether the method needs `x[a]` to be a shared reference, mutable reference or an owned value. -/// -/// So instead, we take the most powerful access we can have for `x[a]`, and convert it later. -pub(crate) enum LateBoundValue { - /// An owned value that can be converted to any ownership type - Owned(LateBoundOwnedValue), - /// A copy-on-write value that can be converted to an owned value - CopyOnWrite(AnyValueCopyOnWrite), - /// A mutable reference - Mutable(AnyValueMutable), - /// A shared reference where mutable access failed for a specific reason - Shared(LateBoundSharedValue), -} - -impl Spanned { - pub(crate) fn resolve(self, ownership: ArgumentOwnership) -> FunctionResult { - ownership.map_from_late_bound(self) - } -} - -impl LateBoundValue { - /// Maps the late-bound value through the appropriate accessor function. - /// - /// If the mutable mapping fails with a retryable reason, we fall back to `map_shared`. - /// This allows operations that don't need mutable access (like reading a non-existent - /// key from an object) to still work, with the mutable error preserved as `reason_not_mutable`. - pub(crate) fn map_any( - self, - map_shared: impl FnOnce(AnyValueShared) -> FunctionResult, - map_mutable: impl FnOnce( - AnyValueMutable, - ) -> Result, - map_owned: impl FnOnce(AnyValueOwned) -> FunctionResult, - ) -> FunctionResult { - Ok(match self { - LateBoundValue::Owned(owned) => LateBoundValue::Owned(LateBoundOwnedValue { - owned: map_owned(owned.owned)?, - is_from_last_use: owned.is_from_last_use, - }), - LateBoundValue::CopyOnWrite(copy_on_write) => { - LateBoundValue::CopyOnWrite(copy_on_write.map(map_shared, map_owned)?) - } - LateBoundValue::Mutable(mutable) => match map_mutable(mutable) { - Ok(mapped) => LateBoundValue::Mutable(mapped), - Err((error, recovered_mutable)) => { - // Check if this error can be caught for fallback to shared access - let reason_not_mutable = error.into_caught_mutable_map_attempt_error()?; - let shared = recovered_mutable.into_shared(); - let mapped_shared = map_shared(shared)?; - LateBoundValue::Shared(LateBoundSharedValue::new( - mapped_shared, - reason_not_mutable, - )) - } - }, - LateBoundValue::Shared(LateBoundSharedValue { - shared, - reason_not_mutable, - }) => LateBoundValue::Shared(LateBoundSharedValue::new( - map_shared(shared)?, - reason_not_mutable, + VariableContent::Mutable(inactive) => Ok(LateBound::Mutable(inactive.activate(span)?)), + VariableContent::Shared(inactive) => Ok(LateBound::new_shared( + inactive.activate(span)?, + self.variable_span + .syn_error(SHARED_TO_MUTABLE_ERROR_MESSAGE), )), - }) - } - - pub(crate) fn as_value(&self) -> &AnyValue { - match self { - LateBoundValue::Owned(owned) => &owned.owned, - LateBoundValue::CopyOnWrite(cow) => cow.as_ref(), - LateBoundValue::Mutable(mutable) => mutable.as_ref(), - LateBoundValue::Shared(shared) => shared.shared.as_ref(), } } } -impl Deref for LateBoundValue { - type Target = AnyValue; - - fn deref(&self) -> &Self::Target { - self.as_value() - } -} +static SHARED_TO_MUTABLE_ERROR_MESSAGE: &str = "Cannot mutate a non-mutable reference"; From 5f53bb1480015318a43c59e5f83dab8cd25f6508 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 1 Mar 2026 20:26:55 +0000 Subject: [PATCH 22/25] fix: Remove extra new line from error message --- src/misc/dynamic_references/referenceable.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 6af5c6bc..aaf73b5c 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -158,7 +158,7 @@ impl ReferenceableData { self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveMutable)); let _ = write!(error_message, "\nOther reference: "); let _ = self.display_path(&mut error_message, other_id, None); - let _ = write!(error_message, "\nReason : {}\n", error_reason); + let _ = write!(error_message, "\nReason : {}", error_reason); return data.creation_span.ownership_err(error_message); } } @@ -200,7 +200,7 @@ impl ReferenceableData { self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveShared)); let _ = write!(error_message, "\nOther reference: "); let _ = self.display_path(&mut error_message, other_id, None); - let _ = write!(error_message, "\nReason : {}\n", error_reason); + let _ = write!(error_message, "\nReason : {}", error_reason); return data.creation_span.ownership_err(error_message); } } From 2e320e0bb28984c8752c47d12a91d67c3869d815 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 1 Mar 2026 21:26:05 +0000 Subject: [PATCH 23/25] tests: Add tests for dynamic references --- plans/TODO.md | 12 ++++--- src/expressions/closures.rs | 1 - src/expressions/values/array.rs | 7 ++-- src/expressions/values/none.rs | 4 +++ src/misc/dynamic_references/referenceable.rs | 20 +++++++---- ...ctive_ref_attempt_mutate_its_descendent.rs | 13 ++++++++ ...e_ref_attempt_mutate_its_descendent.stderr | 8 +++++ ...nactive_mut_attempt_mutate_its_ancestor.rs | 8 +++++ ...ive_mut_attempt_mutate_its_ancestor.stderr | 8 +++++ ...nactive_ref_attempt_mutate_its_ancestor.rs | 11 +++++++ ...ive_ref_attempt_mutate_its_ancestor.stderr | 8 +++++ tests/expressions.rs | 4 +++ tests/references.rs | 33 +++++++++++++++++++ 13 files changed, 123 insertions(+), 14 deletions(-) create mode 100644 tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs create mode 100644 tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.stderr create mode 100644 tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs create mode 100644 tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr create mode 100644 tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs create mode 100644 tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr create mode 100644 tests/references.rs diff --git a/plans/TODO.md b/plans/TODO.md index 9d62ea27..23b1e3bc 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -251,11 +251,11 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [ ] Salvage half-baked `FunctionValue` changes to allow invocation - [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` - [ ] Add tests for iterable methods -- [ ] Add `array.sort`, `array.sort_by` - [ ] Look into if a closure like `|| { }()` can evade `attempt` block statue mutation checks. Maybe lean into it as a way to avoid them, and mention it in the error message - [ ] Resolve all `TODO[functions]` Possible punted: +- [ ] Add `array.sort`, `array.sort_by` - [ ] Allow destructuring shared and mutable variables and arguments - [ ] Support optional arguments in closures - [ ] Support for `move()` expressions in closures. @@ -301,13 +301,15 @@ Suddenly dawned on me - my Disabled arguments might not be safe. - [x] See what else can be deleted from `bindings.rs` - [x] Unify LateBound into `LateBound` - [x] Unify CopyOnWrite into `QqqCopyOnWrite` -- [ ] Add various tests: - - [ ] Stretching different error messages - - [ ] Showing I can do e.g. `x.a += x.b` - - [ ] Show that `let my_arr = [[]]; my_arr[0].push(my_arr.pop())` gives a suitable error +- [x] Add various tests: + - [x] Stretching different error messages + - [x] Showing I can do e.g. `x.a += x.b` + - [x] Show that `let my_arr = [[]]; my_arr[0].push(my_arr.pop())` gives a suitable error ### Other ideas +- [ ] Make it so that we disable the parent whilst resolving a property/index + We could imagine a world where we are more clever over our mutation: * Note that I can reference `x.a` and `x.b` separately, or `x[0]` and `x[1]` but in that case, can't mutate `x` itself to create new fields. * Or we use a `ExpandableVec` which allocates a `Vec>` and doesn't move the inner chunks; allowing us to add new fields without breaking pointers to existing ones. diff --git a/src/expressions/closures.rs b/src/expressions/closures.rs index 1bff2d1c..7eb38710 100644 --- a/src/expressions/closures.rs +++ b/src/expressions/closures.rs @@ -33,7 +33,6 @@ impl ClosureExpression { pub(crate) struct ClosureValue { definition: Rc, closed_references: Vec<(VariableDefinitionId, VariableContent)>, - // TODO[functions]: Add closed_values from moves here } impl PartialEq for ClosureValue { diff --git a/src/expressions/values/array.rs b/src/expressions/values/array.rs index d40ade4c..4d2cbe01 100644 --- a/src/expressions/values/array.rs +++ b/src/expressions/values/array.rs @@ -161,9 +161,12 @@ define_type_features! { impl ArrayType, pub(crate) mod array_interface { methods { - fn push(mut this: Mutable, item: AnyValue) -> FunctionResult<()> { + fn push(mut this: Mutable, item: AnyValue) -> (){ this.items.push(item); - Ok(()) + } + + fn pop(mut this: Mutable) -> AnyValue { + this.items.pop().unwrap_or(none()) } [context] fn to_stream_grouped(this: ArrayValue) -> FunctionResult { diff --git a/src/expressions/values/none.rs b/src/expressions/values/none.rs index 0f79e10a..2106cebe 100644 --- a/src/expressions/values/none.rs +++ b/src/expressions/values/none.rs @@ -9,6 +9,10 @@ define_leaf_type! { dyn_impls: {}, } +pub(crate) const fn none() -> AnyValue { + AnyValue::None(()) +} + impl ResolvableArgumentTarget for () { type ValueType = NoneType; } diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index aaf73b5c..411510ac 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -227,7 +227,7 @@ impl ReferenceableData { let last_path_part = data.path.parts.last_mut().expect("path is non-empty"); match (last_path_part, path_extension) { (last_path_part, PathExtension::Child(specifier, child_bound_as)) => { - let parent_type = specifier.bound_type_kind(); + let parent_type = specifier.parent_type_kind(); match last_path_part { PathPart::Value { bound_as } => { if !bound_as.is_narrowing_to(&parent_type) { @@ -378,6 +378,13 @@ enum PathComparison { impl PathComparison { fn error_comparing_mutable_with_other(self, other_is_active: bool) -> Option<&'static str> { + // We are looking to start actively mutating a reference (the "Left" reference), + // with a pre-existing other reference around (the "Right" reference). + // The method protects against the various ways this may break invariants of the Right: + // - If the other reference is active, it must not overlap with any mutable reference + // (i.e. the aliasing rules must be respected) + // - If the other reference is inactive, observable mutation may happen, but + // Right must remain pointing at valid memory for its type Some(match self { PathComparison::Divergent => return None, PathComparison::Overlapping => { @@ -394,7 +401,7 @@ impl PathComparison { TypeBindingComparison::LeftDerivesFromRightButIsNotSubtype, ) | PathComparison::ReferencesEqual(TypeBindingComparison::Incomparable) => { - "mutation may invalidate the other reference with an incompatible type" + "mutation may invalidate the other reference by setting it to an incompatible type" } // Mutable reference is a descendent of the other reference PathComparison::ReferencesEqual(TypeBindingComparison::Equal) @@ -461,7 +468,7 @@ pub(crate) enum ChildSpecifier { } impl ChildSpecifier { - fn bound_type_kind(&self) -> TypeKind { + fn parent_type_kind(&self) -> TypeKind { match self { ChildSpecifier::ArrayChild(_) => ArrayType::type_kind(), ChildSpecifier::ObjectChild(_) => ObjectType::type_kind(), @@ -505,23 +512,24 @@ impl PathPart { (ChildSpecifier::ArrayChild(_), ChildSpecifier::ArrayChild(_)) => { PathPartComparison::Divergent } + (ChildSpecifier::ArrayChild(_), _) => PathPartComparison::Incompatible, (ChildSpecifier::ObjectChild(a), ChildSpecifier::ObjectChild(b)) if a == b => { PathPartComparison::IdenticalChildReference } (ChildSpecifier::ObjectChild(_), ChildSpecifier::ObjectChild(_)) => { PathPartComparison::Divergent } - _ => PathPartComparison::Incompatible, + (ChildSpecifier::ObjectChild(_), _) => PathPartComparison::Incompatible, }, (PathPart::Child(a), PathPart::Value { bound_as }) => { - if a.bound_type_kind() == *bound_as { + if bound_as.is_narrowing_to(&a.parent_type_kind()) { PathPartComparison::LeftIsDescendent } else { PathPartComparison::Incompatible } } (PathPart::Value { bound_as }, PathPart::Child(b)) => { - if b.bound_type_kind() == *bound_as { + if bound_as.is_narrowing_to(&b.parent_type_kind()) { PathPartComparison::RightIsDescendent } else { PathPartComparison::Incompatible diff --git a/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs new file mode 100644 index 00000000..29e87fdc --- /dev/null +++ b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs @@ -0,0 +1,13 @@ +use preinterpret::*; + +fn main() { + let _ = run! { + let x = %{ a: 1 }; + // Technically x could be inactive here during the resolution + // of its index, but for now, it's an error. + x[{ + x.a += 1; + "a" + }] = 3; + }; +} \ No newline at end of file diff --git a/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.stderr b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.stderr new file mode 100644 index 00000000..2aa888c1 --- /dev/null +++ b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.stderr @@ -0,0 +1,8 @@ +error: Cannot create a shared reference because it clashes with an existing mutable reference: + This reference : [*active*] &x (of type any) + Other reference: [*active*] &mut x (of type any) + Reason : mutation may be observed from the other active reference, which breaks aliasing rules + --> tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs:9:13 + | +9 | x.a += 1; + | ^ diff --git a/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs new file mode 100644 index 00000000..d1131b6e --- /dev/null +++ b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs @@ -0,0 +1,8 @@ +use preinterpret::*; + +fn main() { + let _ = run!{ + let my_arr = [[]]; + my_arr[0].push(my_arr.pop()) + }; +} \ No newline at end of file diff --git a/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr new file mode 100644 index 00000000..0faeb961 --- /dev/null +++ b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr @@ -0,0 +1,8 @@ +error: Cannot create a mutable reference because it clashes with an existing reference: + This reference : [*active*] &mut my_arr (of type any) + Other reference: [inactive] &mut my_arr[0] (of type any) + Reason : mutation may invalidate the other descendent reference + --> tests/compilation_failures/references/with_active_mut_attempt_mutate_its_ancestor.rs:6:24 + | +6 | my_arr[0].push(my_arr.pop()) + | ^^^^^^ diff --git a/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs new file mode 100644 index 00000000..c7b3b69b --- /dev/null +++ b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs @@ -0,0 +1,11 @@ +use preinterpret::*; + +fn main() { + let _ = run! { + let x = %{ a: 1 }; + let f = |x_field: &any| { + x.b = "new!!"; + }; + f(x.a); + }; +} \ No newline at end of file diff --git a/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr new file mode 100644 index 00000000..8e37c29e --- /dev/null +++ b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr @@ -0,0 +1,8 @@ +error: Cannot create a mutable reference because it clashes with an existing reference: + This reference : [*active*] &mut x (of type any) + Other reference: [inactive] &x.a (of type any) + Reason : mutation may invalidate the other descendent reference + --> tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs:7:13 + | +7 | x.b = "new!!"; + | ^ diff --git a/tests/expressions.rs b/tests/expressions.rs index effbae4e..4e2cecc1 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -632,6 +632,10 @@ fn test_method_calls() { ); // Push returns None assert_eq!(run!([1, 2, 3].as_mut().push(4).to_debug_string()), "None"); + // Pop returns last value + assert_eq!(run!([1, 2].as_mut().pop().to_debug_string()), "2"); + // Pop empty returns None + assert_eq!(run!([].as_mut().pop().to_debug_string()), "None"); // Converting to mut and then to shared works assert_eq!(run!([].as_mut().len().to_debug_string()), "0usize"); assert_eq!( diff --git a/tests/references.rs b/tests/references.rs new file mode 100644 index 00000000..3cc5081b --- /dev/null +++ b/tests/references.rs @@ -0,0 +1,33 @@ +#![allow(clippy::assertions_on_constants)] + +#[path = "helpers/prelude.rs"] +mod prelude; +use prelude::*; + +#[test] +#[cfg_attr(miri, ignore = "incompatible with miri")] +fn test_core_compilation_failures() { + if !should_run_ui_tests() { + // Some of the outputs are different on nightly, so don't test these + return; + } + let t = trybuild::TestCases::new(); + t.compile_fail("tests/compilation_failures/references/*.rs"); +} + +#[test] +fn test_can_use_distinct_paths() { + run! { + let x = %{ + a: 1, + b: 2, + }; + x.a += x.b; + %[_].assert_eq(x.a, 3); + }; + run! { + let arr = [0, 1, 2, 3]; + arr[0] += arr[1]; + %[_].assert_eq(arr[0], 1); + }; +} From 64ffbad3132618dc44e71993ebd404af0d2e0725 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 1 Mar 2026 21:32:25 +0000 Subject: [PATCH 24/25] fix: Fix filename in stderr file --- .../with_inactive_mut_attempt_mutate_its_ancestor.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr index 0faeb961..cbcadc52 100644 --- a/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr +++ b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr @@ -2,7 +2,7 @@ error: Cannot create a mutable reference because it clashes with an existing ref This reference : [*active*] &mut my_arr (of type any) Other reference: [inactive] &mut my_arr[0] (of type any) Reason : mutation may invalidate the other descendent reference - --> tests/compilation_failures/references/with_active_mut_attempt_mutate_its_ancestor.rs:6:24 + --> tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs:6:24 | 6 | my_arr[0].push(my_arr.pop()) | ^^^^^^ From 9c8f2661d77b677039ab6dd9df3eef8d296c9c52 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 1 Mar 2026 22:31:26 +0000 Subject: [PATCH 25/25] fix: Markups --- plans/TODO.md | 2 +- .../concepts/forms/copy_on_write.rs | 2 +- src/expressions/type_resolution/arguments.rs | 2 +- src/interpretation/variable_state.rs | 12 +++---- src/misc/dynamic_references/referenceable.rs | 32 +++++++++---------- ...tive_ref_attempt_mutate_its_descendant.rs} | 0 ..._ref_attempt_mutate_its_descendant.stderr} | 2 +- ...ive_mut_attempt_mutate_its_ancestor.stderr | 2 +- ...ive_ref_attempt_mutate_its_ancestor.stderr | 2 +- 9 files changed, 28 insertions(+), 28 deletions(-) rename tests/compilation_failures/references/{with_active_ref_attempt_mutate_its_descendent.rs => with_active_ref_attempt_mutate_its_descendant.rs} (100%) rename tests/compilation_failures/references/{with_active_ref_attempt_mutate_its_descendent.stderr => with_active_ref_attempt_mutate_its_descendant.stderr} (92%) diff --git a/plans/TODO.md b/plans/TODO.md index 23b1e3bc..2a8ada46 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -261,7 +261,7 @@ Possible punted: - [ ] Support for `move()` expressions in closures. - `move(x)` / `move(a.b.as_ref())` / `move(a.b.as_mut())` => we hoist up the content into the previous frame (effectively temporarily change `current_frame_id` to be the parent in the `FlowAnalysisState` - pretty easy). - These can be an anonymous definition in the root frame of the closure, which is referenced inline. --[ ] Possibly - not require `Clone` on iterators: +- [ ] Possibly - not require `Clone` on iterators: - Make `TryClone -> Result` ## Fix broken "Disabled" abstraction diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index 0b80e81f..e2561d3b 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -132,7 +132,7 @@ impl CopyOnWrite { } } -/// A disabled copy-on-write value that can be safely cloned and dropped. +/// An inactive copy-on-write value that can be safely cloned and dropped. pub(crate) enum InactiveCopyOnWrite { Owned(Owned), SharedWithInfallibleCloning(InactiveShared), diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index 12ef60b1..e2e38ae7 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -164,7 +164,7 @@ impl IsArgument for CopyOnWrite { } impl IsArgument for CopyOnWrite { - type ValueType = AnyType; + type ValueType = FloatType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::CopyOnWrite; fn from_argument(Spanned(value, span): Spanned) -> FunctionResult { diff --git a/src/interpretation/variable_state.rs b/src/interpretation/variable_state.rs index fdbb9dd4..54f8ee77 100644 --- a/src/interpretation/variable_state.rs +++ b/src/interpretation/variable_state.rs @@ -30,8 +30,8 @@ impl VariableContent { } } -const UNITIALIZED_ERR: &str = "Cannot resolve uninitialized variable. This shouldn't be possible, because all variables are set on first use."; -const FINISHED_ERR: &str = "Cannot resolve finished variable. This shouldn't be possible, because is_final should be marked correctly. If you see this error, please report a bug to preinterpret on github with a reproduction case."; +const UNINITIALIZED_ERR: &str = "Cannot resolve uninitialized variable. This shouldn't be possible, because all variables are set on first use."; +const FINISHED_ERR: &str = "Cannot resolve finished variable. This shouldn't be possible, because is_final should be marked correctly. If you see this error, please report a bug to preinterpret on GitHub with a reproduction case."; impl VariableState { pub(crate) fn define(&mut self, value: VariableContent) { @@ -52,13 +52,13 @@ impl VariableState { if is_final && blocked_from_mutation.is_none() { let content = std::mem::replace(self, VariableState::Finished); match content { - VariableState::Uninitialized => panic!("{}", UNITIALIZED_ERR), + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), VariableState::Value(content) => content, VariableState::Finished => panic!("{}", FINISHED_ERR), } } else { match self { - VariableState::Uninitialized => panic!("{}", UNITIALIZED_ERR), + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), VariableState::Value(content) => content.clone(), VariableState::Finished => panic!("{}", FINISHED_ERR), } @@ -80,7 +80,7 @@ impl VariableState { let content = if is_final && blocked_from_mutation.is_none() { let content = std::mem::replace(self, VariableState::Finished); match content { - VariableState::Uninitialized => panic!("{}", UNITIALIZED_ERR), + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), VariableState::Value(content) => match content.into_owned_as_only_owner() { Ok(owned) => { if matches!( @@ -109,7 +109,7 @@ impl VariableState { } } else { match self { - VariableState::Uninitialized => panic!("{}", UNITIALIZED_ERR), + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), VariableState::Value(content) => content.clone(), VariableState::Finished => panic!("{}", FINISHED_ERR), } diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 411510ac..d22b4f0c 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -362,12 +362,12 @@ enum PathComparison { /// but they are not identical or in an ancestor/descendant relationship. /// e.g. left = x[0..10], right = x[5..15] Overlapping, - /// The right path is a descendent of the left path. + /// The right path is a descendant of the left path. /// e.g. right = left.x or right = left[0]["key"] - RightIsDescendent, - /// The left path is a descendent of the right path. + RightIsDescendant, + /// The left path is a descendant of the right path. /// e.g. left = right.x or left = right[0]["key"] - LeftIsDescendent, + LeftIsDescendant, /// The left and right path refer to the same leaf value. /// But they may be in different forms. e.g. left = &any and right = &integer ReferencesEqual(TypeBindingComparison), @@ -390,8 +390,8 @@ impl PathComparison { PathComparison::Overlapping => { "mutation may invalidate the other overlapping reference" } - PathComparison::RightIsDescendent => { - "mutation may invalidate the other descendent reference" + PathComparison::RightIsDescendant => { + "mutation may invalidate the other descendant reference" } PathComparison::ReferencesEqual(TypeBindingComparison::RightIsSubtypeOfLeft) | PathComparison::ReferencesEqual( @@ -403,10 +403,10 @@ impl PathComparison { | PathComparison::ReferencesEqual(TypeBindingComparison::Incomparable) => { "mutation may invalidate the other reference by setting it to an incompatible type" } - // Mutable reference is a descendent of the other reference + // Mutable reference is a descendant of the other reference PathComparison::ReferencesEqual(TypeBindingComparison::Equal) | PathComparison::ReferencesEqual(TypeBindingComparison::LeftIsSubtypeOfRight) - | PathComparison::LeftIsDescendent => { + | PathComparison::LeftIsDescendant => { if other_is_active { "mutation may be observed from the other active reference, which breaks aliasing rules" } else { @@ -436,8 +436,8 @@ impl ReferencePath { PathPartComparison::Divergent => PathComparison::Divergent, PathPartComparison::IdenticalChildReference => continue, PathPartComparison::OverlappingChildReference => PathComparison::Overlapping, - PathPartComparison::RightIsDescendent => PathComparison::RightIsDescendent, - PathPartComparison::LeftIsDescendent => PathComparison::LeftIsDescendent, + PathPartComparison::RightIsDescendant => PathComparison::RightIsDescendant, + PathPartComparison::LeftIsDescendant => PathComparison::LeftIsDescendant, PathPartComparison::ReferencesEqual(inner) => { PathComparison::ReferencesEqual(inner) } @@ -488,12 +488,12 @@ enum PathPartComparison { /// e.g. left = x[0..10], right = x[5..15] #[allow(unused)] // Kept for future, and to ensure we have the correct abstraction OverlappingChildReference, - /// The right path is a descendent of the left path. + /// The right path is a descendant of the left path. /// e.g. right = left.x or right = left[0]["key"] - RightIsDescendent, - /// The left path is a descendent of the right path. + RightIsDescendant, + /// The left path is a descendant of the right path. /// e.g. left = right.x or left = right[0]["key"] - LeftIsDescendent, + LeftIsDescendant, /// The left and right path refer to the same leaf value. /// But they may be in different forms. e.g. left = &any and right = &integer ReferencesEqual(TypeBindingComparison), @@ -523,14 +523,14 @@ impl PathPart { }, (PathPart::Child(a), PathPart::Value { bound_as }) => { if bound_as.is_narrowing_to(&a.parent_type_kind()) { - PathPartComparison::LeftIsDescendent + PathPartComparison::LeftIsDescendant } else { PathPartComparison::Incompatible } } (PathPart::Value { bound_as }, PathPart::Child(b)) => { if bound_as.is_narrowing_to(&b.parent_type_kind()) { - PathPartComparison::RightIsDescendent + PathPartComparison::RightIsDescendant } else { PathPartComparison::Incompatible } diff --git a/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.rs similarity index 100% rename from tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs rename to tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.rs diff --git a/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.stderr b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.stderr similarity index 92% rename from tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.stderr rename to tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.stderr index 2aa888c1..2a474bd3 100644 --- a/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.stderr +++ b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.stderr @@ -2,7 +2,7 @@ error: Cannot create a shared reference because it clashes with an existing muta This reference : [*active*] &x (of type any) Other reference: [*active*] &mut x (of type any) Reason : mutation may be observed from the other active reference, which breaks aliasing rules - --> tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs:9:13 + --> tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.rs:9:13 | 9 | x.a += 1; | ^ diff --git a/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr index cbcadc52..81913905 100644 --- a/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr +++ b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr @@ -1,7 +1,7 @@ error: Cannot create a mutable reference because it clashes with an existing reference: This reference : [*active*] &mut my_arr (of type any) Other reference: [inactive] &mut my_arr[0] (of type any) - Reason : mutation may invalidate the other descendent reference + Reason : mutation may invalidate the other descendant reference --> tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs:6:24 | 6 | my_arr[0].push(my_arr.pop()) diff --git a/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr index 8e37c29e..60626c9d 100644 --- a/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr +++ b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr @@ -1,7 +1,7 @@ error: Cannot create a mutable reference because it clashes with an existing reference: This reference : [*active*] &mut x (of type any) Other reference: [inactive] &x.a (of type any) - Reason : mutation may invalidate the other descendent reference + Reason : mutation may invalidate the other descendant reference --> tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs:7:13 | 7 | x.b = "new!!";