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 94fb0b8b..2a8ada46 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -241,34 +241,87 @@ 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. - [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` -- [ ] 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. +- [ ] 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. - `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())`. + +### 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` +- [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. +- [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` +- [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` +- [x] See what else can be deleted from `bindings.rs` + - [x] Unify LateBound into `LateBound` + - [x] Unify CopyOnWrite into `QqqCopyOnWrite` +- [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. + 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 @@ -301,7 +354,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/closures.rs b/src/expressions/closures.rs index 5c1da71a..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 { @@ -89,7 +88,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 +99,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/content.rs b/src/expressions/concepts/content.rs index 641cbcd0..4b42b108 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 @@ -193,7 +180,7 @@ where fn clone_to_owned_transparently<'r>( &'r self, span_range: SpanRange, - ) -> ExecutionResult> + ) -> FunctionResult> where 'a: 'r, Self::Form: LeafAsRefForm, @@ -202,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) } } @@ -226,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 6337ce19..bda41811 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 { @@ -32,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)) @@ -53,20 +60,21 @@ 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>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + leaf: Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn; + T::Leaf: CastDyn; } pub(crate) trait MapFromArgument: IsHierarchicalForm { const ARGUMENT_OWNERSHIP: ArgumentOwnership; - 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 43f623e9..bf08c3ce 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -22,20 +22,34 @@ 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 { - 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>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.emplace_map(|content, emplacer| match ::map_mut(content) { + Ok(mapped) => { + // SAFETY: PathExtension is correct for mapping to a dyn type + let mapped_mut = unsafe { + MappedMut::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_mut)) + } + Err(_this) => Err(emplacer.revert()), }) } } @@ -56,11 +70,12 @@ 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() - .0 - .replace(|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, Some(span)) + })) } } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index b813264b..e073e31a 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -23,20 +23,34 @@ 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 { - 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>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_ref(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.emplace_map(|content, emplacer| match ::map_ref(content) { + Ok(mapped) => { + // SAFETY: PathExtension is correct for mapping to a dyn type + let mapped_ref = unsafe { + MappedRef::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_ref)) + } + Err(_this) => Err(emplacer.revert()), }) } } @@ -51,11 +65,12 @@ 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() - .0 - .replace(|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, Some(span)) + })) } } diff --git a/src/expressions/concepts/forms/argument.rs b/src/expressions/concepts/forms/argument.rs index f7cafdfc..85b1dcb4 100644 --- a/src/expressions/concepts/forms/argument.rs +++ b/src/expressions/concepts/forms/argument.rs @@ -2,10 +2,10 @@ use super::*; pub(crate) enum Argument { Owned(L), - CopyOnWrite(QqqCopyOnWrite), - Mutable(QqqMutable), - Assignee(QqqAssignee), - Shared(QqqShared), + CopyOnWrite(CopyOnWrite), + Mutable(Mutable), + Assignee(Assignee), + Shared(Shared), } impl IsValueContent for Argument { @@ -31,13 +31,21 @@ 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 { 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 489bb712..9973b56c 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -1,45 +1,90 @@ use super::*; -pub(crate) struct QqqAssignee(pub(crate) MutableSubRcRefCell); +/// 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> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeAssignee { - type DynLeaf<'a, D: 'static + ?Sized> = QqqAssignee; + type DynLeaf<'a, D: IsDynType> = Assignee; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + 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))), + .emplace_map(|content, emplacer| match ::map_mut(content) { + Ok(mapped) => { + // SAFETY: PathExtension is correct for mapping to a dyn type + let mapped_mut = unsafe { + MappedMut::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) + }; + Ok(Assignee(emplacer.emplace(mapped_mut))) + } + Err(_this) => Err(Assignee(emplacer.revert())), }) } } @@ -61,7 +106,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 8a644ddb..e2561d3b 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -1,39 +1,205 @@ 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). /// 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 { - 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) + } + } + } +} + +/// An inactive 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> + where + 'a: 'b, + { + leaf + } } impl BeCopyOnWrite { @@ -43,7 +209,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) } } } @@ -54,7 +220,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) } } } @@ -65,7 +231,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) } } } @@ -75,14 +241,14 @@ 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) => { + 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)) } } @@ -92,9 +258,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, } } @@ -102,11 +268,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) } } @@ -115,13 +281,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) } } @@ -141,9 +307,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), } } } @@ -157,9 +323,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), } } } @@ -177,9 +343,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)?, }) } } @@ -190,59 +356,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>( @@ -289,26 +454,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 0395e1ca..2628b357 100644 --- a/src/expressions/concepts/forms/late_bound.rs +++ b/src/expressions/concepts/forms/late_bound.rs @@ -1,58 +1,134 @@ 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(QqqCopyOnWrite), + CopyOnWrite(CopyOnWrite), /// A mutable reference - Mutable(QqqMutable), + Mutable(Mutable), /// A shared reference where mutable access failed for a specific reason - Shared(QqqLateBoundShared), + Shared(LateBoundShared), +} + +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 IsValueContent for QqqLateBound { +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> + where + 'a: 'b, + { + leaf + } } -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) shared: QqqShared, +pub(crate) struct LateBoundShared { + pub(crate) shared: Shared, pub(crate) reason_not_mutable: syn::Error, } 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 d8c79110..e26fcf36 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -1,19 +1,26 @@ use super::*; -pub(crate) type QqqMutable = MutableSubRcRefCell; - -impl IsValueContent for QqqMutable { - type Type = L::Type; +impl IsValueContent for Mutable { + type Type = X::Type; type Form = BeMutable; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqMutable { +impl<'a, X: IsValueContent> IntoValueContent<'a> for Mutable +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)) } } -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqMutable { +// 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 Mutable { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } @@ -24,21 +31,35 @@ 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> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeMutable { - type DynLeaf<'a, D: 'static + ?Sized> = QqqMutable; + type DynLeaf<'a, D: IsDynType> = Mutable; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.emplace_map(|content, emplacer| match ::map_mut(content) { + Ok(mapped) => { + // SAFETY: PathExtension is correct for mapping to a dyn type + let mapped_mut = unsafe { + MappedMut::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_mut)) + } + Err(_) => Err(emplacer.revert()), }) } } @@ -59,9 +80,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().into_content()) + Ok(value + .expect_mutable() + .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 09873161..5a575a03 100644 --- a/src/expressions/concepts/forms/owned.rs +++ b/src/expressions/concepts/forms/owned.rs @@ -18,16 +18,24 @@ 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 { - type DynLeaf<'a, D: 'static + ?Sized> = Box; + type DynLeaf<'a, D: IsDynType> = Box; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, _span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { ::map_boxed(Box::new(leaf)).map_err(|boxed| *boxed) } @@ -49,7 +57,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/referenceable.rs b/src/expressions/concepts/forms/referenceable.rs deleted file mode 100644 index 47b7b622..00000000 --- a/src/expressions/concepts/forms/referenceable.rs +++ /dev/null @@ -1,33 +0,0 @@ -use super::*; - -pub(crate) type Referenceable = Rc>; - -impl IsValueContent for Referenceable { - type Type = L::Type; - type Form = BeReferenceable; -} - -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for Referenceable { - fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self - } -} - -impl<'a, L: IsValueLeaf> FromValueContent<'a> for Referenceable { - 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> = Referenceable; -} diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index e9a21302..330d2065 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -1,19 +1,27 @@ use super::*; -pub(crate) type QqqShared = SharedSubRcRefCell; - -impl IsValueContent for QqqShared { - type Type = L::Type; +impl IsValueContent for Shared { + type Type = X::Type; type Form = BeShared; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqShared { +impl<'a, X: IsValueContent> IntoValueContent<'a> for Shared +where + X: 'static, + X: IsSelfValueContent<'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)) } } -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqShared { +// 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 Shared { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } @@ -24,21 +32,35 @@ 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> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeShared { - type DynLeaf<'a, D: 'static + ?Sized> = QqqShared; + type DynLeaf<'a, D: IsDynType> = Shared; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_ref(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.emplace_map(|content, emplacer| match ::map_ref(content) { + Ok(mapped) => { + // SAFETY: PathExtension is correct for mapping to a dyn type + let mapped_ref = unsafe { + MappedRef::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_ref)) + } + Err(_) => Err(emplacer.revert()), }) } } @@ -53,9 +75,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().into_content()) + Ok(value + .expect_shared() + .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 fcd4bbe4..e7175dd5 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -26,16 +26,24 @@ 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 { - 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>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, _span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { ::map_mut(leaf) } @@ -52,15 +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>, - ) -> 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: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeMutable>; @@ -75,23 +85,34 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { self.emplacer.emplace_unchecked(leaf) } + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension correctly describes mapping to a leaf type T + let mapped = unsafe { + MappedMut::new_unchecked( + leaf, + PathExtension::TypeNarrowing(T::type_kind()), + self.span, + ) + }; + self.emplacer.emplace(mapped) } }; - let __mapper = __InlineMapper { emplacer }; - ::map_with::(__mapper, self) + let __mapper = __InlineMapper { emplacer, span }; + 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>, - ) -> 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: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAssignee>; @@ -106,23 +127,34 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { QqqAssignee(self.emplacer.emplace_unchecked(leaf)) } + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension correctly describes mapping to a leaf type T + let mapped = unsafe { + MappedMut::new_unchecked( + leaf, + PathExtension::TypeNarrowing(T::type_kind()), + self.span, + ) + }; + Assignee(self.emplacer.emplace(mapped)) } }; - let __mapper = __InlineMapper { emplacer }; - ::map_with::(__mapper, self) + let __mapper = __InlineMapper { emplacer, span }; + 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>, - ) -> 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: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAnyMut>; @@ -137,12 +169,21 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { Mutable(self.emplacer.emplace_unchecked(leaf)).into() } + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension correctly describes mapping to a leaf type T + let mapped = unsafe { + MappedMut::new_unchecked( + leaf, + PathExtension::TypeNarrowing(T::type_kind()), + self.span, + ) + }; + self.emplacer.emplace(mapped).into() } }; - let __mapper = __InlineMapper { emplacer }; - ::map_with::(__mapper, self) + let __mapper = __InlineMapper { emplacer, span }; + 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 d466b492..1d36b72a 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -26,16 +26,24 @@ 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 { - 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>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, _span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { ::map_ref(leaf) } @@ -52,17 +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>, - ) -> 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>, + 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>( @@ -75,23 +85,34 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { self.emplacer.emplace_unchecked(leaf) } + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension correctly describes mapping to a leaf type T + let mapped = unsafe { + MappedRef::new_unchecked( + leaf, + PathExtension::TypeNarrowing(T::type_kind()), + self.span, + ) + }; + self.emplacer.emplace(mapped) } }; - let __mapper = __InlineMapper { emplacer }; - ::map_with::(__mapper, self) + let __mapper = __InlineMapper { emplacer, span }; + 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>, - ) -> 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: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAnyRef>; @@ -106,12 +127,21 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { Shared(self.emplacer.emplace_unchecked(leaf)).into() } + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension correctly describes mapping to a leaf type T + let mapped = unsafe { + MappedRef::new_unchecked( + leaf, + PathExtension::TypeNarrowing(T::type_kind()), + self.span, + ) + }; + self.emplacer.emplace(mapped).into() } }; - let __mapper = __InlineMapper { emplacer }; - ::map_with::(__mapper, self) + let __mapper = __InlineMapper { emplacer, span }; + let content = ::map_with::(__mapper, self); + ::covariant::(content) } } 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/concepts/type_traits.rs b/src/expressions/concepts/type_traits.rs index d267a660..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: @@ -57,7 +66,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>> @@ -106,6 +114,7 @@ pub(crate) trait DowncastFrom: IsHierarchicalType { pub(crate) trait DynResolveFrom: IsDynType { 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 +122,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); @@ -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> { @@ -472,7 +495,6 @@ macro_rules! define_parent_type { } } - impl<'a, F: IsHierarchicalForm> Copy for $content<'a, F> where $( Content<'a, $variant_type, F>: Copy ),* @@ -634,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 { @@ -712,11 +744,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) +impl DynMapper { + pub(crate) fn new(span: SpanRange) -> Self { + Self { + _phantom: std::marker::PhantomData, + span, + } } } @@ -770,7 +808,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,53 +840,53 @@ 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 { - 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") } } - 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 { - 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") } } - 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 { - 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::<$dyn_type>::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) } } - 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>, @@ -863,7 +901,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/evaluator.rs b/src/expressions/evaluation/evaluator.rs index 1576b1ad..0f7b8dba 100644 --- a/src/expressions/evaluation/evaluator.rs +++ b/src/expressions/evaluation/evaluator.rs @@ -149,12 +149,12 @@ pub(crate) enum RequestedValue { Owned(AnyValueOwned), Shared(AnyValueShared), Mutable(AnyValueMutable), - CopyOnWrite(CopyOnWriteValue), - Assignee(AssigneeValue), + CopyOnWrite(AnyValueCopyOnWrite), + Assignee(AnyValueAssignee), // RequestedOwnership::LateBound // ------------------------------- - LateBound(LateBoundValue), + LateBound(AnyValueLateBound), // Marks completion of an assignment frame // --------------------------------------- @@ -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"), @@ -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/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 f32972a8..10ffd94a 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,66 +49,66 @@ 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(mutable) => { - Ok(ArgumentValue::Mutable(mutable.enable(span)?)) + InactiveArgumentValue::Mutable(inactive) => { + Ok(ArgumentValue::Mutable(inactive.activate(span)?)) } - DisabledArgumentValue::Assignee(assignee) => { - Ok(ArgumentValue::Assignee(Assignee(assignee.enable(span)?))) + InactiveArgumentValue::Assignee(inactive) => { + Ok(ArgumentValue::Assignee(Assignee(inactive.activate(span)?))) } - DisabledArgumentValue::Shared(shared) => { - Ok(ArgumentValue::Shared(shared.enable(span)?)) + 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. @@ -212,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), @@ -282,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, })) @@ -297,12 +296,12 @@ impl RequestedOwnership { pub(crate) fn map_from_copy_on_write( &self, - Spanned(cow, span): Spanned, + Spanned(cow, span): Spanned, ) -> FunctionResult> { 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))?) @@ -319,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))?) @@ -331,12 +330,12 @@ impl RequestedOwnership { pub(crate) fn map_from_assignee( &self, - Spanned(assignee, span): Spanned, + Spanned(assignee, span): Spanned, ) -> FunctionResult> { 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))?) @@ -348,12 +347,12 @@ impl RequestedOwnership { pub(crate) fn map_from_shared( &self, - Spanned(shared, span): Spanned, + Spanned(shared, span): Spanned, ) -> FunctionResult> { 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))?) @@ -420,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( @@ -446,19 +445,21 @@ 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( 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, ))) } } @@ -477,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, @@ -487,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), )), @@ -514,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) } @@ -527,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.") } @@ -559,9 +558,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 +568,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, + ))), } } } @@ -870,7 +871,7 @@ enum BinaryPath { right: ExpressionNodeId, }, OnRightBranch { - left: Spanned, + left: Spanned, interface: BinaryOperationInterface, }, } @@ -909,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) @@ -931,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) @@ -956,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))?; @@ -1030,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], @@ -1050,6 +1051,7 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { let ctx = PropertyAccessCallContext { property: &self.access, + output_span_range: result_span, }; let auto_create = context.requested_ownership().requests_auto_create(); @@ -1145,8 +1147,10 @@ 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(); @@ -1163,7 +1167,6 @@ impl EvaluationFrame for ValueIndexAccessBuilder { }, |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))? } }) @@ -1342,7 +1345,7 @@ enum InvocationPath { ArgumentsPath { function_span: SpanRange, invokable: InvokableFunction, - disabled_evaluated_arguments: Vec>, + disabled_evaluated_arguments: Vec>, }, } @@ -1465,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 7784557c..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()), + 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()), + 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)), + 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 57f97a28..e2e38ae7 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -58,12 +58,13 @@ impl IsArgument for ArgumentValue { } } -impl + ResolvableArgumentTarget + ?Sized> IsArgument for Shared { - type ValueType = T::ValueType; +impl IsArgument for Shared { + 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) } } @@ -79,23 +80,55 @@ impl + ResolvableArgumentTarget + ?Sized> IsArgume // } // } -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") } } -impl + ResolvableArgumentTarget + ?Sized> IsArgument for Mutable { - type ValueType = T::ValueType; +impl IsArgument for Mutable { + 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) } } @@ -120,23 +153,24 @@ impl + ResolvableArgumentTarget + ?Sized> IsArgum // } // } -impl + ResolvableArgumentTarget + ToOwned> IsArgument - for CopyOnWrite -where - T::Owned: 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 = FloatType; 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| { - >::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"), ) } } @@ -165,7 +199,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 +221,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) } @@ -211,7 +249,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( @@ -235,23 +273,34 @@ 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" fn resolve_shared( Spanned(value, span): Spanned>, resolution_target: &str, - ) -> FunctionResult> { + ) -> FunctionResult> + where + Self: 'static, + { value .try_map(|v| { - Self::resolve_from_ref( + let resolved = Self::resolve_from_ref( v, ResolutionContext { span_range: &span, resolution_target, }, - ) + )?; + // SAFETY: Tightened(T::Type) correctly describes type narrowing + Ok(unsafe { + MappedRef::new( + resolved, + PathExtension::TypeNarrowing(T::Type::type_kind()), + span, + ) + }) }) .map_err(|(err, _)| err) } @@ -285,7 +334,7 @@ pub(crate) trait ResolvableShared { } } -pub(crate) trait ResolvableMutable { +pub(crate) trait ResolvableMutable { fn resolve_from_mut<'a>( value: &'a mut T, context: ResolutionContext, @@ -304,16 +353,27 @@ pub(crate) trait ResolvableMutable { fn resolve_mutable( Spanned(value, span): Spanned>, resolution_target: &str, - ) -> FunctionResult> { + ) -> FunctionResult> + where + Self: 'static, + { value .try_map(|v| { - Self::resolve_from_mut( + let resolved = Self::resolve_from_mut( v, ResolutionContext { span_range: &span, resolution_target, }, - ) + )?; + // SAFETY: Tightened(T::Type) correctly describes type narrowing + Ok(unsafe { + MappedMut::new( + resolved, + PathExtension::TypeNarrowing(T::Type::type_kind()), + span, + ) + }) }) .map_err(|(err, _)| err) } @@ -409,46 +469,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/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/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_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/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index df4d25ce..14a75a14 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), @@ -89,8 +90,96 @@ impl TypeKind { TypeKind::Dyn(dyn_kind) => dyn_kind.source_name(), } } + + pub(crate) fn is_narrowing_to(&self, other: &Self) -> bool { + match self.compare_bindings(other) { + TypeBindingComparison::Equal => true, + TypeBindingComparison::RightDerivesFromLeftButIsNotSubtype => true, + TypeBindingComparison::RightIsSubtypeOfLeft => true, + TypeBindingComparison::LeftDerivesFromRightButIsNotSubtype => false, + TypeBindingComparison::LeftIsSubtypeOfRight => 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" + /// + /// 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 TypeBindingComparison::Equal; + } + match (self, other) { + // 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::LeftDerivesFromRightButIsNotSubtype, + (_, TypeKind::Dyn(_)) => TypeBindingComparison::RightDerivesFromLeftButIsNotSubtype, + // All non-any-values are strict subtypes of AnyValue + (TypeKind::Parent(ParentTypeKind::Value(_)), _) => { + TypeBindingComparison::RightIsSubtypeOfLeft + } + (_, TypeKind::Parent(ParentTypeKind::Value(_))) => { + TypeBindingComparison::LeftIsSubtypeOfRight + } + // Integer types + ( + TypeKind::Parent(ParentTypeKind::Integer(_)), + TypeKind::Leaf(AnyValueLeafKind::Integer(_)), + ) => TypeBindingComparison::RightIsSubtypeOfLeft, + ( + TypeKind::Leaf(AnyValueLeafKind::Integer(_)), + TypeKind::Parent(ParentTypeKind::Integer(_)), + ) => TypeBindingComparison::LeftIsSubtypeOfRight, + (TypeKind::Parent(ParentTypeKind::Integer(_)), _) => { + TypeBindingComparison::Incompatible + } + (_, TypeKind::Parent(ParentTypeKind::Integer(_))) => { + TypeBindingComparison::Incompatible + } + // Float types + ( + TypeKind::Parent(ParentTypeKind::Float(_)), + TypeKind::Leaf(AnyValueLeafKind::Float(_)), + ) => TypeBindingComparison::RightIsSubtypeOfLeft, + ( + TypeKind::Leaf(AnyValueLeafKind::Float(_)), + TypeKind::Parent(ParentTypeKind::Float(_)), + ) => TypeBindingComparison::LeftIsSubtypeOfRight, + (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, + // 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. + Incompatible, } +#[derive(PartialEq, Eq, Clone, Copy)] pub(crate) enum ParentTypeKind { Value(AnyValueTypeKind), Integer(IntegerTypeKind), @@ -123,6 +212,7 @@ impl ParentTypeKind { } } +#[derive(PartialEq, Eq, Clone, Copy)] pub(crate) enum DynTypeKind { Iterable, } @@ -240,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()), + AnyValueShared::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()), + AnyValueShared::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()), + AnyValueShared::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..7dd20b02 100644 --- a/src/expressions/values/any_value.rs +++ b/src/expressions/values/any_value.rs @@ -8,6 +8,8 @@ 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 AnyValueLateBound = LateBound; // pub(crate) type AnyValueShared = AnyValueContent<'static, BeShared>; // pub(crate) type AnyValueMutable = AnyValueContent<'static, BeMutable>; // pub(crate) type AnyValueAssignee = AnyValueContent<'static, BeAssignee>; @@ -44,13 +46,13 @@ 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() } 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(), @@ -64,28 +66,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 +97,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 +107,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 +123,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/array.rs b/src/expressions/values/array.rs index daf4b5ea..4d2cbe01 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, @@ -197,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 { @@ -233,11 +200,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/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/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/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/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 deleted file mode 100644 index c83c0355..00000000 --- a/src/interpretation/bindings.rs +++ /dev/null @@ -1,866 +0,0 @@ -use super::*; - -use std::borrow::{Borrow, ToOwned}; -use std::rc::Rc; - -pub(super) enum VariableState { - Uninitialized, - Value(VariableContent), - Finished, -} - -/// Cheaply clonable, non-active content of a variable. -/// By non-active, we mean that the shared/mutable 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), -} - -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()), - Err(original) => Err(VariableContent::Referenceable(original)), - }, - other => Err(other), - } - } -} - -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."; - -impl VariableState { - pub(crate) fn define(&mut self, value: VariableContent) { - match self { - content @ VariableState::Uninitialized => { - *content = VariableState::Value(value); - } - VariableState::Value(_) => panic!("Cannot define existing variable"), - VariableState::Finished => panic!("Cannot define finished variable"), - } - } - - pub(crate) fn resolve_content( - &mut self, - is_final: bool, - blocked_from_mutation: Option, - ) -> VariableContent { - if is_final && blocked_from_mutation.is_none() { - let content = std::mem::replace(self, VariableState::Finished); - match content { - VariableState::Uninitialized => panic!("{}", UNITIALIZED_ERR), - VariableState::Value(content) => content, - VariableState::Finished => panic!("{}", FINISHED_ERR), - } - } else { - match self { - VariableState::Uninitialized => panic!("{}", UNITIALIZED_ERR), - VariableState::Value(content) => content.clone(), - VariableState::Finished => panic!("{}", FINISHED_ERR), - } - } - } - - pub(crate) fn resolve( - &mut self, - variable_span: Span, - is_final: bool, - ownership: RequestedOwnership, - blocked_from_mutation: Option, - ) -> FunctionResult> { - let span_range = variable_span.span_range(); - - // If blocked from mutation, we technically could allow is_final to work and - // return a fully owned value without observable mutation, - // but it's likely confusingly inconsistent, so it's better to just block it entirely. - 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::Value(content) => match content.into_owned_as_only_owner() { - Ok(owned) => { - if matches!( - ownership, - RequestedOwnership::Concrete(ArgumentOwnership::Assignee { .. }) - ) { - 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 { - owned, - is_from_last_use: true, - }), - span_range, - )); - } - // It's currently referenced elsewhere, proceed with normal late-bound resolution. - // e.g. - // * `let x = %[]; x.assert_eq(x, %[]);` - the final `x` resolves to a shared reference - // * `let x = %[]; x.assert_eq(x + %[], %[]);` - errors because the final `x` is shared but it needs to be owned - // Or, it could be captured somewhere else, e.g. in a closure: - // * `let x = %[]; let f = || x; let y = x;` - the final `x` resolves to a shared reference - Err(content) => content, - }, - VariableState::Finished => panic!("{}", FINISHED_ERR), - } - } else { - match self { - VariableState::Uninitialized => panic!("{}", UNITIALIZED_ERR), - VariableState::Value(content) => content.clone(), - VariableState::Finished => panic!("{}", FINISHED_ERR), - } - }; - let binding = VariableBinding { - content, - variable_span, - }; - let resolved = match ownership { - RequestedOwnership::LateBound => binding.into_late_bound(), - RequestedOwnership::Concrete(ownership) => match ownership { - ArgumentOwnership::Owned => binding.into_transparently_cloned().map(|owned| { - LateBoundValue::Owned(LateBoundOwnedValue { - owned, - is_from_last_use: false, - }) - }), - ArgumentOwnership::Shared => binding - .into_shared() - .map(CopyOnWrite::shared_in_place_of_shared) - .map(LateBoundValue::CopyOnWrite), - ArgumentOwnership::Assignee { .. } => { - binding.into_mut().map(LateBoundValue::Mutable) - } - ArgumentOwnership::Mutable => binding.into_mut().map(LateBoundValue::Mutable), - ArgumentOwnership::CopyOnWrite | ArgumentOwnership::AsIs => binding - .into_shared() - .map(CopyOnWrite::shared_in_place_of_shared) - .map(LateBoundValue::CopyOnWrite), - }, - }; - let late_bound = if let Some(mutation_block_reason) = blocked_from_mutation { - match resolved { - Ok(LateBoundValue::Mutable(mutable)) => { - let reason_not_mutable = variable_span - .syn_error(mutation_block_reason.error_message("mutate this variable")); - Ok(LateBoundValue::Shared(LateBoundSharedValue::new( - mutable.into_shared(), - reason_not_mutable, - ))) - } - x => x, - } - } else { - resolved - }?; - Ok(Spanned(late_bound, span_range)) - } -} - -#[derive(Clone)] -pub(crate) struct VariableBinding { - content: VariableContent, - variable_span: Span, -} - -#[allow(unused)] -impl VariableBinding { - /// Gets the cloned expression value - /// This only works if the value can be transparently cloned - pub(crate) fn into_transparently_cloned(self) -> FunctionResult { - let span_range = self.variable_span.span_range(); - let shared = self.into_shared()?; - let value = shared.as_ref().try_transparent_clone(span_range)?; - Ok(value) - } - - fn into_mut(self) -> FunctionResult { - match self.content { - VariableContent::Referenceable(referenceable) => { - let inner = MutableSubRcRefCell::new(referenceable).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 - .ownership_err(SHARED_TO_MUTABLE_ERROR_MESSAGE), - } - } - - fn into_shared(self) -> FunctionResult { - match self.content { - VariableContent::Referenceable(referenceable) => { - let inner = SharedSubRcRefCell::new(referenceable).map_err(|_| { - self.variable_span - .ownership_error::(SHARED_ERROR_MESSAGE) - })?; - Ok(Shared(inner)) - } - VariableContent::Mutable(mutable) => mutable - .into_shared() - .enable(self.variable_span.span_range()), - VariableContent::Shared(shared) => shared.enable(self.variable_span.span_range()), - } - } - - 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(|_| { - self.variable_span - .ownership_error::(SHARED_ERROR_MESSAGE) - })?; - Ok(LateBoundValue::Shared(LateBoundSharedValue::new( - Shared(shared), - self.variable_span.syn_error(SHARED_ERROR_MESSAGE), - ))) - } - } - } - VariableContent::Mutable(mutable) => Ok(LateBoundValue::Mutable( - mutable.enable(self.variable_span.span_range())?, - )), - VariableContent::Shared(shared) => { - Ok(LateBoundValue::Shared(LateBoundSharedValue::new( - shared.enable(self.variable_span.span_range())?, - self.variable_span - .syn_error(SHARED_TO_MUTABLE_ERROR_MESSAGE), - ))) - } - } - } -} - -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: SharedValue, - pub(crate) reason_not_mutable: syn::Error, -} - -impl LateBoundSharedValue { - pub(crate) fn new(shared: SharedValue, 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(CopyOnWriteValue), - /// 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, - )), - }) - } - - 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() - } -} - -pub(crate) type AssigneeValue = AnyValueAssignee; - -/// 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); - -impl AssigneeValue { - pub(crate) fn set(&mut self, content: impl IntoAnyValue) { - *self.0 .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 - .0 - .replace(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer)) - } -} - -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 - } -} - -/// 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 = - "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 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)?; - Ok(value) - } -} - -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)) - } -} - -impl AsRef for Shared { - fn as_ref(&self) -> &T { - &self.0 - } -} - -impl Deref for Shared { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// 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::Owned) -> 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), - } - } - - /// Disables this copy-on-write value, releasing any borrow on the RefCell. - /// Returns a `DisabledCopyOnWrite` which can be cloned and later re-enabled. - pub(crate) fn disable(self) -> DisabledCopyOnWrite { - let inner = match self.inner { - CopyOnWriteInner::Owned(owned) => DisabledCopyOnWriteInner::Owned(owned), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - DisabledCopyOnWriteInner::SharedWithInfallibleCloning(shared.disable()) - } - CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - DisabledCopyOnWriteInner::SharedWithTransparentCloning(shared.disable()) - } - }; - DisabledCopyOnWrite { inner } - } -} - -/// A disabled copy-on-write value that can be safely cloned and dropped. -pub(crate) struct DisabledCopyOnWrite { - inner: DisabledCopyOnWriteInner, -} - -enum DisabledCopyOnWriteInner { - Owned(Owned), - SharedWithInfallibleCloning(DisabledShared), - SharedWithTransparentCloning(DisabledShared), -} - -impl Clone for DisabledCopyOnWrite -where - T::Owned: Clone, -{ - fn clone(&self) -> Self { - let inner = match &self.inner { - DisabledCopyOnWriteInner::Owned(owned) => { - DisabledCopyOnWriteInner::Owned(owned.clone()) - } - DisabledCopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - DisabledCopyOnWriteInner::SharedWithInfallibleCloning(shared.clone()) - } - DisabledCopyOnWriteInner::SharedWithTransparentCloning(shared) => { - DisabledCopyOnWriteInner::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> { - let inner = match self.inner { - DisabledCopyOnWriteInner::Owned(owned) => CopyOnWriteInner::Owned(owned), - DisabledCopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - CopyOnWriteInner::SharedWithInfallibleCloning(shared.enable(span)?) - } - DisabledCopyOnWriteInner::SharedWithTransparentCloning(shared) => { - CopyOnWriteInner::SharedWithTransparentCloning(shared.enable(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) -> SharedValue { - match self.inner { - CopyOnWriteInner::Owned(owned) => SharedValue::new_from_owned(owned), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => shared, - CopyOnWriteInner::SharedWithTransparentCloning(shared) => shared, - } - } -} - -pub(crate) type CopyOnWriteValue = CopyOnWrite; 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/refs.rs b/src/interpretation/refs.rs index 216638ef..38efaccc 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -9,77 +9,135 @@ pub(crate) struct AnyRef<'a, T: ?Sized + 'static> { } impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { + /// 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) fn map(self, f: impl for<'r> FnOnce(&'r T) -> &'r S) -> AnyRef<'a, S> { + pub(crate) fn map( + self, + 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 (value, _path_extension, _span) = f(value).into_parts(); + AnyRef { + inner: AnyRefInner::Direct(value), + } + } AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.map(|x| f(x))), + inner: AnyRefInner::Encapsulated(shared.map(f)), }, } } + /// 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) fn map_optional( + pub(crate) fn map_optional( self, - f: impl for<'r> FnOnce(&'r T) -> Option<&'r S>, + 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 (value, _path_extension, _span) = f(value)?.into_parts(); + AnyRef { + inner: AnyRefInner::Direct(value), + } + } AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.map_optional(f)?), + inner: AnyRefInner::Encapsulated(shared.emplace_map(|input, emplacer| match f( + input, + ) { + Some(mapped) => Some(emplacer.emplace(mapped)), + None => { + let _ = emplacer.revert(); + None + } + })?), }, }) } - pub(crate) fn replace( + 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>), } -impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { - pub(crate) fn emplace(&mut self, value: &'e V) -> 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) +struct DirectRefEmplacerState<'a, T: 'static + ?Sized> { + ptr: Option<*const T>, + original_lifetime: PhantomData<&'a T>, +} + +impl<'x, 'a, 'm, T: 'static + ?Sized> AnyRefEmplacer<'x, 'a, 'm, T> { + pub(crate) fn revert(&mut self) -> AnyRef<'a, T> { + 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()), + }, } } - // SAFETY: - // * The caller must ensure that the value's lifetime is derived from the original content - 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, + mapped: MappedRef<'m, V>, ) -> AnyRef<'a, V> { - self.inner - .take() - .expect("You can only emplace to create a new AnyRef value once") - .map(|_| - // SAFETY: As defined in the rustdoc above - unsafe { transmute::<&V, &'static V>(value) }) + 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)), + }, + } } } @@ -110,7 +168,7 @@ impl<'a, T: ?Sized> ToSpannedRef<'a> for &'a T { impl<'a, T: ?Sized> From> for AnyRef<'a, T> { fn from(value: Shared) -> Self { Self { - inner: AnyRefInner::Encapsulated(value.0), + inner: AnyRefInner::Encapsulated(value), } } } @@ -127,7 +185,7 @@ impl ToSpannedRef<'static> for Shared { enum AnyRefInner<'a, T: 'static + ?Sized> { Direct(&'a T), - Encapsulated(SharedSubRcRefCell), + Encapsulated(Shared), } impl<'a, T: 'static + ?Sized> Deref for AnyRef<'a, T> { @@ -156,81 +214,138 @@ impl<'a, T: ?Sized> From<&'a mut T> for AnyMut<'a, T> { } impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { + /// 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) fn map( + pub(crate) fn map( self, - f: impl for<'r> FnOnce(&'r mut T) -> &'r mut S, + 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 (value, _path_extension, _span) = f(value).into_parts(); + AnyMut { + inner: AnyMutInner::Direct(value), + } + } AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.map(|x| f(x))), + inner: AnyMutInner::Encapsulated(mutable.map(f)), }, } } + /// 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) fn map_optional( + pub(crate) fn map_optional( self, - f: impl for<'r> FnOnce(&'r mut T) -> Option<&'r mut S>, + 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 (value, _path_extension, _span) = f(value)?.into_parts(); + AnyMut { + inner: AnyMutInner::Direct(value), + } + } AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.map_optional(f)?), + inner: AnyMutInner::Encapsulated(mutable.emplace_map( + |input, emplacer| match f(input) { + Some(mapped) => Some(emplacer.emplace(mapped)), + None => { + let _ = emplacer.revert(); + None + } + }, + )?), }, }) } - pub(crate) fn replace( - mut self, - f: impl for<'e> FnOnce(&'e mut T, &mut AnyMutEmplacer<'a, 'e, T>) -> O, + pub(crate) fn emplace_map( + self, + f: impl for<'m> FnOnce(&'m mut T, &mut AnyMutEmplacer<'_, 'a, 'm, T>) -> O, ) -> O { - let copied_mut = self.deref_mut() as *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: 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(mutable) => mutable.emplace_map(|x, mut_emplacer| { + let mut emplacer = AnyMutEmplacer { + inner: AnyMutEmplacerState::Encapsulated(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>), } -impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { - pub(crate) fn emplace(&mut self, value: &'e mut V) -> 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) +struct DirectMutEmplacerState<'a, T: 'static + ?Sized> { + ptr: Option<*mut T>, + original_lifetime: PhantomData<&'a mut T>, +} + +impl<'x, 'a, 'm, T: 'static + ?Sized> AnyMutEmplacer<'x, 'a, 'm, T> { + pub(crate) fn revert(&mut self) -> AnyMut<'a, T> { + 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()), + }, } } - // SAFETY: - // * The caller must ensure that the value's lifetime is derived from the original content - 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, + mapped: MappedMut<'m, V>, ) -> AnyMut<'a, V> { - self.inner - .take() - .expect("You can only emplace to create a new AnyMut value once") - .map(|_| - // SAFETY: As defined in the rustdoc above - unsafe { transmute::<&mut V, &'static mut V>(value) }) + 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)), + }, + } } } @@ -251,14 +366,14 @@ 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 { Self { - inner: AnyMutInner::Encapsulated(value.0), + inner: AnyMutInner::Encapsulated(value), } } } enum AnyMutInner<'a, T: 'static + ?Sized> { Direct(&'a mut T), - Encapsulated(MutableSubRcRefCell), + 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 dd67fc24..d4004b92 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(), + Some(self.ident.to_string()), + self.ident.span_range(), + )), ); } } @@ -140,7 +144,7 @@ impl VariableReference { pub(crate) fn resolve_late_bound( &self, interpreter: &mut Interpreter, - ) -> FunctionResult> { + ) -> FunctionResult> { interpreter.resolve(self, RequestedOwnership::LateBound) } @@ -157,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/interpretation/variable_state.rs b/src/interpretation/variable_state.rs new file mode 100644 index 00000000..54f8ee77 --- /dev/null +++ b/src/interpretation/variable_state.rs @@ -0,0 +1,222 @@ +use super::*; + +pub(super) enum VariableState { + Uninitialized, + Value(VariableContent), + Finished, +} + +/// 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(InactiveShared), + Mutable(InactiveMutable), +} + +impl VariableContent { + fn into_owned_as_only_owner(self) -> Result, VariableContent> { + match self { + VariableContent::Referenceable(data) => match data.try_into_inner() { + Ok(owned) => Ok(owned), + Err(original) => Err(VariableContent::Referenceable(original)), + }, + other => Err(other), + } + } +} + +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) { + match self { + content @ VariableState::Uninitialized => { + *content = VariableState::Value(value); + } + VariableState::Value(_) => panic!("Cannot define existing variable"), + VariableState::Finished => panic!("Cannot define finished variable"), + } + } + + pub(crate) fn resolve_content( + &mut self, + is_final: bool, + blocked_from_mutation: Option, + ) -> VariableContent { + if is_final && blocked_from_mutation.is_none() { + let content = std::mem::replace(self, VariableState::Finished); + match content { + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), + VariableState::Value(content) => content, + VariableState::Finished => panic!("{}", FINISHED_ERR), + } + } else { + match self { + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), + VariableState::Value(content) => content.clone(), + VariableState::Finished => panic!("{}", FINISHED_ERR), + } + } + } + + pub(crate) fn resolve( + &mut self, + variable_span: Span, + is_final: bool, + ownership: RequestedOwnership, + blocked_from_mutation: Option, + ) -> FunctionResult> { + let span_range = variable_span.span_range(); + + // If blocked from mutation, we technically could allow is_final to work and + // return a fully owned value without observable mutation, + // but it's likely confusingly inconsistent, so it's better to just block it entirely. + let content = if is_final && blocked_from_mutation.is_none() { + let content = std::mem::replace(self, VariableState::Finished); + match content { + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), + VariableState::Value(content) => match content.into_owned_as_only_owner() { + Ok(owned) => { + if matches!( + ownership, + RequestedOwnership::Concrete(ArgumentOwnership::Assignee { .. }) + ) { + 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( + LateBound::Owned(LateBoundOwned { + owned, + is_from_last_use: true, + }), + span_range, + )); + } + // It's currently referenced elsewhere, proceed with normal late-bound resolution. + // e.g. + // * `let x = %[]; x.assert_eq(x, %[]);` - the final `x` resolves to a shared reference + // * `let x = %[]; x.assert_eq(x + %[], %[]);` - errors because the final `x` is shared but it needs to be owned + // Or, it could be captured somewhere else, e.g. in a closure: + // * `let x = %[]; let f = || x; let y = x;` - the final `x` resolves to a shared reference + Err(content) => content, + }, + VariableState::Finished => panic!("{}", FINISHED_ERR), + } + } else { + match self { + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), + VariableState::Value(content) => content.clone(), + VariableState::Finished => panic!("{}", FINISHED_ERR), + } + }; + let binding = VariableBinding { + content, + variable_span, + }; + let resolved = match ownership { + RequestedOwnership::LateBound => binding.into_late_bound(), + RequestedOwnership::Concrete(ownership) => match ownership { + ArgumentOwnership::Owned => binding.into_transparently_cloned().map(|owned| { + LateBound::Owned(LateBoundOwned { + owned, + is_from_last_use: false, + }) + }), + ArgumentOwnership::Shared => binding + .into_shared() + .map(CopyOnWrite::shared_in_place_of_shared) + .map(AnyValueLateBound::CopyOnWrite), + ArgumentOwnership::Assignee { .. } => { + binding.into_mut().map(AnyValueLateBound::Mutable) + } + ArgumentOwnership::Mutable => binding.into_mut().map(AnyValueLateBound::Mutable), + ArgumentOwnership::CopyOnWrite | ArgumentOwnership::AsIs => binding + .into_shared() + .map(CopyOnWrite::shared_in_place_of_shared) + .map(AnyValueLateBound::CopyOnWrite), + }, + }; + let late_bound = if let Some(mutation_block_reason) = blocked_from_mutation { + match resolved { + Ok(AnyValueLateBound::Mutable(mutable)) => { + let reason_not_mutable = variable_span + .syn_error(mutation_block_reason.error_message("mutate this variable")); + Ok(LateBound::new_shared( + mutable.into_shared(), + reason_not_mutable, + )) + } + x => x, + } + } else { + resolved + }?; + Ok(Spanned(late_bound, span_range)) + } +} + +#[derive(Clone)] +struct VariableBinding { + content: VariableContent, + variable_span: Span, +} + +impl VariableBinding { + /// Gets the cloned expression value + /// This only works if the value can be transparently cloned + pub(crate) fn into_transparently_cloned(self) -> FunctionResult { + let span_range = self.variable_span.span_range(); + let shared = self.into_shared()?; + let value = shared.as_ref().try_transparent_clone(span_range)?; + Ok(value) + } + + fn into_mut(self) -> FunctionResult { + let span = self.variable_span.span_range(); + match self.content { + VariableContent::Referenceable(referenceable) => referenceable.new_active_mutable(span), + VariableContent::Mutable(inactive) => inactive.activate(span), + VariableContent::Shared(_) => self + .variable_span + .ownership_err(SHARED_TO_MUTABLE_ERROR_MESSAGE), + } + } + + fn into_shared(self) -> FunctionResult { + let span = self.variable_span.span_range(); + match self.content { + VariableContent::Referenceable(referenceable) => referenceable.new_active_shared(span), + VariableContent::Mutable(inactive) => inactive.into_shared().activate(span), + VariableContent::Shared(inactive) => inactive.activate(span), + } + } + + 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(AnyValueLateBound::Mutable(mutable)), + Err(err) => { + // Mutable failed, try shared + let shared = referenceable.new_active_shared(span)?; + Ok(LateBound::new_shared(shared, err.expect_ownership_error()?)) + } + } + } + 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), + )), + } + } +} + +static SHARED_TO_MUTABLE_ERROR_MESSAGE: &str = "Cannot mutate a non-mutable reference"; diff --git a/src/misc/dynamic_references/mod.rs b/src/misc/dynamic_references/mod.rs new file mode 100644 index 00000000..c07ac467 --- /dev/null +++ b/src/misc/dynamic_references/mod.rs @@ -0,0 +1,99 @@ +//! ## 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) +//! +//! Basically, a.1 and a.2 are our two key invariants, and all our actions must preserve them. +//! +//! 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 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::mem::ManuallyDrop; +use std::ptr::NonNull; + +pub(crate) use mutable_reference::*; +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 new file mode 100644 index 00000000..68af8048 --- /dev/null +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -0,0 +1,211 @@ +use std::mem::transmute; + +use super::*; + +/// Represents an active `&mut T` reference, to something derived from a Referenceable root. +pub(crate) struct Mutable(pub(super) ReferenceCore); + +impl Mutable { + pub(crate) fn deactivate(self) -> InactiveMutable { + self.0.core.data_mut().deactivate_reference(self.0.id); + 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<'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. + // 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 = MutableEmplacer(self.0.into_emplacer()); + f(copied_mut, &mut emplacer) + } + + /// 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 for<'a> FnOnce(&'a mut T) -> MappedMut<'a, V>, + ) -> Mutable { + self.emplace_map(move |input, emplacer| emplacer.emplace(f(input))) + } + + /// 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 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())), + }) + } +} + +impl Mutable { + /// Creates a new MutableReference from an owned value, wrapping it in a Referenceable. + 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_active_mutable(span_range) + .expect("Freshly created referenceable must be borrowable as mutable") + } +} + +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 Mutable { + 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.as_ref() } + } +} + +impl AsRef for Mutable { + fn as_ref(&self) -> &T { + self + } +} + +impl AsMut for Mutable { + fn as_mut(&mut self) -> &mut T { + self + } +} + +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: + // - 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 { self.0.pointer.as_mut() } + } +} + +/// 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 InactiveMutable { + fn clone(&self) -> Self { + InactiveMutable(self.0.clone()) + } +} + +impl InactiveMutable { + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { + self.0 + .core + .data_mut() + .activate_mutable_reference(self.0.id, span)?; + Ok(Mutable(self.0)) + } + + 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); + InactiveShared(self.0) + } +} + +/// 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: Option, +} + +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: Some(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: Option, + ) -> 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, Option) { + (self.value, self.path_extension, self.span) + } +} + +pub(crate) struct MutableEmplacer<'a, T: ?Sized>(EmplacerCore<'a, T>); + +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<'e: 'a, V: 'static + ?Sized>( + &mut self, + mapped: MappedMut<'e, V>, + ) -> 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 { 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 new file mode 100644 index 00000000..ed30dea9 --- /dev/null +++ b/src/misc/dynamic_references/reference_core.rs @@ -0,0 +1,125 @@ +use super::*; + +pub(super) struct ReferenceCore { + pub(super) pointer: NonNull, + pub(super) id: LocalReferenceId, + pub(super) core: Rc>, +} + +impl ReferenceCore { + pub(super) fn new_root( + core: Rc>, + reference_kind: ReferenceKind, + ) -> Self { + 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, + core, + } + } +} + +impl ReferenceCore { + 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 { + 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 an active 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); + } +} + +/// 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, + /// 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, + pointer: NonNull, + path_extension: PathExtension, + new_span: Option, + ) -> 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 new file mode 100644 index 00000000..d22b4f0c --- /dev/null +++ b/src/misc/dynamic_references/referenceable.rs @@ -0,0 +1,546 @@ +use super::*; + +#[derive(Clone)] +pub(crate) struct Referenceable { + core: Rc>, +} + +impl Referenceable { + pub(crate) fn new(root: AnyValue, root_name: Option, root_span: SpanRange) -> Self { + Self { + core: Rc::new(ReferenceableCore::new(root, root_name, root_span)), + } + } + + pub(crate) fn new_inactive_shared(&self) -> InactiveShared { + InactiveShared(ReferenceCore::new_root( + self.core.clone(), + ReferenceKind::InactiveShared, + )) + } + + 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> { + 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) { + Ok(core) => Ok(core.into_inner()), + Err(core) => Err(Self { core }), + } + } +} + +pub(super) struct ReferenceableCore { + // Guaranteed not-null + root: UnsafeCell, + data: RefCell, +} + +impl ReferenceableCore { + pub(super) fn new(root: T, root_name: Option, root_span: SpanRange) -> Self { + Self { + root: UnsafeCell::new(root), + data: RefCell::new(ReferenceableData { + root_name, + root_span, + 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_mut(&self) -> RefMut<'_, ReferenceableData> { + self.data.borrow_mut() + } + + pub(super) fn into_inner(self) -> T { + self.root.into_inner() + } +} + +new_key_type! { + pub(crate) struct LocalReferenceId; +} + +pub(super) struct ReferenceableData { + // Could be in Referencable Core, but having it here makes the error message API easier + root_name: Option, + // 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) + } + + 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) { + 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( + &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() { + if other_id != id { + 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, + }; + 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 : {}", error_reason); + return data.creation_span.ownership_err(error_message); + } + } + + let data = self.for_reference_mut(id); + data.reference_kind = match data.reference_kind { + ReferenceKind::InactiveMutable => ReferenceKind::ActiveMutable, + ReferenceKind::InactiveShared => { + panic!("cannot mut-activate an inactive shared reference") + } + _ => panic!("cannot mut-activate an active reference"), + }; + + Ok(()) + } + + 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() { + if other_id != id && other_data.reference_kind == ReferenceKind::ActiveMutable { + 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 = 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 : {}", error_reason); + return data.creation_span.ownership_err(error_message); + } + } + let data = self.for_reference_mut(id); + data.reference_kind = match data.reference_kind { + ReferenceKind::InactiveShared | ReferenceKind::InactiveMutable => { + ReferenceKind::ActiveShared + } + _ => panic!("cannot shared-activate an active reference"), + }; + Ok(()) + } + + pub(super) fn derive_reference( + &mut self, + id: LocalReferenceId, + path_extension: PathExtension, + new_span: Option, + ) { + let data = self.for_reference_mut(id); + 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)) => { + let parent_type = specifier.parent_type_kind(); + match last_path_part { + PathPart::Value { bound_as } => { + if !bound_as.is_narrowing_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::TypeNarrowing(new_bound_as)) => { + if bound_as.is_narrowing_to(&new_bound_as) { + *bound_as = new_bound_as; + } else { + panic!( + "Invalid path extension: cannot narrow to {} 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( + &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 ")?, + } + 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 } => { + 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)] +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, +} + +#[derive(PartialEq, Eq, Copy, Clone)] +pub(super) enum ReferenceKind { + InactiveShared, + InactiveMutable, + ActiveShared, + ActiveMutable, +} + +impl ReferenceKind { + fn is_active(&self) -> bool { + match self { + ReferenceKind::InactiveShared | ReferenceKind::InactiveMutable => 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, +} + +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 descendant of the left path. + /// e.g. right = left.x or right = left[0]["key"] + RightIsDescendant, + /// The left path is a descendant of the right path. + /// e.g. left = right.x or left = right[0]["key"] + 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), + /// 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> { + // 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 => { + "mutation may invalidate the other overlapping reference" + } + PathComparison::RightIsDescendant => { + "mutation may invalidate the other descendant reference" + } + PathComparison::ReferencesEqual(TypeBindingComparison::RightIsSubtypeOfLeft) + | PathComparison::ReferencesEqual( + TypeBindingComparison::RightDerivesFromLeftButIsNotSubtype, + ) + | PathComparison::ReferencesEqual( + TypeBindingComparison::LeftDerivesFromRightButIsNotSubtype, + ) + | PathComparison::ReferencesEqual(TypeBindingComparison::Incomparable) => { + "mutation may invalidate the other reference by setting it to an incompatible type" + } + // Mutable reference is a descendant of the other reference + PathComparison::ReferencesEqual(TypeBindingComparison::Equal) + | PathComparison::ReferencesEqual(TypeBindingComparison::LeftIsSubtypeOfRight) + | PathComparison::LeftIsDescendant => { + if other_is_active { + "mutation may be observed from the other active 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 }], + } + } + + fn compare(&self, other: &Self) -> PathComparison { + for (own, other) in self.parts.iter().zip(&other.parts) { + return match own.compare(other) { + PathPartComparison::Divergent => PathComparison::Divergent, + PathPartComparison::IdenticalChildReference => continue, + PathPartComparison::OverlappingChildReference => PathComparison::Overlapping, + PathPartComparison::RightIsDescendant => PathComparison::RightIsDescendant, + PathPartComparison::LeftIsDescendant => PathComparison::LeftIsDescendant, + PathPartComparison::ReferencesEqual(inner) => { + PathComparison::ReferencesEqual(inner) + } + PathPartComparison::Incompatible => PathComparison::Incompatible, + }; + } + unreachable!("BUG: PathParts should be [Child* Value] and so can't end with a comparison of PathPartComparison::IdenticalDeeperReference") + } +} + +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 + TypeNarrowing(TypeKind), +} + +#[derive(PartialEq, Eq, Clone)] +enum PathPart { + Value { bound_as: TypeKind }, + Child(ChildSpecifier), +} + +#[derive(PartialEq, Eq, Clone)] +pub(crate) enum ChildSpecifier { + ArrayChild(usize), + ObjectChild(String), +} + +impl ChildSpecifier { + fn parent_type_kind(&self) -> TypeKind { + match self { + ChildSpecifier::ArrayChild(_) => ArrayType::type_kind(), + ChildSpecifier::ObjectChild(_) => ObjectType::type_kind(), + } + } +} + +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 descendant of the left path. + /// e.g. right = left.x or right = left[0]["key"] + RightIsDescendant, + /// The left path is a descendant of the right path. + /// e.g. left = right.x or left = right[0]["key"] + 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), + /// 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::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::ArrayChild(_), _) => PathPartComparison::Incompatible, + (ChildSpecifier::ObjectChild(a), ChildSpecifier::ObjectChild(b)) if a == b => { + PathPartComparison::IdenticalChildReference + } + (ChildSpecifier::ObjectChild(_), ChildSpecifier::ObjectChild(_)) => { + PathPartComparison::Divergent + } + (ChildSpecifier::ObjectChild(_), _) => PathPartComparison::Incompatible, + }, + (PathPart::Child(a), PathPart::Value { bound_as }) => { + if bound_as.is_narrowing_to(&a.parent_type_kind()) { + PathPartComparison::LeftIsDescendant + } else { + PathPartComparison::Incompatible + } + } + (PathPart::Value { bound_as }, PathPart::Child(b)) => { + if bound_as.is_narrowing_to(&b.parent_type_kind()) { + PathPartComparison::RightIsDescendant + } 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 new file mode 100644 index 00000000..fc1d842e --- /dev/null +++ b/src/misc/dynamic_references/shared_reference.rs @@ -0,0 +1,187 @@ +use std::mem::transmute; + +use super::*; + +/// Represents an active `&T` reference, to something derived from a Referenceable root. +pub(crate) struct Shared(pub(super) ReferenceCore); + +impl Clone for Shared { + fn clone(&self) -> Self { + Shared(self.0.clone()) + } +} + +impl Shared { + pub(crate) fn deactivate(self) -> InactiveShared { + self.0.core.data_mut().deactivate_reference(self.0.id); + 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<'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. + // 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 = SharedEmplacer(self.0.into_emplacer()); + f(copied_ref, &mut emplacer) + } + + /// 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 for<'a> FnOnce(&'a T) -> MappedRef<'a, V>, + ) -> Shared { + self.emplace_map(move |input, emplacer| emplacer.emplace(f(input))) + } + + /// 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 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())), + }) + } +} + +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( + value: AnyValue, + root_name: Option, + span_range: SpanRange, + ) -> Self { + let referenceable = Referenceable::new(value, root_name, span_range); + referenceable + .new_active_shared(span_range) + .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 Deref for Shared { + 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.as_ref() } + } +} + +impl AsRef for Shared { + fn as_ref(&self) -> &T { + self + } +} + +/// 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 InactiveShared { + fn clone(&self) -> Self { + InactiveShared(self.0.clone()) + } +} + +impl InactiveShared { + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { + self.0 + .core + .data_mut() + .activate_shared_reference(self.0.id, span)?; + Ok(Shared(self.0)) + } +} + +/// 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: Option, +} + +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: Some(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: Option, + ) -> Self { + Self { + value: unsafe { transmute::<&'any V, &'a V>(value) }, + path_extension, + span, + } + } + + pub(crate) fn into_parts(self) -> (&'a V, PathExtension, Option) { + (self.value, self.path_extension, self.span) + } +} + +pub(crate) struct SharedEmplacer<'a, T: ?Sized>(EmplacerCore<'a, T>); + +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<'e: 'a, V: 'static + ?Sized>( + &mut self, + mapped: MappedRef<'e, V>, + ) -> 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 { 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 { diff --git a/src/misc/mod.rs b/src/misc/mod.rs index 36aa4457..2ce22658 100644 --- a/src/misc/mod.rs +++ b/src/misc/mod.rs @@ -1,18 +1,18 @@ mod arena; +mod dynamic_references; mod errors; mod field_inputs; mod iterators; mod keywords; -mod mut_rc_ref_cell; 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::*; pub(crate) use keywords::*; -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 8bf147a1..00000000 --- a/src/misc/mut_rc_ref_cell.rs +++ /dev/null @@ -1,461 +0,0 @@ -use crate::internal_prelude::*; -use std::cell::{BorrowError, BorrowMutError}; - -/// 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/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 29c94dab..a15b6faf 100644 --- a/tests/compilation_failures/expressions/swap_itself.stderr +++ b/tests/compilation_failures/expressions/swap_itself.stderr @@ -1,4 +1,7 @@ -error: The variable cannot be modified as it is already being modified +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: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); diff --git a/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.rs b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.rs new file mode 100644 index 00000000..29e87fdc --- /dev/null +++ b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.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_descendant.stderr b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.stderr new file mode 100644 index 00000000..2a474bd3 --- /dev/null +++ b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.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_descendant.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..81913905 --- /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 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.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..60626c9d --- /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 descendant 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); + }; +}