From b11b0786c1108f840005a1200acb40842de244d4 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 24 Jan 2026 00:21:56 +0000 Subject: [PATCH 01/49] feat: Add type functions with type::function(...) syntax --- CHANGELOG.md | 2 +- plans/TODO.md | 63 +++-- src/expressions/closures.rs | 189 +++++++++++++ src/expressions/concepts/type_traits.rs | 7 +- src/expressions/equality.rs | 8 +- .../evaluation/control_flow_analysis.rs | 13 +- src/expressions/evaluation/evaluator.rs | 15 ++ src/expressions/evaluation/node_conversion.rs | 15 +- src/expressions/evaluation/value_frames.rs | 220 +++++++++++++-- src/expressions/expression.rs | 40 ++- src/expressions/expression_parsing.rs | 90 +++++-- src/expressions/mod.rs | 2 + .../type_resolution/interface_macros.rs | 250 +++++++++++------- src/expressions/type_resolution/type_data.rs | 73 ++--- src/expressions/type_resolution/type_kinds.rs | 32 ++- src/expressions/values/any_value.rs | 23 +- src/expressions/values/array.rs | 6 +- src/expressions/values/boolean.rs | 6 +- src/expressions/values/character.rs | 6 +- src/expressions/values/float.rs | 6 +- src/expressions/values/float_subtypes.rs | 6 +- src/expressions/values/float_untyped.rs | 6 +- src/expressions/values/function.rs | 63 +++++ src/expressions/values/integer.rs | 6 +- src/expressions/values/integer_subtypes.rs | 6 +- src/expressions/values/integer_untyped.rs | 6 +- src/expressions/values/iterable.rs | 5 +- src/expressions/values/iterator.rs | 5 +- src/expressions/values/mod.rs | 4 + src/expressions/values/none.rs | 19 +- src/expressions/values/object.rs | 5 +- src/expressions/values/parser.rs | 9 +- src/expressions/values/preinterpret.rs | 24 ++ src/expressions/values/range.rs | 5 +- src/expressions/values/stream.rs | 6 +- src/expressions/values/string.rs | 6 +- src/expressions/values/unsupported_literal.rs | 7 +- src/interpretation/bindings.rs | 59 ++++- src/interpretation/interpreter.rs | 3 +- src/interpretation/source_parsing.rs | 1 + src/misc/parse_traits.rs | 1 + .../control_flow/while_infinite_loop.stderr | 2 +- ..._iteration_limit.rs => iteration_limit.rs} | 1 - .../core/iteration_limit.stderr | 6 + .../settings_update_iteration_limit.stderr | 6 - .../core/typed_eq_nested_type_mismatch.stderr | 4 +- ...typed_eq_object_value_type_mismatch.stderr | 4 +- ...ped_eq_object_value_type_mismatch_2.stderr | 4 +- .../core/typed_eq_type_mismatch.stderr | 4 +- .../abort_on_iteration_limit_exceeded.rs | 8 + .../abort_on_iteration_limit_exceeded.stderr | 5 + .../functions/incorrect_type_function_name.rs | 8 + .../incorrect_type_function_name.stderr | 6 + .../functions/too_few_args.rs | 8 + .../functions/too_few_args.stderr | 5 + .../functions/too_many_args.rs | 8 + .../functions/too_many_args.stderr | 5 + .../iteration/infinite_range_to_vec.stderr | 6 +- .../iteration/long_iterable_to_string.stderr | 4 +- .../iteration/long_iterable_to_vec.stderr | 6 +- .../iteration/long_range_to_vec.stderr | 6 +- .../zip_different_length_streams.stderr | 4 +- tests/expressions.rs | 4 +- tests/functions.rs | 34 +++ 64 files changed, 1087 insertions(+), 379 deletions(-) create mode 100644 src/expressions/closures.rs create mode 100644 src/expressions/values/function.rs create mode 100644 src/expressions/values/preinterpret.rs rename tests/compilation_failures/core/{settings_update_iteration_limit.rs => iteration_limit.rs} (52%) create mode 100644 tests/compilation_failures/core/iteration_limit.stderr delete mode 100644 tests/compilation_failures/core/settings_update_iteration_limit.stderr create mode 100644 tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.rs create mode 100644 tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.stderr create mode 100644 tests/compilation_failures/functions/incorrect_type_function_name.rs create mode 100644 tests/compilation_failures/functions/incorrect_type_function_name.stderr create mode 100644 tests/compilation_failures/functions/too_few_args.rs create mode 100644 tests/compilation_failures/functions/too_few_args.stderr create mode 100644 tests/compilation_failures/functions/too_many_args.rs create mode 100644 tests/compilation_failures/functions/too_many_args.stderr create mode 100644 tests/functions.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7815b6b4..4b9bbd5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ This moves preinterpret to an expression-based language, inspired by Rust, but w * `#(let _ = %[...];)` interprets its arguments but then ignores any outputs. * `%[...]` can be used to just output its interpreted contents. It's useful to create a stream value inside an expression. * `%[...].reinterpret_as_run()` is like an `eval` command in scripting languages. It takes a stream, and runs it as a preinterpret expression block content like `run!{ ... }`. Similarly, `%[...].reinterpret_as_stream()` runs it as a stream literal, like `stream!{ ... }`. Each is pure - the reinterpreted code can't read from or write to variables, and can only return values. - * `None.configure_preinterpret(%{ iteration_limit: XXX })` can be used to adjust the iteration limit. + * `preinterpret::set_iteration_limit(xxx)` can be used to adjust the iteration limit. * Expression commands: * The expression block `#(let x = 123; let y = 1.0; y /= x; y + 1)` which is discussed in more detail below. * Control flow commands: diff --git a/plans/TODO.md b/plans/TODO.md index 89162502..48872bba 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -204,40 +204,49 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). ## Methods and closures -- [ ] Consider pre-requisite work on [2026-01-types-and-forms.md](./2026-01-types-and-forms.md) for type annotations. Instead, let's move forward without support for specific types for now. To start, let's just support: `x` or `x: any`; `x: &any` and `x: &mut any`. -- [ ] Change bindings (currently just variables) to be able to store any of the following: (nb we still restrict variables to be owned for now). -```rust -enum VariableContent { - Owned(Referenceable), - Shared(Shared), - Mutable(Mutable), -} -``` -- [ ] Introduce basic function values +- [x] Consider pre-requisite work on [2026-01-types-and-forms.md](./2026-01-types-and-forms.md) for type annotations. Instead, let's move forward without support for specific types for now. To start, let's just support: `x` or `x: any`; `x: &any` and `x: &mut any`. +- [x] Introduce basic function values + - [x] Add `type::method` function resolution + - [x] New node extension in the expression parser: invocation `(...)` + - [x] Add tests that e.g. `array::push(arr, 1)` works. + - [x] Add type functions + - [x] Create a `preinterpret` parent type with no children. + - [x] Allow definition of static functions on a type + - [x] Move `().configure_preinterpret` to `preinterpret::set_iteration_limit` and update the message: `If needed, the limit can be reconfigured with` + +- [ ] `x.hello` accessing a method creates a `NativeFunction` with the first parameter bound to `x` + - [ ] Add some bound receiver or something or more generally `bound_arguments: Vec` - these should be deactivated + - [ ] `my_array.push` returns a function with `my_array` bound. The LateBound `my_array` can then be deactivated whilst the rest of the arguments are resolved, like what we do at the moment. This approach avoids the horrible javascript issues with `this` not being bound when referencing `x.y`. + - [ ] And then for objects, the method wins; BUT you can use `x["obj"]` to access the field instead of the method + - [ ] We can try commenting out the code to resolve a method extension, + and it should still work, courtesy of property access + invocation +- [ ] We can define closures (without any closed values for now) * Value type function `let my_func = |x, y, z| { ... };` * Parameters can be `x` / `x: any` (Owned), `x: &any` (Shared) or `x: &mut any` (Mutable). - * To start with, they are not closures (i.e. they can't capture any outer variables) + * To start with, they can't capture any outer variables - [ ] Break/continue label resolution in functions/closures * Functions and closures must resolve break/continue labels statically * Break and continue statements should not leak out of function boundaries * This needs to be validated during the control flow pass -- [ ] New node extension in the expression parser: invocation `(...)` -- [ ] Closures +- [ ] Closed Bindings + - [ ] Change bindings (currently just variables) to be able to store any of the following: (nb we still restrict variables to be owned for now). + ```rust + enum VariableContent { + Owned(Referenceable), + Shared(Shared), + Mutable(Mutable), + } + ``` * A function may capture variable bindings from the parent scope, these are converted into a `VariableBinding::Closure()` * The closure consists of a set of bindings attached to the function value, either: - `ClosedVariable::Owned(Value)` if it's the last mention of the closed variable, so it can be moved in - - `ClosedVariable::Referenced(Rc>)` otherwise - * Invocation requests `CopyOnWrite`, and can be on a shared function or an owned function - (if it is the last usage of that value, as per normal red/owned binding rules) - * If invocation is on an owned function, then owned values from the closure can be consumed - by the invocation + - `ClosedVariable::Referenced(Rc>)` otherwise. + The nice thing about this is can pull shared/mutable at runtime, + without needing to magically work out what to move. + * Invocation requests `CopyOnWrite`, and can be on a shared function or an owned function (if it is the last usage of that value, as per normal red/owned binding rules) + * If invocation is on an owned function, then owned values from the closure can be consumed by the invocation * Otherwise, the values are only available as shared/mut -- [ ] Try to unify methods under a "resolve, then invoke" structure - * `array::push` should resolve to the method, and `array::push(arr, value)` should work - we'll likely want an explicit `function` section and `constants` section on type data; which we can merge with methods when resolving what `array::push` resolves to. - * `my_array.push` returns a closure with `my_array` bound. The LateBound `my_array` can then be deactivated whilst the rest of the arguments are resolved, like what we do at the moment. This approach avoids the horrible javascript issues with `this` not being bound when referencing `x.y`. - * And then for objects, the method wins; BUT you can use `x["obj"]` to access the field instead of the method -- [ ] Create a `preinterpret` type, and move preinterpret settings to `preinterpret::...` -- [ ] Optional arguments +- [ ] Support optional arguments - [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` - [ ] Add `array.sort`, `array.sort_by` @@ -465,7 +474,7 @@ The following are less important tasks which maybe we don't even want/need to do let @input[{ let _ = input.rest(); let _ = input.token_tree(); }] = %[Hello World]; ``` -## Match block [blocked on slices] +## Match block * Delay this probably - without enums it's not super important. * We'll need to add destructuring references, and allow destructuring `x.as_ref()` @@ -486,6 +495,7 @@ Also: - [x] Merge `assignee_frames` into `value_frames` as per comment as the top of `assignee_frames` - [x] Rename `EvaluationItem` to `RequestedValue` and consider making `RequestedValue::AssignmentCompletion` wrap an `Owned<()>` so that it becomes truly a value. - [x] Merge `HasValueType` with `ValueLeafKind` +- [ ] Fix the end span of `ClosureExpressionInner` and maybe `Expression` more generally? - [ ] Add `preinterpret::macro` - can this be a declarative macro? Would be slightly more efficient, as it just needs to wrap a call to `preinterpret::stream` or `preinterpret::run`... - [ ] When we create `input = %raw[..]` we will need to set its `end_of_stream` span to the end of the macro_rules! macro somehow... I'm not sure how to get that span though. @@ -496,8 +506,7 @@ Also: - [ ] We might need to auto-change the span of all outputted tokens to `Span::call_site()` to get hygiene to be most flexible. Perhaps this can be disabled with `preinterpret::set_auto_call_site_hygiene(false)` - [ ] Add `LiteralPattern` (wrapping a `Literal`) -- [ ] Better handling of `configure_preinterpret`: - * Move `None.configure_preinterpret` to `preinterpret::set_iteration_limit(..)` +- [x] Better handling of `configure_preinterpret` - [ ] CastTarget revision: * The `as untyped_int` operator is not supported for string values * The `as char` operator is not supported for untyped integer values diff --git a/src/expressions/closures.rs b/src/expressions/closures.rs new file mode 100644 index 00000000..4d9cddf3 --- /dev/null +++ b/src/expressions/closures.rs @@ -0,0 +1,189 @@ +use super::*; + +#[derive(Clone)] +pub(crate) struct ClosureExpression(Rc); + +impl ParseSource for ClosureExpression { + fn parse(input: SourceParser) -> ParseResult { + Ok(Self(Rc::new(input.parse()?))) + } + + fn control_flow_pass(&mut self, context: FlowCapturer) -> ParseResult<()> { + Rc::get_mut(&mut self.0) + .expect("ClosureExpressionInner should be uniquely owned during the control flow pass") + .control_flow_pass(context) + } +} + +impl ClosureExpression { + pub(crate) fn evaluate_spanned( + &self, + _interpreter: &mut Interpreter, + ownership: RequestedOwnership, + ) -> ExecutionResult> { + let span_range = self.0.span_range; + ownership.map_from_owned( + self.clone().into_any_value().spanned(span_range) + ) + } +} + +impl PartialEq for ClosureExpression { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0, &other.0) + } +} + +impl Eq for ClosureExpression {} + +pub(crate) struct ClosureExpressionInner { + _closure_id: ClosureId, + scope_id: ScopeId, + _left_bar: Unused, + arguments: Punctuated, + _right_bar: Unused, + body: Expression, + span_range: SpanRange, +} + +impl ParseSource for ClosureExpressionInner { + fn parse(input: SourceParser) -> ParseResult { + let left_bar: syn::Token![|] = input.parse()?; + let start_span = left_bar.span; + let arguments = input.parse_terminated()?; + let _right_bar = input.parse()?; + let body = input.parse()?; + + // Really we want to use the span of the last token in the body expression. + // But painfully in syn, Cursor::prev_span is not public. + // + // We could do a few alternatives: + // - Do some unsafe transmuting + // - Fork syn to expose prev_span (ouch - compile times) + // - Expose an expensive walker on Expression that finds the last span + // - Pass the current end span through the ExpressionParser during parsing + // + // For now we'll use the off-by-one current span of the input. + let end_span = input.cursor().span(); + + Ok(Self { + _closure_id: ClosureId::new_placeholder(), + scope_id: ScopeId::new_placeholder(), + _left_bar: Unused::new(left_bar), + arguments, + _right_bar, + body, + span_range: SpanRange::new_between( + start_span, + end_span, + ), + }) + } + + fn control_flow_pass(&mut self, context: FlowCapturer) -> ParseResult<()> { + context.register_scope(&mut self.scope_id); + context.enter_scope(self.scope_id); + for argument in &mut self.arguments { + argument.pattern.control_flow_pass(context)?; + } + self.body.control_flow_pass(context)?; + context.exit_scope(self.scope_id); + Ok(()) + } +} + +struct FunctionArgument { + pattern: Pattern, + _annotation: Option, +} + +impl ParseSource for FunctionArgument { + fn parse(input: SourceParser) -> ParseResult { + Ok(Self { + pattern: input.parse()?, + _annotation: if input.peek(syn::Token![:]) { + Some(input.parse()?) + } else { + None + }, + }) + } + + fn control_flow_pass(&mut self, context: FlowCapturer) -> ParseResult<()> { + self.pattern.control_flow_pass(context) + } +} + +struct FunctionArgumentAnnotation { + _colon: Unused, + _argument_specifier: ArgumentSpecifier, +} + +enum ArgumentSpecifier { + ByValue { + _argument_type: ArgumentType, + }, + BySharedRef { + _ampersand: Unused, + _argument_type: ArgumentType, + }, + ByMutableRef { + _ampersand: Unused, + _mut_token: Unused, + _argument_type: ArgumentType, + }, +} + +impl ParseSource for FunctionArgumentAnnotation { + fn parse(input: SourceParser) -> ParseResult { + let _colon = input.parse()?; + let _argument_specifier = if input.peek(syn::Token![&]) { + let _ampersand = input.parse()?; + if input.peek(syn::Token![mut]) { + let _mut_token = input.parse()?; + let _argument_type = input.parse()?; + ArgumentSpecifier::ByMutableRef { + _ampersand, + _mut_token, + _argument_type, + } + } else { + let _argument_type = input.parse()?; + ArgumentSpecifier::BySharedRef { + _ampersand, + _argument_type, + } + } + } else { + let _argument_type = input.parse()?; + ArgumentSpecifier::ByValue { _argument_type } + }; + Ok(Self { + _colon, + _argument_specifier, + }) + } + + fn control_flow_pass(&mut self, _context: FlowCapturer) -> ParseResult<()> { + Ok(()) + } +} + +struct ArgumentType { + _type_ident: TypeIdent, +} + +impl ParseSource for ArgumentType { + fn parse(input: SourceParser) -> ParseResult { + if !input.peek_ident_matching("any") { + return input.parse_err("Only the type `any` is currently supported as an argument type annotation") + } + Ok(Self { + _type_ident: input.parse()?, + }) + } + + fn control_flow_pass(&mut self, _context: FlowCapturer) -> ParseResult<()> { + Ok(()) + } +} \ No newline at end of file diff --git a/src/expressions/concepts/type_traits.rs b/src/expressions/concepts/type_traits.rs index e1bd9f26..880ba73f 100644 --- a/src/expressions/concepts/type_traits.rs +++ b/src/expressions/concepts/type_traits.rs @@ -144,7 +144,7 @@ pub(crate) trait IsChildType: IsHierarchicalType { macro_rules! impl_type_feature_resolver { (impl TypeFeatureResolver for $type_def:ty: [$($type_defs:ty)+]) => { impl TypeFeatureResolver for $type_def { - fn resolve_method(&self, method_name: &str) -> Option { + fn resolve_method(&self, method_name: &str) -> Option { $( if let Some(method) = <$type_defs as TypeData>::resolve_own_method(method_name) { return Some(method); @@ -177,6 +177,11 @@ macro_rules! impl_type_feature_resolver { None } + fn resolve_type_function(&self, function_name: &str) -> Option { + // Purposefully doesn't resolve parents, but TBC if this is right + <$type_def as TypeData>::resolve_type_function(function_name) + } + fn resolve_type_property(&self, property_name: &str) -> Option { // Purposefully doesn't resolve parents, but TBC if this is right <$type_def as TypeData>::resolve_type_property(property_name) diff --git a/src/expressions/equality.rs b/src/expressions/equality.rs index 4f3b78f2..d46d257f 100644 --- a/src/expressions/equality.rs +++ b/src/expressions/equality.rs @@ -50,7 +50,7 @@ pub(crate) trait EqualityContext { fn values_equal(&mut self) -> Self::Result; /// Values of the same type are not equal. - fn leaf_values_not_equal(&mut self, lhs: &T, rhs: &T) -> Self::Result; + fn leaf_values_not_equal(&mut self, lhs: &T, rhs: &T) -> Self::Result; /// Values have different kinds. fn kind_mismatch(&mut self, lhs: &L, rhs: &R) -> Self::Result; @@ -106,7 +106,7 @@ impl EqualityContext for SimpleEquality { } #[inline] - fn leaf_values_not_equal(&mut self, _lhs: &T, _rhs: &T) -> bool { + fn leaf_values_not_equal(&mut self, _lhs: &T, _rhs: &T) -> bool { false } @@ -185,7 +185,7 @@ impl EqualityContext for TypedEquality { } #[inline] - fn leaf_values_not_equal(&mut self, _lhs: &T, _rhs: &T) -> ExecutionResult { + fn leaf_values_not_equal(&mut self, _lhs: &T, _rhs: &T) -> ExecutionResult { Ok(false) } @@ -424,7 +424,7 @@ impl EqualityContext for DebugEquality { } #[inline] - fn leaf_values_not_equal( + fn leaf_values_not_equal( &mut self, lhs: &T, rhs: &T, diff --git a/src/expressions/evaluation/control_flow_analysis.rs b/src/expressions/evaluation/control_flow_analysis.rs index e26f3782..dc805d6d 100644 --- a/src/expressions/evaluation/control_flow_analysis.rs +++ b/src/expressions/evaluation/control_flow_analysis.rs @@ -46,10 +46,16 @@ pub(in super::super) fn control_flow_visit( stack.push(*node); } ExpressionNode::MethodCall { - node, parameters, .. + receiver, invocation: Invocation { parameters, .. }, .. } => { stack.push_reversed(parameters.iter().copied()); - stack.push(*node); // This is a stack so this executes first + stack.push(*receiver); // This is a stack so this executes first + } + ExpressionNode::Invocation { + invokable, invocation: Invocation { parameters, .. }, .. + } => { + stack.push_reversed(parameters.iter().copied()); + stack.push(*invokable); // This is a stack so this executes first } ExpressionNode::Index { node, index, .. } => { stack.push_reversed([*node, *index]); @@ -120,6 +126,9 @@ impl Leaf { Leaf::ParseExpression(parse_expression) => parse_expression.control_flow_pass(context), Leaf::Discarded(_) => Ok(()), Leaf::Value(_) => Ok(()), + Leaf::ClosureExpression(inline_function) => { + inline_function.control_flow_pass(context) + }, } } } diff --git a/src/expressions/evaluation/evaluator.rs b/src/expressions/evaluation/evaluator.rs index 0348ec2d..eca06240 100644 --- a/src/expressions/evaluation/evaluator.rs +++ b/src/expressions/evaluation/evaluator.rs @@ -176,6 +176,13 @@ impl RequestedValue { } } + pub(crate) fn expect_copy_on_write(self) -> CopyOnWriteValue { + match self { + RequestedValue::CopyOnWrite(copy_on_write) => copy_on_write, + _ => panic!("expect_copy_on_write() called on non-copy-on-write RequestedValue"), + } + } + pub(super) fn expect_assignee(self) -> AnyValueAssignee { match self { RequestedValue::Assignee(assignee) => assignee, @@ -337,6 +344,14 @@ impl<'a, T: RequestedValueType> Context<'a, T> { self.request_argument_value(handler, node, ArgumentOwnership::Owned) } + pub(super) fn request_copy_on_write>( + self, + handler: H, + node: ExpressionNodeId, + ) -> NextAction { + self.request_argument_value(handler, node, ArgumentOwnership::CopyOnWrite) + } + pub(super) fn request_shared>( self, handler: H, diff --git a/src/expressions/evaluation/node_conversion.rs b/src/expressions/evaluation/node_conversion.rs index 8ceb680d..713952f3 100644 --- a/src/expressions/evaluation/node_conversion.rs +++ b/src/expressions/evaluation/node_conversion.rs @@ -81,6 +81,11 @@ impl ExpressionNode { parse_expression.evaluate_spanned(interpreter, ownership) })? } + Leaf::ClosureExpression(closure_expression) => { + context.evaluate(|interpreter, ownership| { + closure_expression.evaluate_spanned(interpreter, ownership) + })? + } } } ExpressionNode::Grouped { delim_span, inner } => { @@ -119,10 +124,14 @@ impl ExpressionNode { value, } => AssignmentBuilder::start(context, *assignee, *equals_token, *value), ExpressionNode::MethodCall { - node, + receiver, method, - parameters, - } => MethodCallBuilder::start(context, *node, method.clone(), parameters), + invocation, + } => MethodCallBuilder::start(context, *receiver, method, invocation), + ExpressionNode::Invocation { + invokable, + invocation, + } => InvocationBuilder::start(context, *invokable, invocation), }) } diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index c0debd2a..373bce19 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -548,6 +548,7 @@ pub(super) enum AnyValueFrame { Range(RangeBuilder), Assignment(AssignmentBuilder), MethodCall(MethodCallBuilder), + Invocation(InvocationBuilder), } impl AnyValueFrame { @@ -567,6 +568,7 @@ impl AnyValueFrame { AnyValueFrame::Range(frame) => frame.handle_next(context, value), AnyValueFrame::Assignment(frame) => frame.handle_next(context, value), AnyValueFrame::MethodCall(frame) => frame.handle_next(context, value), + AnyValueFrame::Invocation(frame) => frame.handle_next(context, value), } } } @@ -1249,7 +1251,8 @@ impl EvaluationFrame for AssignmentBuilder { } pub(super) struct MethodCallBuilder { - method: MethodAccess, + method: PropertyAccess, + parentheses: Parentheses, unevaluated_parameters_stack: Vec<(ExpressionNodeId, ArgumentOwnership)>, state: MethodCallPath, } @@ -1257,7 +1260,7 @@ pub(super) struct MethodCallBuilder { enum MethodCallPath { CallerPath, ArgumentsPath { - method: MethodInterface, + method: FunctionInterface, disabled_evaluated_arguments_including_caller: Vec>, }, } @@ -1265,13 +1268,15 @@ enum MethodCallPath { impl MethodCallBuilder { pub(super) fn start( context: ValueContext, - caller: ExpressionNodeId, - method: MethodAccess, - parameters: &[ExpressionNodeId], + receiver: ExpressionNodeId, + method: &PropertyAccess, + invocation: &Invocation, ) -> NextAction { let frame = Self { - method, - unevaluated_parameters_stack: parameters + method: method.clone(), + parentheses: invocation.parentheses.clone(), + unevaluated_parameters_stack: invocation + .parameters .iter() .rev() .map(|x| { @@ -1281,7 +1286,7 @@ impl MethodCallBuilder { .collect(), state: MethodCallPath::CallerPath, }; - context.request_late_bound(frame, caller) + context.request_late_bound(frame, receiver) } } @@ -1302,16 +1307,18 @@ impl EvaluationFrame for MethodCallBuilder { MethodCallPath::CallerPath => { let caller_span = span; let caller = value.expect_late_bound(); + let method_name = self.method.property.to_string(); + let method_name = method_name.as_str(); let method = caller .kind() .feature_resolver() - .resolve_method(self.method.method.to_string().as_str()); + .resolve_method(method_name); let method = match method { Some(m) => m, None => { - return self.method.method.type_err(format!( + return self.method.property.type_err(format!( "The method {} does not exist on {}", - self.method.method, + method_name, caller.articled_kind(), )) } @@ -1329,9 +1336,9 @@ impl EvaluationFrame for MethodCallBuilder { if non_caller_arguments < non_caller_min_arguments || non_caller_arguments > non_caller_max_arguments { - return self.method.method.type_err(format!( + return self.method.property.type_err(format!( "The method {} expects {} non-self argument/s, but {} were provided", - self.method.method, + method_name, if non_caller_min_arguments == non_caller_max_arguments { (non_caller_min_arguments).to_string() } else { @@ -1414,8 +1421,8 @@ impl EvaluationFrame for MethodCallBuilder { (arguments, method) } }; - let mut call_context = MethodCallContext { - output_span_range: self.method.span_range(), + let mut call_context = FunctionCallContext { + output_span_range: SpanRange::new_between(self.method.property, self.parentheses.close()), interpreter: context.interpreter(), }; let output = method.execute(arguments, &mut call_context)?; @@ -1424,3 +1431,186 @@ impl EvaluationFrame for MethodCallBuilder { }) } } + +pub(super) struct InvocationBuilder { + parentheses: Parentheses, + unevaluated_parameters_stack: Vec<(ExpressionNodeId, ArgumentOwnership)>, + state: InvocationPath, +} + +enum InvocationPath { + InvokablePath, + ArgumentsPath { + function_span: SpanRange, + interface: FunctionInterface, + disabled_evaluated_arguments: Vec>, + }, +} + +impl InvocationBuilder { + pub(super) fn start( + context: ValueContext, + invokable: ExpressionNodeId, + invocation: &Invocation, + ) -> NextAction { + let frame = Self { + parentheses: invocation.parentheses.clone(), + unevaluated_parameters_stack: invocation + .parameters + .iter() + .rev() + .map(|x| { + // This is just a placeholder - we'll fix it up shortly + (*x, ArgumentOwnership::Owned) + }) + .collect(), + state: InvocationPath::InvokablePath, + }; + context.request_copy_on_write(frame, invokable) + } +} + +impl EvaluationFrame for InvocationBuilder { + type ReturnType = ReturnsValue; + + fn into_any(self) -> AnyValueFrame { + AnyValueFrame::Invocation(self) + } + + fn handle_next( + mut self, + mut context: ValueContext, + Spanned(value, span): Spanned, + ) -> ExecutionResult { + match self.state { + InvocationPath::InvokablePath => { + let function_span = span; + let function = value.expect_copy_on_write(); + + let function = function.into_content() + .spanned(function_span) + .downcast_resolve::>("An invoked value")?; + + // I need to extract + // (A): Function interface + // (B): Already bound disabled arguments -> assumed empty + let interface = match &function.as_ref_value().definition { + FunctionDefinition::Native(interface) => interface.clone(), + FunctionDefinition::Closure(_) => todo!(), + }; + // Placeholder for already bound arguments. We can assume they're already disabled, and of the correct ownership/s. + let disabled_bound_arguments = vec![]; + + let unbound_arguments_count = self.unevaluated_parameters_stack.len(); + + let (argument_ownerships, min_arguments) = interface.argument_ownerships(); + let max_arguments = argument_ownerships.len(); + + if disabled_bound_arguments.len() > max_arguments { + return function_span.type_err(format!( + "This function expects at most {} argument/s, but {} were already bound before this invocation", + max_arguments, + disabled_bound_arguments.len(), + )); + } + + let unbound_min_arguments = min_arguments - disabled_bound_arguments.len(); + let unbound_max_arguments = max_arguments - disabled_bound_arguments.len(); + + if unbound_arguments_count < unbound_min_arguments + || unbound_arguments_count > unbound_max_arguments + { + let expected_arguments = if unbound_min_arguments == unbound_max_arguments { + if unbound_min_arguments == 1 { + "1 argument".to_string() + } else { + format!("{} arguments", unbound_min_arguments) + } + } else { + format!( + "{} to {} arguments", + (unbound_min_arguments), + (unbound_max_arguments) + ) + }; + let actual_arguments = if unbound_arguments_count == 1 { + "1 was provided".to_string() + } else { + format!("{} were provided", unbound_arguments_count) + }; + return function_span.type_err(format!( + "This function expects {}, but {}", + expected_arguments, + actual_arguments, + )); + } + + // We skip 1 to ignore the caller + let unbound_argument_ownerships: iter::Skip< + std::slice::Iter<'_, ArgumentOwnership>, + > = argument_ownerships.iter().skip(disabled_bound_arguments.len()); + for ((_, requested_ownership), ownership) in self + .unevaluated_parameters_stack + .iter_mut() + .rev() // Swap the stack back to the normal order + .zip(unbound_argument_ownerships) + { + *requested_ownership = *ownership; + } + + self.state = InvocationPath::ArgumentsPath { + function_span, + interface, + disabled_evaluated_arguments: disabled_bound_arguments, + }; + } + InvocationPath::ArgumentsPath { + ref mut disabled_evaluated_arguments, + .. + } => { + let argument = value.expect_argument_value(); + let mut argument = Spanned(argument, span); + unsafe { + // SAFETY: We enable it again before use + argument.to_mut().disable(); + } + disabled_evaluated_arguments.push(argument); + } + }; + // Now plan the next action + Ok(match self.unevaluated_parameters_stack.pop() { + Some((parameter, ownership)) => { + context.request_any_value(self, parameter, RequestedOwnership::Concrete(ownership)) + } + None => { + let (arguments, interface, function_span) = match self.state { + InvocationPath::InvokablePath => unreachable!("Already updated above"), + InvocationPath::ArgumentsPath { + interface, + disabled_evaluated_arguments: mut arguments, + function_span, + } => { + // NOTE: + // - This disable/enable flow allows us to do things like vec.push(vec.len()) + // - Read https://rust-lang.github.io/rfcs/2025-nested-method-calls.html for more details + // - We enable left-to-right for intuitive error messages: later borrows will report errors + unsafe { + for argument in &mut arguments { + // SAFETY: We disabled them above + // NOTE: enable() may fail if arguments conflict (e.g., same variable) + argument.to_mut().enable()?; + } + } + (arguments, interface, function_span) + } + }; + let mut call_context = FunctionCallContext { + output_span_range: SpanRange::new_between(function_span, self.parentheses.close()), + interpreter: context.interpreter(), + }; + let output = interface.execute(arguments, &mut call_context)?; + context.return_returned_value(output)? + } + }) + } +} diff --git a/src/expressions/expression.rs b/src/expressions/expression.rs index 3912aa90..a92d36d1 100644 --- a/src/expressions/expression.rs +++ b/src/expressions/expression.rs @@ -122,10 +122,15 @@ pub(super) enum ExpressionNode { node: ExpressionNodeId, access: PropertyAccess, }, + // This could be merged into Invocation, once Invocation is feature complete MethodCall { - node: ExpressionNodeId, - method: MethodAccess, - parameters: Vec, + receiver: ExpressionNodeId, + method: PropertyAccess, + invocation: Invocation, + }, + Invocation { + invokable: ExpressionNodeId, + invocation: Invocation, }, Index { node: ExpressionNodeId, @@ -144,6 +149,11 @@ pub(super) enum ExpressionNode { }, } +pub(super) struct Invocation { + pub(super) parentheses: Parentheses, + pub(super) parameters: Vec, +} + // We Box some of these variants to reduce the size of ExpressionNode pub(super) enum Leaf { Block(Box), @@ -159,26 +169,7 @@ pub(super) enum Leaf { ForExpression(Box), AttemptExpression(Box), ParseExpression(Box), -} - -impl HasSpanRange for Leaf { - fn span_range(&self) -> SpanRange { - match self { - Leaf::Variable(variable) => variable.span_range(), - Leaf::TypeProperty(type_property) => type_property.span_range(), - Leaf::Discarded(token) => token.span_range(), - Leaf::Block(block) => block.span_range(), - Leaf::Value(value) => value.span_range(), - Leaf::StreamLiteral(stream) => stream.span_range(), - Leaf::ParseTemplateLiteral(stream) => stream.span_range(), - Leaf::IfExpression(expression) => expression.span_range(), - Leaf::LoopExpression(expression) => expression.span_range(), - Leaf::WhileExpression(expression) => expression.span_range(), - Leaf::ForExpression(expression) => expression.span_range(), - Leaf::AttemptExpression(expression) => expression.span_range(), - Leaf::ParseExpression(expression) => expression.span_range(), - } - } + ClosureExpression(Box), } impl Leaf { @@ -196,7 +187,8 @@ impl Leaf { | Leaf::Discarded(_) | Leaf::Value(_) | Leaf::StreamLiteral(_) - | Leaf::ParseTemplateLiteral(_) => false, + | Leaf::ParseTemplateLiteral(_) + | Leaf::ClosureExpression(_) => false, } } } diff --git a/src/expressions/expression_parsing.rs b/src/expressions/expression_parsing.rs index 2fc27fdf..b9eeee6e 100644 --- a/src/expressions/expression_parsing.rs +++ b/src/expressions/expression_parsing.rs @@ -107,6 +107,8 @@ impl<'a> ExpressionParser<'a> { } if punct.as_char() == '@' { UnaryAtom::Leaf(Leaf::ParseTemplateLiteral(input.parse()?)) + } else if punct.as_char() == '|' { + UnaryAtom::Leaf(Leaf::ClosureExpression(Box::new(input.parse()?))) } else if punct.as_char() == '.' { UnaryAtom::Range(input.parse()?) } else if punct.as_char() == '-' || punct.as_char() == '!' { @@ -181,10 +183,17 @@ impl<'a> ExpressionParser<'a> { brackets: Brackets { delim_span }, })); } + SourcePeekMatch::Group(Delimiter::Parenthesis) => { + let (_, delim_span) = input.parse_and_enter_group(None)?; + return Ok(NodeExtension::Invocation(Invocation { + parentheses: Parentheses { delim_span }, + parameters: vec![], + })); + } SourcePeekMatch::Punct(punct) if punct.as_char() == ',' => { match parent_stack_frame { ExpressionStackFrame::NonEmptyArray { .. } - | ExpressionStackFrame::NonEmptyMethodCallParametersList { .. } + | ExpressionStackFrame::NonEmptyInvocationParametersList { .. } | ExpressionStackFrame::NonEmptyObject { state: ObjectStackFrameState::EntryValue { .. }, .. @@ -260,7 +269,7 @@ impl<'a> ExpressionParser<'a> { state: ObjectStackFrameState::EntryValue { .. }, .. } => input.parse_err("Expected comma, }, or operator"), - ExpressionStackFrame::NonEmptyMethodCallParametersList { .. } => { + ExpressionStackFrame::NonEmptyInvocationParametersList { .. } => { input.parse_err("Expected comma, ) or operator") } // e.g. I've just matched the true in !true or false || true, @@ -335,7 +344,7 @@ impl<'a> ExpressionParser<'a> { } NodeExtension::NonTerminalComma => { let next_item = match self.expression_stack.last_mut().unwrap() { - ExpressionStackFrame::NonEmptyMethodCallParametersList { parameters, .. } => { + ExpressionStackFrame::NonEmptyInvocationParametersList { invocation: Invocation { parameters, .. }, .. } => { parameters.push(node); Some(WorkItem::RequireUnaryAtom) } @@ -376,20 +385,40 @@ impl<'a> ExpressionParser<'a> { .add_node(ExpressionNode::Property { node, access }), }, NodeExtension::MethodCall(method) => { + let property: PropertyAccess = PropertyAccess { dot: method.dot, property: method.method }; + let invocation = Invocation { parentheses: method.parentheses, parameters: Vec::new() }; if self.streams.is_current_empty() { self.streams.exit_group(None)?; let node = self.nodes.add_node(ExpressionNode::MethodCall { - node, - method, - parameters: Vec::new(), + receiver: node, + method: property, + invocation, }); WorkItem::TryParseAndApplyExtension { node } } else { self.push_stack_frame( - ExpressionStackFrame::NonEmptyMethodCallParametersList { + ExpressionStackFrame::NonEmptyInvocationParametersList { node, - method, - parameters: Vec::new(), + method: Some(property), + invocation, + }, + ) + } + } + NodeExtension::Invocation(invocation) => { + if self.streams.is_current_empty() { + self.streams.exit_group(None)?; + let node = self.nodes.add_node(ExpressionNode::Invocation { + invokable: node, + invocation, + }); + WorkItem::TryParseAndApplyExtension { node } + } else { + self.push_stack_frame( + ExpressionStackFrame::NonEmptyInvocationParametersList { + node, + method: None, + invocation, }, ) } @@ -448,19 +477,29 @@ impl<'a> ExpressionParser<'a> { .add_node(ExpressionNode::Array { brackets, items }), } } - ExpressionStackFrame::NonEmptyMethodCallParametersList { + ExpressionStackFrame::NonEmptyInvocationParametersList { node: source, - mut parameters, method, + mut invocation, } => { assert!(matches!(extension, NodeExtension::EndOfStreamOrGroup)); - parameters.push(node); + invocation.parameters.push(node); self.streams.exit_group(None)?; - let node = self.nodes.add_node(ExpressionNode::MethodCall { - node: source, - method, - parameters, - }); + let node = match method { + Some(method) => { + self.nodes.add_node(ExpressionNode::MethodCall { + receiver: source, + method, + invocation, + }) + } + None => { + self.nodes.add_node(ExpressionNode::Invocation { + invokable: source, + invocation, + }) + } + }; WorkItem::TryParseAndApplyExtension { node } } ExpressionStackFrame::NonEmptyObject { @@ -919,13 +958,13 @@ pub(super) enum ExpressionStackFrame { complete_entries: Vec<(ObjectKey, ExpressionNodeId)>, state: ObjectStackFrameState, }, - /// A method call with a possibly incomplete list of parameters. - /// * When the method parameters list is opened, we add its inside to the parse stream stack - /// * When the method parameters list is closed, we pop it from the parse stream stack - NonEmptyMethodCallParametersList { + /// An invocation call with a possibly incomplete list of parameters. + /// * When the invocation parameters list is opened, we add its inside to the parse stream stack + /// * When the invocation parameters list is closed, we pop it from the parse stream stack + NonEmptyInvocationParametersList { node: ExpressionNodeId, - method: MethodAccess, - parameters: Vec, + method: Option, + invocation: Invocation, }, /// An incomplete unary prefix operation /// NB: unary postfix operations such as `as` casting go straight to ExtendableNode @@ -977,7 +1016,7 @@ impl ExpressionStackFrame { ExpressionStackFrame::Group { .. } => OperatorPrecendence::MIN, ExpressionStackFrame::NonEmptyArray { .. } => OperatorPrecendence::MIN, ExpressionStackFrame::NonEmptyObject { .. } => OperatorPrecendence::MIN, - ExpressionStackFrame::NonEmptyMethodCallParametersList { .. } => { + ExpressionStackFrame::NonEmptyInvocationParametersList { .. } => { OperatorPrecendence::MIN } ExpressionStackFrame::IncompleteIndex { .. } => OperatorPrecendence::MIN, @@ -1040,6 +1079,7 @@ pub(super) enum NodeExtension { Property(PropertyAccess), MethodCall(MethodAccess), Index(IndexAccess), + Invocation(Invocation), Range(syn::RangeLimits), AssignmentOperation(Token![=]), EndOfStreamOrGroup, @@ -1055,6 +1095,7 @@ impl NodeExtension { NodeExtension::Property { .. } => OperatorPrecendence::Unambiguous, NodeExtension::MethodCall { .. } => OperatorPrecendence::Unambiguous, NodeExtension::Index { .. } => OperatorPrecendence::Unambiguous, + NodeExtension::Invocation { .. } => OperatorPrecendence::Unambiguous, NodeExtension::Range(_) => OperatorPrecendence::Range, NodeExtension::EndOfStreamOrGroup => OperatorPrecendence::MIN, NodeExtension::AssignmentOperation(_) => OperatorPrecendence::AssignExtension, @@ -1071,6 +1112,7 @@ impl NodeExtension { | NodeExtension::Property { .. } | NodeExtension::MethodCall { .. } | NodeExtension::Index { .. } + | NodeExtension::Invocation { .. } | NodeExtension::Range { .. } | NodeExtension::AssignmentOperation { .. } | NodeExtension::EndOfStreamOrGroup) => { diff --git a/src/expressions/mod.rs b/src/expressions/mod.rs index e24690e4..b0659bc1 100644 --- a/src/expressions/mod.rs +++ b/src/expressions/mod.rs @@ -10,6 +10,7 @@ mod expression; mod expression_block; mod expression_label; mod expression_parsing; +mod closures; mod operations; mod patterns; mod statements; @@ -24,6 +25,7 @@ pub(crate) use evaluation::*; pub(crate) use expression::*; pub(crate) use expression_block::*; pub(crate) use expression_label::*; +pub(crate) use closures::*; pub(crate) use operations::*; pub(crate) use patterns::*; pub(crate) use statements::*; diff --git a/src/expressions/type_resolution/interface_macros.rs b/src/expressions/type_resolution/interface_macros.rs index 0786469f..a1611d8f 100644 --- a/src/expressions/type_resolution/interface_macros.rs +++ b/src/expressions/type_resolution/interface_macros.rs @@ -51,19 +51,19 @@ macro_rules! generate_binary_interface { macro_rules! generate_method_interface { (REQUIRED [] OPTIONAL [] ARGS[$method:path]) => { - MethodInterface::Arity0 { + FunctionInterface::Arity0 { method: |context| apply_fn0($method, context), argument_ownership: [], } }; (REQUIRED [$ty1:ty,] OPTIONAL [] ARGS[$method:path]) => { - MethodInterface::Arity1 { + FunctionInterface::Arity1 { method: |context, a| apply_fn1($method, a, context), argument_ownership: [<$ty1 as IsArgument>::OWNERSHIP], } }; (REQUIRED [$ty1:ty, $ty2:ty,] OPTIONAL [] ARGS[$method:path]) => { - MethodInterface::Arity2 { + FunctionInterface::Arity2 { method: |context, a, b| apply_fn2($method, a, b, context), argument_ownership: [ <$ty1 as IsArgument>::OWNERSHIP, @@ -72,7 +72,7 @@ macro_rules! generate_method_interface { } }; (REQUIRED [$ty1:ty, $ty2:ty, $ty3:ty,] OPTIONAL [] ARGS[$method:path]) => { - MethodInterface::Arity3 { + FunctionInterface::Arity3 { method: |context, a, b, c| apply_fn3($method, a, b, c, context), argument_ownership: [ <$ty1 as IsArgument>::OWNERSHIP, @@ -82,7 +82,7 @@ macro_rules! generate_method_interface { } }; (REQUIRED [$ty1:ty,] OPTIONAL [$ty2:ty,] ARGS[$method:path]) => { - MethodInterface::Arity1PlusOptional1 { + FunctionInterface::Arity1PlusOptional1 { method: |context, a, b| apply_fn1_optional1($method, a, b, context), argument_ownership: [ <$ty1 as IsArgument>::OWNERSHIP, @@ -91,7 +91,7 @@ macro_rules! generate_method_interface { } }; (REQUIRED [$ty1:ty, $ty2:ty,] OPTIONAL [$ty3:ty,] ARGS[$method:path]) => { - MethodInterface::Arity2PlusOptional1 { + FunctionInterface::Arity2PlusOptional1 { method: |context, a, b, c| apply_fn2_optional1($method, a, b, c, context), argument_ownership: [ <$ty1 as IsArgument>::OWNERSHIP, @@ -101,7 +101,7 @@ macro_rules! generate_method_interface { } }; (REQUIRED [$ty1:ty, $ty2:ty, $ty3:ty,] OPTIONAL [$ty4:ty,] ARGS[$method:path]) => { - MethodInterface::Arity3PlusOptional1 { + FunctionInterface::Arity3PlusOptional1 { method: |context, a, b, c, d| apply_fn3_optional1($method, a, b, c, d, context), argument_ownership: [ <$ty1 as IsArgument>::OWNERSHIP, @@ -112,7 +112,7 @@ macro_rules! generate_method_interface { } }; (REQUIRED $req:tt OPTIONAL $opt:tt ARGS[$($arg:tt)*]) => { - compile_error!(stringify!("This method arity is currently unsupported - add support in `MethodInterface` and `generate_method_interface`: ", $($arg)*)); + compile_error!(stringify!("This method arity is currently unsupported - add support in `FunctionInterface` and `generate_method_interface`: ", $($arg)*)); }; } @@ -155,8 +155,8 @@ macro_rules! parse_arg_types { #[allow(unused)] pub(crate) fn apply_fn0( - f: fn(&mut MethodCallContext) -> R, - context: &mut MethodCallContext, + f: fn(&mut FunctionCallContext) -> R, + context: &mut FunctionCallContext, ) -> ExecutionResult where R: IsReturnable, @@ -166,9 +166,9 @@ where } pub(crate) fn apply_fn1( - f: fn(&mut MethodCallContext, A) -> R, + f: fn(&mut FunctionCallContext, A) -> R, a: Spanned, - context: &mut MethodCallContext, + context: &mut FunctionCallContext, ) -> ExecutionResult where A: IsArgument, @@ -179,10 +179,10 @@ where #[allow(unused)] pub(crate) fn apply_fn1_optional1( - f: fn(&mut MethodCallContext, A, Option) -> C, + f: fn(&mut FunctionCallContext, A, Option) -> C, a: Spanned, b: Option>, - context: &mut MethodCallContext, + context: &mut FunctionCallContext, ) -> ExecutionResult where A: IsArgument, @@ -199,10 +199,10 @@ where } pub(crate) fn apply_fn2( - f: fn(&mut MethodCallContext, A, B) -> C, + f: fn(&mut FunctionCallContext, A, B) -> C, a: Spanned, b: Spanned, - context: &mut MethodCallContext, + context: &mut FunctionCallContext, ) -> ExecutionResult where A: IsArgument, @@ -213,11 +213,11 @@ where } pub(crate) fn apply_fn2_optional1( - f: fn(&mut MethodCallContext, A, B, Option) -> D, + f: fn(&mut FunctionCallContext, A, B, Option) -> D, a: Spanned, b: Spanned, c: Option>, - context: &mut MethodCallContext, + context: &mut FunctionCallContext, ) -> ExecutionResult where A: IsArgument, @@ -236,11 +236,11 @@ where #[allow(unused)] pub(crate) fn apply_fn3( - f: fn(&mut MethodCallContext, A, B, C) -> R, + f: fn(&mut FunctionCallContext, A, B, C) -> R, a: Spanned, b: Spanned, c: Spanned, - context: &mut MethodCallContext, + context: &mut FunctionCallContext, ) -> ExecutionResult where A: IsArgument, @@ -258,12 +258,12 @@ where } pub(crate) fn apply_fn3_optional1( - f: fn(&mut MethodCallContext, A, B, C, Option) -> R, + f: fn(&mut FunctionCallContext, A, B, C, Option) -> R, a: Spanned, b: Spanned, c: Spanned, d: Option>, - context: &mut MethodCallContext, + context: &mut FunctionCallContext, ) -> ExecutionResult where A: IsArgument, @@ -402,12 +402,12 @@ pub(crate) fn apply_index_owned>( f(ctx, source, index) } -pub(crate) struct MethodCallContext<'a> { +pub(crate) struct FunctionCallContext<'a> { pub interpreter: &'a mut Interpreter, pub output_span_range: SpanRange, } -impl<'a> HasSpanRange for MethodCallContext<'a> { +impl<'a> HasSpanRange for FunctionCallContext<'a> { fn span_range(&self) -> SpanRange { self.output_span_range } @@ -438,21 +438,26 @@ macro_rules! define_type_features { ( impl $type_def:ident, $mod_vis:vis mod $mod_name:ident { - $mod_methods_vis:vis mod methods { + $(functions { + $( + $([$function_context:ident])? fn $function_name:ident($($function_args:tt)*) $(-> $function_output_ty:ty)? $([ignore_type_assertion $function_ignore_type_assertion:tt])? $function_body:block + )* + })? + $(methods { $( $([$method_context:ident])? fn $method_name:ident($($method_args:tt)*) $(-> $method_output_ty:ty)? $([ignore_type_assertion $method_ignore_type_assertion:tt])? $method_body:block )* - } - $mod_unary_operations_vis:vis mod unary_operations { + })? + $(unary_operations { $( $([$unary_context:ident])? fn $unary_name:ident($($unary_args:tt)*) $(-> $unary_output_ty:ty)? $([ignore_type_assertion $unary_ignore_type_assertion:tt])? $unary_body:block )* - } - $mod_binary_operations_vis:vis mod binary_operations { + })? + $(binary_operations { $( $([$binary_context:ident])? fn $binary_name:ident($($binary_args:tt)*) $(-> $binary_output_ty:ty)? $([ignore_type_assertion $binary_ignore_type_assertion:tt])? $binary_body:block )* - } + })? $(property_access($property_source_ty:ty) { $([$property_shared_context:ident])? fn shared($($property_shared_args:tt)*) $property_shared_body:block $([$property_mutable_context:ident])? fn mutable($($property_mutable_args:tt)*) $property_mutable_body:block @@ -463,9 +468,9 @@ macro_rules! define_type_features { $([$index_mutable_context:ident])? fn mutable($($index_mutable_args:tt)*) $index_mutable_body:block $([$index_owned_context:ident])? fn owned($($index_owned_args:tt)*) $index_owned_body:block })? - interface_items { + $(interface_items { $($items:item)* - } + })? } ) => { $mod_vis mod $mod_name { @@ -476,93 +481,120 @@ macro_rules! define_type_features { fn asserts() { fn assert_first_argument>() {} fn assert_output_type() {} - $( + $($( assert_first_argument::(); if_exists! { {$($method_ignore_type_assertion)?} {} {$(assert_output_type::<$method_output_ty>();)?} } - )* - $( + )*)? + $($( assert_first_argument::(); if_exists! { {$($unary_ignore_type_assertion)?} {} {$(assert_output_type::<$unary_output_ty>();)?} } - )* - $( + )*)? + $($( assert_first_argument::(); if_exists! { {$($binary_ignore_type_assertion)?} {} {$(assert_output_type::<$binary_output_ty>();)?} } - )* + )*)? // Note: property_access and index_access source types are verified // at compile time through the apply_* wrapper functions } - $mod_methods_vis mod methods { - #[allow(unused)] - use super::*; - $( - pub(crate) fn $method_name(if_empty!([$($method_context)?][_context]): &mut MethodCallContext, $($method_args)*) $(-> $method_output_ty)? { - $method_body - } - )* - } + $( + pub(crate) mod functions { + #[allow(unused)] + use super::*; + $( + pub(crate) fn $function_name(if_empty!([$($function_context)?][_context]): &mut FunctionCallContext, $($function_args)*) $(-> $function_output_ty)? { + $function_body + } + )* + } - $mod_methods_vis mod method_definitions { - #[allow(unused)] - use super::*; - $( - $mod_methods_vis fn $method_name() -> MethodInterface { - parse_arg_types!([CALLBACK: generate_method_interface![methods::$method_name]] $($method_args)*) - } - )* - } + pub(crate) mod function_definitions { + #[allow(unused)] + use super::*; + $( + pub(crate) fn $function_name() -> FunctionInterface { + parse_arg_types!([CALLBACK: generate_method_interface![functions::$function_name]] $($function_args)*) + } + )* + } + )? - $mod_unary_operations_vis mod unary_operations { - #[allow(unused)] - use super::*; - $( - $mod_unary_operations_vis fn $unary_name(if_empty!([$($unary_context)?][_context]): UnaryOperationCallContext, $($unary_args)*) $(-> $unary_output_ty)? { - $unary_body - } - )* - } + $( + pub(crate) mod methods { + #[allow(unused)] + use super::*; + $( + pub(crate) fn $method_name(if_empty!([$($method_context)?][_context]): &mut FunctionCallContext, $($method_args)*) $(-> $method_output_ty)? { + $method_body + } + )* + } + + pub(crate) mod method_definitions { + #[allow(unused)] + use super::*; + $( + pub(crate) fn $method_name() -> FunctionInterface { + parse_arg_types!([CALLBACK: generate_method_interface![methods::$method_name]] $($method_args)*) + } + )* + } + )? - $mod_unary_operations_vis mod unary_definitions { - #[allow(unused)] - use super::*; - $( - $mod_unary_operations_vis fn $unary_name() -> UnaryOperationInterface { - parse_arg_types!([CALLBACK: generate_unary_interface![unary_operations::$unary_name]] $($unary_args)*) - } - )* - } + $( + pub(crate) mod unary_operations { + #[allow(unused)] + use super::*; + $( + pub(crate) fn $unary_name(if_empty!([$($unary_context)?][_context]): UnaryOperationCallContext, $($unary_args)*) $(-> $unary_output_ty)? { + $unary_body + } + )* + } - $mod_binary_operations_vis mod binary_operations { - #[allow(unused)] - use super::*; - $( - $mod_binary_operations_vis fn $binary_name(if_empty!([$($binary_context)?][_context]): BinaryOperationCallContext, $($binary_args)*) $(-> $binary_output_ty)? { - $binary_body - } - )* - } + pub(crate) mod unary_definitions { + #[allow(unused)] + use super::*; + $( + pub(crate) fn $unary_name() -> UnaryOperationInterface { + parse_arg_types!([CALLBACK: generate_unary_interface![unary_operations::$unary_name]] $($unary_args)*) + } + )* + } + )? - $mod_binary_operations_vis mod binary_definitions { - #[allow(unused)] - use super::*; - $( - $mod_binary_operations_vis fn $binary_name() -> BinaryOperationInterface { - parse_arg_types!([CALLBACK: generate_binary_interface![binary_operations::$binary_name]] $($binary_args)*) - } - )* - } + $( + pub(crate) mod binary_operations { + #[allow(unused)] + use super::*; + $( + pub(crate) fn $binary_name(if_empty!([$($binary_context)?][_context]): BinaryOperationCallContext, $($binary_args)*) $(-> $binary_output_ty)? { + $binary_body + } + )* + } + pub(crate) mod binary_definitions { + #[allow(unused)] + use super::*; + $( + pub(crate) fn $binary_name() -> BinaryOperationInterface { + parse_arg_types!([CALLBACK: generate_binary_interface![binary_operations::$binary_name]] $($binary_args)*) + } + )* + } + )? $( pub(crate) mod property_access { @@ -608,22 +640,36 @@ macro_rules! define_type_features { )? impl TypeData for $type_def { - #[allow(unreachable_code)] - fn resolve_own_method(method_name: &str) -> Option { - Some(match method_name { - $( - stringify!($method_name) => method_definitions::$method_name(), - )* - _ => return None, - }) - } + $( + #[allow(unreachable_code)] + fn resolve_own_method(method_name: &str) -> Option { + Some(match method_name { + $( + stringify!($method_name) => method_definitions::$method_name(), + )* + _ => return None, + }) + } + )? + + $( + #[allow(unreachable_code)] + fn resolve_type_function(function_name: &str) -> Option { + Some(match function_name { + $( + stringify!($function_name) => function_definitions::$function_name(), + )* + _ => return None, + }) + } + )? define_type_features!(@property_access_impl $($property_source_ty)?); define_type_features!(@index_access_impl $($index_source_ty)?); // Pass through resolve_own_unary_operation and resolve_own_binary_operation // until there's a better way to define them - $($items)* + $($($items)*)? } } }; diff --git a/src/expressions/type_resolution/type_data.rs b/src/expressions/type_resolution/type_data.rs index c7877739..e392ec21 100644 --- a/src/expressions/type_resolution/type_data.rs +++ b/src/expressions/type_resolution/type_data.rs @@ -2,8 +2,10 @@ use super::*; pub(crate) trait TypeFeatureResolver { - /// Resolves a unary operation as a method interface for this type. - fn resolve_method(&self, method_name: &str) -> Option; + /// Resolves a method with the given name defined on this type. + /// A method is like a function, but guaranteed to have a first argument + /// (the receiver) which is assignable from the type. + fn resolve_method(&self, method_name: &str) -> Option; /// Resolves a unary operation as a method interface for this type. fn resolve_unary_operation( @@ -17,6 +19,9 @@ pub(crate) trait TypeFeatureResolver { operation: &BinaryOperation, ) -> Option; + /// Resolves a function with the given name defined on this type. + fn resolve_type_function(&self, function_name: &str) -> Option; + /// Resolves a property of this type. fn resolve_type_property(&self, _property_name: &str) -> Option; @@ -36,7 +41,7 @@ pub(crate) trait TypeFeatureResolver { pub(crate) trait TypeData { /// Returns None if the method is not supported on this type itself. /// The method may still be supported on a type further up the resolution chain. - fn resolve_own_method(_method_name: &str) -> Option { + fn resolve_own_method(_method_name: &str) -> Option { None } @@ -61,6 +66,11 @@ pub(crate) trait TypeData { None } + /// Returns None if the function is not supported on this type itself. + fn resolve_type_function(_function_name: &str) -> Option { + None + } + /// Returns the property access interface for this type, if supported. fn resolve_own_property_access() -> Option { None @@ -73,20 +83,23 @@ pub(crate) trait TypeData { } #[allow(unused)] -pub(crate) enum MethodInterface { +// It's good enough for our needs for now +#[allow(unpredictable_function_pointer_comparisons)] +#[derive(Clone, PartialEq, Eq)] +pub(crate) enum FunctionInterface { Arity0 { - method: fn(&mut MethodCallContext) -> ExecutionResult, + method: fn(&mut FunctionCallContext) -> ExecutionResult, argument_ownership: [ArgumentOwnership; 0], }, Arity1 { method: - fn(&mut MethodCallContext, Spanned) -> ExecutionResult, + fn(&mut FunctionCallContext, Spanned) -> ExecutionResult, argument_ownership: [ArgumentOwnership; 1], }, /// 1 argument, 1 optional argument Arity1PlusOptional1 { method: fn( - &mut MethodCallContext, + &mut FunctionCallContext, Spanned, Option>, ) -> ExecutionResult, @@ -94,7 +107,7 @@ pub(crate) enum MethodInterface { }, Arity2 { method: fn( - &mut MethodCallContext, + &mut FunctionCallContext, Spanned, Spanned, ) -> ExecutionResult, @@ -102,7 +115,7 @@ pub(crate) enum MethodInterface { }, Arity2PlusOptional1 { method: fn( - &mut MethodCallContext, + &mut FunctionCallContext, Spanned, Spanned, Option>, @@ -111,7 +124,7 @@ pub(crate) enum MethodInterface { }, Arity3 { method: fn( - &mut MethodCallContext, + &mut FunctionCallContext, Spanned, Spanned, Spanned, @@ -120,7 +133,7 @@ pub(crate) enum MethodInterface { }, Arity3PlusOptional1 { method: fn( - &mut MethodCallContext, + &mut FunctionCallContext, Spanned, Spanned, Spanned, @@ -130,33 +143,33 @@ pub(crate) enum MethodInterface { }, ArityAny { method: fn( - &mut MethodCallContext, + &mut FunctionCallContext, Vec>, ) -> ExecutionResult, argument_ownership: Vec, }, } -impl MethodInterface { +impl FunctionInterface { pub(crate) fn execute( &self, arguments: Vec>, - context: &mut MethodCallContext, + context: &mut FunctionCallContext, ) -> ExecutionResult> { let output_value = match self { - MethodInterface::Arity0 { method, .. } => { + FunctionInterface::Arity0 { method, .. } => { if !arguments.is_empty() { return context.output_span_range.type_err("Expected 0 arguments"); } method(context) } - MethodInterface::Arity1 { method, .. } => { + FunctionInterface::Arity1 { method, .. } => { match <[Spanned; 1]>::try_from(arguments) { Ok([a]) => method(context, a), Err(_) => context.output_span_range.type_err("Expected 1 argument"), } } - MethodInterface::Arity1PlusOptional1 { method, .. } => match arguments.len() { + FunctionInterface::Arity1PlusOptional1 { method, .. } => match arguments.len() { 1 => { let [a] = <[Spanned; 1]>::try_from(arguments) .ok() @@ -173,13 +186,13 @@ impl MethodInterface { .output_span_range .type_err("Expected 1 or 2 arguments"), }, - MethodInterface::Arity2 { method, .. } => { + FunctionInterface::Arity2 { method, .. } => { match <[Spanned; 2]>::try_from(arguments) { Ok([a, b]) => method(context, a, b), Err(_) => context.output_span_range.type_err("Expected 2 arguments"), } } - MethodInterface::Arity2PlusOptional1 { method, .. } => match arguments.len() { + FunctionInterface::Arity2PlusOptional1 { method, .. } => match arguments.len() { 2 => { let [a, b] = <[Spanned; 2]>::try_from(arguments) .ok() @@ -196,13 +209,13 @@ impl MethodInterface { .output_span_range .type_err("Expected 2 or 3 arguments"), }, - MethodInterface::Arity3 { method, .. } => { + FunctionInterface::Arity3 { method, .. } => { match <[Spanned; 3]>::try_from(arguments) { Ok([a, b, c]) => method(context, a, b, c), Err(_) => context.output_span_range.type_err("Expected 3 arguments"), } } - MethodInterface::Arity3PlusOptional1 { method, .. } => match arguments.len() { + FunctionInterface::Arity3PlusOptional1 { method, .. } => match arguments.len() { 3 => { let [a, b, c] = <[Spanned; 3]>::try_from(arguments) .ok() @@ -219,7 +232,7 @@ impl MethodInterface { .output_span_range .type_err("Expected 3 or 4 arguments"), }, - MethodInterface::ArityAny { method, .. } => method(context, arguments), + FunctionInterface::ArityAny { method, .. } => method(context, arguments), }; output_value.map(|v| v.spanned(context.output_span_range)) } @@ -227,28 +240,28 @@ impl MethodInterface { /// Returns (argument_ownerships, required_argument_count) pub(crate) fn argument_ownerships(&self) -> (&[ArgumentOwnership], usize) { match self { - MethodInterface::Arity0 { + FunctionInterface::Arity0 { argument_ownership, .. } => (argument_ownership, 0), - MethodInterface::Arity1 { + FunctionInterface::Arity1 { argument_ownership, .. } => (argument_ownership, 1), - MethodInterface::Arity1PlusOptional1 { + FunctionInterface::Arity1PlusOptional1 { argument_ownership, .. } => (argument_ownership, 1), - MethodInterface::Arity2 { + FunctionInterface::Arity2 { argument_ownership, .. } => (argument_ownership, 2), - MethodInterface::Arity2PlusOptional1 { + FunctionInterface::Arity2PlusOptional1 { argument_ownership, .. } => (argument_ownership, 2), - MethodInterface::Arity3 { + FunctionInterface::Arity3 { argument_ownership, .. } => (argument_ownership, 3), - MethodInterface::Arity3PlusOptional1 { + FunctionInterface::Arity3PlusOptional1 { argument_ownership, .. } => (argument_ownership, 3), - MethodInterface::ArityAny { + FunctionInterface::ArityAny { argument_ownership, .. } => (argument_ownership, 0), } diff --git a/src/expressions/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index 7c27e743..a3b5805c 100644 --- a/src/expressions/type_resolution/type_kinds.rs +++ b/src/expressions/type_resolution/type_kinds.rs @@ -33,6 +33,8 @@ impl AnyValueLeafKind { // A parser is a handle, so can be cloned transparently. // It may fail to be able to be used to parse if the underlying stream is out of scope of course. AnyValueLeafKind::Parser(_) => true, + AnyValueLeafKind::Function(_) => true, + AnyValueLeafKind::PreinterpretApi(_) => true, } } } @@ -238,18 +240,30 @@ impl TypeProperty { ) -> ExecutionResult> { let resolver = self.source_type.kind.feature_resolver(); // TODO[performance] - lazily initialize properties as Shared - let resolved_property = resolver.resolve_type_property(&self.property.to_string()); - match resolved_property { - Some(value) => ownership.map_from_shared(Spanned( + 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()), self.span_range(), - )), - None => self.type_err(format!( - "Type '{}' has no property named '{}'", - self.source_type.kind.source_name(), - self.property, - )), + )); } + if let Some(method) = resolver.resolve_method(property_name) { + return ownership.map_from_shared(Spanned( + SharedValue::new_from_owned(method.into_any_value()), + self.span_range(), + )); + } + if let Some(function) = resolver.resolve_type_function(property_name) { + return ownership.map_from_shared(Spanned( + SharedValue::new_from_owned(function.into_any_value()), + self.span_range(), + )); + } + return self.type_err(format!( + "Type '{}' has no property, function or method named '{}'", + self.source_type.kind.source_name(), + self.property, + )); } } diff --git a/src/expressions/values/any_value.rs b/src/expressions/values/any_value.rs index 3c1a6f96..10dfa71f 100644 --- a/src/expressions/values/any_value.rs +++ b/src/expressions/values/any_value.rs @@ -33,6 +33,8 @@ define_parent_type! { Range => RangeType, Iterator => IteratorType, Parser => ParserType, + Function => FunctionType, + PreinterpretApi => PreinterpretApiType, }, type_name: "value", articled_display_name: "any value", @@ -41,7 +43,7 @@ define_parent_type! { define_type_features! { impl AnyType, pub(crate) mod value_interface { - pub(crate) mod methods { + methods { fn clone(this: CopyOnWriteValue) -> AnyValue { this.clone_to_owned_infallible() } @@ -158,7 +160,7 @@ define_type_features! { stream_interface::methods::to_literal(context, spanned) } } - pub(crate) mod unary_operations { + unary_operations { fn cast_to_string(Spanned(input, span_range): Spanned) -> ExecutionResult { input.as_ref_value().concat_recursive(&ConcatBehaviour::standard(span_range)) } @@ -167,7 +169,7 @@ define_type_features! { input.into_stream() } } - pub(crate) mod binary_operations { + binary_operations { fn eq(lhs: AnyValueAnyRef, rhs: AnyValueAnyRef) -> bool { AnyValue::values_equal(lhs.as_ref_value(), rhs.as_ref_value()) } @@ -303,6 +305,11 @@ impl<'a> ValuesEqual for AnyValueRef<'a> { (AnyValueContent::Parser(_), _) => ctx.kind_mismatch(self, other), (AnyValueContent::Iterator(l), AnyValueContent::Iterator(r)) => l.test_equality(r, ctx), (AnyValueContent::Iterator(_), _) => ctx.kind_mismatch(self, other), + (AnyValueContent::Function(l), AnyValueContent::Function(r)) => { + l.test_equality(r, ctx) + } + (AnyValueContent::Function(_), _) => ctx.kind_mismatch(self, other), + (AnyValueContent::PreinterpretApi(not_a_value), _) => match **not_a_value {}, } } } @@ -404,6 +411,10 @@ impl<'a> AnyValueRef<'a> { AnyValueContent::Parser(_) => { return output.type_err("Parsers cannot be output to a stream"); } + AnyValueContent::Function(_) => { + return output.type_err("Functions cannot be output to a stream"); + } + AnyValueContent::PreinterpretApi(not_a_value) => match *not_a_value {}, }; Ok(()) } @@ -445,6 +456,11 @@ impl<'a> AnyValueRef<'a> { .error_span_range .type_err("Parsers cannot be output to a string"); } + AnyValueContent::Function(_) => { + return behaviour + .error_span_range + .type_err("Functions cannot be output to a string"); + } AnyValueContent::Integer(_) | AnyValueContent::Float(_) | AnyValueContent::Char(_) @@ -457,6 +473,7 @@ impl<'a> AnyValueRef<'a> { .expect("Non-composite values should all be able to be outputted to a stream"); stream.concat_content_into(output, behaviour); } + AnyValueContent::PreinterpretApi(not_a_value) => match *not_a_value {} } Ok(()) } diff --git a/src/expressions/values/array.rs b/src/expressions/values/array.rs index eaa02b03..4b8a0227 100644 --- a/src/expressions/values/array.rs +++ b/src/expressions/values/array.rs @@ -194,7 +194,7 @@ impl_resolvable_argument_for! { define_type_features! { impl ArrayType, pub(crate) mod array_interface { - pub(crate) mod methods { + methods { fn push(mut this: Mutable, item: AnyValue) -> ExecutionResult<()> { this.items.push(item); Ok(()) @@ -205,7 +205,7 @@ define_type_features! { StreamOutput::new(move |stream| this.output_items_to(&mut ToStreamContext::new(stream, error_span_range), Grouping::Grouped)) } } - pub(crate) mod unary_operations { + unary_operations { [context] fn cast_singleton_to_value(Spanned(mut this, span): Spanned) -> ExecutionResult { let length = this.items.len(); if length == 1 { @@ -218,7 +218,7 @@ define_type_features! { } } } - pub(crate) mod binary_operations { + binary_operations { fn add(mut lhs: ArrayValue, rhs: ArrayValue) -> ArrayValue { lhs.items.extend(rhs.items); lhs diff --git a/src/expressions/values/boolean.rs b/src/expressions/values/boolean.rs index adce8579..47084730 100644 --- a/src/expressions/values/boolean.rs +++ b/src/expressions/values/boolean.rs @@ -24,9 +24,7 @@ impl ValuesEqual for bool { define_type_features! { impl BoolType, pub(crate) mod boolean_interface { - pub(crate) mod methods { - } - pub(crate) mod unary_operations { + unary_operations { fn not(this: bool) -> bool { !this } @@ -91,7 +89,7 @@ define_type_features! { input.to_string() } } - pub(crate) mod binary_operations { + binary_operations { fn and(lhs: bool, rhs: bool) -> bool { lhs && rhs } diff --git a/src/expressions/values/character.rs b/src/expressions/values/character.rs index af15e2e8..be1ec4cd 100644 --- a/src/expressions/values/character.rs +++ b/src/expressions/values/character.rs @@ -22,9 +22,7 @@ impl ValuesEqual for char { define_type_features! { impl CharType, pub(crate) mod char_interface { - pub(crate) mod methods { - } - pub(crate) mod unary_operations { + unary_operations { fn cast_to_untyped_integer(input: char) -> UntypedInteger { UntypedInteger::from_fallback(input as FallbackInteger) } @@ -85,7 +83,7 @@ define_type_features! { input.to_string() } } - pub(crate) mod binary_operations { + binary_operations { fn eq(lhs: char, rhs: char) -> bool { lhs == rhs } diff --git a/src/expressions/values/float.rs b/src/expressions/values/float.rs index 3f80c925..251a2a60 100644 --- a/src/expressions/values/float.rs +++ b/src/expressions/values/float.rs @@ -170,7 +170,7 @@ impl<'a> ValuesEqual for FloatValueRef<'a> { define_type_features! { impl FloatType, pub(crate) mod float_interface { - pub(crate) mod methods { + methods { fn is_nan(this: FloatValue) -> bool { match this { FloatContent::Untyped(x) => x.into_fallback().is_nan(), @@ -211,9 +211,7 @@ define_type_features! { } } } - pub(crate) mod unary_operations { - } - pub(crate) mod binary_operations { + binary_operations { fn add(left: FloatValue, right: Spanned) -> ExecutionResult { match left.resolve_untyped_to_match(right.as_ref_value()) { FloatContent::Untyped(left) => left.paired_operation(right, |a, b| a + b), diff --git a/src/expressions/values/float_subtypes.rs b/src/expressions/values/float_subtypes.rs index c3b21c33..58167b95 100644 --- a/src/expressions/values/float_subtypes.rs +++ b/src/expressions/values/float_subtypes.rs @@ -7,9 +7,7 @@ macro_rules! impl_float_operations { define_type_features! { impl $type_data, pub(crate) mod $mod_name { - pub(crate) mod methods { - } - pub(crate) mod unary_operations { + unary_operations { fn neg(input: $float_type) -> $float_type { -input } @@ -82,8 +80,6 @@ macro_rules! impl_float_operations { input.to_string() } } - pub(crate) mod binary_operations { - } interface_items { fn resolve_own_unary_operation(operation: &UnaryOperation) -> Option { Some(match operation { diff --git a/src/expressions/values/float_untyped.rs b/src/expressions/values/float_untyped.rs index 360feb4f..6ee21fdd 100644 --- a/src/expressions/values/float_untyped.rs +++ b/src/expressions/values/float_untyped.rs @@ -73,9 +73,7 @@ impl UntypedFloat { define_type_features! { impl UntypedFloatType, pub(crate) mod untyped_float_interface { - pub(crate) mod methods { - } - pub(crate) mod unary_operations { + unary_operations { fn neg(input: UntypedFloatFallback) -> UntypedFloat { UntypedFloat::from_fallback(-input.0) } @@ -148,8 +146,6 @@ define_type_features! { input.0.to_string() } } - pub(crate) mod binary_operations { - } interface_items { fn resolve_own_unary_operation(operation: &UnaryOperation) -> Option { Some(match operation { diff --git a/src/expressions/values/function.rs b/src/expressions/values/function.rs new file mode 100644 index 00000000..55963810 --- /dev/null +++ b/src/expressions/values/function.rs @@ -0,0 +1,63 @@ +use super::*; + +define_leaf_type! { + pub(crate) FunctionType => AnyType(AnyValueContent::Function), + content: FunctionValue, + kind: pub(crate) FunctionValueKind, + type_name: "fn", + articled_display_name: "a function", + dyn_impls: {}, +} + +#[derive(Clone)] +pub(crate) struct FunctionValue { + pub(crate) definition: FunctionDefinition, +} + +#[derive(Clone, PartialEq, Eq)] +pub(crate) enum FunctionDefinition { + Native(FunctionInterface), + // TODO[closures]: Add closed_variables: Vec to Closure + Closure(ClosureExpression), +} + +impl ValuesEqual for FunctionValue { + fn test_equality(&self, other: &Self, ctx: &mut C) -> C::Result { + if self.definition == other.definition { + return ctx.values_equal(); + } else { + return ctx.leaf_values_not_equal("left_anon_function", "right_anon_function"); + } + } +} + +impl IsValueContent for FunctionInterface { + type Type = FunctionType; + type Form = BeOwned; +} + +impl IntoValueContent<'static> for FunctionInterface { + fn into_content(self) -> Content<'static, Self::Type, Self::Form> { + FunctionValue { + definition: FunctionDefinition::Native(self), + } + } +} + +impl IsValueContent for ClosureExpression { + type Type = FunctionType; + type Form = BeOwned; +} + +impl IntoValueContent<'static> for ClosureExpression { + fn into_content(self) -> Content<'static, Self::Type, Self::Form> { + FunctionValue { + definition: FunctionDefinition::Closure(self), + } + } +} + +define_type_features! { + impl FunctionType, + pub(crate) mod function_value_interface {} +} \ No newline at end of file diff --git a/src/expressions/values/integer.rs b/src/expressions/values/integer.rs index 532f267b..ea7dba32 100644 --- a/src/expressions/values/integer.rs +++ b/src/expressions/values/integer.rs @@ -206,11 +206,7 @@ impl<'a> ValuesEqual for IntegerValueRef<'a> { define_type_features! { impl IntegerType, pub(crate) mod integer_interface { - pub(crate) mod methods { - } - pub(crate) mod unary_operations { - } - pub(crate) mod binary_operations { + binary_operations { [context] fn add(left: Spanned, right: Spanned) -> ExecutionResult { match IntegerValue::resolve_untyped_to_match(left, &right)? { IntegerValue::Untyped(left) => left.paired_operation(right, context, FallbackInteger::checked_add), diff --git a/src/expressions/values/integer_subtypes.rs b/src/expressions/values/integer_subtypes.rs index ef053838..52bf405f 100644 --- a/src/expressions/values/integer_subtypes.rs +++ b/src/expressions/values/integer_subtypes.rs @@ -8,9 +8,7 @@ macro_rules! impl_int_operations { define_type_features! { impl $integer_type_data, pub(crate) mod $mod_name { - pub(crate) mod methods { - } - pub(crate) mod unary_operations { + unary_operations { $( fn neg(Spanned(value, span): Spanned<$integer_type>) -> ExecutionResult<$integer_type> { ignore_all!($signed); // Include only for signed types @@ -96,8 +94,6 @@ macro_rules! impl_int_operations { input.to_string() } } - pub(crate) mod binary_operations { - } interface_items { fn resolve_own_unary_operation(operation: &UnaryOperation) -> Option { Some(match operation { diff --git a/src/expressions/values/integer_untyped.rs b/src/expressions/values/integer_untyped.rs index 0a38b75a..29168330 100644 --- a/src/expressions/values/integer_untyped.rs +++ b/src/expressions/values/integer_untyped.rs @@ -124,9 +124,7 @@ impl Spanned { define_type_features! { impl UntypedIntegerType, pub(crate) mod untyped_integer_interface { - pub(crate) mod methods { - } - pub(crate) mod unary_operations { + unary_operations { fn neg(Spanned(value, span): Spanned) -> ExecutionResult { let input = value.into_fallback(); match input.checked_neg() { @@ -203,8 +201,6 @@ define_type_features! { input.0.to_string() } } - pub(crate) mod binary_operations { - } interface_items { fn resolve_own_unary_operation(operation: &UnaryOperation) -> Option { Some(match operation { diff --git a/src/expressions/values/iterable.rs b/src/expressions/values/iterable.rs index 5a87f161..6f00edba 100644 --- a/src/expressions/values/iterable.rs +++ b/src/expressions/values/iterable.rs @@ -19,7 +19,7 @@ pub(crate) type IterableAnyRef<'a> = AnyRef<'a, dyn IsIterable>; define_type_features! { impl IterableType, pub(crate) mod iterable_interface { - pub(crate) mod methods { + methods { fn into_iter(this: IterableValue) -> ExecutionResult { this.into_iterator() } @@ -63,12 +63,11 @@ define_type_features! { Ok(vec) } } - pub(crate) mod unary_operations { + unary_operations { fn cast_into_iterator(this: IterableValue) -> ExecutionResult { this.into_iterator() } } - pub(crate) mod binary_operations {} interface_items { fn resolve_own_unary_operation(operation: &UnaryOperation) -> Option { Some(match operation { diff --git a/src/expressions/values/iterator.rs b/src/expressions/values/iterator.rs index b9031076..885bf141 100644 --- a/src/expressions/values/iterator.rs +++ b/src/expressions/values/iterator.rs @@ -304,7 +304,7 @@ impl Iterator for Mutable { define_type_features! { impl IteratorType, pub(crate) mod iterator_interface { - pub(crate) mod methods { + methods { fn next(mut this: Mutable) -> AnyValue { match this.next() { Some(value) => value, @@ -330,7 +330,7 @@ define_type_features! { IteratorValue::new_for_array(ArrayValue::new(taken)) } } - pub(crate) mod unary_operations { + unary_operations { [context] fn cast_singleton_to_value(Spanned(this, span): Spanned) -> ExecutionResult { match this.singleton_value() { Some(value) => Ok(context.operation.evaluate(Spanned(value, span))?.0), @@ -338,7 +338,6 @@ define_type_features! { } } } - pub(crate) mod binary_operations {} interface_items { fn resolve_own_unary_operation(operation: &UnaryOperation) -> Option { Some(match operation { diff --git a/src/expressions/values/mod.rs b/src/expressions/values/mod.rs index 14fb0485..9c5ecadd 100644 --- a/src/expressions/values/mod.rs +++ b/src/expressions/values/mod.rs @@ -10,9 +10,11 @@ mod integer_subtypes; mod integer_untyped; mod iterable; mod iterator; +mod function; mod none; mod object; mod parser; +mod preinterpret; mod range; mod stream; mod string; @@ -30,9 +32,11 @@ pub(crate) use integer_subtypes::*; pub(crate) use integer_untyped::*; pub(crate) use iterable::*; pub(crate) use iterator::*; +pub(crate) use function::*; pub(crate) use none::*; pub(crate) use object::*; pub(crate) use parser::*; +pub(crate) use preinterpret::*; pub(crate) use range::*; pub(crate) use stream::*; pub(crate) use string::*; diff --git a/src/expressions/values/none.rs b/src/expressions/values/none.rs index 1984a6b1..17f0e1e1 100644 --- a/src/expressions/values/none.rs +++ b/src/expressions/values/none.rs @@ -23,24 +23,7 @@ impl ResolvableOwned for () { } } -define_optional_object! { - pub(crate) struct SettingsInputs { - iteration_limit: usize => (DEFAULT_ITERATION_LIMIT_STR, "The new iteration limit"), - } -} - define_type_features! { impl NoneType, - pub(crate) mod none_interface { - pub(crate) mod methods { - [context] fn configure_preinterpret(_none: (), inputs: SettingsInputs) { - if let Some(limit) = inputs.iteration_limit { - context.interpreter.set_iteration_limit(Some(limit)); - } - } - } - pub(crate) mod unary_operations {} - pub(crate) mod binary_operations {} - interface_items {} - } + pub(crate) mod none_interface {} } diff --git a/src/expressions/values/object.rs b/src/expressions/values/object.rs index b410cce6..a697a8f8 100644 --- a/src/expressions/values/object.rs +++ b/src/expressions/values/object.rs @@ -272,7 +272,7 @@ impl IntoValueContent<'static> for BTreeMap { define_type_features! { impl ObjectType, pub(crate) mod object_interface { - pub(crate) mod methods { + methods { [context] fn zip(this: ObjectValue) -> ExecutionResult { ZipIterators::new_from_object(this, context.span_range())?.run_zip(context.interpreter, true) } @@ -281,9 +281,6 @@ define_type_features! { ZipIterators::new_from_object(this, context.span_range())?.run_zip(context.interpreter, false) } } - pub(crate) mod unary_operations { - } - pub(crate) mod binary_operations {} property_access(ObjectValue) { [ctx] fn shared(source: &'a ObjectValue) { source.property_ref(ctx.property) diff --git a/src/expressions/values/parser.rs b/src/expressions/values/parser.rs index 492c6ad5..f6a52999 100644 --- a/src/expressions/values/parser.rs +++ b/src/expressions/values/parser.rs @@ -70,7 +70,7 @@ impl Spanned> { fn parser<'a>( this: Spanned>, - context: &'a mut MethodCallContext, + context: &'a mut FunctionCallContext, ) -> ExecutionResult> { this.parser(context.interpreter) } @@ -78,7 +78,7 @@ fn parser<'a>( define_type_features! { impl ParserType, pub(crate) mod parser_interface { - pub(crate) mod methods { + methods { // GENERAL // ======= @@ -252,11 +252,6 @@ define_type_features! { Ok(FloatValue::for_litfloat(&float)?) } } - pub(crate) mod unary_operations { - } - pub(crate) mod binary_operations {} - interface_items { - } } } diff --git a/src/expressions/values/preinterpret.rs b/src/expressions/values/preinterpret.rs new file mode 100644 index 00000000..599b4399 --- /dev/null +++ b/src/expressions/values/preinterpret.rs @@ -0,0 +1,24 @@ +use super::*; + +define_leaf_type! { + pub(crate) PreinterpretApiType => AnyType(AnyValueContent::PreinterpretApi), + content: PreinterpretApiValue, + kind: pub(crate) PreinterpretApiKind, + type_name: "preinterpret", + articled_display_name: "a preinterpret api", + dyn_impls: {}, +} + +#[derive(Clone)] +pub(crate) enum PreinterpretApiValue {} + +define_type_features! { + impl PreinterpretApiType, + pub(crate) mod preinterpret_api_interface { + functions { + [context] fn set_iteration_limit(new_limit: OptionalSuffix) { + context.interpreter.set_iteration_limit(Some(new_limit.0)); + } + } + } +} diff --git a/src/expressions/values/range.rs b/src/expressions/values/range.rs index d20da791..fa421cb0 100644 --- a/src/expressions/values/range.rs +++ b/src/expressions/values/range.rs @@ -398,15 +398,12 @@ impl_resolvable_argument_for! { define_type_features! { impl RangeType, pub(crate) mod range_interface { - pub(crate) mod methods { - } - pub(crate) mod unary_operations { + unary_operations { [context] fn cast_via_iterator(Spanned(this, span): Spanned) -> ExecutionResult { let this_iterator = IteratorValue::new_for_range(this)?; Ok(context.operation.evaluate(Spanned(this_iterator, span))?.0) } } - pub(crate) mod binary_operations {} interface_items { fn resolve_own_unary_operation(operation: &UnaryOperation) -> Option { Some(match operation { diff --git a/src/expressions/values/stream.rs b/src/expressions/values/stream.rs index fc856de3..0cadc891 100644 --- a/src/expressions/values/stream.rs +++ b/src/expressions/values/stream.rs @@ -121,7 +121,7 @@ impl_resolvable_argument_for! { define_type_features! { impl StreamType, pub(crate) mod stream_interface { - pub(crate) mod methods { + methods { // This is also on iterable, but is specialized here for performance fn len(this: AnyRef) -> usize { this.len() @@ -249,7 +249,7 @@ define_type_features! { Ok(inner_interpreter.complete()) } } - pub(crate) mod unary_operations { + unary_operations { [context] fn cast_coerced_to_value(Spanned(this, span): Spanned) -> ExecutionResult { let coerced = this.coerce_into_value(); if let AnyValue::Stream(_) = &coerced { @@ -259,7 +259,7 @@ define_type_features! { Ok(context.operation.evaluate(Spanned(coerced, span))?.0) } } - pub(crate) mod binary_operations { + binary_operations { fn add(mut lhs: OutputStream, rhs: OutputStream) -> OutputStream { rhs.append_into(&mut lhs); lhs diff --git a/src/expressions/values/string.rs b/src/expressions/values/string.rs index 99124508..7560f5cb 100644 --- a/src/expressions/values/string.rs +++ b/src/expressions/values/string.rs @@ -67,7 +67,7 @@ pub(crate) fn string_to_literal( define_type_features! { impl StringType, pub(crate) mod string_interface { - pub(crate) mod methods { + methods { // ================== // CONVERSION METHODS // ================== @@ -141,12 +141,12 @@ define_type_features! { string_conversion::insert_spaces_between_words(&this) } } - pub(crate) mod unary_operations { + unary_operations { fn cast_to_string(this: String) -> String { this } } - pub(crate) mod binary_operations { + binary_operations { fn add(mut lhs: String, rhs: AnyRef) -> String { lhs.push_str(rhs.deref()); lhs diff --git a/src/expressions/values/unsupported_literal.rs b/src/expressions/values/unsupported_literal.rs index 071a4b8e..385231d0 100644 --- a/src/expressions/values/unsupported_literal.rs +++ b/src/expressions/values/unsupported_literal.rs @@ -30,10 +30,5 @@ impl Debug for UnsupportedLiteral { define_type_features! { impl UnsupportedLiteralType, - pub(crate) mod unsupported_literal_interface { - pub(crate) mod methods {} - pub(crate) mod unary_operations {} - pub(crate) mod binary_operations {} - interface_items {} - } + pub(crate) mod unsupported_literal_interface {} } diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index a5378fa9..43a35861 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -264,12 +264,17 @@ impl AssigneeValue { } } -impl IsValueContent for Assignee { - type Type = AnyType; +impl IsValueContent for Assignee { + type Type = X::Type; type Form = BeAssignee; } -impl IntoValueContent<'static> for Assignee { +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 @@ -362,12 +367,17 @@ impl AnyValueMutable { } } -impl IsValueContent for Mutable { - type Type = AnyType; +impl IsValueContent for Mutable { + type Type = X::Type; type Form = BeMutable; } -impl IntoValueContent<'static> for Mutable { +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)) @@ -474,12 +484,17 @@ impl AnyValueShared { } } -impl IsValueContent for Shared { - type Type = AnyType; +impl IsValueContent for Shared { + type Type = X::Type; type Form = BeShared; } -impl IntoValueContent<'static> for Shared { +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)) @@ -622,6 +637,32 @@ impl Spanned<&mut CopyOnWrite> { } } +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 diff --git a/src/interpretation/interpreter.rs b/src/interpretation/interpreter.rs index 8416046a..052369ec 100644 --- a/src/interpretation/interpreter.rs +++ b/src/interpretation/interpreter.rs @@ -437,7 +437,7 @@ impl IterationCounter<'_, S> { pub(crate) fn check(&self) -> ExecutionResult<()> { if let Some(limit) = self.iteration_limit { if self.count > limit { - return self.span_source.control_flow_err(format!("Iteration limit of {} exceeded.\nIf needed, the limit can be reconfigured with None.configure_preinterpret(%{{ iteration_limit: XXX }})", limit)); + return self.span_source.control_flow_err(format!("Iteration limit of {} exceeded.\nIf needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX)", limit)); } } Ok(()) @@ -449,7 +449,6 @@ pub(crate) struct InterpreterConfig { } pub(crate) const DEFAULT_ITERATION_LIMIT: usize = 1000; -pub(crate) const DEFAULT_ITERATION_LIMIT_STR: &str = "1000"; impl Default for InterpreterConfig { fn default() -> Self { diff --git a/src/interpretation/source_parsing.rs b/src/interpretation/source_parsing.rs index 181b8f67..3e152a21 100644 --- a/src/interpretation/source_parsing.rs +++ b/src/interpretation/source_parsing.rs @@ -6,6 +6,7 @@ new_key!(pub(crate) VariableDefinitionId); new_key!(pub(crate) VariableReferenceId); new_key!(pub(crate) ControlFlowSegmentId); new_key!(pub(crate) CatchLocationId); +new_key!(pub(crate) ClosureId); pub(crate) enum InterruptDetails<'a> { /// Break statement (targets loops or labeled blocks) diff --git a/src/misc/parse_traits.rs b/src/misc/parse_traits.rs index 473a4517..e15368af 100644 --- a/src/misc/parse_traits.rs +++ b/src/misc/parse_traits.rs @@ -598,6 +598,7 @@ impl<'a, K> AnyParseStream for ParseStream<'a, K> { /// This is useful for keywords or syntax which we'd like to parse /// in a uniform way, but don't need to keep around bloating the /// size of our types. +#[derive(Copy, Clone)] pub(crate) struct Unused { _marker: std::marker::PhantomData, } diff --git a/tests/compilation_failures/control_flow/while_infinite_loop.stderr b/tests/compilation_failures/control_flow/while_infinite_loop.stderr index ac3ad2f2..80780418 100644 --- a/tests/compilation_failures/control_flow/while_infinite_loop.stderr +++ b/tests/compilation_failures/control_flow/while_infinite_loop.stderr @@ -1,5 +1,5 @@ error: Iteration limit of 1000 exceeded. - If needed, the limit can be reconfigured with None.configure_preinterpret(%{ iteration_limit: XXX }) + If needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX) --> tests/compilation_failures/control_flow/while_infinite_loop.rs:4:21 | 4 | run!(while true {}); diff --git a/tests/compilation_failures/core/settings_update_iteration_limit.rs b/tests/compilation_failures/core/iteration_limit.rs similarity index 52% rename from tests/compilation_failures/core/settings_update_iteration_limit.rs rename to tests/compilation_failures/core/iteration_limit.rs index 1daa4185..d8f65b63 100644 --- a/tests/compilation_failures/core/settings_update_iteration_limit.rs +++ b/tests/compilation_failures/core/iteration_limit.rs @@ -2,7 +2,6 @@ use preinterpret::*; fn main() { run!{ - None.configure_preinterpret(%{ iteration_limit: 5 }); loop {} }; } \ No newline at end of file diff --git a/tests/compilation_failures/core/iteration_limit.stderr b/tests/compilation_failures/core/iteration_limit.stderr new file mode 100644 index 00000000..84d7af12 --- /dev/null +++ b/tests/compilation_failures/core/iteration_limit.stderr @@ -0,0 +1,6 @@ +error: Iteration limit of 1000 exceeded. + If needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX) + --> tests/compilation_failures/core/iteration_limit.rs:5:14 + | +5 | loop {} + | ^^ diff --git a/tests/compilation_failures/core/settings_update_iteration_limit.stderr b/tests/compilation_failures/core/settings_update_iteration_limit.stderr deleted file mode 100644 index bfeead41..00000000 --- a/tests/compilation_failures/core/settings_update_iteration_limit.stderr +++ /dev/null @@ -1,6 +0,0 @@ -error: Iteration limit of 5 exceeded. - If needed, the limit can be reconfigured with None.configure_preinterpret(%{ iteration_limit: XXX }) - --> tests/compilation_failures/core/settings_update_iteration_limit.rs:6:14 - | -6 | loop {} - | ^^ diff --git a/tests/compilation_failures/core/typed_eq_nested_type_mismatch.stderr b/tests/compilation_failures/core/typed_eq_nested_type_mismatch.stderr index 57b155a5..e72688bd 100644 --- a/tests/compilation_failures/core/typed_eq_nested_type_mismatch.stderr +++ b/tests/compilation_failures/core/typed_eq_nested_type_mismatch.stderr @@ -1,5 +1,5 @@ error: lhs[1] is an untyped integer, but rhs[1] is a string - --> tests/compilation_failures/core/typed_eq_nested_type_mismatch.rs:5:28 + --> tests/compilation_failures/core/typed_eq_nested_type_mismatch.rs:5:29 | 5 | run!(%[_].assert([1, 2].typed_eq([1, "two"]))); - | ^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/core/typed_eq_object_value_type_mismatch.stderr b/tests/compilation_failures/core/typed_eq_object_value_type_mismatch.stderr index acbc1223..a464790d 100644 --- a/tests/compilation_failures/core/typed_eq_object_value_type_mismatch.stderr +++ b/tests/compilation_failures/core/typed_eq_object_value_type_mismatch.stderr @@ -1,5 +1,5 @@ error: lhs.b is an untyped integer, but rhs.b is a string - --> tests/compilation_failures/core/typed_eq_object_value_type_mismatch.rs:5:37 + --> tests/compilation_failures/core/typed_eq_object_value_type_mismatch.rs:5:38 | 5 | let result = %{ a: 1, b: 2 }.typed_eq(%{ a: 1, b: "two" }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.stderr b/tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.stderr index 9a7cfed9..9c428a23 100644 --- a/tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.stderr +++ b/tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.stderr @@ -1,5 +1,5 @@ error: lhs["multi-word key"] is an untyped integer, but rhs["multi-word key"] is a string - --> tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.rs:5:54 + --> tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.rs:5:55 | 5 | let result = %{ a: 1, ["multi-word key"]: 2 }.typed_eq(%{ a: 1, ["multi-word key"]: "two" }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/core/typed_eq_type_mismatch.stderr b/tests/compilation_failures/core/typed_eq_type_mismatch.stderr index 535cfbc8..0d332253 100644 --- a/tests/compilation_failures/core/typed_eq_type_mismatch.stderr +++ b/tests/compilation_failures/core/typed_eq_type_mismatch.stderr @@ -1,5 +1,5 @@ error: lhs is an untyped integer, but rhs is a string - --> tests/compilation_failures/core/typed_eq_type_mismatch.rs:5:23 + --> tests/compilation_failures/core/typed_eq_type_mismatch.rs:5:24 | 5 | run!(%[_].assert(1.typed_eq("hello"))); - | ^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.rs b/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.rs new file mode 100644 index 00000000..77fe7228 --- /dev/null +++ b/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.rs @@ -0,0 +1,8 @@ +use preinterpret::*; + +fn main() { + run!{ + let arr = [1, 2, 3]; + array::push_me(arr, 4, 5); + } +} \ No newline at end of file diff --git a/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.stderr b/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.stderr new file mode 100644 index 00000000..7cd55a08 --- /dev/null +++ b/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.stderr @@ -0,0 +1,5 @@ +error: Type 'array' has no property, function or method named 'push_me' + --> tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.rs:6:9 + | +6 | array::push_me(arr, 4, 5); + | ^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/functions/incorrect_type_function_name.rs b/tests/compilation_failures/functions/incorrect_type_function_name.rs new file mode 100644 index 00000000..20724f28 --- /dev/null +++ b/tests/compilation_failures/functions/incorrect_type_function_name.rs @@ -0,0 +1,8 @@ +use preinterpret::*; + +fn main() { + run!{ + preinterpret::set_iteration_limit(14); + for i in 0..15 {} + } +} \ No newline at end of file diff --git a/tests/compilation_failures/functions/incorrect_type_function_name.stderr b/tests/compilation_failures/functions/incorrect_type_function_name.stderr new file mode 100644 index 00000000..e65faf4b --- /dev/null +++ b/tests/compilation_failures/functions/incorrect_type_function_name.stderr @@ -0,0 +1,6 @@ +error: Iteration limit of 14 exceeded. + If needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX) + --> tests/compilation_failures/functions/incorrect_type_function_name.rs:6:24 + | +6 | for i in 0..15 {} + | ^^ diff --git a/tests/compilation_failures/functions/too_few_args.rs b/tests/compilation_failures/functions/too_few_args.rs new file mode 100644 index 00000000..240198f1 --- /dev/null +++ b/tests/compilation_failures/functions/too_few_args.rs @@ -0,0 +1,8 @@ +use preinterpret::*; + +fn main() { + run!{ + let arr = [1, 2, 3]; + array::push(arr); + } +} \ No newline at end of file diff --git a/tests/compilation_failures/functions/too_few_args.stderr b/tests/compilation_failures/functions/too_few_args.stderr new file mode 100644 index 00000000..881c9fb0 --- /dev/null +++ b/tests/compilation_failures/functions/too_few_args.stderr @@ -0,0 +1,5 @@ +error: This function expects 2 arguments, but 1 was provided + --> tests/compilation_failures/functions/too_few_args.rs:6:9 + | +6 | array::push(arr); + | ^^^^^^^^^^^ diff --git a/tests/compilation_failures/functions/too_many_args.rs b/tests/compilation_failures/functions/too_many_args.rs new file mode 100644 index 00000000..0802da0e --- /dev/null +++ b/tests/compilation_failures/functions/too_many_args.rs @@ -0,0 +1,8 @@ +use preinterpret::*; + +fn main() { + run!{ + let arr = [1, 2, 3]; + array::push(arr, 4, 5); + } +} \ No newline at end of file diff --git a/tests/compilation_failures/functions/too_many_args.stderr b/tests/compilation_failures/functions/too_many_args.stderr new file mode 100644 index 00000000..56eb277c --- /dev/null +++ b/tests/compilation_failures/functions/too_many_args.stderr @@ -0,0 +1,5 @@ +error: This function expects 2 arguments, but 3 were provided + --> tests/compilation_failures/functions/too_many_args.rs:6:9 + | +6 | array::push(arr, 4, 5); + | ^^^^^^^^^^^ diff --git a/tests/compilation_failures/iteration/infinite_range_to_vec.stderr b/tests/compilation_failures/iteration/infinite_range_to_vec.stderr index ff582f02..01cda0fd 100644 --- a/tests/compilation_failures/iteration/infinite_range_to_vec.stderr +++ b/tests/compilation_failures/iteration/infinite_range_to_vec.stderr @@ -1,6 +1,6 @@ error: Iteration limit of 1000 exceeded. - If needed, the limit can be reconfigured with None.configure_preinterpret(%{ iteration_limit: XXX }) - --> tests/compilation_failures/iteration/infinite_range_to_vec.rs:4:23 + If needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX) + --> tests/compilation_failures/iteration/infinite_range_to_vec.rs:4:24 | 4 | let _ = run!((0..).to_vec()); - | ^^^^^^^^^ + | ^^^^^^^^ diff --git a/tests/compilation_failures/iteration/long_iterable_to_string.stderr b/tests/compilation_failures/iteration/long_iterable_to_string.stderr index 7178f763..56d0813b 100644 --- a/tests/compilation_failures/iteration/long_iterable_to_string.stderr +++ b/tests/compilation_failures/iteration/long_iterable_to_string.stderr @@ -1,5 +1,5 @@ error: To protect against infinite loops, only a maximum of 1000 items can be output to a string from an iterator. You can use .to_vec() to avoid this limit. This can't currently be reconfigured with the iteration limit. - --> tests/compilation_failures/iteration/long_iterable_to_string.rs:4:20 + --> tests/compilation_failures/iteration/long_iterable_to_string.rs:4:21 | 4 | run!((0..10000).into_iter().to_string()) - | ^^^^^^^^^^^^ + | ^^^^^^^^^^^ diff --git a/tests/compilation_failures/iteration/long_iterable_to_vec.stderr b/tests/compilation_failures/iteration/long_iterable_to_vec.stderr index 32591675..76eae9f8 100644 --- a/tests/compilation_failures/iteration/long_iterable_to_vec.stderr +++ b/tests/compilation_failures/iteration/long_iterable_to_vec.stderr @@ -1,6 +1,6 @@ error: Iteration limit of 1000 exceeded. - If needed, the limit can be reconfigured with None.configure_preinterpret(%{ iteration_limit: XXX }) - --> tests/compilation_failures/iteration/long_iterable_to_vec.rs:4:32 + If needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX) + --> tests/compilation_failures/iteration/long_iterable_to_vec.rs:4:33 | 4 | run!((0..10000).into_iter().to_vec()) - | ^^^^^^^^^ + | ^^^^^^^^ diff --git a/tests/compilation_failures/iteration/long_range_to_vec.stderr b/tests/compilation_failures/iteration/long_range_to_vec.stderr index 9b603952..e9dc623c 100644 --- a/tests/compilation_failures/iteration/long_range_to_vec.stderr +++ b/tests/compilation_failures/iteration/long_range_to_vec.stderr @@ -1,6 +1,6 @@ error: Iteration limit of 1000 exceeded. - If needed, the limit can be reconfigured with None.configure_preinterpret(%{ iteration_limit: XXX }) - --> tests/compilation_failures/iteration/long_range_to_vec.rs:4:20 + If needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX) + --> tests/compilation_failures/iteration/long_range_to_vec.rs:4:21 | 4 | run!((0..10000).to_vec()) - | ^^^^^^^^^ + | ^^^^^^^^ diff --git a/tests/compilation_failures/iteration/zip_different_length_streams.stderr b/tests/compilation_failures/iteration/zip_different_length_streams.stderr index 87093ef1..b0a7e1de 100644 --- a/tests/compilation_failures/iteration/zip_different_length_streams.stderr +++ b/tests/compilation_failures/iteration/zip_different_length_streams.stderr @@ -1,5 +1,5 @@ error: The iterables have different lengths. The lengths vary from 3 to 4. To truncate to the shortest, use `zip_truncated` instead of `zip` - --> tests/compilation_failures/iteration/zip_different_length_streams.rs:5:40 + --> tests/compilation_failures/iteration/zip_different_length_streams.rs:5:41 | 5 | [["A", "B", "C"], [1, 2, 3, 4]].zip() - | ^^^^^^ + | ^^^^^ diff --git a/tests/expressions.rs b/tests/expressions.rs index 750dfff9..f4e9b7f4 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -167,9 +167,7 @@ fn test_reinterpret() { fn test_very_long_expression_works() { assert_eq!( run! { - None.configure_preinterpret(%{ - iteration_limit: 100000, - }); + preinterpret::set_iteration_limit(100000); let expression = %[]; for _ in 0..100000 { expression += %[1 +] diff --git a/tests/functions.rs b/tests/functions.rs new file mode 100644 index 00000000..b5f05b12 --- /dev/null +++ b/tests/functions.rs @@ -0,0 +1,34 @@ +#![allow(clippy::assertions_on_constants)] +#[path = "helpers/prelude.rs"] +mod prelude; +use prelude::*; + +#[test] +#[cfg_attr(miri, ignore = "incompatible with miri")] +fn test_expression_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/functions/*.rs"); +} + +#[test] +fn test_type_function_works() { + run!{ + let arr = [1, 2, 3]; + array::push(arr, 4); + %[_].assert_eq(arr.to_debug_string(), "[1, 2, 3, 4]"); + } +} + +#[test] +fn test_preinterpret_api() { + // This should complete OK + run!{ + preinterpret::set_iteration_limit(15); + for i in 0..15 {} + } + // See `abort_on_iteration_limit_exceeded` in the failure tests for a negative case +} \ No newline at end of file From c4ea88f2871b74afb2bbcc78f0668b912d2e36f9 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 24 Jan 2026 01:02:35 +0000 Subject: [PATCH 02/49] fix: Fix MSRV --- src/expressions/closures.rs | 17 ++++----- src/expressions/concepts/type_traits.rs | 4 +- src/expressions/equality.rs | 6 ++- .../evaluation/control_flow_analysis.rs | 12 +++--- src/expressions/evaluation/value_frames.rs | 37 +++++++++++-------- src/expressions/expression_parsing.rs | 32 ++++++++-------- src/expressions/mod.rs | 4 +- .../type_resolution/interface_macros.rs | 20 +++++----- src/expressions/type_resolution/type_data.rs | 12 +++--- src/expressions/type_resolution/type_kinds.rs | 4 +- src/expressions/values/any_value.rs | 6 +-- src/expressions/values/function.rs | 26 +++++++++---- src/expressions/values/mod.rs | 4 +- src/interpretation/bindings.rs | 7 ++-- tests/functions.rs | 6 +-- 15 files changed, 109 insertions(+), 88 deletions(-) diff --git a/src/expressions/closures.rs b/src/expressions/closures.rs index 4d9cddf3..5264732b 100644 --- a/src/expressions/closures.rs +++ b/src/expressions/closures.rs @@ -22,9 +22,7 @@ impl ClosureExpression { ownership: RequestedOwnership, ) -> ExecutionResult> { let span_range = self.0.span_range; - ownership.map_from_owned( - self.clone().into_any_value().spanned(span_range) - ) + ownership.map_from_owned(self.clone().into_any_value().spanned(span_range)) } } @@ -73,10 +71,7 @@ impl ParseSource for ClosureExpressionInner { arguments, _right_bar, body, - span_range: SpanRange::new_between( - start_span, - end_span, - ), + span_range: SpanRange::new_between(start_span, end_span), }) } @@ -119,6 +114,8 @@ struct FunctionArgumentAnnotation { _argument_specifier: ArgumentSpecifier, } +// They're clearer with a common prefix By +#[allow(clippy::enum_variant_names)] enum ArgumentSpecifier { ByValue { _argument_type: ArgumentType, @@ -176,7 +173,9 @@ struct ArgumentType { impl ParseSource for ArgumentType { fn parse(input: SourceParser) -> ParseResult { if !input.peek_ident_matching("any") { - return input.parse_err("Only the type `any` is currently supported as an argument type annotation") + return input.parse_err( + "Only the type `any` is currently supported as an argument type annotation", + ); } Ok(Self { _type_ident: input.parse()?, @@ -186,4 +185,4 @@ impl ParseSource for ArgumentType { fn control_flow_pass(&mut self, _context: FlowCapturer) -> ParseResult<()> { Ok(()) } -} \ No newline at end of file +} diff --git a/src/expressions/concepts/type_traits.rs b/src/expressions/concepts/type_traits.rs index 880ba73f..ca849d90 100644 --- a/src/expressions/concepts/type_traits.rs +++ b/src/expressions/concepts/type_traits.rs @@ -144,7 +144,7 @@ pub(crate) trait IsChildType: IsHierarchicalType { macro_rules! impl_type_feature_resolver { (impl TypeFeatureResolver for $type_def:ty: [$($type_defs:ty)+]) => { impl TypeFeatureResolver for $type_def { - fn resolve_method(&self, method_name: &str) -> Option { + fn resolve_method(&self, method_name: &str) -> Option<&'static FunctionInterface> { $( if let Some(method) = <$type_defs as TypeData>::resolve_own_method(method_name) { return Some(method); @@ -177,7 +177,7 @@ macro_rules! impl_type_feature_resolver { None } - fn resolve_type_function(&self, function_name: &str) -> Option { + fn resolve_type_function(&self, function_name: &str) -> Option<&'static FunctionInterface> { // Purposefully doesn't resolve parents, but TBC if this is right <$type_def as TypeData>::resolve_type_function(function_name) } diff --git a/src/expressions/equality.rs b/src/expressions/equality.rs index d46d257f..b9debddf 100644 --- a/src/expressions/equality.rs +++ b/src/expressions/equality.rs @@ -185,7 +185,11 @@ impl EqualityContext for TypedEquality { } #[inline] - fn leaf_values_not_equal(&mut self, _lhs: &T, _rhs: &T) -> ExecutionResult { + fn leaf_values_not_equal( + &mut self, + _lhs: &T, + _rhs: &T, + ) -> ExecutionResult { Ok(false) } diff --git a/src/expressions/evaluation/control_flow_analysis.rs b/src/expressions/evaluation/control_flow_analysis.rs index dc805d6d..0339c771 100644 --- a/src/expressions/evaluation/control_flow_analysis.rs +++ b/src/expressions/evaluation/control_flow_analysis.rs @@ -46,13 +46,17 @@ pub(in super::super) fn control_flow_visit( stack.push(*node); } ExpressionNode::MethodCall { - receiver, invocation: Invocation { parameters, .. }, .. + receiver, + invocation: Invocation { parameters, .. }, + .. } => { stack.push_reversed(parameters.iter().copied()); stack.push(*receiver); // This is a stack so this executes first } ExpressionNode::Invocation { - invokable, invocation: Invocation { parameters, .. }, .. + invokable, + invocation: Invocation { parameters, .. }, + .. } => { stack.push_reversed(parameters.iter().copied()); stack.push(*invokable); // This is a stack so this executes first @@ -126,9 +130,7 @@ impl Leaf { Leaf::ParseExpression(parse_expression) => parse_expression.control_flow_pass(context), Leaf::Discarded(_) => Ok(()), Leaf::Value(_) => Ok(()), - Leaf::ClosureExpression(inline_function) => { - inline_function.control_flow_pass(context) - }, + Leaf::ClosureExpression(inline_function) => inline_function.control_flow_pass(context), } } } diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index 373bce19..1a5afda5 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -1260,7 +1260,7 @@ pub(super) struct MethodCallBuilder { enum MethodCallPath { CallerPath, ArgumentsPath { - method: FunctionInterface, + method: &'static FunctionInterface, disabled_evaluated_arguments_including_caller: Vec>, }, } @@ -1274,7 +1274,7 @@ impl MethodCallBuilder { ) -> NextAction { let frame = Self { method: method.clone(), - parentheses: invocation.parentheses.clone(), + parentheses: invocation.parentheses, unevaluated_parameters_stack: invocation .parameters .iter() @@ -1309,10 +1309,7 @@ impl EvaluationFrame for MethodCallBuilder { let caller = value.expect_late_bound(); let method_name = self.method.property.to_string(); let method_name = method_name.as_str(); - let method = caller - .kind() - .feature_resolver() - .resolve_method(method_name); + let method = caller.kind().feature_resolver().resolve_method(method_name); let method = match method { Some(m) => m, None => { @@ -1422,7 +1419,10 @@ impl EvaluationFrame for MethodCallBuilder { } }; let mut call_context = FunctionCallContext { - output_span_range: SpanRange::new_between(self.method.property, self.parentheses.close()), + output_span_range: SpanRange::new_between( + self.method.property, + self.parentheses.close(), + ), interpreter: context.interpreter(), }; let output = method.execute(arguments, &mut call_context)?; @@ -1442,7 +1442,7 @@ enum InvocationPath { InvokablePath, ArgumentsPath { function_span: SpanRange, - interface: FunctionInterface, + interface: &'static FunctionInterface, disabled_evaluated_arguments: Vec>, }, } @@ -1454,7 +1454,7 @@ impl InvocationBuilder { invocation: &Invocation, ) -> NextAction { let frame = Self { - parentheses: invocation.parentheses.clone(), + parentheses: invocation.parentheses, unevaluated_parameters_stack: invocation .parameters .iter() @@ -1487,15 +1487,16 @@ impl EvaluationFrame for InvocationBuilder { let function_span = span; let function = value.expect_copy_on_write(); - let function = function.into_content() + let function = function + .into_content() .spanned(function_span) .downcast_resolve::>("An invoked value")?; - + // I need to extract // (A): Function interface // (B): Already bound disabled arguments -> assumed empty let interface = match &function.as_ref_value().definition { - FunctionDefinition::Native(interface) => interface.clone(), + FunctionDefinition::Native(interface) => *interface, FunctionDefinition::Closure(_) => todo!(), }; // Placeholder for already bound arguments. We can assume they're already disabled, and of the correct ownership/s. @@ -1540,15 +1541,16 @@ impl EvaluationFrame for InvocationBuilder { }; return function_span.type_err(format!( "This function expects {}, but {}", - expected_arguments, - actual_arguments, + expected_arguments, actual_arguments, )); } // We skip 1 to ignore the caller let unbound_argument_ownerships: iter::Skip< std::slice::Iter<'_, ArgumentOwnership>, - > = argument_ownerships.iter().skip(disabled_bound_arguments.len()); + > = argument_ownerships + .iter() + .skip(disabled_bound_arguments.len()); for ((_, requested_ownership), ownership) in self .unevaluated_parameters_stack .iter_mut() @@ -1605,7 +1607,10 @@ impl EvaluationFrame for InvocationBuilder { } }; let mut call_context = FunctionCallContext { - output_span_range: SpanRange::new_between(function_span, self.parentheses.close()), + output_span_range: SpanRange::new_between( + function_span, + self.parentheses.close(), + ), interpreter: context.interpreter(), }; let output = interface.execute(arguments, &mut call_context)?; diff --git a/src/expressions/expression_parsing.rs b/src/expressions/expression_parsing.rs index b9eeee6e..695fbba9 100644 --- a/src/expressions/expression_parsing.rs +++ b/src/expressions/expression_parsing.rs @@ -385,8 +385,14 @@ impl<'a> ExpressionParser<'a> { .add_node(ExpressionNode::Property { node, access }), }, NodeExtension::MethodCall(method) => { - let property: PropertyAccess = PropertyAccess { dot: method.dot, property: method.method }; - let invocation = Invocation { parentheses: method.parentheses, parameters: Vec::new() }; + let property: PropertyAccess = PropertyAccess { + dot: method.dot, + property: method.method, + }; + let invocation = Invocation { + parentheses: method.parentheses, + parameters: Vec::new(), + }; if self.streams.is_current_empty() { self.streams.exit_group(None)?; let node = self.nodes.add_node(ExpressionNode::MethodCall { @@ -486,19 +492,15 @@ impl<'a> ExpressionParser<'a> { invocation.parameters.push(node); self.streams.exit_group(None)?; let node = match method { - Some(method) => { - self.nodes.add_node(ExpressionNode::MethodCall { - receiver: source, - method, - invocation, - }) - } - None => { - self.nodes.add_node(ExpressionNode::Invocation { - invokable: source, - invocation, - }) - } + Some(method) => self.nodes.add_node(ExpressionNode::MethodCall { + receiver: source, + method, + invocation, + }), + None => self.nodes.add_node(ExpressionNode::Invocation { + invokable: source, + invocation, + }), }; WorkItem::TryParseAndApplyExtension { node } } diff --git a/src/expressions/mod.rs b/src/expressions/mod.rs index b0659bc1..8c430e1f 100644 --- a/src/expressions/mod.rs +++ b/src/expressions/mod.rs @@ -2,6 +2,7 @@ use crate::internal_prelude::*; use expression_parsing::*; +mod closures; mod concepts; mod control_flow; mod equality; @@ -10,13 +11,13 @@ mod expression; mod expression_block; mod expression_label; mod expression_parsing; -mod closures; mod operations; mod patterns; mod statements; mod type_resolution; mod values; +pub(crate) use closures::*; #[allow(unused_imports)] // Whilst we're building it out pub(crate) use concepts::*; pub(crate) use control_flow::*; @@ -25,7 +26,6 @@ pub(crate) use evaluation::*; pub(crate) use expression::*; pub(crate) use expression_block::*; pub(crate) use expression_label::*; -pub(crate) use closures::*; pub(crate) use operations::*; pub(crate) use patterns::*; pub(crate) use statements::*; diff --git a/src/expressions/type_resolution/interface_macros.rs b/src/expressions/type_resolution/interface_macros.rs index a1611d8f..54f7490d 100644 --- a/src/expressions/type_resolution/interface_macros.rs +++ b/src/expressions/type_resolution/interface_macros.rs @@ -49,7 +49,7 @@ macro_rules! generate_binary_interface { }; } -macro_rules! generate_method_interface { +macro_rules! generate_function_interface { (REQUIRED [] OPTIONAL [] ARGS[$method:path]) => { FunctionInterface::Arity0 { method: |context| apply_fn0($method, context), @@ -112,7 +112,7 @@ macro_rules! generate_method_interface { } }; (REQUIRED $req:tt OPTIONAL $opt:tt ARGS[$($arg:tt)*]) => { - compile_error!(stringify!("This method arity is currently unsupported - add support in `FunctionInterface` and `generate_method_interface`: ", $($arg)*)); + compile_error!(stringify!("This method arity is currently unsupported - add support in `FunctionInterface` and `generate_function_interface`: ", $($arg)*)); }; } @@ -524,8 +524,8 @@ macro_rules! define_type_features { #[allow(unused)] use super::*; $( - pub(crate) fn $function_name() -> FunctionInterface { - parse_arg_types!([CALLBACK: generate_method_interface![functions::$function_name]] $($function_args)*) + pub(crate) fn $function_name() -> &'static FunctionInterface { + &parse_arg_types!([CALLBACK: generate_function_interface![functions::$function_name]] $($function_args)*) } )* } @@ -541,13 +541,13 @@ macro_rules! define_type_features { } )* } - + pub(crate) mod method_definitions { #[allow(unused)] use super::*; $( - pub(crate) fn $method_name() -> FunctionInterface { - parse_arg_types!([CALLBACK: generate_method_interface![methods::$method_name]] $($method_args)*) + pub(crate) fn $method_name() -> &'static FunctionInterface { + &parse_arg_types!([CALLBACK: generate_function_interface![methods::$method_name]] $($method_args)*) } )* } @@ -642,7 +642,7 @@ macro_rules! define_type_features { impl TypeData for $type_def { $( #[allow(unreachable_code)] - fn resolve_own_method(method_name: &str) -> Option { + fn resolve_own_method(method_name: &str) -> Option<&'static FunctionInterface> { Some(match method_name { $( stringify!($method_name) => method_definitions::$method_name(), @@ -654,7 +654,7 @@ macro_rules! define_type_features { $( #[allow(unreachable_code)] - fn resolve_type_function(function_name: &str) -> Option { + fn resolve_type_function(function_name: &str) -> Option<&'static FunctionInterface> { Some(match function_name { $( stringify!($function_name) => function_definitions::$function_name(), @@ -694,6 +694,6 @@ macro_rules! define_type_features { #[cfg(test)] pub(crate) use handle_first_arg_type; pub(crate) use { - define_type_features, generate_binary_interface, generate_method_interface, + define_type_features, generate_binary_interface, generate_function_interface, generate_unary_interface, if_empty, ignore_all, parse_arg_types, }; diff --git a/src/expressions/type_resolution/type_data.rs b/src/expressions/type_resolution/type_data.rs index e392ec21..4cb30b5b 100644 --- a/src/expressions/type_resolution/type_data.rs +++ b/src/expressions/type_resolution/type_data.rs @@ -5,7 +5,7 @@ pub(crate) trait TypeFeatureResolver { /// Resolves a method with the given name defined on this type. /// A method is like a function, but guaranteed to have a first argument /// (the receiver) which is assignable from the type. - fn resolve_method(&self, method_name: &str) -> Option; + fn resolve_method(&self, method_name: &str) -> Option<&'static FunctionInterface>; /// Resolves a unary operation as a method interface for this type. fn resolve_unary_operation( @@ -20,7 +20,7 @@ pub(crate) trait TypeFeatureResolver { ) -> Option; /// Resolves a function with the given name defined on this type. - fn resolve_type_function(&self, function_name: &str) -> Option; + fn resolve_type_function(&self, function_name: &str) -> Option<&'static FunctionInterface>; /// Resolves a property of this type. fn resolve_type_property(&self, _property_name: &str) -> Option; @@ -41,7 +41,7 @@ pub(crate) trait TypeFeatureResolver { pub(crate) trait TypeData { /// Returns None if the method is not supported on this type itself. /// The method may still be supported on a type further up the resolution chain. - fn resolve_own_method(_method_name: &str) -> Option { + fn resolve_own_method(_method_name: &str) -> Option<&'static FunctionInterface> { None } @@ -67,7 +67,7 @@ pub(crate) trait TypeData { } /// Returns None if the function is not supported on this type itself. - fn resolve_type_function(_function_name: &str) -> Option { + fn resolve_type_function(_function_name: &str) -> Option<&'static FunctionInterface> { None } @@ -83,9 +83,7 @@ pub(crate) trait TypeData { } #[allow(unused)] -// It's good enough for our needs for now -#[allow(unpredictable_function_pointer_comparisons)] -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone)] pub(crate) enum FunctionInterface { Arity0 { method: fn(&mut FunctionCallContext) -> ExecutionResult, diff --git a/src/expressions/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index a3b5805c..021de770 100644 --- a/src/expressions/type_resolution/type_kinds.rs +++ b/src/expressions/type_resolution/type_kinds.rs @@ -259,11 +259,11 @@ impl TypeProperty { self.span_range(), )); } - return self.type_err(format!( + self.type_err(format!( "Type '{}' has no property, function or method named '{}'", self.source_type.kind.source_name(), self.property, - )); + )) } } diff --git a/src/expressions/values/any_value.rs b/src/expressions/values/any_value.rs index 10dfa71f..208c5f8f 100644 --- a/src/expressions/values/any_value.rs +++ b/src/expressions/values/any_value.rs @@ -305,9 +305,7 @@ impl<'a> ValuesEqual for AnyValueRef<'a> { (AnyValueContent::Parser(_), _) => ctx.kind_mismatch(self, other), (AnyValueContent::Iterator(l), AnyValueContent::Iterator(r)) => l.test_equality(r, ctx), (AnyValueContent::Iterator(_), _) => ctx.kind_mismatch(self, other), - (AnyValueContent::Function(l), AnyValueContent::Function(r)) => { - l.test_equality(r, ctx) - } + (AnyValueContent::Function(l), AnyValueContent::Function(r)) => l.test_equality(r, ctx), (AnyValueContent::Function(_), _) => ctx.kind_mismatch(self, other), (AnyValueContent::PreinterpretApi(not_a_value), _) => match **not_a_value {}, } @@ -473,7 +471,7 @@ impl<'a> AnyValueRef<'a> { .expect("Non-composite values should all be able to be outputted to a stream"); stream.concat_content_into(output, behaviour); } - AnyValueContent::PreinterpretApi(not_a_value) => match *not_a_value {} + AnyValueContent::PreinterpretApi(not_a_value) => match *not_a_value {}, } Ok(()) } diff --git a/src/expressions/values/function.rs b/src/expressions/values/function.rs index 55963810..e1a99a36 100644 --- a/src/expressions/values/function.rs +++ b/src/expressions/values/function.rs @@ -14,29 +14,41 @@ pub(crate) struct FunctionValue { pub(crate) definition: FunctionDefinition, } -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone)] pub(crate) enum FunctionDefinition { - Native(FunctionInterface), + Native(&'static FunctionInterface), // TODO[closures]: Add closed_variables: Vec to Closure Closure(ClosureExpression), } +impl Eq for FunctionDefinition {} + +impl PartialEq for FunctionDefinition { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (FunctionDefinition::Native(a), FunctionDefinition::Native(b)) => std::ptr::eq(*a, *b), + (FunctionDefinition::Closure(a), FunctionDefinition::Closure(b)) => a == b, + _ => false, + } + } +} + impl ValuesEqual for FunctionValue { fn test_equality(&self, other: &Self, ctx: &mut C) -> C::Result { if self.definition == other.definition { - return ctx.values_equal(); + ctx.values_equal() } else { - return ctx.leaf_values_not_equal("left_anon_function", "right_anon_function"); + ctx.leaf_values_not_equal("left_anon_function", "right_anon_function") } } } -impl IsValueContent for FunctionInterface { +impl IsValueContent for &'static FunctionInterface { type Type = FunctionType; type Form = BeOwned; } -impl IntoValueContent<'static> for FunctionInterface { +impl IntoValueContent<'static> for &'static FunctionInterface { fn into_content(self) -> Content<'static, Self::Type, Self::Form> { FunctionValue { definition: FunctionDefinition::Native(self), @@ -60,4 +72,4 @@ impl IntoValueContent<'static> for ClosureExpression { define_type_features! { impl FunctionType, pub(crate) mod function_value_interface {} -} \ No newline at end of file +} diff --git a/src/expressions/values/mod.rs b/src/expressions/values/mod.rs index 9c5ecadd..421d2e0a 100644 --- a/src/expressions/values/mod.rs +++ b/src/expressions/values/mod.rs @@ -5,12 +5,12 @@ mod character; mod float; mod float_subtypes; mod float_untyped; +mod function; mod integer; mod integer_subtypes; mod integer_untyped; mod iterable; mod iterator; -mod function; mod none; mod object; mod parser; @@ -27,12 +27,12 @@ pub(crate) use character::*; pub(crate) use float::*; pub(crate) use float_subtypes::*; pub(crate) use float_untyped::*; +pub(crate) use function::*; pub(crate) use integer::*; pub(crate) use integer_subtypes::*; pub(crate) use integer_untyped::*; pub(crate) use iterable::*; pub(crate) use iterator::*; -pub(crate) use function::*; pub(crate) use none::*; pub(crate) use object::*; pub(crate) use parser::*; diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 43a35861..dfcc158d 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -653,16 +653,17 @@ where AnyLevelCopyOnWrite::::Owned(owned).into_copy_on_write() } CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(shared.into_content()).into_copy_on_write() + AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(shared.into_content()) + .into_copy_on_write() } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - AnyLevelCopyOnWrite::::SharedWithTransparentCloning(shared.into_content()).into_copy_on_write() + AnyLevelCopyOnWrite::::SharedWithTransparentCloning(shared.into_content()) + .into_copy_on_write() } } } } - impl AsRef for CopyOnWrite { fn as_ref(&self) -> &T { self diff --git a/tests/functions.rs b/tests/functions.rs index b5f05b12..bddc5bc3 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -16,7 +16,7 @@ fn test_expression_compilation_failures() { #[test] fn test_type_function_works() { - run!{ + run! { let arr = [1, 2, 3]; array::push(arr, 4); %[_].assert_eq(arr.to_debug_string(), "[1, 2, 3, 4]"); @@ -26,9 +26,9 @@ fn test_type_function_works() { #[test] fn test_preinterpret_api() { // This should complete OK - run!{ + run! { preinterpret::set_iteration_limit(15); for i in 0..15 {} } // See `abort_on_iteration_limit_exceeded` in the failure tests for a negative case -} \ No newline at end of file +} From 97dae24356e4d056f6b30462135895933cc919b6 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 24 Jan 2026 09:26:26 +0000 Subject: [PATCH 03/49] feat: Methods x.y are values with prebound receiver --- plans/TODO.md | 7 +- src/expressions/evaluation/evaluator.rs | 2 + src/expressions/evaluation/value_frames.rs | 253 ++++++++++++--------- src/expressions/expression_parsing.rs | 1 + src/expressions/values/function.rs | 13 +- src/extensions/errors_and_spans.rs | 5 - src/interpretation/bindings.rs | 149 +++++++----- src/misc/mut_rc_ref_cell.rs | 140 ++++++++---- tests/functions.rs | 16 ++ 9 files changed, 371 insertions(+), 215 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index 48872bba..756a2c6e 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -215,9 +215,10 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] Move `().configure_preinterpret` to `preinterpret::set_iteration_limit` and update the message: `If needed, the limit can be reconfigured with` - [ ] `x.hello` accessing a method creates a `NativeFunction` with the first parameter bound to `x` - - [ ] Add some bound receiver or something or more generally `bound_arguments: Vec` - these should be deactivated - - [ ] `my_array.push` returns a function with `my_array` bound. The LateBound `my_array` can then be deactivated whilst the rest of the arguments are resolved, like what we do at the moment. This approach avoids the horrible javascript issues with `this` not being bound when referencing `x.y`. - - [ ] And then for objects, the method wins; BUT you can use `x["obj"]` to access the field instead of the method + - [x] Add some bound receiver or something or more generally `bound_arguments: Vec` - these should be deactivated + - [x] `my_array.push` returns a function with `my_array` bound. The LateBound `my_array` can then be deactivated whilst the rest of the arguments are resolved, like what we do at the moment. This approach avoids the horrible javascript issues with `this` not being bound when referencing `x.y`. + - [x] And then for objects, the method wins; BUT you can use `x["obj"]` to access the field instead of the method + - [ ] It's an error to set `x.y` if `y` is a method on object - [ ] We can try commenting out the code to resolve a method extension, and it should still work, courtesy of property access + invocation - [ ] We can define closures (without any closed values for now) diff --git a/src/expressions/evaluation/evaluator.rs b/src/expressions/evaluation/evaluator.rs index eca06240..53c4449d 100644 --- a/src/expressions/evaluation/evaluator.rs +++ b/src/expressions/evaluation/evaluator.rs @@ -176,6 +176,7 @@ impl RequestedValue { } } + #[allow(dead_code)] pub(crate) fn expect_copy_on_write(self) -> CopyOnWriteValue { match self { RequestedValue::CopyOnWrite(copy_on_write) => copy_on_write, @@ -344,6 +345,7 @@ impl<'a, T: RequestedValueType> Context<'a, T> { self.request_argument_value(handler, node, ArgumentOwnership::Owned) } + #[allow(dead_code)] pub(super) fn request_copy_on_write>( self, handler: H, diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index 1a5afda5..3b973050 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -48,6 +48,70 @@ impl ArgumentValue { _ => panic!("expect_shared() called on a non-shared 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 { + match self { + ArgumentValue::Owned(owned) => DisabledArgumentValue::Owned(owned), + ArgumentValue::CopyOnWrite(copy_on_write) => { + DisabledArgumentValue::CopyOnWrite(copy_on_write.disable()) + } + ArgumentValue::Mutable(mutable) => DisabledArgumentValue::Mutable(mutable.disable()), + ArgumentValue::Assignee(Assignee(mutable)) => { + DisabledArgumentValue::Assignee(mutable.disable()) + } + ArgumentValue::Shared(shared) => DisabledArgumentValue::Shared(shared.disable()), + } + } +} + +/// A disabled argument value that can be safely cloned and dropped. +pub(crate) enum DisabledArgumentValue { + Owned(AnyValueOwned), + CopyOnWrite(DisabledCopyOnWrite), + Mutable(DisabledMutable), + Assignee(DisabledMutable), + Shared(DisabledShared), +} + +impl Clone for DisabledArgumentValue { + fn clone(&self) -> Self { + match self { + DisabledArgumentValue::Owned(owned) => DisabledArgumentValue::Owned(owned.clone()), + DisabledArgumentValue::CopyOnWrite(copy_on_write) => { + DisabledArgumentValue::CopyOnWrite(copy_on_write.clone()) + } + DisabledArgumentValue::Mutable(mutable) => { + DisabledArgumentValue::Mutable(mutable.clone()) + } + DisabledArgumentValue::Assignee(assignee) => { + DisabledArgumentValue::Assignee(assignee.clone()) + } + DisabledArgumentValue::Shared(shared) => DisabledArgumentValue::Shared(shared.clone()), + } + } +} + +impl DisabledArgumentValue { + /// Re-enables this disabled argument value by re-acquiring any borrow. + pub(crate) fn enable(self, span: SpanRange) -> ExecutionResult { + match self { + DisabledArgumentValue::Owned(owned) => Ok(ArgumentValue::Owned(owned)), + DisabledArgumentValue::CopyOnWrite(copy_on_write) => { + Ok(ArgumentValue::CopyOnWrite(copy_on_write.enable(span)?)) + } + DisabledArgumentValue::Mutable(mutable) => { + Ok(ArgumentValue::Mutable(mutable.enable(span)?)) + } + DisabledArgumentValue::Assignee(assignee) => { + Ok(ArgumentValue::Assignee(Assignee(assignee.enable(span)?))) + } + DisabledArgumentValue::Shared(shared) => { + Ok(ArgumentValue::Shared(shared.enable(span)?)) + } + } + } } impl Spanned { @@ -72,39 +136,6 @@ impl Spanned { } } -// Note: ArgumentValue no longer implements HasSpanRange or WithSpanRangeExt -// since the inner value types no longer carry spans internally. -// Spans should be tracked separately at a higher level if needed. - -impl Spanned<&mut ArgumentValue> { - /// SAFETY: - /// * Must be paired with a call to `enable()` before any further use of the value. - /// * Must not use the value while disabled. - pub(crate) unsafe fn disable(&mut self) { - match &mut self.0 { - ArgumentValue::Owned(_) => {} - ArgumentValue::CopyOnWrite(copy_on_write) => copy_on_write.spanned(self.1).disable(), - ArgumentValue::Mutable(mutable) => mutable.spanned(self.1).disable(), - ArgumentValue::Assignee(assignee) => (&mut assignee.0).spanned(self.1).disable(), - ArgumentValue::Shared(shared) => shared.spanned(self.1).disable(), - } - } - - /// SAFETY: - /// * Must only be used after a call to `disable()`. - /// - /// Returns an ownership error if re-enabling fails (e.g., due to conflicting borrows). - pub(crate) unsafe fn enable(&mut self) -> ExecutionResult<()> { - match &mut self.0 { - ArgumentValue::Owned(_) => Ok(()), - ArgumentValue::CopyOnWrite(copy_on_write) => copy_on_write.spanned(self.1).enable(), - ArgumentValue::Mutable(mutable) => mutable.spanned(self.1).enable(), - ArgumentValue::Assignee(assignee) => (&mut assignee.0).spanned(self.1).enable(), - ArgumentValue::Shared(shared) => shared.spanned(self.1).enable(), - } - } -} - impl Deref for ArgumentValue { type Target = AnyValue; @@ -176,15 +207,14 @@ impl RequestedOwnership { &self, Spanned(late_bound, span): Spanned, ) -> ExecutionResult> { - Ok(Spanned( - match self { - RequestedOwnership::LateBound => RequestedValue::LateBound(late_bound), - RequestedOwnership::Concrete(_) => { - panic!("Returning a late-bound reference when concrete ownership was requested") - } - }, - span, - )) + Ok(match self { + RequestedOwnership::LateBound => RequestedValue::LateBound(late_bound), + RequestedOwnership::Concrete(argument_ownership) => { + let argument = argument_ownership.map_from_late_bound(Spanned(late_bound, span))?; + Self::item_from_argument(argument) + } + } + .spanned(span)) } pub(crate) fn map_from_argument( @@ -824,7 +854,7 @@ enum BinaryPath { right: ExpressionNodeId, }, OnRightBranch { - left: Spanned, + left: Spanned, interface: BinaryOperationInterface, }, } @@ -882,12 +912,10 @@ impl EvaluationFrame for BinaryOperationBuilder { let left = interface .lhs_ownership() .map_from_late_bound(Spanned(left, left_span))?; - let mut left = Spanned(left, left_span); + let left = Spanned(left, left_span); - unsafe { - // SAFETY: We re-enable it below and don't use it while disabled - left.to_mut().disable(); - } + // Disable left so we can evaluate right without borrow conflicts + let left = left.map(|v| v.disable()); self.state = BinaryPath::OnRightBranch { left, interface }; context.request_argument_value(self, right, rhs_ownership) @@ -902,24 +930,22 @@ impl EvaluationFrame for BinaryOperationBuilder { } } } - BinaryPath::OnRightBranch { - mut left, - interface, - } => { - let mut right = value.expect_argument_value(); + BinaryPath::OnRightBranch { left, interface } => { + let right = value.expect_argument_value(); // NOTE: // - This disable/enable flow allows us to do x += x without issues // - Read https://rust-lang.github.io/rfcs/2025-nested-method-calls.html for more details // - We enable left-to-right for more intuitive error messages: // If left and right clash, then the error message should be on the right, not the left - unsafe { - // SAFETY: We disabled left above - right.to_mut().disable(); - // SAFETY: enable() may fail if left and right reference the same variable - left.to_mut().enable()?; - right.to_mut().enable()?; - } + + // Disable right, then re-enable left first (for intuitive error messages) + let right = right.map(|v| v.disable()); + let left_span = left.1; + let right_span = right.1; + let left = left.try_map(|v| v.enable(left_span))?; + let right = right.try_map(|v| v.enable(right_span))?; + let result = interface.execute(left, right, &self.operation)?; return context.return_returned_value(result); } @@ -938,13 +964,8 @@ impl ValuePropertyAccessBuilder { node: ExpressionNodeId, ) -> NextAction { let frame = Self { access }; - // If we need an owned value, we can try resolving a reference and - // clone the outputted value if needed - which can be much cheaper. - // e.g. `let x = arr[0]` only copies `arr[0]` instead of the whole array. - let ownership_request = context - .requested_ownership() - .replace_owned_with_copy_on_write(); - context.request_any_value(frame, node, ownership_request) + // We need late-bound in case we resolve a method + context.request_late_bound(frame, node) } } @@ -960,11 +981,33 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { context: ValueContext, Spanned(value, source_span): Spanned, ) -> ExecutionResult { + // The result span covers source through property + let result_span = SpanRange::new_between(source_span, self.access.span_range()); + // Get the source kind to check if property access is supported let source_kind = value.value_kind(); + let resolver = source_kind.feature_resolver(); + + // Attempt to resolve a method first + // (NB: It's an error to set an object property which is already set to a native method) + if let Some(method) = resolver.resolve_method(&self.access.property.to_string()) { + let receiver = value.expect_late_bound(); + let receiver_ownership = method.argument_ownerships().0[0]; + let receiver = receiver_ownership + .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 function_value = FunctionValue { + definition: FunctionDefinition::Native(method), + disabled_bound_arguments: vec![receiver], + }; + return context.return_value(function_value.spanned(result_span)); + }; + // Query type resolution for property access capability - let Some(interface) = source_kind.feature_resolver().resolve_property_access() else { + let Some(interface) = resolver.resolve_property_access() else { return self.access.type_err(format!( "Cannot access properties on {}", source_kind.articled_display_name() @@ -983,8 +1026,6 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { |owned| (interface.owned_access)(ctx, owned), )?; - // The result span covers source through property - let result_span = SpanRange::new_between(source_span, self.access.span_range()); context.return_not_necessarily_matching_requested(Spanned(mapped, result_span)) } } @@ -1261,7 +1302,7 @@ enum MethodCallPath { CallerPath, ArgumentsPath { method: &'static FunctionInterface, - disabled_evaluated_arguments_including_caller: Vec>, + disabled_evaluated_arguments_including_caller: Vec>, }, } @@ -1350,7 +1391,7 @@ impl EvaluationFrame for MethodCallBuilder { } let caller = argument_ownerships[0].map_from_late_bound(Spanned(caller, caller_span))?; - let mut caller = Spanned(caller, caller_span); + let caller = Spanned(caller, caller_span); // We skip 1 to ignore the caller let non_self_argument_ownerships: iter::Skip< @@ -1369,10 +1410,8 @@ impl EvaluationFrame for MethodCallBuilder { disabled_evaluated_arguments_including_caller: { let mut params = Vec::with_capacity(1 + self.unevaluated_parameters_stack.len()); - unsafe { - // SAFETY: We enable it again before use - caller.to_mut().disable(); - } + // Disable caller so we can evaluate remaining arguments without borrow conflicts + let caller = caller.map(|v| v.disable()); params.push(caller); params }, @@ -1384,11 +1423,9 @@ impl EvaluationFrame for MethodCallBuilder { .. } => { let argument = value.expect_argument_value(); - let mut argument = Spanned(argument, span); - unsafe { - // SAFETY: We enable it again before use - argument.to_mut().disable(); - } + let argument = Spanned(argument, span); + // Disable argument so we can evaluate remaining arguments without borrow conflicts + let argument = argument.map(|v| v.disable()); disabled_evaluated_arguments_including_caller.push(argument); } }; @@ -1401,20 +1438,20 @@ impl EvaluationFrame for MethodCallBuilder { let (arguments, method) = match self.state { MethodCallPath::CallerPath => unreachable!("Already updated above"), MethodCallPath::ArgumentsPath { - disabled_evaluated_arguments_including_caller: mut arguments, + disabled_evaluated_arguments_including_caller: disabled_arguments, method, } => { // NOTE: // - This disable/enable flow allows us to do things like vec.push(vec.len()) // - Read https://rust-lang.github.io/rfcs/2025-nested-method-calls.html for more details // - We enable left-to-right for intuitive error messages: later borrows will report errors - unsafe { - for argument in &mut arguments { - // SAFETY: We disabled them above - // NOTE: enable() may fail if arguments conflict (e.g., same variable) - argument.to_mut().enable()?; - } - } + let arguments = disabled_arguments + .into_iter() + .map(|arg| { + let span = arg.1; + arg.try_map(|v| v.enable(span)) + }) + .collect::>>()?; (arguments, method) } }; @@ -1443,7 +1480,7 @@ enum InvocationPath { ArgumentsPath { function_span: SpanRange, interface: &'static FunctionInterface, - disabled_evaluated_arguments: Vec>, + disabled_evaluated_arguments: Vec>, }, } @@ -1466,7 +1503,7 @@ impl InvocationBuilder { .collect(), state: InvocationPath::InvokablePath, }; - context.request_copy_on_write(frame, invokable) + context.request_owned(frame, invokable) } } @@ -1485,22 +1522,22 @@ impl EvaluationFrame for InvocationBuilder { match self.state { InvocationPath::InvokablePath => { let function_span = span; - let function = value.expect_copy_on_write(); + let function = value.expect_owned(); let function = function .into_content() .spanned(function_span) - .downcast_resolve::>("An invoked value")?; + .downcast_resolve::("An invoked value")?; // I need to extract // (A): Function interface // (B): Already bound disabled arguments -> assumed empty - let interface = match &function.as_ref_value().definition { - FunctionDefinition::Native(interface) => *interface, + let interface = match function.definition { + FunctionDefinition::Native(interface) => interface, FunctionDefinition::Closure(_) => todo!(), }; // Placeholder for already bound arguments. We can assume they're already disabled, and of the correct ownership/s. - let disabled_bound_arguments = vec![]; + let disabled_bound_arguments = function.disabled_bound_arguments; let unbound_arguments_count = self.unevaluated_parameters_stack.len(); @@ -1571,11 +1608,9 @@ impl EvaluationFrame for InvocationBuilder { .. } => { let argument = value.expect_argument_value(); - let mut argument = Spanned(argument, span); - unsafe { - // SAFETY: We enable it again before use - argument.to_mut().disable(); - } + let argument = Spanned(argument, span); + // Disable argument so we can evaluate remaining arguments without borrow conflicts + let argument = argument.map(|v| v.disable()); disabled_evaluated_arguments.push(argument); } }; @@ -1589,20 +1624,20 @@ impl EvaluationFrame for InvocationBuilder { InvocationPath::InvokablePath => unreachable!("Already updated above"), InvocationPath::ArgumentsPath { interface, - disabled_evaluated_arguments: mut arguments, + disabled_evaluated_arguments, function_span, } => { // NOTE: // - This disable/enable flow allows us to do things like vec.push(vec.len()) // - Read https://rust-lang.github.io/rfcs/2025-nested-method-calls.html for more details // - We enable left-to-right for intuitive error messages: later borrows will report errors - unsafe { - for argument in &mut arguments { - // SAFETY: We disabled them above - // NOTE: enable() may fail if arguments conflict (e.g., same variable) - argument.to_mut().enable()?; - } - } + let arguments = disabled_evaluated_arguments + .into_iter() + .map(|arg| { + let span = arg.1; + arg.try_map(|v| v.enable(span)) + }) + .collect::>>()?; (arguments, interface, function_span) } }; diff --git a/src/expressions/expression_parsing.rs b/src/expressions/expression_parsing.rs index 695fbba9..d31c33fa 100644 --- a/src/expressions/expression_parsing.rs +++ b/src/expressions/expression_parsing.rs @@ -218,6 +218,7 @@ impl<'a> ExpressionParser<'a> { if punct.as_char() == '.' && input.peek2(syn::Ident) { let dot = input.parse()?; let ident = input.parse()?; + // TODO[functions]: Disable me if input.peek(token::Paren) { let (_, delim_span) = input.parse_and_enter_group(None)?; return Ok(NodeExtension::MethodCall(MethodAccess { diff --git a/src/expressions/values/function.rs b/src/expressions/values/function.rs index e1a99a36..f9ed8696 100644 --- a/src/expressions/values/function.rs +++ b/src/expressions/values/function.rs @@ -9,9 +9,18 @@ define_leaf_type! { dyn_impls: {}, } -#[derive(Clone)] pub(crate) struct FunctionValue { pub(crate) definition: FunctionDefinition, + pub(crate) disabled_bound_arguments: Vec>, +} + +impl Clone for FunctionValue { + fn clone(&self) -> Self { + Self { + definition: self.definition.clone(), + disabled_bound_arguments: self.disabled_bound_arguments.clone(), + } + } } #[derive(Clone)] @@ -52,6 +61,7 @@ impl IntoValueContent<'static> for &'static FunctionInterface { fn into_content(self) -> Content<'static, Self::Type, Self::Form> { FunctionValue { definition: FunctionDefinition::Native(self), + disabled_bound_arguments: vec![], } } } @@ -65,6 +75,7 @@ impl IntoValueContent<'static> for ClosureExpression { fn into_content(self) -> Content<'static, Self::Type, Self::Form> { FunctionValue { definition: FunctionDefinition::Closure(self), + disabled_bound_arguments: vec![], } } } diff --git a/src/extensions/errors_and_spans.rs b/src/extensions/errors_and_spans.rs index f3ce267e..9a1c04df 100644 --- a/src/extensions/errors_and_spans.rs +++ b/src/extensions/errors_and_spans.rs @@ -457,11 +457,6 @@ impl Spanned { Spanned(&self.0, self.1) } - #[inline] - pub(crate) fn to_mut(&mut self) -> Spanned<&mut T> { - Spanned(&mut self.0, self.1) - } - #[inline] pub(crate) fn map(self, f: impl FnOnce(T) -> U) -> Spanned { Spanned(f(self.0), self.1) diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index dfcc158d..466287b8 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -322,31 +322,38 @@ impl Mutable { ) -> Result, E> { Ok(Mutable(self.0.try_map(value_map)?)) } + + /// 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()) + } } -pub(crate) static MUTABLE_ERROR_MESSAGE: &str = - "The variable cannot be modified as it is already being modified"; +/// A disabled mutable reference that can be safely cloned and dropped. +pub(crate) struct DisabledMutable( + pub(crate) DisabledMutableSubRcRefCell, +); -impl Spanned<&mut AnyValueMutable> { - /// SAFETY: - /// * Must be paired with a call to `enable()` before any further use of the value. - /// * Must not use the value while disabled. - pub(crate) unsafe fn disable(&mut self) { - self.0 .0.disable(); +impl Clone for DisabledMutable { + fn clone(&self) -> Self { + Self(self.0.clone()) } +} - /// SAFETY: - /// * Must only be used after a call to `disable()`. - /// - /// Returns an ownership error if re-enabling fails (e.g., due to conflicting borrows). - pub(crate) unsafe fn enable(&mut self) -> ExecutionResult<()> { +impl DisabledMutable { + /// Re-enables this disabled mutable reference by re-acquiring the borrow. + pub(crate) fn enable(self, span: SpanRange) -> ExecutionResult> { self.0 - .0 .enable() - .map_err(|_| self.1.ownership_error(MUTABLE_ERROR_MESSAGE)) + .map(Mutable) + .map_err(|_| span.ownership_error(MUTABLE_ERROR_MESSAGE)) } } +pub(crate) static MUTABLE_ERROR_MESSAGE: &str = + "The variable cannot be modified as it is already being modified"; + impl Spanned { pub(crate) fn transparent_clone(&self) -> ExecutionResult { let value = self.0.as_ref().try_transparent_clone(self.1)?; @@ -435,31 +442,38 @@ impl Shared { 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()) + } } -pub(crate) static SHARED_ERROR_MESSAGE: &str = - "The variable cannot be read as it is already being modified"; +/// A disabled shared reference that can be safely cloned and dropped. +pub(crate) struct DisabledShared( + pub(crate) DisabledSharedSubRcRefCell, +); -impl Spanned<&mut AnyValueShared> { - /// SAFETY: - /// * Must be paired with a call to `enable()` before any further use of the value. - /// * Must not use the value while disabled. - pub(crate) unsafe fn disable(&mut self) { - self.0 .0.disable(); +impl Clone for DisabledShared { + fn clone(&self) -> Self { + Self(self.0.clone()) } +} - /// SAFETY: - /// * Must only be used after a call to `disable()`. - /// - /// Returns an ownership error if re-enabling fails (e.g., due to conflicting borrows). - pub(crate) unsafe fn enable(&mut self) -> ExecutionResult<()> { +impl DisabledShared { + /// Re-enables this disabled shared reference by re-acquiring the borrow. + pub(crate) fn enable(self, span: SpanRange) -> ExecutionResult> { self.0 - .0 .enable() - .map_err(|_| self.1.ownership_error(SHARED_ERROR_MESSAGE)) + .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) -> ExecutionResult { let value = self.0.as_ref().try_transparent_clone(self.1)?; @@ -602,38 +616,67 @@ impl CopyOnWrite { CopyOnWriteInner::SharedWithTransparentCloning(shared) => map_shared(shared), } } -} -impl Spanned<&mut CopyOnWrite> { - /// SAFETY: - /// * Must be paired with a call to `enable()` before any further use of the value. - /// * Must not use the value while disabled. - pub(crate) unsafe fn disable(&mut self) { - match &mut self.0.inner { - CopyOnWriteInner::Owned(_) => {} + /// 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) => { - shared.spanned(self.1).disable() + DisabledCopyOnWriteInner::SharedWithInfallibleCloning(shared.disable()) } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - shared.spanned(self.1).disable() + DisabledCopyOnWriteInner::SharedWithTransparentCloning(shared.disable()) } - } + }; + DisabledCopyOnWrite { inner } } +} - /// SAFETY: - /// * Must only be used after a call to `disable()`. - /// - /// Returns an ownership error if re-enabling fails (e.g., due to conflicting borrows). - pub(crate) unsafe fn enable(&mut self) -> ExecutionResult<()> { - match &mut self.0.inner { - CopyOnWriteInner::Owned(_) => Ok(()), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - shared.spanned(self.1).enable() +/// 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()) } - CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - shared.spanned(self.1).enable() + 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) -> ExecutionResult> { + 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 }) } } diff --git a/src/misc/mut_rc_ref_cell.rs b/src/misc/mut_rc_ref_cell.rs index 777e55d0..3cd34894 100644 --- a/src/misc/mut_rc_ref_cell.rs +++ b/src/misc/mut_rc_ref_cell.rs @@ -47,28 +47,15 @@ impl MutableSubRcRefCell { } } - /// SAFETY: - /// * Must be paired with a call to `enable()` before any further use of the value. - /// * Must not use the value while disabled. - pub(crate) unsafe fn disable(&mut self) { - // Ideally we'd just decrement the ref count, but RefCell doesn't expose that. - // Instead, we duplicate it, so the old value gets dropped automatically, - // decrementing the ref count. - self.ref_mut = unsafe { core::ptr::read(&self.ref_mut) }; - } - - /// SAFETY: - /// * Must only be used after a call to `disable()`. - pub(crate) unsafe fn enable(&mut self) -> Result<(), BorrowMutError> { - // Ideally we'd just increment the ref count, but RefCell doesn't expose that. - // Instead, we re-borrow it mutably, which increments the ref count, then forget - // the new borrow. - match self.pointed_at.try_borrow_mut() { - Ok(new_ref_mut) => { - std::mem::forget(new_ref_mut); - Ok(()) - } - Err(e) => Err(e), + /// 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, } } @@ -180,6 +167,45 @@ impl Deref for MutableSubRcRefCell { + 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, + }) + } +} + /// 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. @@ -268,28 +294,15 @@ impl SharedSubRcRefCell { f(&*copied_ref, &mut emplacer) } - /// SAFETY: - /// * Must be paired with a call to `enable()` before any further use of the value. - /// * Must not use the value while disabled. - pub(crate) unsafe fn disable(&mut self) { - // Ideally we'd just decrement the ref count, but RefCell doesn't expose that. - // Instead, we duplicate it, so the old value gets dropped automatically, - // decrementing the ref count. - self.shared_ref = unsafe { core::ptr::read(&self.shared_ref) }; - } - - /// SAFETY: - /// * Must only be used after a call to `disable()`. - pub(crate) unsafe fn enable(&mut self) -> Result<(), BorrowError> { - // Ideally we'd just increment the ref count, but RefCell doesn't expose that. - // Instead, we re-borrow it mutably, which increments the ref count, then forget - // the new borrow. - match self.pointed_at.try_borrow() { - Ok(new_ref_mut) => { - std::mem::forget(new_ref_mut); - Ok(()) - } - Err(e) => Err(e), + /// 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, } } } @@ -336,6 +349,45 @@ impl Deref for SharedSubRcRefCell { } } +/// 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 { diff --git a/tests/functions.rs b/tests/functions.rs index bddc5bc3..03d69511 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -23,6 +23,22 @@ fn test_type_function_works() { } } +#[test] +fn test_method_function_has_receiver_bound() { + run! { + let arr = [1, 2, 3]; + let push_me = arr.push; + push_me(4); + %[_].assert_eq(arr.to_debug_string(), "[1, 2, 3, 4]"); + } + // run! { + // let arr = [1, 2, 3]; + // let x = %{ push: arr.push }; + // x.push(4); + // %[_].assert_eq(arr.to_debug_string(), "[1, 2, 3, 4]"); + // } +} + #[test] fn test_preinterpret_api() { // This should complete OK From 849f53d1be40c4b6f1ef7b7a77ba2306fe7d4590 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 24 Jan 2026 09:38:07 +0000 Subject: [PATCH 04/49] chore: Remove MethodCall now that it composes from a property access and an invocation --- plans/TODO.md | 5 +- .../evaluation/control_flow_analysis.rs | 8 - src/expressions/evaluation/node_conversion.rs | 5 - src/expressions/evaluation/value_frames.rs | 180 ------------------ src/expressions/expression.rs | 6 - src/expressions/expression_parsing.rs | 63 +----- src/expressions/operations.rs | 13 -- .../complex/nested.stderr | 6 +- .../core/typed_eq_nested_type_mismatch.stderr | 4 +- ...typed_eq_object_value_type_mismatch.stderr | 4 +- ...ped_eq_object_value_type_mismatch_2.stderr | 4 +- .../core/typed_eq_type_mismatch.stderr | 4 +- .../iteration/infinite_range_to_vec.stderr | 4 +- .../intersperse_too_few_arguments.stderr | 6 +- .../intersperse_too_many_arguments.stderr | 6 +- .../iteration/long_iterable_to_string.stderr | 4 +- .../iteration/long_iterable_to_vec.stderr | 4 +- .../iteration/long_range_to_vec.stderr | 4 +- .../zip_different_length_streams.stderr | 4 +- 19 files changed, 35 insertions(+), 299 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index 756a2c6e..c5fa0942 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -218,9 +218,8 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] Add some bound receiver or something or more generally `bound_arguments: Vec` - these should be deactivated - [x] `my_array.push` returns a function with `my_array` bound. The LateBound `my_array` can then be deactivated whilst the rest of the arguments are resolved, like what we do at the moment. This approach avoids the horrible javascript issues with `this` not being bound when referencing `x.y`. - [x] And then for objects, the method wins; BUT you can use `x["obj"]` to access the field instead of the method - - [ ] It's an error to set `x.y` if `y` is a method on object - - [ ] We can try commenting out the code to resolve a method extension, - and it should still work, courtesy of property access + invocation + - [x] We can remove method extension code + - [ ] It's an error to set `x.y = z` or `%{ y: z }` if `y` is a method on object - [ ] We can define closures (without any closed values for now) * Value type function `let my_func = |x, y, z| { ... };` * Parameters can be `x` / `x: any` (Owned), `x: &any` (Shared) or `x: &mut any` (Mutable). diff --git a/src/expressions/evaluation/control_flow_analysis.rs b/src/expressions/evaluation/control_flow_analysis.rs index 0339c771..9603f92c 100644 --- a/src/expressions/evaluation/control_flow_analysis.rs +++ b/src/expressions/evaluation/control_flow_analysis.rs @@ -45,14 +45,6 @@ pub(in super::super) fn control_flow_visit( ExpressionNode::Property { node, .. } => { stack.push(*node); } - ExpressionNode::MethodCall { - receiver, - invocation: Invocation { parameters, .. }, - .. - } => { - stack.push_reversed(parameters.iter().copied()); - stack.push(*receiver); // This is a stack so this executes first - } ExpressionNode::Invocation { invokable, invocation: Invocation { parameters, .. }, diff --git a/src/expressions/evaluation/node_conversion.rs b/src/expressions/evaluation/node_conversion.rs index 713952f3..443f9e33 100644 --- a/src/expressions/evaluation/node_conversion.rs +++ b/src/expressions/evaluation/node_conversion.rs @@ -123,11 +123,6 @@ impl ExpressionNode { equals_token, value, } => AssignmentBuilder::start(context, *assignee, *equals_token, *value), - ExpressionNode::MethodCall { - receiver, - method, - invocation, - } => MethodCallBuilder::start(context, *receiver, method, invocation), ExpressionNode::Invocation { invokable, invocation, diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index 3b973050..766d7e65 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -577,7 +577,6 @@ pub(super) enum AnyValueFrame { IndexAccess(ValueIndexAccessBuilder), Range(RangeBuilder), Assignment(AssignmentBuilder), - MethodCall(MethodCallBuilder), Invocation(InvocationBuilder), } @@ -597,7 +596,6 @@ impl AnyValueFrame { AnyValueFrame::IndexAccess(frame) => frame.handle_next(context, value), AnyValueFrame::Range(frame) => frame.handle_next(context, value), AnyValueFrame::Assignment(frame) => frame.handle_next(context, value), - AnyValueFrame::MethodCall(frame) => frame.handle_next(context, value), AnyValueFrame::Invocation(frame) => frame.handle_next(context, value), } } @@ -1291,184 +1289,6 @@ impl EvaluationFrame for AssignmentBuilder { } } -pub(super) struct MethodCallBuilder { - method: PropertyAccess, - parentheses: Parentheses, - unevaluated_parameters_stack: Vec<(ExpressionNodeId, ArgumentOwnership)>, - state: MethodCallPath, -} - -enum MethodCallPath { - CallerPath, - ArgumentsPath { - method: &'static FunctionInterface, - disabled_evaluated_arguments_including_caller: Vec>, - }, -} - -impl MethodCallBuilder { - pub(super) fn start( - context: ValueContext, - receiver: ExpressionNodeId, - method: &PropertyAccess, - invocation: &Invocation, - ) -> NextAction { - let frame = Self { - method: method.clone(), - parentheses: invocation.parentheses, - unevaluated_parameters_stack: invocation - .parameters - .iter() - .rev() - .map(|x| { - // This is just a placeholder - we'll fix it up shortly - (*x, ArgumentOwnership::Owned) - }) - .collect(), - state: MethodCallPath::CallerPath, - }; - context.request_late_bound(frame, receiver) - } -} - -impl EvaluationFrame for MethodCallBuilder { - type ReturnType = ReturnsValue; - - fn into_any(self) -> AnyValueFrame { - AnyValueFrame::MethodCall(self) - } - - fn handle_next( - mut self, - mut context: ValueContext, - Spanned(value, span): Spanned, - ) -> ExecutionResult { - // Handle expected item based on current state - match self.state { - MethodCallPath::CallerPath => { - let caller_span = span; - let caller = value.expect_late_bound(); - let method_name = self.method.property.to_string(); - let method_name = method_name.as_str(); - let method = caller.kind().feature_resolver().resolve_method(method_name); - let method = match method { - Some(m) => m, - None => { - return self.method.property.type_err(format!( - "The method {} does not exist on {}", - method_name, - caller.articled_kind(), - )) - } - }; - let non_caller_arguments = self.unevaluated_parameters_stack.len(); - let (argument_ownerships, min_arguments) = method.argument_ownerships(); - let max_arguments = argument_ownerships.len(); - assert!( - max_arguments >= 1 && min_arguments >= 1, - "Method calls must have at least one argument (the caller)" - ); - let non_caller_min_arguments = min_arguments - 1; - let non_caller_max_arguments = max_arguments - 1; - - if non_caller_arguments < non_caller_min_arguments - || non_caller_arguments > non_caller_max_arguments - { - return self.method.property.type_err(format!( - "The method {} expects {} non-self argument/s, but {} were provided", - method_name, - if non_caller_min_arguments == non_caller_max_arguments { - (non_caller_min_arguments).to_string() - } else { - format!( - "{} to {}", - (non_caller_min_arguments), - (non_caller_max_arguments) - ) - }, - non_caller_arguments, - )); - } - let caller = - argument_ownerships[0].map_from_late_bound(Spanned(caller, caller_span))?; - let caller = Spanned(caller, caller_span); - - // We skip 1 to ignore the caller - let non_self_argument_ownerships: iter::Skip< - std::slice::Iter<'_, ArgumentOwnership>, - > = argument_ownerships.iter().skip(1); - for ((_, requested_ownership), ownership) in self - .unevaluated_parameters_stack - .iter_mut() - .rev() // Swap the stack back to the normal order - .zip(non_self_argument_ownerships) - { - *requested_ownership = *ownership; - } - - self.state = MethodCallPath::ArgumentsPath { - disabled_evaluated_arguments_including_caller: { - let mut params = - Vec::with_capacity(1 + self.unevaluated_parameters_stack.len()); - // Disable caller so we can evaluate remaining arguments without borrow conflicts - let caller = caller.map(|v| v.disable()); - params.push(caller); - params - }, - method, - }; - } - MethodCallPath::ArgumentsPath { - ref mut disabled_evaluated_arguments_including_caller, - .. - } => { - 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()); - disabled_evaluated_arguments_including_caller.push(argument); - } - }; - // Now plan the next action - Ok(match self.unevaluated_parameters_stack.pop() { - Some((parameter, ownership)) => { - context.request_any_value(self, parameter, RequestedOwnership::Concrete(ownership)) - } - None => { - let (arguments, method) = match self.state { - MethodCallPath::CallerPath => unreachable!("Already updated above"), - MethodCallPath::ArgumentsPath { - disabled_evaluated_arguments_including_caller: disabled_arguments, - method, - } => { - // NOTE: - // - This disable/enable flow allows us to do things like vec.push(vec.len()) - // - Read https://rust-lang.github.io/rfcs/2025-nested-method-calls.html for more details - // - We enable left-to-right for intuitive error messages: later borrows will report errors - let arguments = disabled_arguments - .into_iter() - .map(|arg| { - let span = arg.1; - arg.try_map(|v| v.enable(span)) - }) - .collect::>>()?; - (arguments, method) - } - }; - let mut call_context = FunctionCallContext { - output_span_range: SpanRange::new_between( - self.method.property, - self.parentheses.close(), - ), - interpreter: context.interpreter(), - }; - let output = method.execute(arguments, &mut call_context)?; - context.return_returned_value(output)? - } - }) - } -} - pub(super) struct InvocationBuilder { parentheses: Parentheses, unevaluated_parameters_stack: Vec<(ExpressionNodeId, ArgumentOwnership)>, diff --git a/src/expressions/expression.rs b/src/expressions/expression.rs index a92d36d1..6c3a4829 100644 --- a/src/expressions/expression.rs +++ b/src/expressions/expression.rs @@ -122,12 +122,6 @@ pub(super) enum ExpressionNode { node: ExpressionNodeId, access: PropertyAccess, }, - // This could be merged into Invocation, once Invocation is feature complete - MethodCall { - receiver: ExpressionNodeId, - method: PropertyAccess, - invocation: Invocation, - }, Invocation { invokable: ExpressionNodeId, invocation: Invocation, diff --git a/src/expressions/expression_parsing.rs b/src/expressions/expression_parsing.rs index d31c33fa..7784557c 100644 --- a/src/expressions/expression_parsing.rs +++ b/src/expressions/expression_parsing.rs @@ -216,20 +216,9 @@ impl<'a> ExpressionParser<'a> { // TODO[performance]: Get rid of the try_parse_or_revert and convert this into // a parse tree if punct.as_char() == '.' && input.peek2(syn::Ident) { - let dot = input.parse()?; - let ident = input.parse()?; - // TODO[functions]: Disable me - if input.peek(token::Paren) { - let (_, delim_span) = input.parse_and_enter_group(None)?; - return Ok(NodeExtension::MethodCall(MethodAccess { - dot, - method: ident, - parentheses: Parentheses { delim_span }, - })); - } return Ok(NodeExtension::Property(PropertyAccess { - dot, - property: ident, + dot: input.parse()?, + property: input.parse()?, })); } if let Some((punct, _)) = input.cursor().punct_matching('=') { @@ -385,33 +374,6 @@ impl<'a> ExpressionParser<'a> { .nodes .add_node(ExpressionNode::Property { node, access }), }, - NodeExtension::MethodCall(method) => { - let property: PropertyAccess = PropertyAccess { - dot: method.dot, - property: method.method, - }; - let invocation = Invocation { - parentheses: method.parentheses, - parameters: Vec::new(), - }; - if self.streams.is_current_empty() { - self.streams.exit_group(None)?; - let node = self.nodes.add_node(ExpressionNode::MethodCall { - receiver: node, - method: property, - invocation, - }); - WorkItem::TryParseAndApplyExtension { node } - } else { - self.push_stack_frame( - ExpressionStackFrame::NonEmptyInvocationParametersList { - node, - method: Some(property), - invocation, - }, - ) - } - } NodeExtension::Invocation(invocation) => { if self.streams.is_current_empty() { self.streams.exit_group(None)?; @@ -424,7 +386,6 @@ impl<'a> ExpressionParser<'a> { self.push_stack_frame( ExpressionStackFrame::NonEmptyInvocationParametersList { node, - method: None, invocation, }, ) @@ -486,23 +447,15 @@ impl<'a> ExpressionParser<'a> { } ExpressionStackFrame::NonEmptyInvocationParametersList { node: source, - method, mut invocation, } => { assert!(matches!(extension, NodeExtension::EndOfStreamOrGroup)); invocation.parameters.push(node); self.streams.exit_group(None)?; - let node = match method { - Some(method) => self.nodes.add_node(ExpressionNode::MethodCall { - receiver: source, - method, - invocation, - }), - None => self.nodes.add_node(ExpressionNode::Invocation { - invokable: source, - invocation, - }), - }; + let node = self.nodes.add_node(ExpressionNode::Invocation { + invokable: source, + invocation, + }); WorkItem::TryParseAndApplyExtension { node } } ExpressionStackFrame::NonEmptyObject { @@ -966,7 +919,6 @@ pub(super) enum ExpressionStackFrame { /// * When the invocation parameters list is closed, we pop it from the parse stream stack NonEmptyInvocationParametersList { node: ExpressionNodeId, - method: Option, invocation: Invocation, }, /// An incomplete unary prefix operation @@ -1080,7 +1032,6 @@ pub(super) enum NodeExtension { /// Used under Arrays, Objects, and Method Parameter List parents NonTerminalComma, Property(PropertyAccess), - MethodCall(MethodAccess), Index(IndexAccess), Invocation(Invocation), Range(syn::RangeLimits), @@ -1096,7 +1047,6 @@ impl NodeExtension { NodeExtension::BinaryOperation(op) => OperatorPrecendence::of_binary_operation(op), NodeExtension::NonTerminalComma => OperatorPrecendence::NonTerminalComma, NodeExtension::Property { .. } => OperatorPrecendence::Unambiguous, - NodeExtension::MethodCall { .. } => OperatorPrecendence::Unambiguous, NodeExtension::Index { .. } => OperatorPrecendence::Unambiguous, NodeExtension::Invocation { .. } => OperatorPrecendence::Unambiguous, NodeExtension::Range(_) => OperatorPrecendence::Range, @@ -1113,7 +1063,6 @@ impl NodeExtension { extension @ (NodeExtension::PostfixOperation { .. } | NodeExtension::BinaryOperation { .. } | NodeExtension::Property { .. } - | NodeExtension::MethodCall { .. } | NodeExtension::Index { .. } | NodeExtension::Invocation { .. } | NodeExtension::Range { .. } diff --git a/src/expressions/operations.rs b/src/expressions/operations.rs index 271198be..88fe6c63 100644 --- a/src/expressions/operations.rs +++ b/src/expressions/operations.rs @@ -501,19 +501,6 @@ impl HasSpanRange for PropertyAccess { } } -#[derive(Clone)] -pub(crate) struct MethodAccess { - pub(super) dot: Token![.], - pub(crate) method: Ident, - pub(crate) parentheses: Parentheses, -} - -impl HasSpanRange for MethodAccess { - fn span_range(&self) -> SpanRange { - SpanRange::new_between(self.dot.span, self.parentheses.span()) - } -} - #[derive(Copy, Clone)] pub(crate) struct IndexAccess { pub(crate) brackets: Brackets, diff --git a/tests/compilation_failures/complex/nested.stderr b/tests/compilation_failures/complex/nested.stderr index c08422b5..a00832a8 100644 --- a/tests/compilation_failures/complex/nested.stderr +++ b/tests/compilation_failures/complex/nested.stderr @@ -1,5 +1,5 @@ -error: The method error expects 1 non-self argument/s, but 0 were provided - --> tests/compilation_failures/complex/nested.rs:9:25 +error: This function expects 1 argument, but 0 were provided + --> tests/compilation_failures/complex/nested.rs:9:21 | 9 | %[].error() - | ^^^^^ + | ^^^^^^^^^ diff --git a/tests/compilation_failures/core/typed_eq_nested_type_mismatch.stderr b/tests/compilation_failures/core/typed_eq_nested_type_mismatch.stderr index e72688bd..e48f2b27 100644 --- a/tests/compilation_failures/core/typed_eq_nested_type_mismatch.stderr +++ b/tests/compilation_failures/core/typed_eq_nested_type_mismatch.stderr @@ -1,5 +1,5 @@ error: lhs[1] is an untyped integer, but rhs[1] is a string - --> tests/compilation_failures/core/typed_eq_nested_type_mismatch.rs:5:29 + --> tests/compilation_failures/core/typed_eq_nested_type_mismatch.rs:5:22 | 5 | run!(%[_].assert([1, 2].typed_eq([1, "two"]))); - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/core/typed_eq_object_value_type_mismatch.stderr b/tests/compilation_failures/core/typed_eq_object_value_type_mismatch.stderr index a464790d..39253866 100644 --- a/tests/compilation_failures/core/typed_eq_object_value_type_mismatch.stderr +++ b/tests/compilation_failures/core/typed_eq_object_value_type_mismatch.stderr @@ -1,5 +1,5 @@ error: lhs.b is an untyped integer, but rhs.b is a string - --> tests/compilation_failures/core/typed_eq_object_value_type_mismatch.rs:5:38 + --> tests/compilation_failures/core/typed_eq_object_value_type_mismatch.rs:5:23 | 5 | let result = %{ a: 1, b: 2 }.typed_eq(%{ a: 1, b: "two" }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.stderr b/tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.stderr index 9c428a23..f82846f1 100644 --- a/tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.stderr +++ b/tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.stderr @@ -1,5 +1,5 @@ error: lhs["multi-word key"] is an untyped integer, but rhs["multi-word key"] is a string - --> tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.rs:5:55 + --> tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.rs:5:23 | 5 | let result = %{ a: 1, ["multi-word key"]: 2 }.typed_eq(%{ a: 1, ["multi-word key"]: "two" }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/core/typed_eq_type_mismatch.stderr b/tests/compilation_failures/core/typed_eq_type_mismatch.stderr index 0d332253..0cfcb6ae 100644 --- a/tests/compilation_failures/core/typed_eq_type_mismatch.stderr +++ b/tests/compilation_failures/core/typed_eq_type_mismatch.stderr @@ -1,5 +1,5 @@ error: lhs is an untyped integer, but rhs is a string - --> tests/compilation_failures/core/typed_eq_type_mismatch.rs:5:24 + --> tests/compilation_failures/core/typed_eq_type_mismatch.rs:5:22 | 5 | run!(%[_].assert(1.typed_eq("hello"))); - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/iteration/infinite_range_to_vec.stderr b/tests/compilation_failures/iteration/infinite_range_to_vec.stderr index 01cda0fd..6381af47 100644 --- a/tests/compilation_failures/iteration/infinite_range_to_vec.stderr +++ b/tests/compilation_failures/iteration/infinite_range_to_vec.stderr @@ -1,6 +1,6 @@ error: Iteration limit of 1000 exceeded. If needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX) - --> tests/compilation_failures/iteration/infinite_range_to_vec.rs:4:24 + --> tests/compilation_failures/iteration/infinite_range_to_vec.rs:4:18 | 4 | let _ = run!((0..).to_vec()); - | ^^^^^^^^ + | ^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/iteration/intersperse_too_few_arguments.stderr b/tests/compilation_failures/iteration/intersperse_too_few_arguments.stderr index 862a971e..f84ff8fa 100644 --- a/tests/compilation_failures/iteration/intersperse_too_few_arguments.stderr +++ b/tests/compilation_failures/iteration/intersperse_too_few_arguments.stderr @@ -1,5 +1,5 @@ -error: The method intersperse expects 1 to 2 non-self argument/s, but 0 were provided - --> tests/compilation_failures/iteration/intersperse_too_few_arguments.rs:5:16 +error: This function expects 1 to 2 arguments, but 0 were provided + --> tests/compilation_failures/iteration/intersperse_too_few_arguments.rs:5:9 | 5 | [1, 2].intersperse() - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/iteration/intersperse_too_many_arguments.stderr b/tests/compilation_failures/iteration/intersperse_too_many_arguments.stderr index 699bac24..44737415 100644 --- a/tests/compilation_failures/iteration/intersperse_too_many_arguments.stderr +++ b/tests/compilation_failures/iteration/intersperse_too_many_arguments.stderr @@ -1,5 +1,5 @@ -error: The method intersperse expects 1 to 2 non-self argument/s, but 3 were provided - --> tests/compilation_failures/iteration/intersperse_too_many_arguments.rs:5:16 +error: This function expects 1 to 2 arguments, but 3 were provided + --> tests/compilation_failures/iteration/intersperse_too_many_arguments.rs:5:9 | 5 | [1, 2].intersperse("", %{}, %{}) - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/iteration/long_iterable_to_string.stderr b/tests/compilation_failures/iteration/long_iterable_to_string.stderr index 56d0813b..ccf86dff 100644 --- a/tests/compilation_failures/iteration/long_iterable_to_string.stderr +++ b/tests/compilation_failures/iteration/long_iterable_to_string.stderr @@ -1,5 +1,5 @@ error: To protect against infinite loops, only a maximum of 1000 items can be output to a string from an iterator. You can use .to_vec() to avoid this limit. This can't currently be reconfigured with the iteration limit. - --> tests/compilation_failures/iteration/long_iterable_to_string.rs:4:21 + --> tests/compilation_failures/iteration/long_iterable_to_string.rs:4:10 | 4 | run!((0..10000).into_iter().to_string()) - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/iteration/long_iterable_to_vec.stderr b/tests/compilation_failures/iteration/long_iterable_to_vec.stderr index 76eae9f8..236a4ad1 100644 --- a/tests/compilation_failures/iteration/long_iterable_to_vec.stderr +++ b/tests/compilation_failures/iteration/long_iterable_to_vec.stderr @@ -1,6 +1,6 @@ error: Iteration limit of 1000 exceeded. If needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX) - --> tests/compilation_failures/iteration/long_iterable_to_vec.rs:4:33 + --> tests/compilation_failures/iteration/long_iterable_to_vec.rs:4:10 | 4 | run!((0..10000).into_iter().to_vec()) - | ^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/iteration/long_range_to_vec.stderr b/tests/compilation_failures/iteration/long_range_to_vec.stderr index e9dc623c..eff301f7 100644 --- a/tests/compilation_failures/iteration/long_range_to_vec.stderr +++ b/tests/compilation_failures/iteration/long_range_to_vec.stderr @@ -1,6 +1,6 @@ error: Iteration limit of 1000 exceeded. If needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX) - --> tests/compilation_failures/iteration/long_range_to_vec.rs:4:21 + --> tests/compilation_failures/iteration/long_range_to_vec.rs:4:10 | 4 | run!((0..10000).to_vec()) - | ^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/iteration/zip_different_length_streams.stderr b/tests/compilation_failures/iteration/zip_different_length_streams.stderr index b0a7e1de..9b2e2748 100644 --- a/tests/compilation_failures/iteration/zip_different_length_streams.stderr +++ b/tests/compilation_failures/iteration/zip_different_length_streams.stderr @@ -1,5 +1,5 @@ error: The iterables have different lengths. The lengths vary from 3 to 4. To truncate to the shortest, use `zip_truncated` instead of `zip` - --> tests/compilation_failures/iteration/zip_different_length_streams.rs:5:41 + --> tests/compilation_failures/iteration/zip_different_length_streams.rs:5:9 | 5 | [["A", "B", "C"], [1, 2, 3, 4]].zip() - | ^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 0e94e37dbfaa6656d3e164b96f8a5d7f6ab502f6 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 24 Jan 2026 11:02:27 +0000 Subject: [PATCH 05/49] feat: Cannot assign to object property matching a method --- plans/TODO.md | 6 ++- src/expressions/evaluation/value_frames.rs | 48 +++++++++++++++++-- .../expressions/assign_to_method_on_array.rs | 8 ++++ .../assign_to_method_on_array.stderr | 5 ++ .../expressions/assign_to_method_property.rs | 11 +++++ .../assign_to_method_property.stderr | 5 ++ .../assign_to_property_on_array.rs | 8 ++++ .../assign_to_property_on_array.stderr | 5 ++ .../assign_to_string_key_on_array.rs | 9 ++++ .../assign_to_string_key_on_array.stderr | 5 ++ .../expressions/object_literal_method_key.rs | 8 ++++ .../object_literal_method_key.stderr | 5 ++ tests/expressions.rs | 26 ++++++++++ tests/functions.rs | 12 ++--- 14 files changed, 149 insertions(+), 12 deletions(-) create mode 100644 tests/compilation_failures/expressions/assign_to_method_on_array.rs create mode 100644 tests/compilation_failures/expressions/assign_to_method_on_array.stderr create mode 100644 tests/compilation_failures/expressions/assign_to_method_property.rs create mode 100644 tests/compilation_failures/expressions/assign_to_method_property.stderr create mode 100644 tests/compilation_failures/expressions/assign_to_property_on_array.rs create mode 100644 tests/compilation_failures/expressions/assign_to_property_on_array.stderr create mode 100644 tests/compilation_failures/expressions/assign_to_string_key_on_array.rs create mode 100644 tests/compilation_failures/expressions/assign_to_string_key_on_array.stderr create mode 100644 tests/compilation_failures/expressions/object_literal_method_key.rs create mode 100644 tests/compilation_failures/expressions/object_literal_method_key.stderr diff --git a/plans/TODO.md b/plans/TODO.md index c5fa0942..45b87f7b 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -214,12 +214,13 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] Allow definition of static functions on a type - [x] Move `().configure_preinterpret` to `preinterpret::set_iteration_limit` and update the message: `If needed, the limit can be reconfigured with` -- [ ] `x.hello` accessing a method creates a `NativeFunction` with the first parameter bound to `x` +- [x] `x.hello` accessing a method creates a `NativeFunction` with the first parameter bound to `x` - [x] Add some bound receiver or something or more generally `bound_arguments: Vec` - these should be deactivated - [x] `my_array.push` returns a function with `my_array` bound. The LateBound `my_array` can then be deactivated whilst the rest of the arguments are resolved, like what we do at the moment. This approach avoids the horrible javascript issues with `this` not being bound when referencing `x.y`. - [x] And then for objects, the method wins; BUT you can use `x["obj"]` to access the field instead of the method - [x] We can remove method extension code - - [ ] It's an error to set `x.y = z` or `%{ y: z }` if `y` is a method on object + - [x] It's an error to set `x.y = z` or `%{ y: z }` if `y` is a method on object +- [ ] Fix `x["new_key"]` should be `None` - [ ] We can define closures (without any closed values for now) * Value type function `let my_func = |x, y, z| { ... };` * Parameters can be `x` / `x: any` (Owned), `x: &any` (Shared) or `x: &mut any` (Mutable). @@ -249,6 +250,7 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [ ] Support optional arguments - [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` - [ ] Add `array.sort`, `array.sort_by` +- [ ] Resolve all `TODO[functions]` ## Parser - Methods using closures diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index 766d7e65..e3571aea 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -199,6 +199,13 @@ impl RequestedOwnership { } } + pub(crate) fn is_assignee_request(&self) -> bool { + matches!( + self, + RequestedOwnership::Concrete(ArgumentOwnership::Assignee { .. }) + ) + } + pub(crate) fn map_none(self, span: SpanRange) -> ExecutionResult> { self.map_from_owned(Spanned(().into_any_value(), span)) } @@ -735,6 +742,13 @@ impl ObjectBuilder { if self.evaluated_entries.contains_key(&key) { return ident.syntax_err(format!("The key {} has already been set", key)); } + // Check if the key shadows a method on ObjectType + if ::resolve_own_method(&key).is_some() { + return ident.type_err(format!( + "Cannot use `{}` as an object key because it is a method on objects. Use `[\"{}\"]` instead.", + key, key + )); + } self.pending = Some(PendingEntryPath::OnValueBranch { key, key_span: ident.span(), @@ -988,8 +1002,24 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { let resolver = source_kind.feature_resolver(); // Attempt to resolve a method first - // (NB: It's an error to set an object property which is already set to a native method) - if let Some(method) = resolver.resolve_method(&self.access.property.to_string()) { + let property_name = self.access.property.to_string(); + if let Some(method) = resolver.resolve_method(&property_name) { + // It's an error to assign to a property that shadows a method + if context.requested_ownership().is_assignee_request() { + return self.access.property.type_err(format!( + "Cannot assign to `.{}` because it is a method on {}{}", + property_name, + source_kind.articled_display_name(), + if matches!(source_kind, AnyValueLeafKind::Object(_)) { + format!( + ". Use `[\"{}\"]` instead to set the property.", + property_name + ) + } else { + "".to_string() + }, + )); + } let receiver = value.expect_late_bound(); let receiver_ownership = method.argument_ownerships().0[0]; let receiver = receiver_ownership @@ -1007,8 +1037,10 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { // Query type resolution for property access capability let Some(interface) = resolver.resolve_property_access() else { return self.access.type_err(format!( - "Cannot access properties on {}", - source_kind.articled_display_name() + "`{}` is not a method on {}, and the {} type does not support fields", + property_name, + source_kind.articled_display_name(), + source_kind.source_type_name(), )); }; @@ -1017,6 +1049,14 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { }; let auto_create = context.requested_ownership().requests_auto_create(); + // If we need an owned value, we can try resolving a reference and + // clone the outputted value if needed - which can be much cheaper. + // e.g. `let x = arr[0]` only copies `arr[0]` instead of the whole array. + let Spanned(value, _) = context + .requested_ownership() + .replace_owned_with_copy_on_write() + .map_from_late_bound(Spanned(value.expect_late_bound(), source_span))?; + // Execute the access via the interface let mapped = value.expect_any_value_and_map( |shared| shared.try_map(|value| (interface.shared_access)(ctx, value)), diff --git a/tests/compilation_failures/expressions/assign_to_method_on_array.rs b/tests/compilation_failures/expressions/assign_to_method_on_array.rs new file mode 100644 index 00000000..6d34b000 --- /dev/null +++ b/tests/compilation_failures/expressions/assign_to_method_on_array.rs @@ -0,0 +1,8 @@ +use preinterpret::*; + +fn main() { + run!{ + let arr = [1, 2, 3]; + arr.to_string = "hello"; + }; +} diff --git a/tests/compilation_failures/expressions/assign_to_method_on_array.stderr b/tests/compilation_failures/expressions/assign_to_method_on_array.stderr new file mode 100644 index 00000000..076dc6e8 --- /dev/null +++ b/tests/compilation_failures/expressions/assign_to_method_on_array.stderr @@ -0,0 +1,5 @@ +error: Cannot assign to `.to_string` because it is a method on an array + --> tests/compilation_failures/expressions/assign_to_method_on_array.rs:6:13 + | +6 | arr.to_string = "hello"; + | ^^^^^^^^^ diff --git a/tests/compilation_failures/expressions/assign_to_method_property.rs b/tests/compilation_failures/expressions/assign_to_method_property.rs new file mode 100644 index 00000000..5f5428c0 --- /dev/null +++ b/tests/compilation_failures/expressions/assign_to_method_property.rs @@ -0,0 +1,11 @@ +use preinterpret::*; + +fn main() { + let _ = stream!{ + #({ + let obj = %{}; + // zip is a method on objects, so this should error + obj.zip = 5; + }) + }; +} diff --git a/tests/compilation_failures/expressions/assign_to_method_property.stderr b/tests/compilation_failures/expressions/assign_to_method_property.stderr new file mode 100644 index 00000000..fe6f1f1d --- /dev/null +++ b/tests/compilation_failures/expressions/assign_to_method_property.stderr @@ -0,0 +1,5 @@ +error: Cannot assign to `.zip` because it is a method on an object. Use `["zip"]` instead to set the property. + --> tests/compilation_failures/expressions/assign_to_method_property.rs:8:17 + | +8 | obj.zip = 5; + | ^^^ diff --git a/tests/compilation_failures/expressions/assign_to_property_on_array.rs b/tests/compilation_failures/expressions/assign_to_property_on_array.rs new file mode 100644 index 00000000..56fe8ac0 --- /dev/null +++ b/tests/compilation_failures/expressions/assign_to_property_on_array.rs @@ -0,0 +1,8 @@ +use preinterpret::*; + +fn main() { + run!{ + let arr = [1, 2, 3]; + arr.xyz = "hello"; + }; +} diff --git a/tests/compilation_failures/expressions/assign_to_property_on_array.stderr b/tests/compilation_failures/expressions/assign_to_property_on_array.stderr new file mode 100644 index 00000000..034ad073 --- /dev/null +++ b/tests/compilation_failures/expressions/assign_to_property_on_array.stderr @@ -0,0 +1,5 @@ +error: `xyz` is not a method on an array, and the array type does not support fields + --> tests/compilation_failures/expressions/assign_to_property_on_array.rs:6:12 + | +6 | arr.xyz = "hello"; + | ^^^^ diff --git a/tests/compilation_failures/expressions/assign_to_string_key_on_array.rs b/tests/compilation_failures/expressions/assign_to_string_key_on_array.rs new file mode 100644 index 00000000..b021f1d8 --- /dev/null +++ b/tests/compilation_failures/expressions/assign_to_string_key_on_array.rs @@ -0,0 +1,9 @@ +use preinterpret::*; + +fn main() { + run!{ + let arr = [1, 2, 3]; + arr["to_string"] = "hello"; + let _ = arr; + }; +} diff --git a/tests/compilation_failures/expressions/assign_to_string_key_on_array.stderr b/tests/compilation_failures/expressions/assign_to_string_key_on_array.stderr new file mode 100644 index 00000000..5b0edb99 --- /dev/null +++ b/tests/compilation_failures/expressions/assign_to_string_key_on_array.stderr @@ -0,0 +1,5 @@ +error: The index must be an integer or a range + --> tests/compilation_failures/expressions/assign_to_string_key_on_array.rs:6:13 + | +6 | arr["to_string"] = "hello"; + | ^^^^^^^^^^^ diff --git a/tests/compilation_failures/expressions/object_literal_method_key.rs b/tests/compilation_failures/expressions/object_literal_method_key.rs new file mode 100644 index 00000000..ffc6ebc8 --- /dev/null +++ b/tests/compilation_failures/expressions/object_literal_method_key.rs @@ -0,0 +1,8 @@ +use preinterpret::*; + +fn main() { + let _ = stream!{ + // zip is a method on objects, so using it as an identifier key should error + #(%{ zip: 5 }) + }; +} diff --git a/tests/compilation_failures/expressions/object_literal_method_key.stderr b/tests/compilation_failures/expressions/object_literal_method_key.stderr new file mode 100644 index 00000000..432ae7ec --- /dev/null +++ b/tests/compilation_failures/expressions/object_literal_method_key.stderr @@ -0,0 +1,5 @@ +error: Cannot use `zip` as an object key because it is a method on objects. Use `["zip"]` instead. + --> tests/compilation_failures/expressions/object_literal_method_key.rs:6:14 + | +6 | #(%{ zip: 5 }) + | ^^^ diff --git a/tests/expressions.rs b/tests/expressions.rs index f4e9b7f4..02c906cf 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -549,6 +549,32 @@ fn test_objects() { ), r#"%{ a: 1, b: 7, c: None, x: %{}, z: None }"# ); + // Method-shadowing keys can be set/accessed via indexed syntax + // "zip" is a method on objects, but we can still use it as a key via ["zip"] + run!( + let obj = %{ ["zip"]: 123 }; + %[_].assert_eq(obj["zip"], 123); + ); + // Setting via index access also works + run!( + let obj = %{}; + obj["zip"] = 456; + %[_].assert_eq(obj["zip"], 456); + ); + // Accessing an unset method-named key via index returns None + assert_eq!( + run!( + let obj = %{}; + obj["zip"].to_debug_string() + ), + "None" + ); + // TODO[functions]: Work out why this test variant fails + // Similarly `let y = obj["zip"];` fails to compile currently + // run!( + // let obj = %{}; + // %[_].assert_eq(obj["zip"], None); + // ); } #[test] diff --git a/tests/functions.rs b/tests/functions.rs index 03d69511..feb63c49 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -31,12 +31,12 @@ fn test_method_function_has_receiver_bound() { push_me(4); %[_].assert_eq(arr.to_debug_string(), "[1, 2, 3, 4]"); } - // run! { - // let arr = [1, 2, 3]; - // let x = %{ push: arr.push }; - // x.push(4); - // %[_].assert_eq(arr.to_debug_string(), "[1, 2, 3, 4]"); - // } + run! { + let arr = [1, 2, 3]; + let x = %{ push: arr.push }; + x.push(4); + %[_].assert_eq(arr.to_debug_string(), "[1, 2, 3, 4]"); + } } #[test] From a2583b3ac3d15865fddf8a018d19c1ad51dea6a4 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 24 Jan 2026 11:41:29 +0000 Subject: [PATCH 06/49] fix: Return none key on object when resolving reference --- plans/TODO.md | 4 ++- src/expressions/values/object.rs | 22 ++++++++-------- tests/expressions.rs | 43 +++++++++++++++++++++----------- 3 files changed, 44 insertions(+), 25 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index 45b87f7b..ba2399a3 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -220,7 +220,9 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] And then for objects, the method wins; BUT you can use `x["obj"]` to access the field instead of the method - [x] We can remove method extension code - [x] It's an error to set `x.y = z` or `%{ y: z }` if `y` is a method on object -- [ ] Fix `x["new_key"]` should be `None` +- [x] Fix `x["new_key"]` should be `None` +- [ ] Fix `// TODO[functions]: Work out why this works!` +- [ ] Replace `articled_kind` / `articled_display_name` with `articled_value` being `a(n) value` or `values` ` values` and define an `articled_type` - [ ] We can define closures (without any closed values for now) * Value type function `let my_func = |x, y, z| { ... };` * Parameters can be `x` / `x: any` (Owned), `x: &any` (Shared) or `x: &mut any` (Mutable). diff --git a/src/expressions/values/object.rs b/src/expressions/values/object.rs index a697a8f8..ca33b6cf 100644 --- a/src/expressions/values/object.rs +++ b/src/expressions/values/object.rs @@ -83,10 +83,10 @@ impl ObjectValue { pub(super) fn index_ref(&self, index: Spanned) -> ExecutionResult<&AnyValue> { let key: Spanned<&str> = index.downcast_resolve("An object key")?; - let entry = self.entries.get(*key).ok_or_else(|| { - key.value_error(format!("The object does not have a field named `{}`", *key)) - })?; - Ok(&entry.value) + match self.entries.get(*key) { + Some(entry) => Ok(&entry.value), + None => Ok(&AnyValue::None(())), + } } pub(super) fn property_mut( @@ -102,10 +102,10 @@ impl ObjectValue { pub(super) fn property_ref(&self, access: &PropertyAccess) -> ExecutionResult<&AnyValue> { let key = access.property.to_string(); - let entry = self.entries.get(&key).ok_or_else(|| { - access.value_error(format!("The object does not have a field named `{}`", key)) - })?; - Ok(&entry.value) + match self.entries.get(&key) { + Some(entry) => Ok(&entry.value), + None => Ok(&AnyValue::None(())), + } } fn mut_entry( @@ -125,8 +125,10 @@ impl ObjectValue { }) .value } else { - return key_span - .value_err(format!("No property found for key `{}`", entry.into_key())); + return key_span.value_err(format!( + "No existing property found to mutate for key `{}`", + entry.into_key() + )); } } }) diff --git a/tests/expressions.rs b/tests/expressions.rs index 02c906cf..5f461de9 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -549,32 +549,47 @@ fn test_objects() { ), r#"%{ a: 1, b: 7, c: None, x: %{}, z: None }"# ); + run!( + let obj = %{}; + let y = obj["non_existent_key"]; + %[_].assert_eq(y, None); + ); + run!( + let obj = %{}; + let y = obj["non_existent_key"].as_ref(); + %[_].assert_eq(y, None); + ); + // TODO[functions]: Work out why this works! + // Is autocreate set to true? + run!( + let obj = %{}; + let y = obj["non_existent_key"].as_mut().clone(); + %[_].assert_eq(y, None); + ); + // Regression: The above test looks very similar, but there + // were issues with this test breaking due to propogation through + // index resolution. + run!( + let obj = %{}; + %[_].assert_eq(obj["non_existent_key"], None); + ); // Method-shadowing keys can be set/accessed via indexed syntax // "zip" is a method on objects, but we can still use it as a key via ["zip"] run!( let obj = %{ ["zip"]: 123 }; %[_].assert_eq(obj["zip"], 123); ); - // Setting via index access also works + // Setting "zip" via index access also works run!( let obj = %{}; obj["zip"] = 456; %[_].assert_eq(obj["zip"], 456); ); - // Accessing an unset method-named key via index returns None - assert_eq!( - run!( - let obj = %{}; - obj["zip"].to_debug_string() - ), - "None" + // Accessing a non-existent "zip" value works, and doesn't give the method + run!( + let obj = %{}; + %[_].assert_eq(obj["zip"], None); ); - // TODO[functions]: Work out why this test variant fails - // Similarly `let y = obj["zip"];` fails to compile currently - // run!( - // let obj = %{}; - // %[_].assert_eq(obj["zip"], None); - // ); } #[test] From 14d604c24f1f75578f44faf9ac51f88108e61d92 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 24 Jan 2026 18:20:33 +0000 Subject: [PATCH 07/49] fix: Fix various object property / index access propagations --- plans/TODO.md | 3 +- src/expressions/evaluation/evaluator.rs | 15 +++++-- src/expressions/evaluation/value_frames.rs | 22 +++++----- src/expressions/type_resolution/arguments.rs | 40 ++++++++++--------- src/expressions/values/object.rs | 2 +- src/interpretation/bindings.rs | 40 ++++++++++++++++--- src/misc/errors.rs | 11 +++++ src/misc/mut_rc_ref_cell.rs | 20 ++++++++-- .../mutate_non_existent_key_on_object.rs | 9 +++++ .../mutate_non_existent_key_on_object.stderr | 5 +++ .../swap_non_existent_key_on_object.rs | 28 +++++++++++++ .../swap_non_existent_key_on_object.stderr | 5 +++ tests/expressions.rs | 34 ++++++++++++---- 13 files changed, 181 insertions(+), 53 deletions(-) create mode 100644 tests/compilation_failures/expressions/mutate_non_existent_key_on_object.rs create mode 100644 tests/compilation_failures/expressions/mutate_non_existent_key_on_object.stderr create mode 100644 tests/compilation_failures/expressions/swap_non_existent_key_on_object.rs create mode 100644 tests/compilation_failures/expressions/swap_non_existent_key_on_object.stderr diff --git a/plans/TODO.md b/plans/TODO.md index ba2399a3..4e10c9f9 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -220,8 +220,7 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] And then for objects, the method wins; BUT you can use `x["obj"]` to access the field instead of the method - [x] We can remove method extension code - [x] It's an error to set `x.y = z` or `%{ y: z }` if `y` is a method on object -- [x] Fix `x["new_key"]` should be `None` -- [ ] Fix `// TODO[functions]: Work out why this works!` +- [x] Fix various noted edge cases in object property / index ownership - [ ] Replace `articled_kind` / `articled_display_name` with `articled_value` being `a(n) value` or `values` ` values` and define an `articled_type` - [ ] We can define closures (without any closed values for now) * Value type function `let my_func = |x, y, z| { ... };` diff --git a/src/expressions/evaluation/evaluator.rs b/src/expressions/evaluation/evaluator.rs index 53c4449d..db95133e 100644 --- a/src/expressions/evaluation/evaluator.rs +++ b/src/expressions/evaluation/evaluator.rs @@ -238,7 +238,10 @@ impl RequestedValue { pub(crate) fn expect_any_value_and_map( self, map_shared: impl FnOnce(AnyValueShared) -> ExecutionResult, - map_mutable: impl FnOnce(AnyValueMutable) -> ExecutionResult, + map_mutable: impl FnOnce( + AnyValueMutable, + ) + -> Result, map_owned: impl FnOnce(AnyValueOwned) -> ExecutionResult, ) -> ExecutionResult { Ok(match self { @@ -247,9 +250,15 @@ impl RequestedValue { } RequestedValue::Owned(value) => RequestedValue::Owned(map_owned(value)?), RequestedValue::Assignee(assignee) => { - RequestedValue::Assignee(Assignee(map_mutable(assignee.0)?)) + // Assignee doesn't support fallback - propagate error directly + let mapped = map_mutable(assignee.0).map_err(|(e, _)| e)?; + RequestedValue::Assignee(Assignee(mapped)) + } + RequestedValue::Mutable(mutable) => { + // Non-late-bound mutable doesn't support fallback - propagate error directly + let mapped = map_mutable(mutable).map_err(|(e, _)| e)?; + RequestedValue::Mutable(mapped) } - RequestedValue::Mutable(mutable) => RequestedValue::Mutable(map_mutable(mutable)?), RequestedValue::Shared(shared) => RequestedValue::Shared(map_shared(shared)?), RequestedValue::CopyOnWrite(cow) => { RequestedValue::CopyOnWrite(cow.map(map_shared, map_owned)?) diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index e3571aea..a163df5c 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -1049,17 +1049,12 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { }; let auto_create = context.requested_ownership().requests_auto_create(); - // If we need an owned value, we can try resolving a reference and - // clone the outputted value if needed - which can be much cheaper. - // e.g. `let x = arr[0]` only copies `arr[0]` instead of the whole array. - let Spanned(value, _) = context - .requested_ownership() - .replace_owned_with_copy_on_write() - .map_from_late_bound(Spanned(value.expect_late_bound(), source_span))?; - - // Execute the access via the interface let mapped = value.expect_any_value_and_map( - |shared| shared.try_map(|value| (interface.shared_access)(ctx, value)), + |shared| { + shared + .try_map(|value| (interface.shared_access)(ctx, value)) + .map_err(|(e, _)| e) + }, |mutable| mutable.try_map(|value| (interface.mutable_access)(ctx, value, auto_create)), |owned| (interface.owned_access)(ctx, owned), )?; @@ -1151,9 +1146,12 @@ impl EvaluationFrame for ValueIndexAccessBuilder { }; let auto_create = context.requested_ownership().requests_auto_create(); - // Execute the access via the interface let result = source.expect_any_value_and_map( - |shared| shared.try_map(|value| (interface.shared_access)(ctx, value, index)), + |shared| { + shared + .try_map(|value| (interface.shared_access)(ctx, value, index)) + .map_err(|(e, _)| e) + }, |mutable| { mutable.try_map(|value| { (interface.mutable_access)(ctx, value, index, auto_create) diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index 4e272095..ac68a535 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -243,15 +243,17 @@ pub(crate) trait ResolvableShared { Spanned(value, span): Spanned>, resolution_target: &str, ) -> ExecutionResult> { - value.try_map(|v| { - Self::resolve_from_ref( - v, - ResolutionContext { - span_range: &span, - resolution_target, - }, - ) - }) + value + .try_map(|v| { + Self::resolve_from_ref( + v, + ResolutionContext { + span_range: &span, + resolution_target, + }, + ) + }) + .map_err(|(err, _)| err) } fn resolve_ref<'a>( @@ -303,15 +305,17 @@ pub(crate) trait ResolvableMutable { Spanned(value, span): Spanned>, resolution_target: &str, ) -> ExecutionResult> { - value.try_map(|v| { - Self::resolve_from_mut( - v, - ResolutionContext { - span_range: &span, - resolution_target, - }, - ) - }) + value + .try_map(|v| { + Self::resolve_from_mut( + v, + ResolutionContext { + span_range: &span, + resolution_target, + }, + ) + }) + .map_err(|(err, _)| err) } fn resolve_ref_mut<'a>( diff --git a/src/expressions/values/object.rs b/src/expressions/values/object.rs index ca33b6cf..9ffbbfc1 100644 --- a/src/expressions/values/object.rs +++ b/src/expressions/values/object.rs @@ -126,7 +126,7 @@ impl ObjectValue { .value } else { return key_span.value_err(format!( - "No existing property found to mutate for key `{}`", + "There is no pre-existing entry with key `{}` available to mutate", entry.into_key() )); } diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 466287b8..f263bbba 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -206,10 +206,18 @@ impl Spanned { } 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) -> ExecutionResult, - map_mutable: impl FnOnce(AnyValueMutable) -> ExecutionResult, + map_mutable: impl FnOnce( + AnyValueMutable, + ) + -> Result, map_owned: impl FnOnce(AnyValueOwned) -> ExecutionResult, ) -> ExecutionResult { Ok(match self { @@ -220,7 +228,19 @@ impl LateBoundValue { LateBoundValue::CopyOnWrite(copy_on_write) => { LateBoundValue::CopyOnWrite(copy_on_write.map(map_shared, map_owned)?) } - LateBoundValue::Mutable(mutable) => LateBoundValue::Mutable(map_mutable(mutable)?), + 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, @@ -316,11 +336,15 @@ impl 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> { - Ok(Mutable(self.0.try_map(value_map)?)) + ) -> 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. @@ -432,11 +456,15 @@ impl Shared { 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> { - Ok(Shared(self.0.try_map(value_map)?)) + ) -> 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 { diff --git a/src/misc/errors.rs b/src/misc/errors.rs index 6dd464c9..b53bb732 100644 --- a/src/misc/errors.rs +++ b/src/misc/errors.rs @@ -210,6 +210,17 @@ impl ExecutionInterrupt { pub(crate) fn control_flow(control_flow: ControlFlowInterrupt) -> Self { Self::new(ExecutionInterruptInner::ControlFlowInterrupt(control_flow)) } + + /// Determines if the error can be caught when attempting to map a late-bound + /// mutable value, to retry as a shared value instead. + pub(crate) fn into_caught_mutable_map_attempt_error(self) -> Result { + match self.inner.as_ref() { + ExecutionInterruptInner::Error(ErrorKind::Value, _) => { + Ok(self.convert_to_final_error()) + } + _ => Err(self), + } + } } impl From for ExecutionInterrupt { diff --git a/src/misc/mut_rc_ref_cell.rs b/src/misc/mut_rc_ref_cell.rs index 3cd34894..d9a11805 100644 --- a/src/misc/mut_rc_ref_cell.rs +++ b/src/misc/mut_rc_ref_cell.rs @@ -82,7 +82,7 @@ impl MutableSubRcRefCell { pub(crate) fn try_map( self, f: impl FnOnce(&mut U) -> Result<&mut V, E>, - ) -> Result, E> { + ) -> Result, (E, MutableSubRcRefCell)> { let mut error = None; let outcome = RefMut::filter_map(self.ref_mut, |inner| match f(inner) { Ok(value) => Some(value), @@ -96,7 +96,13 @@ impl MutableSubRcRefCell { ref_mut, pointed_at: self.pointed_at, }), - Err(_) => Err(error.unwrap()), + Err(original_ref_mut) => Err(( + error.unwrap(), + MutableSubRcRefCell { + ref_mut: original_ref_mut, + pointed_at: self.pointed_at, + }, + )), } } @@ -264,7 +270,7 @@ impl SharedSubRcRefCell { pub(crate) fn try_map( self, f: impl FnOnce(&U) -> Result<&V, E>, - ) -> Result, E> { + ) -> Result, (E, SharedSubRcRefCell)> { let mut error = None; let outcome = Ref::filter_map(self.shared_ref, |inner| match f(inner) { Ok(value) => Some(value), @@ -278,7 +284,13 @@ impl SharedSubRcRefCell { shared_ref, pointed_at: self.pointed_at, }), - Err(_) => Err(error.unwrap()), + Err(original_shared_ref) => Err(( + error.unwrap(), + SharedSubRcRefCell { + shared_ref: original_shared_ref, + pointed_at: self.pointed_at, + }, + )), } } diff --git a/tests/compilation_failures/expressions/mutate_non_existent_key_on_object.rs b/tests/compilation_failures/expressions/mutate_non_existent_key_on_object.rs new file mode 100644 index 00000000..a43de017 --- /dev/null +++ b/tests/compilation_failures/expressions/mutate_non_existent_key_on_object.rs @@ -0,0 +1,9 @@ +use preinterpret::*; + +fn main() { + run!{ + let obj = %{}; + obj["false_key"] += 1; + let _ = obj; + }; +} diff --git a/tests/compilation_failures/expressions/mutate_non_existent_key_on_object.stderr b/tests/compilation_failures/expressions/mutate_non_existent_key_on_object.stderr new file mode 100644 index 00000000..9a9965ae --- /dev/null +++ b/tests/compilation_failures/expressions/mutate_non_existent_key_on_object.stderr @@ -0,0 +1,5 @@ +error: The += operator is not supported for None operand + --> tests/compilation_failures/expressions/mutate_non_existent_key_on_object.rs:6:26 + | +6 | obj["false_key"] += 1; + | ^^ diff --git a/tests/compilation_failures/expressions/swap_non_existent_key_on_object.rs b/tests/compilation_failures/expressions/swap_non_existent_key_on_object.rs new file mode 100644 index 00000000..74fc8293 --- /dev/null +++ b/tests/compilation_failures/expressions/swap_non_existent_key_on_object.rs @@ -0,0 +1,28 @@ +use preinterpret::*; + +fn main() { + run!( + let obj = %{}; + // This *should* work, in the sense that replace takes an assignee... + // And obj.unused_key as an assignee should auto-create an entry. + // + // But it's a limitation of the current implementation: we don't know + // we're mapping an assignee because we can't resolve the replace + // interface yet without knowing the type of the value. So we map + // a late-bound mutable value. + // + // Or in more detail: + // - To resolve the receiver type, we request a late-bound mutable value. + // - This starts by attempting to map a mutable access. + // - But since the key doesn't exist, the mutable mapping fails. + // - We then fall back to shared access + // - Then when we try to map to assignee, we push the reason_not_mutable + // + // All that said, it would be kinda weird if this *did* work, so it + // doesn't need fixing. + obj.unused_key.replace(123); + %[].assert_eq(obj.unused_key, 123); + // Ensure the line above isn't a last-use owned, so resolves a ref + let _ = obj; + ); +} diff --git a/tests/compilation_failures/expressions/swap_non_existent_key_on_object.stderr b/tests/compilation_failures/expressions/swap_non_existent_key_on_object.stderr new file mode 100644 index 00000000..cd7fb9b6 --- /dev/null +++ b/tests/compilation_failures/expressions/swap_non_existent_key_on_object.stderr @@ -0,0 +1,5 @@ +error: There is no pre-existing entry with key `unused_key` available to mutate + --> tests/compilation_failures/expressions/swap_non_existent_key_on_object.rs:23:13 + | +23 | obj.unused_key.replace(123); + | ^^^^^^^^^^ diff --git a/tests/expressions.rs b/tests/expressions.rs index 5f461de9..72a9c09f 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -559,19 +559,39 @@ fn test_objects() { let y = obj["non_existent_key"].as_ref(); %[_].assert_eq(y, None); ); - // TODO[functions]: Work out why this works! - // Is autocreate set to true? + // The following few tests look like duplicates, but they actually + // stretch edge-cases in the propogation of different ownerships + // up the ownership chain. run!( let obj = %{}; - let y = obj["non_existent_key"].as_mut().clone(); - %[_].assert_eq(y, None); + %[_].assert(obj["non_existent_key"].is_none()); + // Ensure the line above isn't a last-use owned, so resolves a ref + let _ = obj; + ); + run!( + let obj = %{}; + %[_].assert(obj.non_existent_key.is_none()); + // Ensure the line above isn't a last-use owned, so resolves a ref + let _ = obj; ); - // Regression: The above test looks very similar, but there - // were issues with this test breaking due to propogation through - // index resolution. run!( let obj = %{}; %[_].assert_eq(obj["non_existent_key"], None); + // Ensure the line above isn't a last-use owned, so resolves a ref + let _ = obj; + ); + run!( + let obj = %{}; + %[_].assert_eq(obj.non_existent_key, None); + // Ensure the line above isn't a last-use owned, so resolves a ref + let _ = obj; + ); + run!( + let obj = %{}; + obj.key = 123; + %[].assert_eq(obj.key, 123); + // Ensure the line above isn't a last-use owned, so resolves a ref + let _ = obj; ); // Method-shadowing keys can be set/accessed via indexed syntax // "zip" is a method on objects, but we can still use it as a key via ["zip"] From 6f7c09cc63a8e347dca1f4e24e449080f5c63782 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 24 Jan 2026 18:54:17 +0000 Subject: [PATCH 08/49] refactor: Change articled_display_name() to articled_value_name() --- plans/TODO.md | 2 +- src/expressions/concepts/form.rs | 2 +- src/expressions/concepts/type_traits.rs | 42 +++++++++---------- src/expressions/equality.rs | 16 +++---- src/expressions/evaluation/value_frames.rs | 10 ++--- src/expressions/operations.rs | 4 +- src/expressions/type_resolution/arguments.rs | 2 +- src/expressions/type_resolution/type_kinds.rs | 22 +++++----- src/expressions/values/any_value.rs | 6 +-- src/expressions/values/array.rs | 2 +- src/expressions/values/boolean.rs | 2 +- src/expressions/values/character.rs | 2 +- src/expressions/values/float.rs | 2 +- src/expressions/values/float_subtypes.rs | 16 +++---- src/expressions/values/float_untyped.rs | 2 +- src/expressions/values/function.rs | 2 +- src/expressions/values/integer.rs | 2 +- src/expressions/values/integer_subtypes.rs | 16 +++---- src/expressions/values/integer_untyped.rs | 4 +- src/expressions/values/iterable.rs | 2 +- src/expressions/values/iterator.rs | 2 +- src/expressions/values/none.rs | 3 +- src/expressions/values/object.rs | 2 +- src/expressions/values/parser.rs | 2 +- src/expressions/values/preinterpret.rs | 2 +- src/expressions/values/range.rs | 4 +- src/expressions/values/stream.rs | 2 +- src/expressions/values/string.rs | 2 +- src/expressions/values/unsupported_literal.rs | 2 +- .../core/assert_eq_value_kind.stderr | 2 +- .../core/typed_eq_nested_type_mismatch.stderr | 2 +- ...typed_eq_object_value_type_mismatch.stderr | 2 +- ...ped_eq_object_value_type_mismatch_2.stderr | 2 +- .../core/typed_eq_type_mismatch.stderr | 2 +- .../expressions/add_float_and_int.stderr | 2 +- .../add_ints_of_different_types.stderr | 2 +- .../assign_to_method_on_array.stderr | 2 +- .../assign_to_method_property.stderr | 2 +- .../assign_to_property_on_array.stderr | 2 +- .../expressions/cast_int_to_bool.stderr | 2 +- .../expressions/compare_int_and_float.stderr | 2 +- .../fix_me_negative_max_int_fails.stderr | 2 +- .../mutate_non_existent_key_on_object.stderr | 2 +- ...ct_pattern_destructuring_wrong_type.stderr | 2 +- ...ject_place_destructuring_wrong_type.stderr | 2 +- ..._integer_too_large_arithmetic_fails.stderr | 2 +- .../operations/add_bool_and_int.stderr | 2 +- .../operations/add_int_and_array.stderr | 2 +- .../operations/bitwise_on_float.stderr | 2 +- .../operations/bitwise_or_on_float.stderr | 2 +- .../operations/compare_bool_and_int.stderr | 2 +- .../operations/compare_int_and_string.stderr | 2 +- .../operations/logical_and_on_int.stderr | 2 +- .../operations/logical_not_on_int.stderr | 2 +- .../operations/logical_or_on_int.stderr | 2 +- .../operations/multiply_strings.stderr | 2 +- .../operations/negate_unsigned.stderr | 2 +- .../operations/shift_left_on_float.stderr | 2 +- .../operations/stream_subtract.stderr | 2 +- .../operations/subtract_strings.stderr | 2 +- 60 files changed, 118 insertions(+), 123 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index 4e10c9f9..93835767 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -221,7 +221,7 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] We can remove method extension code - [x] It's an error to set `x.y = z` or `%{ y: z }` if `y` is a method on object - [x] Fix various noted edge cases in object property / index ownership -- [ ] Replace `articled_kind` / `articled_display_name` with `articled_value` being `a(n) value` or `values` ` values` and define an `articled_type` +- [x] Replace `articled_kind` / `articled_display_name` with `articled_value_name` - [ ] We can define closures (without any closed values for now) * Value type function `let my_func = |x, y, z| { ... };` * Parameters can be `x` / `x: any` (Owned), `x: &any` (Shared) or `x: &mut any` (Mutable). diff --git a/src/expressions/concepts/form.rs b/src/expressions/concepts/form.rs index f4b8e4fb..6e86fb15 100644 --- a/src/expressions/concepts/form.rs +++ b/src/expressions/concepts/form.rs @@ -39,7 +39,7 @@ pub(crate) trait LeafAsRefForm: IsHierarchicalForm { } else { error_span.ownership_err(format!( "An owned value is required, but a reference was received, and {} does not support transparent cloning. You may wish to use .clone() explicitly.", - type_kind.articled_display_name() + type_kind.articled_value_name() )) } } diff --git a/src/expressions/concepts/type_traits.rs b/src/expressions/concepts/type_traits.rs index ca849d90..d5111b6d 100644 --- a/src/expressions/concepts/type_traits.rs +++ b/src/expressions/concepts/type_traits.rs @@ -12,7 +12,7 @@ pub(crate) trait IsType: Sized + TypeData { type Variant: TypeVariant; const SOURCE_TYPE_NAME: &'static str; - const ARTICLED_DISPLAY_NAME: &'static str; + const ARTICLED_VALUE_NAME: &'static str; fn type_kind() -> TypeKind; fn type_kind_from_source_name(name: &str) -> Option; @@ -94,8 +94,8 @@ pub(crate) trait DowncastFrom: IsHierarchicalType { return span_range.value_err(format!( "{} is expected to be {}, but it is {}", resolution_target, - Self::ARTICLED_DISPLAY_NAME, - leaf_kind.articled_display_name(), + Self::ARTICLED_VALUE_NAME, + leaf_kind.articled_value_name(), )); } }; @@ -120,8 +120,8 @@ pub(crate) trait DynResolveFrom: IsDynType { return span_range.value_err(format!( "{} is expected to be {}, but it is {}", resolution_target, - Self::ARTICLED_DISPLAY_NAME, - leaf_kind.articled_display_name(), + Self::ARTICLED_VALUE_NAME, + leaf_kind.articled_value_name(), )); } }; @@ -348,10 +348,6 @@ pub(crate) trait HasLeafKind { fn value_kind(&self) -> AnyValueLeafKind { self.kind().into() } - - fn articled_kind(&self) -> &'static str { - self.kind().articled_display_name() - } } // TODO[concepts]: Remove when we get rid of impl_resolvable_argument_for @@ -382,7 +378,7 @@ macro_rules! define_parent_type { $($variant:ident => $variant_type:ty,)* }, type_name: $source_type_name:literal, - articled_display_name: $articled_display_name:literal, + articled_value_name: $articled_value_name:literal, ) => { #[derive(Copy, Clone)] $type_def_vis struct $type_def; @@ -391,7 +387,7 @@ macro_rules! define_parent_type { type Variant = HierarchicalTypeVariant; const SOURCE_TYPE_NAME: &'static str = $source_type_name; - const ARTICLED_DISPLAY_NAME: &'static str = $articled_display_name; + const ARTICLED_VALUE_NAME: &'static str = concat!($articled_value_name, " value"); fn type_kind() -> TypeKind { TypeKind::Parent(ParentTypeKind::$parent_kind($type_kind)) @@ -500,12 +496,12 @@ macro_rules! define_parent_type { $type_kind_vis struct $type_kind; impl $type_kind { - pub(crate) fn articled_display_name(&self) -> &'static str { - $articled_display_name + pub(crate) fn articled_value_name(&self) -> &'static str { + $type_def::ARTICLED_VALUE_NAME } pub(crate) fn source_type_name(&self) -> &'static str { - $source_type_name + $type_def::SOURCE_TYPE_NAME } pub(crate) fn feature_resolver(&self) -> &'static dyn TypeFeatureResolver { @@ -534,9 +530,9 @@ macro_rules! define_parent_type { } } - fn articled_display_name(&self) -> &'static str { + fn articled_value_name(&self) -> &'static str { match self { - $( Self::$variant(x) => x.articled_display_name(), )* + $( Self::$variant(x) => x.articled_value_name(), )* } } @@ -577,7 +573,7 @@ macro_rules! define_leaf_type { content: $content_type:ty, kind: $kind_vis:vis $kind:ident, type_name: $source_type_name:literal, - articled_display_name: $articled_display_name:literal, + articled_value_name: $articled_value_name:literal, dyn_impls: { $($dyn_type:ty: impl $dyn_trait:ident { $($dyn_trait_impl:tt)* })* }, @@ -589,7 +585,7 @@ macro_rules! define_leaf_type { type Variant = HierarchicalTypeVariant; const SOURCE_TYPE_NAME: &'static str = $source_type_name; - const ARTICLED_DISPLAY_NAME: &'static str = $articled_display_name; + const ARTICLED_VALUE_NAME: &'static str = concat!($articled_value_name, " value"); fn type_kind() -> TypeKind { TypeKind::Leaf(AnyValueLeafKind::from($kind)) @@ -666,11 +662,11 @@ macro_rules! define_leaf_type { impl IsLeafKind for $kind { fn source_type_name(&self) -> &'static str { - $source_type_name + $type_def::SOURCE_TYPE_NAME } - fn articled_display_name(&self) -> &'static str { - $articled_display_name + fn articled_value_name(&self) -> &'static str { + $type_def::ARTICLED_VALUE_NAME } fn feature_resolver(&self) -> &'static dyn TypeFeatureResolver { @@ -777,7 +773,7 @@ macro_rules! define_dyn_type { content: $dyn_type:ty, dyn_kind: DynTypeKind::$dyn_kind:ident, type_name: $source_type_name:literal, - articled_display_name: $articled_display_name:literal, + articled_value_name: $articled_value_name:literal, ) => { #[derive(Copy, Clone)] $type_def_vis struct $type_def; @@ -786,7 +782,7 @@ macro_rules! define_dyn_type { type Variant = DynTypeVariant; const SOURCE_TYPE_NAME: &'static str = $source_type_name; - const ARTICLED_DISPLAY_NAME: &'static str = $articled_display_name; + const ARTICLED_VALUE_NAME: &'static str = concat!($articled_value_name, " value"); fn type_kind() -> TypeKind { TypeKind::Dyn(DynTypeKind::$dyn_kind) diff --git a/src/expressions/equality.rs b/src/expressions/equality.rs index b9debddf..59662f54 100644 --- a/src/expressions/equality.rs +++ b/src/expressions/equality.rs @@ -202,9 +202,9 @@ impl EqualityContext for TypedEquality { Err(self.error_span.type_error(format!( "lhs{} is {}, but rhs{} is {}", path_str, - lhs.articled_kind(), + lhs.kind().articled_value_name(), path_str, - rhs.articled_kind() + rhs.kind().articled_value_name(), ))) } @@ -217,9 +217,9 @@ impl EqualityContext for TypedEquality { Err(self.error_span.type_error(format!( "lhs{} is {}, but rhs{} is {}", path_str, - lhs.articled_display_name(), + lhs.articled_value_name(), path_str, - rhs.articled_display_name() + rhs.articled_value_name() ))) } @@ -354,9 +354,9 @@ impl DebugEqualityError { format!( "lhs{} is {}, but rhs{} is {}", path_str, - lhs_kind.articled_display_name(), + lhs_kind.articled_value_name(), path_str, - rhs_kind.articled_display_name() + rhs_kind.articled_value_name() ) } DebugInequalityReason::RangeStructureMismatch { @@ -366,9 +366,9 @@ impl DebugEqualityError { format!( "lhs{} is {}, but rhs{} is {}", path_str, - lhs_kind.articled_display_name(), + lhs_kind.articled_value_name(), path_str, - rhs_kind.articled_display_name() + rhs_kind.articled_value_name() ) } DebugInequalityReason::LengthMismatch { lhs_len, rhs_len } => { diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index a163df5c..da6e9203 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -851,7 +851,7 @@ impl EvaluationFrame for UnaryOperationBuilder { self.operation.type_err(format!( "The {} operator is not supported for {}", self.operation, - operand.articled_kind(), + operand.kind().articled_value_name(), )) } } @@ -936,7 +936,7 @@ impl EvaluationFrame for BinaryOperationBuilder { return self.operation.type_err(format!( "The {} operator is not supported for {} operand", self.operation.symbolic_description(), - left.articled_kind(), + left.kind().articled_value_name(), )); } } @@ -1009,7 +1009,7 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { return self.access.property.type_err(format!( "Cannot assign to `.{}` because it is a method on {}{}", property_name, - source_kind.articled_display_name(), + source_kind.articled_value_name(), if matches!(source_kind, AnyValueLeafKind::Object(_)) { format!( ". Use `[\"{}\"]` instead to set the property.", @@ -1039,7 +1039,7 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { return self.access.type_err(format!( "`{}` is not a method on {}, and the {} type does not support fields", property_name, - source_kind.articled_display_name(), + source_kind.articled_value_name(), source_kind.source_type_name(), )); }; @@ -1120,7 +1120,7 @@ impl EvaluationFrame for ValueIndexAccessBuilder { let Some(interface) = source_kind.feature_resolver().resolve_index_access() else { return self.access.type_err(format!( "Cannot index into {}", - source_kind.articled_display_name() + source_kind.articled_value_name() )); }; diff --git a/src/expressions/operations.rs b/src/expressions/operations.rs index 88fe6c63..13c30884 100644 --- a/src/expressions/operations.rs +++ b/src/expressions/operations.rs @@ -95,7 +95,7 @@ impl UnaryOperation { self.type_error(format!( "The {} operator is not supported for {} operand", self, - input.articled_kind(), + input.kind().articled_value_name(), )) })?; let input = method @@ -325,7 +325,7 @@ impl BinaryOperation { None => self.type_err(format!( "The {} operator is not supported for {} operand", self.symbolic_description(), - left.articled_kind(), + left.kind().articled_value_name(), )), } } diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index ac68a535..ff442295 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -38,7 +38,7 @@ impl<'a> ResolutionContext<'a> { "{} is expected to be {}, but it is {}", self.resolution_target, articled_expected_value_kind, - value.articled_kind() + value.kind().articled_value_name(), )) } } diff --git a/src/expressions/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index 021de770..e750baf8 100644 --- a/src/expressions/type_resolution/type_kinds.rs +++ b/src/expressions/type_resolution/type_kinds.rs @@ -4,7 +4,7 @@ use super::*; /// This is implemented by `ValueLeafKind`, `IntegerKind`, `FloatKind`, etc. pub(crate) trait IsLeafKind: Copy + Into { fn source_type_name(&self) -> &'static str; - fn articled_display_name(&self) -> &'static str; + fn articled_value_name(&self) -> &'static str; fn feature_resolver(&self) -> &'static dyn TypeFeatureResolver; } @@ -62,11 +62,11 @@ impl TypeKind { } } - pub(crate) fn articled_display_name(&self) -> &'static str { + pub(crate) fn articled_value_name(&self) -> &'static str { match self { - TypeKind::Leaf(leaf_kind) => leaf_kind.articled_display_name(), - TypeKind::Parent(parent_kind) => parent_kind.articled_display_name(), - TypeKind::Dyn(dyn_kind) => dyn_kind.articled_display_name(), + TypeKind::Leaf(leaf_kind) => leaf_kind.articled_value_name(), + TypeKind::Parent(parent_kind) => parent_kind.articled_value_name(), + TypeKind::Dyn(dyn_kind) => dyn_kind.articled_value_name(), } } @@ -98,11 +98,11 @@ pub(crate) enum ParentTypeKind { } impl ParentTypeKind { - pub(crate) fn articled_display_name(&self) -> &'static str { + pub(crate) fn articled_value_name(&self) -> &'static str { match self { - ParentTypeKind::Value(x) => x.articled_display_name(), - ParentTypeKind::Integer(x) => x.articled_display_name(), - ParentTypeKind::Float(x) => x.articled_display_name(), + ParentTypeKind::Value(x) => x.articled_value_name(), + ParentTypeKind::Integer(x) => x.articled_value_name(), + ParentTypeKind::Float(x) => x.articled_value_name(), } } @@ -128,9 +128,9 @@ pub(crate) enum DynTypeKind { } impl DynTypeKind { - pub(crate) fn articled_display_name(&self) -> &'static str { + pub(crate) fn articled_value_name(&self) -> &'static str { match self { - DynTypeKind::Iterable => IterableType::ARTICLED_DISPLAY_NAME, + DynTypeKind::Iterable => IterableType::ARTICLED_VALUE_NAME, } } diff --git a/src/expressions/values/any_value.rs b/src/expressions/values/any_value.rs index 208c5f8f..d320a340 100644 --- a/src/expressions/values/any_value.rs +++ b/src/expressions/values/any_value.rs @@ -36,8 +36,8 @@ define_parent_type! { Function => FunctionType, PreinterpretApi => PreinterpretApiType, }, - type_name: "value", - articled_display_name: "any value", + type_name: "any", + articled_value_name: "any", } define_type_features! { @@ -255,7 +255,7 @@ impl AnyValue { if !self.value_kind().supports_transparent_cloning() { return error_span_range.ownership_err(format!( "An owned value is required, but a reference was received, and {} does not support transparent cloning. You may wish to use .clone() explicitly.", - self.articled_kind() + self.kind().articled_value_name(), )); } Ok(self.clone()) diff --git a/src/expressions/values/array.rs b/src/expressions/values/array.rs index 4b8a0227..e6dc4a77 100644 --- a/src/expressions/values/array.rs +++ b/src/expressions/values/array.rs @@ -5,7 +5,7 @@ define_leaf_type! { content: ArrayValue, kind: pub(crate) ArrayKind, type_name: "array", - articled_display_name: "an array", + articled_value_name: "an array", dyn_impls: { IterableType: impl IsIterable { fn into_iterator(self: Box) -> ExecutionResult { diff --git a/src/expressions/values/boolean.rs b/src/expressions/values/boolean.rs index 47084730..d74d1ba8 100644 --- a/src/expressions/values/boolean.rs +++ b/src/expressions/values/boolean.rs @@ -7,7 +7,7 @@ define_leaf_type! { content: bool, kind: pub(crate) BoolKind, type_name: "bool", - articled_display_name: "a bool", + articled_value_name: "a bool", dyn_impls: {}, } diff --git a/src/expressions/values/character.rs b/src/expressions/values/character.rs index be1ec4cd..4056fbbe 100644 --- a/src/expressions/values/character.rs +++ b/src/expressions/values/character.rs @@ -5,7 +5,7 @@ define_leaf_type! { content: char, kind: pub(crate) CharKind, type_name: "char", - articled_display_name: "a char", + articled_value_name: "a char", dyn_impls: {}, } diff --git a/src/expressions/values/float.rs b/src/expressions/values/float.rs index 251a2a60..899af2bf 100644 --- a/src/expressions/values/float.rs +++ b/src/expressions/values/float.rs @@ -11,7 +11,7 @@ define_parent_type! { F64 => F64Type, }, type_name: "float", - articled_display_name: "a float", + articled_value_name: "a float", } pub(crate) type FloatValue = FloatContent<'static, BeOwned>; diff --git a/src/expressions/values/float_subtypes.rs b/src/expressions/values/float_subtypes.rs index 58167b95..7ec2dd58 100644 --- a/src/expressions/values/float_subtypes.rs +++ b/src/expressions/values/float_subtypes.rs @@ -144,13 +144,13 @@ macro_rules! impl_float_operations { impl_float_operations!(F32Type mod f32_interface: F32(f32), F64Type mod f64_interface: F64(f64)); macro_rules! impl_resolvable_float_subtype { - ($type_def:ident, $kind:ident, $type:ty, $variant:ident, $type_name:literal, $articled_display_name:expr) => { + ($type_def:ident, $kind:ident, $type:ty, $variant:ident, $type_name:literal, $articled_value_name:expr) => { define_leaf_type! { pub(crate) $type_def => FloatType(FloatContent::$variant) => AnyType, content: $type, kind: pub(crate) $kind, type_name: $type_name, - articled_display_name: $articled_display_name, + articled_value_name: $articled_value_name, dyn_impls: {}, } @@ -180,8 +180,8 @@ macro_rules! impl_resolvable_float_subtype { v => span.type_err(format!( "{} is expected to be {}, but it is {}", resolution_target, - $kind.articled_display_name(), - v.articled_kind(), + $kind.articled_value_name(), + v.kind().articled_value_name(), )), } } @@ -205,7 +205,7 @@ macro_rules! impl_resolvable_float_subtype { match value { FloatContent::Untyped(x) => Ok(x.into_fallback() as $type), FloatContent::$variant(x) => Ok(x), - other => context.err($articled_display_name, other), + other => context.err($type_def::ARTICLED_VALUE_NAME, other), } } } @@ -217,7 +217,7 @@ macro_rules! impl_resolvable_float_subtype { ) -> ExecutionResult { match value { AnyValue::Float(x) => <$type>::resolve_from_value(x, context), - other => context.err($articled_display_name, other), + other => context.err($type_def::ARTICLED_VALUE_NAME, other), } } } @@ -229,7 +229,7 @@ macro_rules! impl_resolvable_float_subtype { ) -> ExecutionResult<&'a Self> { match value { AnyValueContent::Float(FloatContent::$variant(x)) => Ok(x), - other => context.err($articled_display_name, other), + other => context.err($type_def::ARTICLED_VALUE_NAME, other), } } } @@ -241,7 +241,7 @@ macro_rules! impl_resolvable_float_subtype { ) -> ExecutionResult<&'a mut Self> { match value { AnyValueContent::Float(FloatContent::$variant(x)) => Ok(x), - other => context.err($articled_display_name, other), + other => context.err($type_def::ARTICLED_VALUE_NAME, other), } } } diff --git a/src/expressions/values/float_untyped.rs b/src/expressions/values/float_untyped.rs index 6ee21fdd..0b4bde7f 100644 --- a/src/expressions/values/float_untyped.rs +++ b/src/expressions/values/float_untyped.rs @@ -5,7 +5,7 @@ define_leaf_type! { content: UntypedFloat, kind: pub(crate) UntypedFloatKind, type_name: "untyped_float", - articled_display_name: "an untyped float", + articled_value_name: "an untyped float", dyn_impls: {}, } diff --git a/src/expressions/values/function.rs b/src/expressions/values/function.rs index f9ed8696..9dd52d09 100644 --- a/src/expressions/values/function.rs +++ b/src/expressions/values/function.rs @@ -5,7 +5,7 @@ define_leaf_type! { content: FunctionValue, kind: pub(crate) FunctionValueKind, type_name: "fn", - articled_display_name: "a function", + articled_value_name: "a function", dyn_impls: {}, } diff --git a/src/expressions/values/integer.rs b/src/expressions/values/integer.rs index ea7dba32..90de463e 100644 --- a/src/expressions/values/integer.rs +++ b/src/expressions/values/integer.rs @@ -21,7 +21,7 @@ define_parent_type! { Isize => IsizeType, }, type_name: "int", - articled_display_name: "an integer", + articled_value_name: "an integer", } pub(crate) type IntegerValue = IntegerContent<'static, BeOwned>; diff --git a/src/expressions/values/integer_subtypes.rs b/src/expressions/values/integer_subtypes.rs index 52bf405f..4be03512 100644 --- a/src/expressions/values/integer_subtypes.rs +++ b/src/expressions/values/integer_subtypes.rs @@ -177,13 +177,13 @@ impl_int_operations!( ); macro_rules! impl_resolvable_integer_subtype { - ($type_def:ident, $kind:ident, $type:ty, $variant:ident, $type_name:literal, $articled_display_name:expr) => { + ($type_def:ident, $kind:ident, $type:ty, $variant:ident, $type_name:literal, $articled_value_name:expr) => { define_leaf_type! { pub(crate) $type_def => IntegerType(IntegerContent::$variant) => AnyType, content: $type, kind: pub(crate) $kind, type_name: $type_name, - articled_display_name: $articled_display_name, + articled_value_name: $articled_value_name, dyn_impls: {}, } @@ -213,8 +213,8 @@ macro_rules! impl_resolvable_integer_subtype { v => span.type_err(format!( "{} is expected to be {}, but it is {}", resolution_target, - $kind.articled_display_name(), - v.articled_kind(), + $kind.articled_value_name(), + v.kind().articled_value_name(), )), } } @@ -238,7 +238,7 @@ macro_rules! impl_resolvable_integer_subtype { match value { IntegerValue::Untyped(x) => Ok(x.into_fallback() as $type), IntegerValue::$variant(x) => Ok(x), - other => context.err($articled_display_name, other), + other => context.err($type_def::ARTICLED_VALUE_NAME, other), } } } @@ -250,7 +250,7 @@ macro_rules! impl_resolvable_integer_subtype { ) -> ExecutionResult { match value { AnyValue::Integer(x) => <$type>::resolve_from_value(x, context), - other => context.err($articled_display_name, other), + other => context.err($type_def::ARTICLED_VALUE_NAME, other), } } } @@ -262,7 +262,7 @@ macro_rules! impl_resolvable_integer_subtype { ) -> ExecutionResult<&'a Self> { match value { AnyValue::Integer(IntegerValue::$variant(x)) => Ok(x), - other => context.err($articled_display_name, other), + other => context.err($type_def::ARTICLED_VALUE_NAME, other), } } } @@ -274,7 +274,7 @@ macro_rules! impl_resolvable_integer_subtype { ) -> ExecutionResult<&'a mut Self> { match value { AnyValue::Integer(IntegerValue::$variant(x)) => Ok(x), - other => context.err($articled_display_name, other), + other => context.err($type_def::ARTICLED_VALUE_NAME, other), } } } diff --git a/src/expressions/values/integer_untyped.rs b/src/expressions/values/integer_untyped.rs index 29168330..44f9c6ae 100644 --- a/src/expressions/values/integer_untyped.rs +++ b/src/expressions/values/integer_untyped.rs @@ -5,7 +5,7 @@ define_leaf_type! { content: UntypedInteger, kind: pub(crate) UntypedIntegerKind, type_name: "untyped_int", - articled_display_name: "an untyped integer", + articled_value_name: "an untyped integer", dyn_impls: {}, } @@ -115,7 +115,7 @@ impl Spanned { span_range.value_error(format!( "The integer value {} does not fit into {}", value.0, - kind.articled_display_name() + kind.articled_value_name() )) }) } diff --git a/src/expressions/values/iterable.rs b/src/expressions/values/iterable.rs index 6f00edba..2a8186c3 100644 --- a/src/expressions/values/iterable.rs +++ b/src/expressions/values/iterable.rs @@ -10,7 +10,7 @@ define_dyn_type!( content: dyn IsIterable, dyn_kind: DynTypeKind::Iterable, type_name: "iterable", - articled_display_name: "an iterable (e.g. array, list, etc.)", + articled_value_name: "an iterable (e.g. array, list, etc.)", ); pub(crate) type IterableValue = Box; diff --git a/src/expressions/values/iterator.rs b/src/expressions/values/iterator.rs index 885bf141..7e1f7b84 100644 --- a/src/expressions/values/iterator.rs +++ b/src/expressions/values/iterator.rs @@ -5,7 +5,7 @@ define_leaf_type! { content: IteratorValue, kind: pub(crate) IteratorKind, type_name: "iterator", - articled_display_name: "an iterator", + articled_value_name: "an iterator", dyn_impls: { IterableType: impl IsIterable { fn into_iterator(self: Box) -> ExecutionResult { diff --git a/src/expressions/values/none.rs b/src/expressions/values/none.rs index 17f0e1e1..b336aebe 100644 --- a/src/expressions/values/none.rs +++ b/src/expressions/values/none.rs @@ -5,8 +5,7 @@ define_leaf_type! { content: (), kind: pub(crate) NoneKind, type_name: "none", - // Instead of saying "expected a none value", we can say "expected None" - articled_display_name: "None", + articled_value_name: "a none", dyn_impls: {}, } diff --git a/src/expressions/values/object.rs b/src/expressions/values/object.rs index 9ffbbfc1..d689523c 100644 --- a/src/expressions/values/object.rs +++ b/src/expressions/values/object.rs @@ -5,7 +5,7 @@ define_leaf_type! { content: ObjectValue, kind: pub(crate) ObjectKind, type_name: "object", - articled_display_name: "an object", + articled_value_name: "an object", dyn_impls: { IterableType: impl IsIterable { fn into_iterator(self: Box) -> ExecutionResult { diff --git a/src/expressions/values/parser.rs b/src/expressions/values/parser.rs index f6a52999..1b40d0bb 100644 --- a/src/expressions/values/parser.rs +++ b/src/expressions/values/parser.rs @@ -5,7 +5,7 @@ define_leaf_type! { content: ParserHandle, kind: pub(crate) ParserKind, type_name: "parser", - articled_display_name: "a parser", + articled_value_name: "a parser", dyn_impls: {}, } diff --git a/src/expressions/values/preinterpret.rs b/src/expressions/values/preinterpret.rs index 599b4399..334d0a9b 100644 --- a/src/expressions/values/preinterpret.rs +++ b/src/expressions/values/preinterpret.rs @@ -5,7 +5,7 @@ define_leaf_type! { content: PreinterpretApiValue, kind: pub(crate) PreinterpretApiKind, type_name: "preinterpret", - articled_display_name: "a preinterpret api", + articled_value_name: "a preinterpret api", dyn_impls: {}, } diff --git a/src/expressions/values/range.rs b/src/expressions/values/range.rs index fa421cb0..656238aa 100644 --- a/src/expressions/values/range.rs +++ b/src/expressions/values/range.rs @@ -7,7 +7,7 @@ define_leaf_type! { content: RangeValue, kind: pub(crate) RangeKind, type_name: "range", - articled_display_name: "a range", + articled_value_name: "a range", dyn_impls: { IterableType: impl IsIterable { fn into_iterator(self: Box) -> ExecutionResult { @@ -184,7 +184,7 @@ pub(crate) enum RangeStructure { } impl RangeStructure { - pub(crate) fn articled_display_name(&self) -> &'static str { + pub(crate) fn articled_value_name(&self) -> &'static str { match self { RangeStructure::FromTo => "a range start..end", RangeStructure::From => "a range start..", diff --git a/src/expressions/values/stream.rs b/src/expressions/values/stream.rs index 0cadc891..8076f66e 100644 --- a/src/expressions/values/stream.rs +++ b/src/expressions/values/stream.rs @@ -5,7 +5,7 @@ define_leaf_type! { content: OutputStream, kind: pub(crate) StreamKind, type_name: "stream", - articled_display_name: "a stream", + articled_value_name: "a stream", dyn_impls: { IterableType: impl IsIterable { fn into_iterator(self: Box) -> ExecutionResult { diff --git a/src/expressions/values/string.rs b/src/expressions/values/string.rs index 7560f5cb..5f32a666 100644 --- a/src/expressions/values/string.rs +++ b/src/expressions/values/string.rs @@ -5,7 +5,7 @@ define_leaf_type! { content: String, kind: pub(crate) StringKind, type_name: "string", - articled_display_name: "a string", + articled_value_name: "a string", dyn_impls: { IterableType: impl IsIterable { fn into_iterator(self: Box) -> ExecutionResult { diff --git a/src/expressions/values/unsupported_literal.rs b/src/expressions/values/unsupported_literal.rs index 385231d0..95877405 100644 --- a/src/expressions/values/unsupported_literal.rs +++ b/src/expressions/values/unsupported_literal.rs @@ -5,7 +5,7 @@ define_leaf_type! { content: UnsupportedLiteral, kind: pub(crate) UnsupportedLiteralKind, type_name: "unsupported_literal", - articled_display_name: "an unsupported literal", + articled_value_name: "an unsupported literal", dyn_impls: {}, } diff --git a/tests/compilation_failures/core/assert_eq_value_kind.stderr b/tests/compilation_failures/core/assert_eq_value_kind.stderr index 489215de..9c8e9840 100644 --- a/tests/compilation_failures/core/assert_eq_value_kind.stderr +++ b/tests/compilation_failures/core/assert_eq_value_kind.stderr @@ -1,4 +1,4 @@ -error: Assertion failed: lhs is an untyped integer, but rhs is a string +error: Assertion failed: lhs is an untyped integer value, but rhs is a string value lhs = 1 rhs = "hello" --> tests/compilation_failures/core/assert_eq_value_kind.rs:4:12 diff --git a/tests/compilation_failures/core/typed_eq_nested_type_mismatch.stderr b/tests/compilation_failures/core/typed_eq_nested_type_mismatch.stderr index e48f2b27..8a044a69 100644 --- a/tests/compilation_failures/core/typed_eq_nested_type_mismatch.stderr +++ b/tests/compilation_failures/core/typed_eq_nested_type_mismatch.stderr @@ -1,4 +1,4 @@ -error: lhs[1] is an untyped integer, but rhs[1] is a string +error: lhs[1] is an untyped integer value, but rhs[1] is a string value --> tests/compilation_failures/core/typed_eq_nested_type_mismatch.rs:5:22 | 5 | run!(%[_].assert([1, 2].typed_eq([1, "two"]))); diff --git a/tests/compilation_failures/core/typed_eq_object_value_type_mismatch.stderr b/tests/compilation_failures/core/typed_eq_object_value_type_mismatch.stderr index 39253866..b02c7a22 100644 --- a/tests/compilation_failures/core/typed_eq_object_value_type_mismatch.stderr +++ b/tests/compilation_failures/core/typed_eq_object_value_type_mismatch.stderr @@ -1,4 +1,4 @@ -error: lhs.b is an untyped integer, but rhs.b is a string +error: lhs.b is an untyped integer value, but rhs.b is a string value --> tests/compilation_failures/core/typed_eq_object_value_type_mismatch.rs:5:23 | 5 | let result = %{ a: 1, b: 2 }.typed_eq(%{ a: 1, b: "two" }); diff --git a/tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.stderr b/tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.stderr index f82846f1..08280ac9 100644 --- a/tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.stderr +++ b/tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.stderr @@ -1,4 +1,4 @@ -error: lhs["multi-word key"] is an untyped integer, but rhs["multi-word key"] is a string +error: lhs["multi-word key"] is an untyped integer value, but rhs["multi-word key"] is a string value --> tests/compilation_failures/core/typed_eq_object_value_type_mismatch_2.rs:5:23 | 5 | let result = %{ a: 1, ["multi-word key"]: 2 }.typed_eq(%{ a: 1, ["multi-word key"]: "two" }); diff --git a/tests/compilation_failures/core/typed_eq_type_mismatch.stderr b/tests/compilation_failures/core/typed_eq_type_mismatch.stderr index 0cfcb6ae..44d50b6f 100644 --- a/tests/compilation_failures/core/typed_eq_type_mismatch.stderr +++ b/tests/compilation_failures/core/typed_eq_type_mismatch.stderr @@ -1,4 +1,4 @@ -error: lhs is an untyped integer, but rhs is a string +error: lhs is an untyped integer value, but rhs is a string value --> tests/compilation_failures/core/typed_eq_type_mismatch.rs:5:22 | 5 | run!(%[_].assert(1.typed_eq("hello"))); diff --git a/tests/compilation_failures/expressions/add_float_and_int.stderr b/tests/compilation_failures/expressions/add_float_and_int.stderr index 96fa9320..5ef4e93b 100644 --- a/tests/compilation_failures/expressions/add_float_and_int.stderr +++ b/tests/compilation_failures/expressions/add_float_and_int.stderr @@ -1,4 +1,4 @@ -error: This argument is expected to be a float, but it is an untyped integer +error: This argument is expected to be a float value, but it is an untyped integer value --> tests/compilation_failures/expressions/add_float_and_int.rs:5:17 | 5 | #(1.2 + 1) diff --git a/tests/compilation_failures/expressions/add_ints_of_different_types.stderr b/tests/compilation_failures/expressions/add_ints_of_different_types.stderr index dc5c6424..09774543 100644 --- a/tests/compilation_failures/expressions/add_ints_of_different_types.stderr +++ b/tests/compilation_failures/expressions/add_ints_of_different_types.stderr @@ -1,4 +1,4 @@ -error: This operand is expected to be a u32, but it is a u64 +error: This operand is expected to be a u32 value, but it is a u64 value --> tests/compilation_failures/expressions/add_ints_of_different_types.rs:5:18 | 5 | #(1u32 + 2u64) diff --git a/tests/compilation_failures/expressions/assign_to_method_on_array.stderr b/tests/compilation_failures/expressions/assign_to_method_on_array.stderr index 076dc6e8..c68050e2 100644 --- a/tests/compilation_failures/expressions/assign_to_method_on_array.stderr +++ b/tests/compilation_failures/expressions/assign_to_method_on_array.stderr @@ -1,4 +1,4 @@ -error: Cannot assign to `.to_string` because it is a method on an array +error: Cannot assign to `.to_string` because it is a method on an array value --> tests/compilation_failures/expressions/assign_to_method_on_array.rs:6:13 | 6 | arr.to_string = "hello"; diff --git a/tests/compilation_failures/expressions/assign_to_method_property.stderr b/tests/compilation_failures/expressions/assign_to_method_property.stderr index fe6f1f1d..71bc3fdd 100644 --- a/tests/compilation_failures/expressions/assign_to_method_property.stderr +++ b/tests/compilation_failures/expressions/assign_to_method_property.stderr @@ -1,4 +1,4 @@ -error: Cannot assign to `.zip` because it is a method on an object. Use `["zip"]` instead to set the property. +error: Cannot assign to `.zip` because it is a method on an object value. Use `["zip"]` instead to set the property. --> tests/compilation_failures/expressions/assign_to_method_property.rs:8:17 | 8 | obj.zip = 5; diff --git a/tests/compilation_failures/expressions/assign_to_property_on_array.stderr b/tests/compilation_failures/expressions/assign_to_property_on_array.stderr index 034ad073..d05d5cd2 100644 --- a/tests/compilation_failures/expressions/assign_to_property_on_array.stderr +++ b/tests/compilation_failures/expressions/assign_to_property_on_array.stderr @@ -1,4 +1,4 @@ -error: `xyz` is not a method on an array, and the array type does not support fields +error: `xyz` is not a method on an array value, and the array type does not support fields --> tests/compilation_failures/expressions/assign_to_property_on_array.rs:6:12 | 6 | arr.xyz = "hello"; diff --git a/tests/compilation_failures/expressions/cast_int_to_bool.stderr b/tests/compilation_failures/expressions/cast_int_to_bool.stderr index b8cfc15b..9bea6c4f 100644 --- a/tests/compilation_failures/expressions/cast_int_to_bool.stderr +++ b/tests/compilation_failures/expressions/cast_int_to_bool.stderr @@ -1,4 +1,4 @@ -error: The as bool operator is not supported for an untyped integer +error: The as bool operator is not supported for an untyped integer value --> tests/compilation_failures/expressions/cast_int_to_bool.rs:5:13 | 5 | #(1 as bool) diff --git a/tests/compilation_failures/expressions/compare_int_and_float.stderr b/tests/compilation_failures/expressions/compare_int_and_float.stderr index 1c6e4f2a..6c2b5114 100644 --- a/tests/compilation_failures/expressions/compare_int_and_float.stderr +++ b/tests/compilation_failures/expressions/compare_int_and_float.stderr @@ -1,4 +1,4 @@ -error: This argument is expected to be an integer, but it is an untyped float +error: This argument is expected to be an integer value, but it is an untyped float value --> tests/compilation_failures/expressions/compare_int_and_float.rs:5:15 | 5 | #(5 < 6.4) diff --git a/tests/compilation_failures/expressions/fix_me_negative_max_int_fails.stderr b/tests/compilation_failures/expressions/fix_me_negative_max_int_fails.stderr index 71c954bf..c4b2022f 100644 --- a/tests/compilation_failures/expressions/fix_me_negative_max_int_fails.stderr +++ b/tests/compilation_failures/expressions/fix_me_negative_max_int_fails.stderr @@ -1,4 +1,4 @@ -error: The - operator is not supported for an unsupported literal +error: The - operator is not supported for an unsupported literal value --> tests/compilation_failures/expressions/fix_me_negative_max_int_fails.rs:8:11 | 8 | #(-128i8) diff --git a/tests/compilation_failures/expressions/mutate_non_existent_key_on_object.stderr b/tests/compilation_failures/expressions/mutate_non_existent_key_on_object.stderr index 9a9965ae..fad2741c 100644 --- a/tests/compilation_failures/expressions/mutate_non_existent_key_on_object.stderr +++ b/tests/compilation_failures/expressions/mutate_non_existent_key_on_object.stderr @@ -1,4 +1,4 @@ -error: The += operator is not supported for None operand +error: The += operator is not supported for a none value operand --> tests/compilation_failures/expressions/mutate_non_existent_key_on_object.rs:6:26 | 6 | obj["false_key"] += 1; diff --git a/tests/compilation_failures/expressions/object_pattern_destructuring_wrong_type.stderr b/tests/compilation_failures/expressions/object_pattern_destructuring_wrong_type.stderr index 42a85458..3d6b739c 100644 --- a/tests/compilation_failures/expressions/object_pattern_destructuring_wrong_type.stderr +++ b/tests/compilation_failures/expressions/object_pattern_destructuring_wrong_type.stderr @@ -1,4 +1,4 @@ -error: The value destructured with an object pattern is expected to be an object, but it is an untyped integer +error: The value destructured with an object pattern is expected to be an object, but it is an untyped integer value --> tests/compilation_failures/expressions/object_pattern_destructuring_wrong_type.rs:5:14 | 5 | let %{ x } = 0; diff --git a/tests/compilation_failures/expressions/object_place_destructuring_wrong_type.stderr b/tests/compilation_failures/expressions/object_place_destructuring_wrong_type.stderr index 6f3e89a4..0e2dbc5d 100644 --- a/tests/compilation_failures/expressions/object_place_destructuring_wrong_type.stderr +++ b/tests/compilation_failures/expressions/object_place_destructuring_wrong_type.stderr @@ -1,4 +1,4 @@ -error: The value destructured as an object is expected to be an object, but it is an array +error: The value destructured as an object is expected to be an object, but it is an array value --> tests/compilation_failures/expressions/object_place_destructuring_wrong_type.rs:6:10 | 6 | %{ x } = [x]; diff --git a/tests/compilation_failures/expressions/untyped_integer_too_large_arithmetic_fails.stderr b/tests/compilation_failures/expressions/untyped_integer_too_large_arithmetic_fails.stderr index a936b0ef..90b91c94 100644 --- a/tests/compilation_failures/expressions/untyped_integer_too_large_arithmetic_fails.stderr +++ b/tests/compilation_failures/expressions/untyped_integer_too_large_arithmetic_fails.stderr @@ -1,4 +1,4 @@ -error: The - operator is not supported for an unsupported literal operand +error: The - operator is not supported for an unsupported literal value operand --> tests/compilation_failures/expressions/untyped_integer_too_large_arithmetic_fails.rs:8:11 | 8 | x - 1 diff --git a/tests/compilation_failures/operations/add_bool_and_int.stderr b/tests/compilation_failures/operations/add_bool_and_int.stderr index 432e7a66..2e3c8592 100644 --- a/tests/compilation_failures/operations/add_bool_and_int.stderr +++ b/tests/compilation_failures/operations/add_bool_and_int.stderr @@ -1,4 +1,4 @@ -error: The + operator is not supported for a bool operand +error: The + operator is not supported for a bool value operand --> tests/compilation_failures/operations/add_bool_and_int.rs:4:23 | 4 | let _ = run!(true + 1); diff --git a/tests/compilation_failures/operations/add_int_and_array.stderr b/tests/compilation_failures/operations/add_int_and_array.stderr index 8e8bc881..7950daa7 100644 --- a/tests/compilation_failures/operations/add_int_and_array.stderr +++ b/tests/compilation_failures/operations/add_int_and_array.stderr @@ -1,4 +1,4 @@ -error: This argument is expected to be an integer, but it is an array +error: This argument is expected to be an integer value, but it is an array value --> tests/compilation_failures/operations/add_int_and_array.rs:4:22 | 4 | let _ = run!(1 + []); diff --git a/tests/compilation_failures/operations/bitwise_on_float.stderr b/tests/compilation_failures/operations/bitwise_on_float.stderr index 04a3b5c8..759a3db2 100644 --- a/tests/compilation_failures/operations/bitwise_on_float.stderr +++ b/tests/compilation_failures/operations/bitwise_on_float.stderr @@ -1,4 +1,4 @@ -error: The & operator is not supported for an untyped float operand +error: The & operator is not supported for an untyped float value operand --> tests/compilation_failures/operations/bitwise_on_float.rs:4:22 | 4 | let _ = run!(1.0 & 2.0); diff --git a/tests/compilation_failures/operations/bitwise_or_on_float.stderr b/tests/compilation_failures/operations/bitwise_or_on_float.stderr index afb1d9cc..4f8eab39 100644 --- a/tests/compilation_failures/operations/bitwise_or_on_float.stderr +++ b/tests/compilation_failures/operations/bitwise_or_on_float.stderr @@ -1,4 +1,4 @@ -error: The | operator is not supported for an untyped float operand +error: The | operator is not supported for an untyped float value operand --> tests/compilation_failures/operations/bitwise_or_on_float.rs:4:22 | 4 | let _ = run!(1.0 | 2.0); diff --git a/tests/compilation_failures/operations/compare_bool_and_int.stderr b/tests/compilation_failures/operations/compare_bool_and_int.stderr index cd8c5ce4..72afabef 100644 --- a/tests/compilation_failures/operations/compare_bool_and_int.stderr +++ b/tests/compilation_failures/operations/compare_bool_and_int.stderr @@ -1,4 +1,4 @@ -error: This argument is expected to be a bool, but it is an untyped integer +error: This argument is expected to be a bool value, but it is an untyped integer value --> tests/compilation_failures/operations/compare_bool_and_int.rs:4:26 | 4 | let _ = run!(true == 1); diff --git a/tests/compilation_failures/operations/compare_int_and_string.stderr b/tests/compilation_failures/operations/compare_int_and_string.stderr index 4bb09d8d..a5c74f5a 100644 --- a/tests/compilation_failures/operations/compare_int_and_string.stderr +++ b/tests/compilation_failures/operations/compare_int_and_string.stderr @@ -1,4 +1,4 @@ -error: This argument is expected to be an integer, but it is a string +error: This argument is expected to be an integer value, but it is a string value --> tests/compilation_failures/operations/compare_int_and_string.rs:4:23 | 4 | let _ = run!(5 == "five"); diff --git a/tests/compilation_failures/operations/logical_and_on_int.stderr b/tests/compilation_failures/operations/logical_and_on_int.stderr index 0e3f47bb..f4d87c64 100644 --- a/tests/compilation_failures/operations/logical_and_on_int.stderr +++ b/tests/compilation_failures/operations/logical_and_on_int.stderr @@ -1,4 +1,4 @@ -error: The left operand to && is expected to be a bool, but it is an untyped integer +error: The left operand to && is expected to be a bool, but it is an untyped integer value --> tests/compilation_failures/operations/logical_and_on_int.rs:4:18 | 4 | let _ = run!(1 && 2); diff --git a/tests/compilation_failures/operations/logical_not_on_int.stderr b/tests/compilation_failures/operations/logical_not_on_int.stderr index f9bf8831..b4ddc7d7 100644 --- a/tests/compilation_failures/operations/logical_not_on_int.stderr +++ b/tests/compilation_failures/operations/logical_not_on_int.stderr @@ -1,4 +1,4 @@ -error: The ! operator is not supported for an untyped integer +error: The ! operator is not supported for an untyped integer value --> tests/compilation_failures/operations/logical_not_on_int.rs:4:18 | 4 | let _ = run!(!5); diff --git a/tests/compilation_failures/operations/logical_or_on_int.stderr b/tests/compilation_failures/operations/logical_or_on_int.stderr index ce7a0f7e..19e5fb83 100644 --- a/tests/compilation_failures/operations/logical_or_on_int.stderr +++ b/tests/compilation_failures/operations/logical_or_on_int.stderr @@ -1,4 +1,4 @@ -error: The left operand to || is expected to be a bool, but it is an untyped integer +error: The left operand to || is expected to be a bool, but it is an untyped integer value --> tests/compilation_failures/operations/logical_or_on_int.rs:4:18 | 4 | let _ = run!(1 || 2); diff --git a/tests/compilation_failures/operations/multiply_strings.stderr b/tests/compilation_failures/operations/multiply_strings.stderr index 7bed64f2..78211107 100644 --- a/tests/compilation_failures/operations/multiply_strings.stderr +++ b/tests/compilation_failures/operations/multiply_strings.stderr @@ -1,4 +1,4 @@ -error: The * operator is not supported for a string operand +error: The * operator is not supported for a string value operand --> tests/compilation_failures/operations/multiply_strings.rs:4:26 | 4 | let _ = run!("hello" * 3); diff --git a/tests/compilation_failures/operations/negate_unsigned.stderr b/tests/compilation_failures/operations/negate_unsigned.stderr index 5316148d..1bc14add 100644 --- a/tests/compilation_failures/operations/negate_unsigned.stderr +++ b/tests/compilation_failures/operations/negate_unsigned.stderr @@ -1,4 +1,4 @@ -error: The - operator is not supported for a u32 +error: The - operator is not supported for a u32 value --> tests/compilation_failures/operations/negate_unsigned.rs:4:18 | 4 | let _ = run!(-5u32); diff --git a/tests/compilation_failures/operations/shift_left_on_float.stderr b/tests/compilation_failures/operations/shift_left_on_float.stderr index 2de6785d..0c34514a 100644 --- a/tests/compilation_failures/operations/shift_left_on_float.stderr +++ b/tests/compilation_failures/operations/shift_left_on_float.stderr @@ -1,4 +1,4 @@ -error: The << operator is not supported for an untyped float operand +error: The << operator is not supported for an untyped float value operand --> tests/compilation_failures/operations/shift_left_on_float.rs:4:22 | 4 | let _ = run!(1.0 << 2); diff --git a/tests/compilation_failures/operations/stream_subtract.stderr b/tests/compilation_failures/operations/stream_subtract.stderr index d1c99f09..8cdee193 100644 --- a/tests/compilation_failures/operations/stream_subtract.stderr +++ b/tests/compilation_failures/operations/stream_subtract.stderr @@ -1,4 +1,4 @@ -error: The - operator is not supported for a stream operand +error: The - operator is not supported for a stream value operand --> tests/compilation_failures/operations/stream_subtract.rs:4:27 | 4 | let _ = run!(%[hello] - %[world]); diff --git a/tests/compilation_failures/operations/subtract_strings.stderr b/tests/compilation_failures/operations/subtract_strings.stderr index 03af4d8b..418935c4 100644 --- a/tests/compilation_failures/operations/subtract_strings.stderr +++ b/tests/compilation_failures/operations/subtract_strings.stderr @@ -1,4 +1,4 @@ -error: The - operator is not supported for a string operand +error: The - operator is not supported for a string value operand --> tests/compilation_failures/operations/subtract_strings.rs:4:26 | 4 | let _ = run!("hello" - "world"); From f9575ef6974e9a9856f1ed895fb3572fd43c5efe Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 25 Jan 2026 01:48:38 +0000 Subject: [PATCH 09/49] feat: Partial implementation for user defined closures --- plans/TODO.md | 2 +- src/expressions/closures.rs | 9 +- src/expressions/control_flow.rs | 4 +- src/expressions/expression_block.rs | 2 +- src/interpretation/control_flow_pass.rs | 4 +- src/interpretation/interpreter.rs | 25 +- src/interpretation/source_parsing.rs | 334 +++++++++++++++++------- src/misc/arena.rs | 8 + src/misc/parse_traits.rs | 23 +- 9 files changed, 302 insertions(+), 109 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index 93835767..3c0aae63 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -159,7 +159,6 @@ First, read the @./2025-11-vision.md - [x] Create new `Parser` value kind - [x] Add ParserHandle to `InputHandler` and use some generational map to store ParseStacks (or import slotmap) - [x] Look at https://donsz.nl/blog/arenas/ - - [ ] If using slotmap / generational-arena, replace the arena implementation too - [x] Create (temporary) `parse X => |Y| { }` expression - [x] Bind `input` to `Parser` at the start of each parse expression - [x] Create `@input[...]` expression @@ -498,6 +497,7 @@ Also: - [x] Merge `assignee_frames` into `value_frames` as per comment as the top of `assignee_frames` - [x] Rename `EvaluationItem` to `RequestedValue` and consider making `RequestedValue::AssignmentCompletion` wrap an `Owned<()>` so that it becomes truly a value. - [x] Merge `HasValueType` with `ValueLeafKind` +- [ ] If using slotmap / generational-arena, replace the arena implementation too - [ ] Fix the end span of `ClosureExpressionInner` and maybe `Expression` more generally? - [ ] Add `preinterpret::macro` - can this be a declarative macro? Would be slightly more efficient, as it just needs to wrap a call to `preinterpret::stream` or `preinterpret::run`... - [ ] When we create `input = %raw[..]` we will need to set its `end_of_stream` span to the end of the diff --git a/src/expressions/closures.rs b/src/expressions/closures.rs index 5264732b..e1533015 100644 --- a/src/expressions/closures.rs +++ b/src/expressions/closures.rs @@ -35,7 +35,7 @@ impl PartialEq for ClosureExpression { impl Eq for ClosureExpression {} pub(crate) struct ClosureExpressionInner { - _closure_id: ClosureId, + frame_id: FrameId, scope_id: ScopeId, _left_bar: Unused, arguments: Punctuated, @@ -65,7 +65,7 @@ impl ParseSource for ClosureExpressionInner { let end_span = input.cursor().span(); Ok(Self { - _closure_id: ClosureId::new_placeholder(), + frame_id: FrameId::new_placeholder(), scope_id: ScopeId::new_placeholder(), _left_bar: Unused::new(left_bar), arguments, @@ -76,13 +76,14 @@ impl ParseSource for ClosureExpressionInner { } fn control_flow_pass(&mut self, context: FlowCapturer) -> ParseResult<()> { + context.register_frame(&mut self.frame_id); context.register_scope(&mut self.scope_id); - context.enter_scope(self.scope_id); + context.enter_frame(self.frame_id, self.scope_id); for argument in &mut self.arguments { argument.pattern.control_flow_pass(context)?; } self.body.control_flow_pass(context)?; - context.exit_scope(self.scope_id); + context.exit_frame(self.frame_id, self.scope_id); Ok(()) } } diff --git a/src/expressions/control_flow.rs b/src/expressions/control_flow.rs index 1080af25..1bf56e1f 100644 --- a/src/expressions/control_flow.rs +++ b/src/expressions/control_flow.rs @@ -382,7 +382,7 @@ impl Evaluate for ForExpression { for item in iterable.into_iterator()? { iteration_counter.increment_and_check()?; - interpreter.enter_scope(self.iteration_scope); + interpreter.enter_child_scope(self.iteration_scope); self.pattern.handle_destructure(interpreter, item)?; let body_result = self.body.evaluate_owned(interpreter); @@ -618,7 +618,7 @@ impl Evaluate for ParseExpression { .evaluate_owned(interpreter)? .resolve_as("The input to a parse expression")?; - interpreter.enter_scope(self.scope); + interpreter.enter_child_scope(self.scope); let output = interpreter.start_parse(input, |interpreter, handle| { self.parser_variable.define(interpreter, handle); diff --git a/src/expressions/expression_block.rs b/src/expressions/expression_block.rs index cacdfcca..ced04e46 100644 --- a/src/expressions/expression_block.rs +++ b/src/expressions/expression_block.rs @@ -212,7 +212,7 @@ impl Evaluate for ScopedBlock { interpreter: &mut Interpreter, ownership: RequestedOwnership, ) -> ExecutionResult { - interpreter.enter_scope(self.scope); + interpreter.enter_child_scope(self.scope); let output = self .content .evaluate_spanned(interpreter, self.span().into(), ownership)?; diff --git a/src/interpretation/control_flow_pass.rs b/src/interpretation/control_flow_pass.rs index b6df240f..df70bb56 100644 --- a/src/interpretation/control_flow_pass.rs +++ b/src/interpretation/control_flow_pass.rs @@ -52,7 +52,7 @@ impl<'a> ControlFlowAnalyzer<'a> { let mut output = HashMap::new(); for (definition_id, definition) in self.definitions.iter() { - let ancestor_scopes = self.create_ancestor_scopes(definition.scope); + let ancestor_scopes = self.create_local_ancestor_scopes(definition.scope); let (last_use_candidates, markers) = self.last_reference_first_phase( definition_id, @@ -75,7 +75,7 @@ impl<'a> ControlFlowAnalyzer<'a> { return output; } - fn create_ancestor_scopes(&self, scope: ScopeId) -> HashSet { + fn create_local_ancestor_scopes(&self, scope: ScopeId) -> HashSet { let mut scopes = HashSet::new(); let mut current = self.scopes.get(scope).parent; while let Some(scope) = current { diff --git a/src/interpretation/interpreter.rs b/src/interpretation/interpreter.rs index 052369ec..3b78de42 100644 --- a/src/interpretation/interpreter.rs +++ b/src/interpretation/interpreter.rs @@ -11,7 +11,7 @@ pub(crate) struct Interpreter { impl Interpreter { pub(crate) fn new(scope_definitions: ScopeDefinitions) -> Self { - let root_scope_id = scope_definitions.root_scope; + let (_, root_scope_id) = scope_definitions.root_frame; let mut interpreter = Self { config: Default::default(), scope_definitions, @@ -20,7 +20,7 @@ impl Interpreter { output_handler: OutputHandler::new(OutputStream::new()), input_handler: InputHandler::new(), }; - interpreter.enter_scope_inner(root_scope_id, false); + interpreter.enter_scope_inner(root_scope_id, ScopeKind::Root); interpreter } @@ -36,8 +36,8 @@ impl Interpreter { self.scopes.last().unwrap().id } - pub(crate) fn enter_scope(&mut self, id: ScopeId) { - self.enter_scope_inner(id, true); + pub(crate) fn enter_child_scope(&mut self, id: ScopeId) { + self.enter_scope_inner(id, ScopeKind::Child); } pub(crate) fn enter_scope_starting_with_revertible_segment( @@ -48,7 +48,7 @@ impl Interpreter { guard_clause: Option ExecutionResult>, reason: MutationBlockReason, ) -> ExecutionResult> { - self.enter_scope_inner(scope_id, true); + self.enter_scope_inner(scope_id, ScopeKind::Child); self.no_mutation_above.push((scope_id, reason)); unsafe { // SAFETY: This is paired with `unfreeze_existing` below, @@ -115,10 +115,19 @@ impl Interpreter { } } - fn enter_scope_inner(&mut self, id: ScopeId, check_parent: bool) { + fn enter_scope_inner(&mut self, id: ScopeId, expected_kind: ScopeKind) { let new_scope = self.scope_definitions.scopes.get(id); - if check_parent { - assert!(new_scope.parent == Some(self.current_scope_id())); + match expected_kind { + ScopeKind::Root => { + assert!(new_scope.parent.is_none()); + assert_eq!(new_scope.frame, self.scope_definitions.root_frame.0); + } + ScopeKind::FunctionBoundary => { + assert!(new_scope.parent.is_none()); + } + ScopeKind::Child => { + assert_eq!(new_scope.parent, Some(self.current_scope_id())); + } } let variables = { let mut map = HashMap::new(); diff --git a/src/interpretation/source_parsing.rs b/src/interpretation/source_parsing.rs index 3e152a21..b65daf0a 100644 --- a/src/interpretation/source_parsing.rs +++ b/src/interpretation/source_parsing.rs @@ -1,12 +1,12 @@ #![allow(unused)] use super::*; +new_key!(pub(crate) FrameId); new_key!(pub(crate) ScopeId); new_key!(pub(crate) VariableDefinitionId); new_key!(pub(crate) VariableReferenceId); new_key!(pub(crate) ControlFlowSegmentId); new_key!(pub(crate) CatchLocationId); -new_key!(pub(crate) ClosureId); pub(crate) enum InterruptDetails<'a> { /// Break statement (targets loops or labeled blocks) @@ -37,7 +37,8 @@ pub(crate) enum FinalUseAssertion { #[derive(Debug)] pub(crate) struct ScopeDefinitions { // Scopes - pub(crate) root_scope: ScopeId, + pub(crate) root_frame: (FrameId, ScopeId), + pub(crate) frames: Arena, pub(crate) scopes: Arena, pub(crate) definitions: Arena, pub(crate) references: Arena, @@ -45,7 +46,7 @@ pub(crate) struct ScopeDefinitions { pub(crate) catch_locations: Arena, // Segments #[cfg(feature = "debug")] - root_segment: ControlFlowSegmentId, + root_segments: Vec<(FrameId, ControlFlowSegmentId)>, #[cfg(feature = "debug")] segments: Arena, #[cfg(feature = "debug")] @@ -54,61 +55,56 @@ pub(crate) struct ScopeDefinitions { #[allow(unused)] pub(crate) struct FlowAnalysisState { - // SCOPE DATA - scope_id_stack: Vec, + frames_stack: Vec, + frames: Arena, scopes: Arena, definitions: Arena, references: Arena, - // CATCH LOCATION DATA catch_locations: Arena, - catch_location_stack: Vec, - // CONTROL FLOW DATA - segments_stack: Vec, segments: Arena, + + // >> Current positions (copied for convenience/performance) + /// This is frames_stack.last() (else placeholder or the last set) + current_frame_id: FrameId, + /// This is frames_stack.last().scopes_stack.last() (else placeholder or the last set) + current_scope_id: ScopeId, + /// This is frames_stack.last().segments_stack.last() (else placeholder) + current_segment_id: ControlFlowSegmentId, } impl FlowAnalysisState { - pub(crate) fn new() -> Self { - let mut scopes = Arena::new(); - let definitions = Arena::new(); - let references = Arena::new(); - let root_scope = scopes.add(AllocatedScope::Defined(ScopeData { - parent: None, - definitions: Vec::new(), - })); - let mut segments = Arena::new(); - let root_segment = segments.add(ControlFlowSegmentData { - scope: root_scope, - parent: None, - segment_kind: SegmentKind::Sequential, - children: SegmentKind::Sequential.new_children(), - }); + pub(crate) fn new_empty() -> Self { Self { - scope_id_stack: vec![root_scope], - scopes, - definitions, - references, + current_frame_id: FrameId::new_placeholder(), + current_scope_id: ScopeId::new_placeholder(), + current_segment_id: ControlFlowSegmentId::new_placeholder(), + frames_stack: vec![], + frames: Arena::new(), + scopes: Arena::new(), + definitions: Arena::new(), + references: Arena::new(), catch_locations: Arena::new(), - catch_location_stack: Vec::new(), - segments_stack: vec![root_segment], - segments, + segments: Arena::new(), } } - pub(crate) fn finish(mut self) -> ParseResult { - let root_scope = self.scope_id_stack.pop().expect("No scope to pop"); + pub(crate) fn finish( + mut self, + root_frame: (FrameId, ScopeId), + ) -> ParseResult { assert!( - self.scope_id_stack.is_empty(), - "Cannot finish - Unpopped scopes remain" + self.frames_stack.is_empty(), + "Cannot finish - Unpopped frames remain" ); - #[allow(unused)] // If debug is off - let root_segment = self.segments_stack.pop().expect("No segment to pop"); - assert!( - self.segments_stack.is_empty(), - "Cannot finish - Unpopped segments remain" - ); + #[cfg(feature = "debug")] + let root_segments: Vec<(FrameId, ControlFlowSegmentId)> = self + .frames + .iter() + .map(|(frame_id, frame)| (frame_id, frame.defined_ref().root_segment)) + .collect(); + let frames = self.frames.map_all(AllocatedFrame::into_runtime); let scopes = self.scopes.map_all(AllocatedScope::into_defined); let definitions = self .definitions @@ -144,7 +140,8 @@ impl FlowAnalysisState { } Ok(ScopeDefinitions { - root_scope, + root_frame, + frames, scopes, definitions, references, @@ -158,6 +155,10 @@ impl FlowAnalysisState { }) } + pub(crate) fn allocate_frame(&mut self) -> FrameId { + self.frames.add(AllocatedFrame::Allocated) + } + pub(crate) fn allocate_scope(&mut self) -> ScopeId { self.scopes.add(AllocatedScope::Allocated) } @@ -177,30 +178,102 @@ impl FlowAnalysisState { }) } - fn current_scope_id(&self) -> ScopeId { - *self.scope_id_stack.last().unwrap() + // FRAMES + // ====== + + pub(crate) fn enter_frame(&mut self, frame_id: FrameId, scope_id: ScopeId) { + let parent_scope = self.current_scope_id; + *self.frames.get_mut(frame_id) = AllocatedFrame::Defined(FrameData { + lexical_parent: if parent_scope.is_placeholder() { + None + } else { + Some(parent_scope) + }, + root_segment: ControlFlowSegmentId::new_placeholder(), + closed_variables: BTreeMap::new(), + scope_stack: Vec::new(), + segment_stack: Vec::new(), + catch_location_stack: Vec::new(), + }); + self.frames_stack.push(frame_id); + self.current_frame_id = frame_id; + self.enter_scope(scope_id); + let segment = self.enter_segment_with_valid_previous(None, None, SegmentKind::Sequential); + let frame = self.current_frame_mut(); + frame.root_segment = segment; + } + + fn current_frame(&self) -> &FrameData { + self.frames.get(self.current_frame_id).defined_ref() + } + + fn current_frame_mut(&mut self) -> &mut FrameData { + self.frames.get_mut(self.current_frame_id).defined_mut() + } + + pub(crate) fn exit_frame(&mut self, frame_id: FrameId, scope_id: ScopeId) { + self.exit_segment(self.current_frame().root_segment); + assert!( + self.segments_stack().is_empty(), + "Segment stack not empty after exiting frame" + ); + + self.exit_scope(scope_id); + assert!( + self.scope_id_stack_mut().is_empty(), + "Scope stack not empty after exiting frame" + ); + + let id = self.frames_stack.pop().expect("No frame to pop"); + assert_eq!(id, frame_id, "Popped frame is not the current frame"); + // If not, leave `current_frame_id` as the root frame + if let Some(current_frame_id) = self.frames_stack.last() { + self.current_frame_id = *current_frame_id; + } + } + + // SCOPES + // ====== + + fn scope_id_stack(&self) -> &[ScopeId] { + &self.current_frame().scope_stack + } + + fn scope_id_stack_mut(&mut self) -> &mut Vec { + &mut self.current_frame_mut().scope_stack } fn current_scope(&mut self) -> &mut ScopeData { - self.scopes.get_mut(self.current_scope_id()).defined_mut() + self.scopes.get_mut(self.current_scope_id).defined_mut() } pub(crate) fn enter_scope(&mut self, scope_id: ScopeId) { *self.scopes.get_mut(scope_id) = AllocatedScope::Defined(ScopeData { - parent: Some(self.current_scope_id()), definitions: Vec::new(), + parent: self.scope_id_stack_mut().last().copied(), + frame: self.current_frame_id, }); - self.scope_id_stack.push(scope_id); + self.scope_id_stack_mut().push(scope_id); + self.current_scope_id = scope_id; + } + + /// The scope parameter is just to help catch bugs. + pub(crate) fn exit_scope(&mut self, scope: ScopeId) { + let id = self.scope_id_stack_mut().pop().expect("No scope to pop"); + assert_eq!(id, scope, "Popped scope is not the current scope"); + + // If not, leave `current_scope_id` as the root scope + if let Some(current_scope_id) = self.scope_id_stack_mut().last() { + self.current_scope_id = *current_scope_id; + } } pub(crate) fn define_variable(&mut self, id: VariableDefinitionId) { - let scope = self.current_scope_id(); - let segment = self.current_segment_id(); let definition = self.definitions.get_mut(id); let (name, definition_name_span) = definition.take_allocated(); *definition = AllocatedVariableDefinition::Defined(VariableDefinitionData { - scope, - segment, + scope: self.current_scope_id, + segment: self.current_segment_id, name, definition_name_span, references: Vec::new(), @@ -216,10 +289,17 @@ impl FlowAnalysisState { id: VariableReferenceId, #[cfg(feature = "debug")] assertion: FinalUseAssertion, ) -> ParseResult<()> { - let segment = self.current_segment_id(); + let segment = self.current_segment_id; let reference = self.references.get_mut(id); let (name, reference_name_span) = reference.take_allocated(); - for scope_id in self.scope_id_stack.iter().rev() { + // self.scope_id_stack() but inlined so that the mutability checker is happy + let scope_stack = self + .frames + .get(self.current_frame_id) + .defined_ref() + .scope_stack + .as_slice(); + for scope_id in scope_stack.iter().rev() { let scope = self.scopes.get(*scope_id).defined_ref(); for &def_id in scope.definitions.iter().rev() { let def = self.definitions.get_mut(def_id).defined_mut(); @@ -244,30 +324,24 @@ impl FlowAnalysisState { reference_name_span.parse_err(format!("Cannot find variable `{}` in this scope", name)) } - /// The scope parameter is just to help catch bugs. - pub(crate) fn exit_scope(&mut self, scope: ScopeId) { - let id = self.scope_id_stack.pop().expect("No scope to pop"); - assert_eq!(id, scope, "Popped scope is not the current scope"); - } - // SEGMENTS // ======== - fn current_segment_id(&self) -> ControlFlowSegmentId { - *self.segments_stack.last().unwrap() + fn segments_stack(&mut self) -> &mut Vec { + &mut self.current_frame_mut().segment_stack } fn current_segment(&mut self) -> &mut ControlFlowSegmentData { - self.segments.get_mut(self.current_segment_id()) + self.segments.get_mut(self.current_segment_id) } pub(crate) fn enter_next_segment(&mut self, segment_kind: SegmentKind) -> ControlFlowSegmentId { - let parent_id = self.current_segment_id(); + let parent_id = self.current_segment_id; let parent = self.segments.get(parent_id); if !matches!(parent.children, SegmentChildren::Sequential { .. }) { panic!("enter_next_segment can only be called with a sequential parent"); } - self.enter_segment_with_valid_previous(parent_id, None, segment_kind) + self.enter_segment_with_valid_previous(Some(parent_id), None, segment_kind) } pub(crate) fn enter_path_segment( @@ -275,7 +349,7 @@ impl FlowAnalysisState { previous_sibling_id: Option, segment_kind: SegmentKind, ) -> ControlFlowSegmentId { - let parent_id = self.current_segment_id(); + let parent_id = self.current_segment_id; if let Some(previous_sibling_id) = previous_sibling_id { let previous_sibling = self.segments.get(previous_sibling_id); // It might be possible if gotos exist to have a non-local parent, @@ -290,51 +364,73 @@ impl FlowAnalysisState { if !matches!(parent.children, SegmentChildren::PathBased { .. }) { panic!("enter_path_segment can only be called with a path-based parent"); } - self.enter_segment_with_valid_previous(parent_id, previous_sibling_id, segment_kind) + self.enter_segment_with_valid_previous(Some(parent_id), previous_sibling_id, segment_kind) } pub(crate) fn exit_segment(&mut self, segment: ControlFlowSegmentId) { - let id = self.segments_stack.pop().expect("No segment to pop"); + let id = self.segments_stack().pop().expect("No segment to pop"); assert_eq!(id, segment, "Popped segment is not the current segment"); + self.current_segment_id = self + .segments_stack() + .last() + .copied() + .unwrap_or_else(ControlFlowSegmentId::new_placeholder); } fn enter_segment_with_valid_previous( &mut self, - parent_id: ControlFlowSegmentId, + parent_id: Option, previous_sibling_id: Option, segment_kind: SegmentKind, ) -> ControlFlowSegmentId { let child_id = self.segments.add(ControlFlowSegmentData { - scope: self.current_scope_id(), - parent: Some(parent_id), + scope: self.current_scope_id, + parent: parent_id, children: segment_kind.new_children(), segment_kind, }); - self.segments_stack.push(child_id); - let parent = self.segments.get_mut(parent_id); - match &mut parent.children { - SegmentChildren::Sequential { ref mut children } => { - children.push(ControlFlowChild::Segment(child_id)); - } - SegmentChildren::PathBased { - node_previous_map: ref mut node_parent_map, - } => { - node_parent_map.insert(child_id, previous_sibling_id); + self.segments_stack().push(child_id); + self.current_segment_id = child_id; + if let Some(parent_id) = parent_id { + let parent = self.segments.get_mut(parent_id); + match &mut parent.children { + SegmentChildren::Sequential { ref mut children } => { + children.push(ControlFlowChild::Segment(child_id)); + } + SegmentChildren::PathBased { + node_previous_map: ref mut node_parent_map, + } => { + node_parent_map.insert(child_id, previous_sibling_id); + } } } child_id } + // CATCH LOCATIONS + // =============== + + fn catch_location_stack(&self) -> &[CatchLocationId] { + &self.current_frame().catch_location_stack + } + + fn catch_location_stack_mut(&mut self) -> &mut Vec { + &mut self.current_frame_mut().catch_location_stack + } + pub(crate) fn register_catch_location(&mut self, data: CatchLocationData) -> CatchLocationId { self.catch_locations.add(data) } pub(crate) fn enter_catch(&mut self, catch_location_id: CatchLocationId) { - self.catch_location_stack.push(catch_location_id); + self.catch_location_stack_mut().push(catch_location_id); } pub(crate) fn exit_catch(&mut self, catch_location_id: CatchLocationId) { - let popped = self.catch_location_stack.pop().expect("No catch to pop"); + let popped = self + .catch_location_stack_mut() + .pop() + .expect("No catch to pop"); assert_eq!( popped, catch_location_id, "Popped catch location is not the expected catch location" @@ -350,7 +446,7 @@ impl FlowAnalysisState { label: Some(label), .. } => { let label_str = label.ident_string(); - for &catch_location_id in self.catch_location_stack.iter().rev() { + for &catch_location_id in self.catch_location_stack().iter().rev() { let catch_location = self.catch_locations.get(catch_location_id); match catch_location { CatchLocationData::Loop { @@ -376,7 +472,7 @@ impl FlowAnalysisState { label: None, break_token, } => { - for &catch_location_id in self.catch_location_stack.iter().rev() { + for &catch_location_id in self.catch_location_stack().iter().rev() { let catch_location = self.catch_locations.get(catch_location_id); if let CatchLocationData::Loop { .. } = catch_location { return Ok(catch_location_id); @@ -390,7 +486,7 @@ impl FlowAnalysisState { label: Some(label), .. } => { let label_str = label.ident_string(); - for &catch_location_id in self.catch_location_stack.iter().rev() { + for &catch_location_id in self.catch_location_stack().iter().rev() { let catch_location = self.catch_locations.get(catch_location_id); if let CatchLocationData::Loop { label: Some(loc_label), @@ -409,7 +505,7 @@ impl FlowAnalysisState { label: None, continue_token, } => { - for &catch_location_id in self.catch_location_stack.iter().rev() { + for &catch_location_id in self.catch_location_stack().iter().rev() { let catch_location = self.catch_locations.get(catch_location_id); if let CatchLocationData::Loop { .. } = catch_location { return Ok(catch_location_id); @@ -424,7 +520,7 @@ impl FlowAnalysisState { label: Some(label), } => { let label_str = label.ident_string(); - for &catch_location_id in self.catch_location_stack.iter().rev() { + for &catch_location_id in self.catch_location_stack().iter().rev() { let catch_location = self.catch_locations.get(catch_location_id); if let CatchLocationData::AttemptBlock { label: Some(loc_label), @@ -442,7 +538,7 @@ impl FlowAnalysisState { revert_token, label: None, } => { - for &catch_location_id in self.catch_location_stack.iter().rev() { + for &catch_location_id in self.catch_location_stack().iter().rev() { let catch_location = self.catch_locations.get(catch_location_id); if let CatchLocationData::AttemptBlock { .. } = catch_location { return Ok(catch_location_id); @@ -534,6 +630,58 @@ pub(super) enum ControlFlowChild { VariableReference(VariableReferenceId, VariableDefinitionId), } +enum AllocatedFrame { + Allocated, + Defined(FrameData), +} + +impl AllocatedFrame { + fn defined_mut(&mut self) -> &mut FrameData { + match self { + AllocatedFrame::Defined(data) => data, + _ => panic!("Frame was accessed before it was defined"), + } + } + + fn defined_ref(&self) -> &FrameData { + match self { + AllocatedFrame::Defined(data) => data, + _ => panic!("Frame was accessed before it was defined"), + } + } + + fn into_runtime(self) -> RuntimeFrame { + match self { + AllocatedFrame::Defined(data) => RuntimeFrame { + closed_variables: data.closed_variables, + }, + _ => panic!("Frame was not defined"), + } + } +} + +struct FrameData { + // Only the actual root has no lexical parent + lexical_parent: Option, + root_segment: ControlFlowSegmentId, + // When a variable name matches to a variable defined in an ancestor scope, + // we need to close over that variable. + // + // To do this, at every function boundary between these, we: + // - Define a closed variable with the same name + // - Create a variable reference which we can use to capture the variable + // from the parent closure when the closure is created. + closed_variables: BTreeMap, + scope_stack: Vec, + catch_location_stack: Vec, + segment_stack: Vec, +} + +#[derive(Debug)] +pub(crate) struct RuntimeFrame { + pub(crate) closed_variables: BTreeMap, +} + enum AllocatedScope { Allocated, Defined(ScopeData), @@ -564,8 +712,16 @@ impl AllocatedScope { #[derive(Debug)] pub(crate) struct ScopeData { - pub(crate) parent: Option, pub(crate) definitions: Vec, + /// Only a None if this is a root scope of a frame + pub(crate) parent: Option, + pub(crate) frame: FrameId, +} + +pub(crate) enum ScopeKind { + Root, + Child, + FunctionBoundary, } enum AllocatedVariableDefinition { diff --git a/src/misc/arena.rs b/src/misc/arena.rs index cdc172c5..9e9cc8f1 100644 --- a/src/misc/arena.rs +++ b/src/misc/arena.rs @@ -73,6 +73,14 @@ impl Arena { .map(|(index, v)| (K::from_inner(Key::new(index)), v)) } + #[allow(unused)] + pub(crate) fn iter_mut(&mut self) -> impl Iterator { + self.data + .iter_mut() + .enumerate() + .map(|(index, v)| (K::from_inner(Key::new(index)), v)) + } + pub(crate) fn map_all(self, f: impl Fn(D) -> D2) -> Arena { Arena { data: self.data.into_iter().map(f).collect(), diff --git a/src/misc/parse_traits.rs b/src/misc/parse_traits.rs index e15368af..507eb77f 100644 --- a/src/misc/parse_traits.rs +++ b/src/misc/parse_traits.rs @@ -201,10 +201,21 @@ impl ControlFlowContext { inner: impl FnOnce(&mut T, FlowCapturer) -> ParseResult<()>, ) -> ParseResult { let mut context = Self { - state: FlowAnalysisState::new(), + state: FlowAnalysisState::new_empty(), }; + let mut root_frame = FrameId::new_placeholder(); + let mut root_scope = ScopeId::new_placeholder(); + context.register_frame(&mut root_frame); + context.register_scope(&mut root_scope); + context.enter_frame(root_frame, root_scope); inner(parsed, &mut context)?; - context.state.finish() + context.exit_frame(root_frame, root_scope); + context.state.finish((root_frame, root_scope)) + } + + pub(crate) fn register_frame(&mut self, id: &mut FrameId) { + assert!(id.is_placeholder()); + *id = self.state.allocate_frame(); } pub(crate) fn register_scope(&mut self, id: &mut ScopeId) { @@ -230,6 +241,14 @@ impl ControlFlowContext { *id = self.state.allocate_variable_reference(ident); } + pub(crate) fn enter_frame(&mut self, frame: FrameId, scope: ScopeId) { + self.state.enter_frame(frame, scope); + } + + pub(crate) fn exit_frame(&mut self, frame: FrameId, scope: ScopeId) { + self.state.exit_frame(frame, scope); + } + pub(crate) fn enter_scope(&mut self, scope: ScopeId) { self.state.enter_scope(scope); } From 62a2752750911be6a4c33d9c16a5b6daf9b2858a Mon Sep 17 00:00:00 2001 From: David Edey Date: Mon, 26 Jan 2026 00:14:10 +0000 Subject: [PATCH 10/49] feat: Added basic closures --- plans/TODO.md | 19 +-- src/expressions/closures.rs | 148 ++++++++++++++++--- src/expressions/evaluation/value_frames.rs | 25 ++-- src/expressions/patterns.rs | 43 ++++-- src/expressions/type_resolution/type_data.rs | 2 +- src/expressions/values/function.rs | 65 ++++---- src/interpretation/interpreter.rs | 4 + src/interpretation/source_parsing.rs | 28 ++-- src/misc/parse_traits.rs | 26 +++- tests/functions.rs | 9 ++ 10 files changed, 262 insertions(+), 107 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index 3c0aae63..ca86a515 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -222,13 +222,12 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] Fix various noted edge cases in object property / index ownership - [x] Replace `articled_kind` / `articled_display_name` with `articled_value_name` - [ ] We can define closures (without any closed values for now) - * Value type function `let my_func = |x, y, z| { ... };` - * Parameters can be `x` / `x: any` (Owned), `x: &any` (Shared) or `x: &mut any` (Mutable). - * To start with, they can't capture any outer variables -- [ ] Break/continue label resolution in functions/closures - * Functions and closures must resolve break/continue labels statically - * Break and continue statements should not leak out of function boundaries - * This needs to be validated during the control flow pass + - [ ] Closure parsing `let my_func = |x, y, z| { ... };`, parameters can be `x` / `x: any` (Owned), `x: &any` (Shared) or `x: &mut any` (Mutable). + - [x] To start with, they can't capture any outer variables + (variable resolution stops at the top of the frame) + - [x] Correct break/continue label resolution in functions/closures + - [ ] Closures expose an interface, and a means of invocation, and are wired into the invocation logic + - [ ] Various tests - [ ] Closed Bindings - [ ] Change bindings (currently just variables) to be able to store any of the following: (nb we still restrict variables to be owned for now). ```rust @@ -242,8 +241,9 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). * The closure consists of a set of bindings attached to the function value, either: - `ClosedVariable::Owned(Value)` if it's the last mention of the closed variable, so it can be moved in - `ClosedVariable::Referenced(Rc>)` otherwise. - The nice thing about this is can pull shared/mutable at runtime, - without needing to magically work out what to move. + The nice thing about this is can pull shared/mutable at runtime, without needing to magically work out what to move. + - DAVID NOTE: This only works if we expect the variables to be passed by reference, not moved. There's often an expectation that value types are passed cloned... + - ... let's think about this a little more. Perhaps being able to support (move) expressions in future might be nice. `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). * Invocation requests `CopyOnWrite`, and can be on a shared function or an owned function (if it is the last usage of that value, as per normal red/owned binding rules) * If invocation is on an owned function, then owned values from the closure can be consumed by the invocation * Otherwise, the values are only available as shared/mut @@ -498,6 +498,7 @@ Also: - [x] Rename `EvaluationItem` to `RequestedValue` and consider making `RequestedValue::AssignmentCompletion` wrap an `Owned<()>` so that it becomes truly a value. - [x] Merge `HasValueType` with `ValueLeafKind` - [ ] If using slotmap / generational-arena, replace the arena implementation too +- [ ] Consider if we can have memory leaks due to `Rc<..>` loops, and whether to document it, or use some kind of Arena instead. - [ ] Fix the end span of `ClosureExpressionInner` and maybe `Expression` more generally? - [ ] Add `preinterpret::macro` - can this be a declarative macro? Would be slightly more efficient, as it just needs to wrap a call to `preinterpret::stream` or `preinterpret::run`... - [ ] When we create `input = %raw[..]` we will need to set its `end_of_stream` span to the end of the diff --git a/src/expressions/closures.rs b/src/expressions/closures.rs index e1533015..eaedd088 100644 --- a/src/expressions/closures.rs +++ b/src/expressions/closures.rs @@ -1,7 +1,6 @@ use super::*; -#[derive(Clone)] -pub(crate) struct ClosureExpression(Rc); +pub(crate) struct ClosureExpression(Rc); impl ParseSource for ClosureExpression { fn parse(input: SourceParser) -> ParseResult { @@ -21,8 +20,87 @@ impl ClosureExpression { _interpreter: &mut Interpreter, ownership: RequestedOwnership, ) -> ExecutionResult> { + // TODO[functions]: Capture variables from the parent frame. let span_range = self.0.span_range; - ownership.map_from_owned(self.clone().into_any_value().spanned(span_range)) + let value = ClosureValue { + definition: Rc::clone(&self.0), + }; + ownership.map_from_owned(value.into_any_value().spanned(span_range)) + } +} + +#[derive(Clone)] +pub(crate) struct ClosureValue { + definition: Rc, + // TODO[functions]: Add closed_variables here +} + +impl PartialEq for ClosureValue { + fn eq(&self, other: &Self) -> bool { + // A few options here: + // 1. Always return false. This is simple, but non-reflexive and non-intuitive. + // e.g. NaN works like this + // 2. Check just for closure equality + // 3. Also check for equality of bound values + // I think 2 makes the most sense for now + Rc::ptr_eq(&self.definition, &other.definition) + } +} + +impl ClosureValue { + /// Returns (argument_ownerships, required_argument_count) + pub(crate) fn argument_ownerships(&self) -> (&[ArgumentOwnership], usize) { + ( + &self.definition.argument_ownerships, + self.definition.required_argument_count, + ) + } + + pub(crate) fn invoke( + self, + arguments: Vec>, + context: &mut FunctionCallContext, + ) -> ExecutionResult> { + let definition = &*self.definition; + + context + .interpreter + .enter_function_boundary_scope(definition.scope_id); + + for (pattern, Spanned(arg, arg_span)) in + definition.argument_definitions.iter().zip(arguments) + { + let value = match arg { + ArgumentValue::Owned(owned) => owned, + _ => { + context.interpreter.exit_scope(definition.scope_id); + return arg_span + .type_err("Only owned arguments are currently supported for closures"); + } + }; + pattern.handle_destructure(context.interpreter, value)?; + } + + let Spanned(output, output_span) = definition.body.evaluate( + context.interpreter, + RequestedOwnership::Concrete(ArgumentOwnership::AsIs), + )?; + + let returned_value = match output { + RequestedValue::Owned(any_value) => ReturnedValue::Owned(any_value), + RequestedValue::Shared(any_value) => ReturnedValue::Shared(any_value), + RequestedValue::Mutable(any_value) => ReturnedValue::Mutable(any_value), + RequestedValue::CopyOnWrite(any_value) => ReturnedValue::CopyOnWrite(any_value), + _ => { + return output_span.type_err( + "Closure body must evaluate to an Owned, Shared, Mutable, or CopyOnWrite value", + ); + } + }; + + context.interpreter.exit_scope(definition.scope_id); + + Ok(Spanned(returned_value, output_span)) } } @@ -34,22 +112,23 @@ impl PartialEq for ClosureExpression { impl Eq for ClosureExpression {} -pub(crate) struct ClosureExpressionInner { +pub(crate) struct ClosureDefinition { frame_id: FrameId, scope_id: ScopeId, - _left_bar: Unused, - arguments: Punctuated, - _right_bar: Unused, + required_argument_count: usize, + argument_ownerships: Vec, + argument_definitions: Vec, body: Expression, span_range: SpanRange, } -impl ParseSource for ClosureExpressionInner { +impl ParseSource for ClosureDefinition { fn parse(input: SourceParser) -> ParseResult { - let left_bar: syn::Token![|] = input.parse()?; - let start_span = left_bar.span; - let arguments = input.parse_terminated()?; - let _right_bar = input.parse()?; + let start_span = input.span(); + let _left_bar = input.parse::()?; + let punctuated_arguments = input + .parse_punctuated_until::(|x| x.peek(Token![|]))?; + let _right_bar = input.parse::()?; let body = input.parse()?; // Really we want to use the span of the last token in the body expression. @@ -64,12 +143,39 @@ impl ParseSource for ClosureExpressionInner { // For now we'll use the off-by-one current span of the input. let end_span = input.cursor().span(); + let mut argument_ownerships = Vec::with_capacity(punctuated_arguments.len()); + let mut argument_definitions = Vec::with_capacity(punctuated_arguments.len()); + for arg in punctuated_arguments.into_iter() { + let ownership = if let Some(annotation) = &arg.annotation { + match &annotation.argument_specifier { + ArgumentSpecifier::ByValue { .. } => ArgumentOwnership::Owned, + ArgumentSpecifier::BySharedRef { .. } => ArgumentOwnership::Shared, + ArgumentSpecifier::ByMutableRef { .. } => ArgumentOwnership::Mutable, + } + } else { + // Default to by-value + ArgumentOwnership::Owned + }; + let pattern = arg.pattern; + match (&ownership, &pattern) { + (ArgumentOwnership::Owned, _) => {} + (_, Pattern::Variable(_)) => {} + _ => { + return pattern.parse_err( + "Destructuring patterns are not currently supported for & and &mut arguments", + ); + } + } + argument_ownerships.push(ownership); + argument_definitions.push(pattern); + } + Ok(Self { frame_id: FrameId::new_placeholder(), scope_id: ScopeId::new_placeholder(), - _left_bar: Unused::new(left_bar), - arguments, - _right_bar, + required_argument_count: argument_ownerships.len(), + argument_ownerships, + argument_definitions, body, span_range: SpanRange::new_between(start_span, end_span), }) @@ -79,8 +185,8 @@ impl ParseSource for ClosureExpressionInner { context.register_frame(&mut self.frame_id); context.register_scope(&mut self.scope_id); context.enter_frame(self.frame_id, self.scope_id); - for argument in &mut self.arguments { - argument.pattern.control_flow_pass(context)?; + for argument in &mut self.argument_definitions { + argument.control_flow_pass(context)?; } self.body.control_flow_pass(context)?; context.exit_frame(self.frame_id, self.scope_id); @@ -90,14 +196,14 @@ impl ParseSource for ClosureExpressionInner { struct FunctionArgument { pattern: Pattern, - _annotation: Option, + annotation: Option, } impl ParseSource for FunctionArgument { fn parse(input: SourceParser) -> ParseResult { Ok(Self { pattern: input.parse()?, - _annotation: if input.peek(syn::Token![:]) { + annotation: if input.peek(syn::Token![:]) { Some(input.parse()?) } else { None @@ -112,7 +218,7 @@ impl ParseSource for FunctionArgument { struct FunctionArgumentAnnotation { _colon: Unused, - _argument_specifier: ArgumentSpecifier, + argument_specifier: ArgumentSpecifier, } // They're clearer with a common prefix By @@ -158,7 +264,7 @@ impl ParseSource for FunctionArgumentAnnotation { }; Ok(Self { _colon, - _argument_specifier, + argument_specifier: _argument_specifier, }) } diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index da6e9203..66a6c82c 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -1028,7 +1028,7 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { // Disable receiver so it can be stored in the FunctionValue let receiver = receiver.map(|v| v.disable()); let function_value = FunctionValue { - definition: FunctionDefinition::Native(method), + invokable: InvokableFunction::Native(method), disabled_bound_arguments: vec![receiver], }; return context.return_value(function_value.spanned(result_span)); @@ -1337,7 +1337,7 @@ enum InvocationPath { InvokablePath, ArgumentsPath { function_span: SpanRange, - interface: &'static FunctionInterface, + invokable: InvokableFunction, disabled_evaluated_arguments: Vec>, }, } @@ -1387,19 +1387,12 @@ impl EvaluationFrame for InvocationBuilder { .spanned(function_span) .downcast_resolve::("An invoked value")?; - // I need to extract - // (A): Function interface - // (B): Already bound disabled arguments -> assumed empty - let interface = match function.definition { - FunctionDefinition::Native(interface) => interface, - FunctionDefinition::Closure(_) => todo!(), - }; - // Placeholder for already bound arguments. We can assume they're already disabled, and of the correct ownership/s. + let invokable = function.invokable; let disabled_bound_arguments = function.disabled_bound_arguments; let unbound_arguments_count = self.unevaluated_parameters_stack.len(); - let (argument_ownerships, min_arguments) = interface.argument_ownerships(); + let (argument_ownerships, min_arguments) = invokable.argument_ownerships(); let max_arguments = argument_ownerships.len(); if disabled_bound_arguments.len() > max_arguments { @@ -1457,7 +1450,7 @@ impl EvaluationFrame for InvocationBuilder { self.state = InvocationPath::ArgumentsPath { function_span, - interface, + invokable, disabled_evaluated_arguments: disabled_bound_arguments, }; } @@ -1478,10 +1471,10 @@ impl EvaluationFrame for InvocationBuilder { context.request_any_value(self, parameter, RequestedOwnership::Concrete(ownership)) } None => { - let (arguments, interface, function_span) = match self.state { + let (arguments, invokable, function_span) = match self.state { InvocationPath::InvokablePath => unreachable!("Already updated above"), InvocationPath::ArgumentsPath { - interface, + invokable, disabled_evaluated_arguments, function_span, } => { @@ -1496,7 +1489,7 @@ impl EvaluationFrame for InvocationBuilder { arg.try_map(|v| v.enable(span)) }) .collect::>>()?; - (arguments, interface, function_span) + (arguments, invokable, function_span) } }; let mut call_context = FunctionCallContext { @@ -1506,7 +1499,7 @@ impl EvaluationFrame for InvocationBuilder { ), interpreter: context.interpreter(), }; - let output = interface.execute(arguments, &mut call_context)?; + let output = invokable.invoke(arguments, &mut call_context)?; context.return_returned_value(output)? } }) diff --git a/src/expressions/patterns.rs b/src/expressions/patterns.rs index 1f07033c..1f9c3ee4 100644 --- a/src/expressions/patterns.rs +++ b/src/expressions/patterns.rs @@ -14,7 +14,7 @@ pub(crate) enum Pattern { Object(ObjectPattern), Stream(StreamPattern), ParseTemplatePattern(ParseTemplatePattern), - Discarded(Unused), + Discarded(Token![_]), } impl ParseSource for Pattern { @@ -64,6 +64,25 @@ impl ParseSource for Pattern { } } +impl HasSpanRange for Pattern { + fn span_range(&self) -> SpanRange { + match self { + Pattern::Variable(variable) => variable.definition.ident.span_range(), + Pattern::Array(array) => array.brackets.span_range(), + Pattern::Object(object) => { + SpanRange::new_between(object.prefix.span, object.braces.close()) + } + Pattern::Stream(stream) => { + SpanRange::new_between(stream.prefix.span, stream.brackets.close()) + } + Pattern::ParseTemplatePattern(pattern) => { + SpanRange::new_between(pattern.prefix.span, pattern.brackets.close()) + } + Pattern::Discarded(token) => token.span_range(), + } + } +} + impl HandleDestructure for Pattern { fn handle_destructure( &self, @@ -94,7 +113,7 @@ impl ParseSource for ArrayPattern { let (brackets, inner) = input.parse_brackets()?; Ok(Self { brackets, - items: inner.parse_terminated()?, + items: inner.parse_punctuated_until_end()?, }) } @@ -197,19 +216,19 @@ impl ParseSource for PatternOrDotDot { } pub struct ObjectPattern { - _prefix: Unused, + prefix: Token![%], braces: Braces, entries: Punctuated, } impl ParseSource for ObjectPattern { fn parse(input: SourceParser) -> ParseResult { - let _prefix = input.parse()?; + let prefix = input.parse()?; let (_braces, inner) = input.parse_braces()?; Ok(Self { - _prefix, + prefix, braces: _braces, - entries: inner.parse_terminated()?, + entries: inner.parse_punctuated_until_end()?, }) } @@ -326,7 +345,7 @@ impl ParseSource for ObjectEntry { } pub struct StreamPattern { - _prefix: Unused, + prefix: Token![%], brackets: Brackets, // TODO[parsers]: Replace with a distinct type that doesn't allow embedded statements, but does allow %[group] and %[raw] content: ParseTemplateStream, @@ -334,10 +353,10 @@ pub struct StreamPattern { impl ParseSource for StreamPattern { fn parse(input: SourceParser) -> ParseResult { - let _prefix = input.parse()?; + let prefix = input.parse()?; let (brackets, inner) = input.parse_brackets()?; Ok(Self { - _prefix, + prefix, brackets, content: ParseTemplateStream::parse_with_span(&inner, brackets.span())?, }) @@ -364,7 +383,7 @@ impl HandleDestructure for StreamPattern { /// and used to capture the consumed stream into a variable. There, the ident is a reference /// to an existing variable, whose value is expected to be a parser. pub(crate) struct ParseTemplatePattern { - _prefix: Unused, + prefix: Token![@], parser_definition: VariableDefinition, brackets: Brackets, content: ParseTemplateStream, @@ -372,12 +391,12 @@ pub(crate) struct ParseTemplatePattern { impl ParseSource for ParseTemplatePattern { fn parse(input: SourceParser) -> ParseResult { - let _prefix = input.parse()?; + let prefix = input.parse()?; let parser_definition = input.parse()?; let (brackets, inner) = input.parse_brackets()?; let content = ParseTemplateStream::parse_with_span(&inner, brackets.span())?; Ok(Self { - _prefix, + prefix, parser_definition, brackets, content, diff --git a/src/expressions/type_resolution/type_data.rs b/src/expressions/type_resolution/type_data.rs index 4cb30b5b..9ebd2726 100644 --- a/src/expressions/type_resolution/type_data.rs +++ b/src/expressions/type_resolution/type_data.rs @@ -149,7 +149,7 @@ pub(crate) enum FunctionInterface { } impl FunctionInterface { - pub(crate) fn execute( + pub(crate) fn invoke( &self, arguments: Vec>, context: &mut FunctionCallContext, diff --git a/src/expressions/values/function.rs b/src/expressions/values/function.rs index 9dd52d09..df7f3563 100644 --- a/src/expressions/values/function.rs +++ b/src/expressions/values/function.rs @@ -9,42 +9,60 @@ define_leaf_type! { dyn_impls: {}, } -pub(crate) struct FunctionValue { - pub(crate) definition: FunctionDefinition, - pub(crate) disabled_bound_arguments: Vec>, +define_type_features! { + impl FunctionType, + pub(crate) mod function_value_interface {} } -impl Clone for FunctionValue { - fn clone(&self) -> Self { - Self { - definition: self.definition.clone(), - disabled_bound_arguments: self.disabled_bound_arguments.clone(), - } - } +#[derive(Clone)] +pub(crate) struct FunctionValue { + pub(crate) disabled_bound_arguments: Vec>, + pub(crate) invokable: InvokableFunction, } #[derive(Clone)] -pub(crate) enum FunctionDefinition { +pub(crate) enum InvokableFunction { Native(&'static FunctionInterface), // TODO[closures]: Add closed_variables: Vec to Closure - Closure(ClosureExpression), + Closure(ClosureValue), } -impl Eq for FunctionDefinition {} +impl Eq for InvokableFunction {} -impl PartialEq for FunctionDefinition { +impl PartialEq for InvokableFunction { fn eq(&self, other: &Self) -> bool { match (self, other) { - (FunctionDefinition::Native(a), FunctionDefinition::Native(b)) => std::ptr::eq(*a, *b), - (FunctionDefinition::Closure(a), FunctionDefinition::Closure(b)) => a == b, + (InvokableFunction::Native(a), InvokableFunction::Native(b)) => std::ptr::eq(*a, *b), + (InvokableFunction::Closure(a), InvokableFunction::Closure(b)) => a.eq(b), _ => false, } } } +impl InvokableFunction { + /// Returns (argument_ownerships, required_argument_count) + pub(crate) fn argument_ownerships(&self) -> (&[ArgumentOwnership], usize) { + match self { + InvokableFunction::Native(interface) => interface.argument_ownerships(), + InvokableFunction::Closure(closure) => closure.argument_ownerships(), + } + } + + pub(crate) fn invoke( + self, + arguments: Vec>, + context: &mut FunctionCallContext, + ) -> ExecutionResult> { + match self { + InvokableFunction::Native(interface) => interface.invoke(arguments, context), + InvokableFunction::Closure(closure) => closure.invoke(arguments, context), + } + } +} + impl ValuesEqual for FunctionValue { fn test_equality(&self, other: &Self, ctx: &mut C) -> C::Result { - if self.definition == other.definition { + if self.invokable == other.invokable { ctx.values_equal() } else { ctx.leaf_values_not_equal("left_anon_function", "right_anon_function") @@ -60,27 +78,22 @@ impl IsValueContent for &'static FunctionInterface { impl IntoValueContent<'static> for &'static FunctionInterface { fn into_content(self) -> Content<'static, Self::Type, Self::Form> { FunctionValue { - definition: FunctionDefinition::Native(self), + invokable: InvokableFunction::Native(self), disabled_bound_arguments: vec![], } } } -impl IsValueContent for ClosureExpression { +impl IsValueContent for ClosureValue { type Type = FunctionType; type Form = BeOwned; } -impl IntoValueContent<'static> for ClosureExpression { +impl IntoValueContent<'static> for ClosureValue { fn into_content(self) -> Content<'static, Self::Type, Self::Form> { FunctionValue { - definition: FunctionDefinition::Closure(self), + invokable: InvokableFunction::Closure(self), disabled_bound_arguments: vec![], } } } - -define_type_features! { - impl FunctionType, - pub(crate) mod function_value_interface {} -} diff --git a/src/interpretation/interpreter.rs b/src/interpretation/interpreter.rs index 3b78de42..5c0bfa61 100644 --- a/src/interpretation/interpreter.rs +++ b/src/interpretation/interpreter.rs @@ -40,6 +40,10 @@ impl Interpreter { self.enter_scope_inner(id, ScopeKind::Child); } + pub(crate) fn enter_function_boundary_scope(&mut self, id: ScopeId) { + self.enter_scope_inner(id, ScopeKind::FunctionBoundary); + } + pub(crate) fn enter_scope_starting_with_revertible_segment( &mut self, scope_id: ScopeId, diff --git a/src/interpretation/source_parsing.rs b/src/interpretation/source_parsing.rs index b65daf0a..0833aa08 100644 --- a/src/interpretation/source_parsing.rs +++ b/src/interpretation/source_parsing.rs @@ -36,15 +36,13 @@ pub(crate) enum FinalUseAssertion { #[derive(Debug)] pub(crate) struct ScopeDefinitions { - // Scopes pub(crate) root_frame: (FrameId, ScopeId), pub(crate) frames: Arena, pub(crate) scopes: Arena, pub(crate) definitions: Arena, pub(crate) references: Arena, - // Catch locations pub(crate) catch_locations: Arena, - // Segments + // Segment Debugging #[cfg(feature = "debug")] root_segments: Vec<(FrameId, ControlFlowSegmentId)>, #[cfg(feature = "debug")] @@ -64,11 +62,11 @@ pub(crate) struct FlowAnalysisState { segments: Arena, // >> Current positions (copied for convenience/performance) - /// This is frames_stack.last() (else placeholder or the last set) + /// This is always frames_stack.last() (else placeholder) current_frame_id: FrameId, - /// This is frames_stack.last().scopes_stack.last() (else placeholder or the last set) + /// This is always frames_stack.last().scopes_stack.last() (else placeholder) current_scope_id: ScopeId, - /// This is frames_stack.last().segments_stack.last() (else placeholder) + /// This is always frames_stack.last().segments_stack.last() (else placeholder) current_segment_id: ControlFlowSegmentId, } @@ -226,10 +224,11 @@ impl FlowAnalysisState { let id = self.frames_stack.pop().expect("No frame to pop"); assert_eq!(id, frame_id, "Popped frame is not the current frame"); - // If not, leave `current_frame_id` as the root frame - if let Some(current_frame_id) = self.frames_stack.last() { - self.current_frame_id = *current_frame_id; - } + self.current_frame_id = self + .frames_stack + .last() + .copied() + .unwrap_or_else(FrameId::new_placeholder); } // SCOPES @@ -262,10 +261,11 @@ impl FlowAnalysisState { let id = self.scope_id_stack_mut().pop().expect("No scope to pop"); assert_eq!(id, scope, "Popped scope is not the current scope"); - // If not, leave `current_scope_id` as the root scope - if let Some(current_scope_id) = self.scope_id_stack_mut().last() { - self.current_scope_id = *current_scope_id; - } + self.current_scope_id = self + .scope_id_stack() + .last() + .copied() + .unwrap_or_else(ScopeId::new_placeholder); } pub(crate) fn define_variable(&mut self, id: VariableDefinitionId) { diff --git a/src/misc/parse_traits.rs b/src/misc/parse_traits.rs index 507eb77f..94e03062 100644 --- a/src/misc/parse_traits.rs +++ b/src/misc/parse_traits.rs @@ -19,10 +19,19 @@ impl<'a> ParseBuffer<'a, Source> { T::parse_optional(self) } - pub fn parse_terminated( + /// This parses T and P in order, finishing when the stream (i.e. group) is done. + pub fn parse_punctuated_until_end( &'a self, ) -> ParseResult> { - Punctuated::parse_terminated_using(self, T::parse, P::parse) + Punctuated::parse_terminated_until(self, T::parse, P::parse, |x| x.is_empty()) + } + + /// This parses T and P in order, finishing when the stream (i.e. group) is done. + pub fn parse_punctuated_until( + &'a self, + end_condition: impl Fn(&Self) -> bool, + ) -> ParseResult> { + Punctuated::parse_terminated_until(self, T::parse, P::parse, end_condition) } pub(crate) fn call ParseResult>( @@ -372,7 +381,7 @@ impl<'a, K> ParseBuffer<'a, K> { pub fn parse_terminated_generic, P: Parse>( &'a self, ) -> ParseResult> { - Punctuated::parse_terminated_using(self, T::parse, P::parse) + Punctuated::parse_terminated_until(self, T::parse, P::parse, |x| x.is_empty()) } pub(crate) fn call_generic) -> ParseResult>( @@ -569,29 +578,30 @@ impl<'a, K> ParseBuffer<'a, K> { // ======================= pub(crate) trait PunctuatedExtensions: Sized { - fn parse_terminated_using( + fn parse_terminated_until( input: I, value_parser: impl Fn(I) -> ParseResult, punct_parser: impl Fn(I) -> ParseResult

, + end_condition: impl Fn(I) -> bool, ) -> ParseResult; } impl PunctuatedExtensions for Punctuated { // More flexible than syn's built-in parse_terminated_with - fn parse_terminated_using( + fn parse_terminated_until( input: I, value_parser: impl Fn(I) -> ParseResult, punct_parser: impl Fn(I) -> ParseResult

, + end_condition: impl Fn(I) -> bool, ) -> ParseResult { let mut punctuated = Punctuated::new(); - loop { - if input.is_empty() { + if end_condition(input) { break; } let value = value_parser(input)?; punctuated.push_value(value); - if input.is_empty() { + if end_condition(input) { break; } let punct = punct_parser(input)?; diff --git a/tests/functions.rs b/tests/functions.rs index feb63c49..7ec72efc 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -48,3 +48,12 @@ fn test_preinterpret_api() { } // See `abort_on_iteration_limit_exceeded` in the failure tests for a negative case } + +#[test] +fn test_basic_closures() { + // TODO[functions]: Fix me! + run! { + // let double_me = |x| x * 2; + // %[_].assert_eq(double_me(4), 8); + } +} From 414ae35049089e6a9270cbe2d1e21ae4cb6ff280 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 31 Jan 2026 11:58:49 +0000 Subject: [PATCH 11/49] fix: Fix closure parsing --- plans/TODO.md | 11 +-- src/expressions/closures.rs | 8 +-- src/expressions/control_flow.rs | 4 +- src/expressions/expression_block.rs | 2 +- src/expressions/values/preinterpret.rs | 6 +- src/interpretation/interpreter.rs | 70 +++++++++++++------ src/interpretation/source_parsing.rs | 57 +++++++++++---- src/misc/arena.rs | 11 ++- .../return_shared_ref_cannot_mutate.rs | 8 +++ tests/functions.rs | 28 +++++++- 10 files changed, 149 insertions(+), 56 deletions(-) create mode 100644 tests/compilation_failures/functions/return_shared_ref_cannot_mutate.rs diff --git a/plans/TODO.md b/plans/TODO.md index ca86a515..6238c86f 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -221,13 +221,16 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] It's an error to set `x.y = z` or `%{ y: z }` if `y` is a method on object - [x] Fix various noted edge cases in object property / index ownership - [x] Replace `articled_kind` / `articled_display_name` with `articled_value_name` -- [ ] We can define closures (without any closed values for now) - - [ ] Closure parsing `let my_func = |x, y, z| { ... };`, parameters can be `x` / `x: any` (Owned), `x: &any` (Shared) or `x: &mut any` (Mutable). +- [x] We can define closures (without any closed values for now) + - [x] Closure parsing `let my_func = |x, y, z| { ... };`, parameters can be `x` / `x: any` (Owned), `x: &any` (Shared) or `x: &mut any` (Mutable). - [x] To start with, they can't capture any outer variables (variable resolution stops at the top of the frame) - [x] Correct break/continue label resolution in functions/closures - - [ ] Closures expose an interface, and a means of invocation, and are wired into the invocation logic - - [ ] Various tests + - [x] Closures expose an interface, and a means of invocation, and are wired into the invocation logic +- [ ] A recursion limit is implemented +- [ ] Lots of tests + - [ ] Loop / Break inside closure + - [ ] Break doesn't compile if it would propagate outside closure - [ ] Closed Bindings - [ ] Change bindings (currently just variables) to be able to store any of the following: (nb we still restrict variables to be owned for now). ```rust diff --git a/src/expressions/closures.rs b/src/expressions/closures.rs index eaedd088..3577fa24 100644 --- a/src/expressions/closures.rs +++ b/src/expressions/closures.rs @@ -65,7 +65,7 @@ impl ClosureValue { context .interpreter - .enter_function_boundary_scope(definition.scope_id); + .enter_function_boundary_scope(definition.scope_id, definition.frame_id, definition.span_range)?; for (pattern, Spanned(arg, arg_span)) in definition.argument_definitions.iter().zip(arguments) @@ -81,7 +81,7 @@ impl ClosureValue { pattern.handle_destructure(context.interpreter, value)?; } - let Spanned(output, output_span) = definition.body.evaluate( + let Spanned(output, body_span) = definition.body.evaluate( context.interpreter, RequestedOwnership::Concrete(ArgumentOwnership::AsIs), )?; @@ -92,7 +92,7 @@ impl ClosureValue { RequestedValue::Mutable(any_value) => ReturnedValue::Mutable(any_value), RequestedValue::CopyOnWrite(any_value) => ReturnedValue::CopyOnWrite(any_value), _ => { - return output_span.type_err( + return body_span.type_err( "Closure body must evaluate to an Owned, Shared, Mutable, or CopyOnWrite value", ); } @@ -100,7 +100,7 @@ impl ClosureValue { context.interpreter.exit_scope(definition.scope_id); - Ok(Spanned(returned_value, output_span)) + Ok(Spanned(returned_value, context.output_span_range)) } } diff --git a/src/expressions/control_flow.rs b/src/expressions/control_flow.rs index 1bf56e1f..3e941f1d 100644 --- a/src/expressions/control_flow.rs +++ b/src/expressions/control_flow.rs @@ -382,7 +382,7 @@ impl Evaluate for ForExpression { for item in iterable.into_iterator()? { iteration_counter.increment_and_check()?; - interpreter.enter_child_scope(self.iteration_scope); + interpreter.enter_child_scope(self.iteration_scope)?; self.pattern.handle_destructure(interpreter, item)?; let body_result = self.body.evaluate_owned(interpreter); @@ -618,7 +618,7 @@ impl Evaluate for ParseExpression { .evaluate_owned(interpreter)? .resolve_as("The input to a parse expression")?; - interpreter.enter_child_scope(self.scope); + interpreter.enter_child_scope(self.scope)?; let output = interpreter.start_parse(input, |interpreter, handle| { self.parser_variable.define(interpreter, handle); diff --git a/src/expressions/expression_block.rs b/src/expressions/expression_block.rs index ced04e46..91ee3c9c 100644 --- a/src/expressions/expression_block.rs +++ b/src/expressions/expression_block.rs @@ -212,7 +212,7 @@ impl Evaluate for ScopedBlock { interpreter: &mut Interpreter, ownership: RequestedOwnership, ) -> ExecutionResult { - interpreter.enter_child_scope(self.scope); + interpreter.enter_child_scope(self.scope)?; let output = self .content .evaluate_spanned(interpreter, self.span().into(), ownership)?; diff --git a/src/expressions/values/preinterpret.rs b/src/expressions/values/preinterpret.rs index 334d0a9b..598de9da 100644 --- a/src/expressions/values/preinterpret.rs +++ b/src/expressions/values/preinterpret.rs @@ -17,7 +17,11 @@ define_type_features! { pub(crate) mod preinterpret_api_interface { functions { [context] fn set_iteration_limit(new_limit: OptionalSuffix) { - context.interpreter.set_iteration_limit(Some(new_limit.0)); + context.interpreter.set_iteration_limit(new_limit.0); + } + + [context] fn set_stack_depth_limit(new_limit: OptionalSuffix) { + context.interpreter.set_stack_depth_limit(new_limit.0); } } } diff --git a/src/interpretation/interpreter.rs b/src/interpretation/interpreter.rs index 5c0bfa61..b280c6c9 100644 --- a/src/interpretation/interpreter.rs +++ b/src/interpretation/interpreter.rs @@ -3,6 +3,7 @@ use super::*; pub(crate) struct Interpreter { config: InterpreterConfig, scope_definitions: ScopeDefinitions, + frame_depth: usize, scopes: Vec, no_mutation_above: Vec<(ScopeId, MutationBlockReason)>, output_handler: OutputHandler, @@ -11,16 +12,18 @@ pub(crate) struct Interpreter { impl Interpreter { pub(crate) fn new(scope_definitions: ScopeDefinitions) -> Self { - let (_, root_scope_id) = scope_definitions.root_frame; + let (root_frame_id, root_scope_id) = scope_definitions.root_frame; let mut interpreter = Self { config: Default::default(), scope_definitions, scopes: vec![], + frame_depth: 0, no_mutation_above: vec![], output_handler: OutputHandler::new(OutputStream::new()), input_handler: InputHandler::new(), }; - interpreter.enter_scope_inner(root_scope_id, ScopeKind::Root); + interpreter.enter_scope_inner(root_scope_id, ScopeKind::Root { root_frame: root_frame_id }) + .expect("The root scope can always be entered"); interpreter } @@ -36,12 +39,12 @@ impl Interpreter { self.scopes.last().unwrap().id } - pub(crate) fn enter_child_scope(&mut self, id: ScopeId) { - self.enter_scope_inner(id, ScopeKind::Child); + pub(crate) fn enter_child_scope(&mut self, id: ScopeId) -> ExecutionResult<()> { + self.enter_scope_inner(id, ScopeKind::Child) } - pub(crate) fn enter_function_boundary_scope(&mut self, id: ScopeId) { - self.enter_scope_inner(id, ScopeKind::FunctionBoundary); + pub(crate) fn enter_function_boundary_scope(&mut self, id: ScopeId, frame_id: FrameId, span: SpanRange) -> ExecutionResult<()> { + self.enter_scope_inner(id, ScopeKind::FunctionBoundary { new_frame: frame_id, span }) } pub(crate) fn enter_scope_starting_with_revertible_segment( @@ -52,7 +55,7 @@ impl Interpreter { guard_clause: Option ExecutionResult>, reason: MutationBlockReason, ) -> ExecutionResult> { - self.enter_scope_inner(scope_id, ScopeKind::Child); + self.enter_scope_inner(scope_id, ScopeKind::Child)?; self.no_mutation_above.push((scope_id, reason)); unsafe { // SAFETY: This is paired with `unfreeze_existing` below, @@ -119,20 +122,23 @@ impl Interpreter { } } - fn enter_scope_inner(&mut self, id: ScopeId, expected_kind: ScopeKind) { + fn enter_scope_inner(&mut self, id: ScopeId, expected_kind: ScopeKind) -> ExecutionResult<()> { let new_scope = self.scope_definitions.scopes.get(id); - match expected_kind { - ScopeKind::Root => { + let frame = match expected_kind { + ScopeKind::Root { root_frame } => { assert!(new_scope.parent.is_none()); - assert_eq!(new_scope.frame, self.scope_definitions.root_frame.0); + assert_eq!(new_scope.frame, root_frame); + Some((root_frame, Span::call_site().span_range())) } - ScopeKind::FunctionBoundary => { + ScopeKind::FunctionBoundary { new_frame, span } => { assert!(new_scope.parent.is_none()); + Some((new_frame, span)) } ScopeKind::Child => { assert_eq!(new_scope.parent, Some(self.current_scope_id())); + None } - } + }; let variables = { let mut map = HashMap::new(); for definition_id in new_scope.definitions.iter() { @@ -140,7 +146,14 @@ impl Interpreter { } map }; - self.scopes.push(RuntimeScope { id, variables }); + if let Some((_, span)) = &frame { + self.frame_depth += 1; + if self.frame_depth > self.config.stack_depth_limit { + return span.control_flow_err(format!("Stack depth limit of {} exceeded.\nIf needed, the limit can be reconfigured with preinterpret::set_stack_depth_limit(XXX)", self.config.stack_depth_limit)); + } + } + self.scopes.push(RuntimeScope { id, frame, variables }); + Ok(()) } pub(crate) fn exit_scope(&mut self, scope_id: ScopeId) { @@ -150,7 +163,11 @@ impl Interpreter { scope_id, self.current_scope_id() ); - self.scopes.pop(); + let scope = self.scopes.pop() + .expect("We've just asserted there's a scope to pop"); + if scope.frame.is_some() { + self.frame_depth -= 1; + } } pub(crate) fn catch_control_flow( @@ -221,10 +238,14 @@ impl Interpreter { } } - pub(crate) fn set_iteration_limit(&mut self, limit: Option) { + pub(crate) fn set_iteration_limit(&mut self, limit: usize) { self.config.iteration_limit = limit; } + pub(crate) fn set_stack_depth_limit(&mut self, limit: usize) { + self.config.stack_depth_limit = limit; + } + // Input pub(crate) fn start_parse( &mut self, @@ -409,6 +430,8 @@ pub(crate) enum AttemptOutcome { struct RuntimeScope { id: ScopeId, + /// Present if it's the start of a new frame + frame: Option<(FrameId, SpanRange)>, variables: HashMap, } @@ -438,7 +461,7 @@ impl RuntimeScope { pub(crate) struct IterationCounter<'a, S: HasSpanRange> { span_source: &'a S, count: usize, - iteration_limit: Option, + iteration_limit: usize, } impl IterationCounter<'_, S> { @@ -448,25 +471,26 @@ impl IterationCounter<'_, S> { } pub(crate) fn check(&self) -> ExecutionResult<()> { - if let Some(limit) = self.iteration_limit { - if self.count > limit { - return self.span_source.control_flow_err(format!("Iteration limit of {} exceeded.\nIf needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX)", limit)); - } + if self.count > self.iteration_limit { + return self.span_source.control_flow_err(format!("Iteration limit of {} exceeded.\nIf needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX)", self.iteration_limit)); } Ok(()) } } pub(crate) struct InterpreterConfig { - iteration_limit: Option, + iteration_limit: usize, + stack_depth_limit: usize, } pub(crate) const DEFAULT_ITERATION_LIMIT: usize = 1000; +pub(crate) const STACK_DEPTH_LIMIT: usize = 200; impl Default for InterpreterConfig { fn default() -> Self { Self { - iteration_limit: Some(DEFAULT_ITERATION_LIMIT), + iteration_limit: DEFAULT_ITERATION_LIMIT, + stack_depth_limit: STACK_DEPTH_LIMIT, } } } diff --git a/src/interpretation/source_parsing.rs b/src/interpretation/source_parsing.rs index 0833aa08..ecafbe28 100644 --- a/src/interpretation/source_parsing.rs +++ b/src/interpretation/source_parsing.rs @@ -201,6 +201,16 @@ impl FlowAnalysisState { frame.root_segment = segment; } + /// Updates `current_frame_id` from the frames stack. + /// Must be called after modifying `frames_stack`. + fn update_current_frame_id(&mut self) { + self.current_frame_id = self + .frames_stack + .last() + .copied() + .unwrap_or_else(FrameId::new_placeholder); + } + fn current_frame(&self) -> &FrameData { self.frames.get(self.current_frame_id).defined_ref() } @@ -224,11 +234,13 @@ impl FlowAnalysisState { let id = self.frames_stack.pop().expect("No frame to pop"); assert_eq!(id, frame_id, "Popped frame is not the current frame"); - self.current_frame_id = self - .frames_stack - .last() - .copied() - .unwrap_or_else(FrameId::new_placeholder); + self.update_current_frame_id(); + + // Restore current_scope_id and current_segment_id from the parent frame + if !self.current_frame_id.is_placeholder() { + self.update_current_scope_id(); + self.update_current_segment_id(); + } } // SCOPES @@ -256,16 +268,23 @@ impl FlowAnalysisState { self.current_scope_id = scope_id; } + /// Updates `current_scope_id` from the current frame's scope stack. + /// Must be called after modifying the scope stack. + fn update_current_scope_id(&mut self) { + let scope_id = self + .current_frame() + .scope_stack + .last() + .copied() + .unwrap_or_else(ScopeId::new_placeholder); + self.current_scope_id = scope_id; + } + /// The scope parameter is just to help catch bugs. pub(crate) fn exit_scope(&mut self, scope: ScopeId) { let id = self.scope_id_stack_mut().pop().expect("No scope to pop"); assert_eq!(id, scope, "Popped scope is not the current scope"); - - self.current_scope_id = self - .scope_id_stack() - .last() - .copied() - .unwrap_or_else(ScopeId::new_placeholder); + self.update_current_scope_id(); } pub(crate) fn define_variable(&mut self, id: VariableDefinitionId) { @@ -370,11 +389,19 @@ impl FlowAnalysisState { pub(crate) fn exit_segment(&mut self, segment: ControlFlowSegmentId) { let id = self.segments_stack().pop().expect("No segment to pop"); assert_eq!(id, segment, "Popped segment is not the current segment"); - self.current_segment_id = self - .segments_stack() + self.update_current_segment_id(); + } + + /// Updates `current_segment_id` from the current frame's segment stack. + /// Must be called after modifying the segment stack. + fn update_current_segment_id(&mut self) { + let segment_id = self + .current_frame() + .segment_stack .last() .copied() .unwrap_or_else(ControlFlowSegmentId::new_placeholder); + self.current_segment_id = segment_id; } fn enter_segment_with_valid_previous( @@ -719,9 +746,9 @@ pub(crate) struct ScopeData { } pub(crate) enum ScopeKind { - Root, + Root { root_frame: FrameId }, Child, - FunctionBoundary, + FunctionBoundary { new_frame: FrameId, span: SpanRange }, } enum AllocatedVariableDefinition { diff --git a/src/misc/arena.rs b/src/misc/arena.rs index 9e9cc8f1..f4794dbc 100644 --- a/src/misc/arena.rs +++ b/src/misc/arena.rs @@ -89,11 +89,16 @@ impl Arena { } } -fn invalid_key_message(key_index: usize) -> &'static str { +fn invalid_key_message(key_index: usize) -> String { if key_index == PLACEHOLDER_KEY_INDEX { - "Attempted to access an arena with a placeholder key. The key must be properly initialized before use." + format!( + "Attempted to access an arena with a placeholder key. \ + The key must be properly initialized before use.\n\ + Backtrace:\n{:?}", + std::backtrace::Backtrace::force_capture() + ) } else { - "Arena key does not exist in this arena." + "Arena key does not exist in this arena.".to_string() } } diff --git a/tests/compilation_failures/functions/return_shared_ref_cannot_mutate.rs b/tests/compilation_failures/functions/return_shared_ref_cannot_mutate.rs new file mode 100644 index 00000000..18536564 --- /dev/null +++ b/tests/compilation_failures/functions/return_shared_ref_cannot_mutate.rs @@ -0,0 +1,8 @@ +use preinterpret::*; + +fn main() { + run! { + let convert_to_ref = |x| x.as_ref(); + convert_to_ref("hello").as_mut() + } +} diff --git a/tests/functions.rs b/tests/functions.rs index 7ec72efc..4fff1a15 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -51,9 +51,31 @@ fn test_preinterpret_api() { #[test] fn test_basic_closures() { - // TODO[functions]: Fix me! run! { - // let double_me = |x| x * 2; - // %[_].assert_eq(double_me(4), 8); + let double_me = |x| x * 2; + %[_].assert_eq(double_me(4), 8); + } + run! { + let double_me = |x| x * 2; + %[_].assert_eq(double_me(double_me(4)), 16); + } + run! { + let x = 3; + let double_me = |x| x * 2; + %[_].assert_eq(double_me(double_me(4)), 16); } } + +#[test] +fn test_recursion() { + run! { + let factorial = |n, f| { + if n == 0 { + 1 + } else { + n * f(n - 1, f) + } + }; + %[_].assert_eq(factorial(5, factorial), 120); + } +} \ No newline at end of file From 6e0f9edf44f6e575b9086ac8f78ab1bb59db6d71 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 31 Jan 2026 14:10:59 +0000 Subject: [PATCH 12/49] test: Add closure tests and recursion limit --- plans/TODO.md | 8 ++-- src/expressions/closures.rs | 2 +- src/expressions/values/preinterpret.rs | 4 +- src/interpretation/interpreter.rs | 42 ++++++++----------- .../core/manual_iteration_limit.rs | 8 ++++ .../core/manual_iteration_limit.stderr | 6 +++ .../abort_on_iteration_limit_exceeded.rs | 4 +- .../abort_on_iteration_limit_exceeded.stderr | 9 ++-- ...bort_on_manual_recursion_limit_exceeded.rs | 15 +++++++ ..._on_manual_recursion_limit_exceeded.stderr | 6 +++ .../abort_on_recursion_limit_exceeded.rs | 8 ++++ .../abort_on_recursion_limit_exceeded.stderr | 6 +++ .../cannot_break_outside_of_closure.rs | 13 ++++++ .../cannot_break_outside_of_closure.stderr | 5 +++ ...annot_break_to_label_outside_of_closure.rs | 13 ++++++ ...t_break_to_label_outside_of_closure.stderr | 5 +++ .../functions/incorrect_type_function_name.rs | 4 +- .../incorrect_type_function_name.stderr | 9 ++-- .../return_shared_ref_cannot_mutate.stderr | 5 +++ tests/functions.rs | 21 +++++++++- 20 files changed, 148 insertions(+), 45 deletions(-) create mode 100644 tests/compilation_failures/core/manual_iteration_limit.rs create mode 100644 tests/compilation_failures/core/manual_iteration_limit.stderr create mode 100644 tests/compilation_failures/functions/abort_on_manual_recursion_limit_exceeded.rs create mode 100644 tests/compilation_failures/functions/abort_on_manual_recursion_limit_exceeded.stderr create mode 100644 tests/compilation_failures/functions/abort_on_recursion_limit_exceeded.rs create mode 100644 tests/compilation_failures/functions/abort_on_recursion_limit_exceeded.stderr create mode 100644 tests/compilation_failures/functions/cannot_break_outside_of_closure.rs create mode 100644 tests/compilation_failures/functions/cannot_break_outside_of_closure.stderr create mode 100644 tests/compilation_failures/functions/cannot_break_to_label_outside_of_closure.rs create mode 100644 tests/compilation_failures/functions/cannot_break_to_label_outside_of_closure.stderr create mode 100644 tests/compilation_failures/functions/return_shared_ref_cannot_mutate.stderr diff --git a/plans/TODO.md b/plans/TODO.md index 6238c86f..fd8c4cc9 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -227,10 +227,10 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). (variable resolution stops at the top of the frame) - [x] Correct break/continue label resolution in functions/closures - [x] Closures expose an interface, and a means of invocation, and are wired into the invocation logic -- [ ] A recursion limit is implemented -- [ ] Lots of tests - - [ ] Loop / Break inside closure - - [ ] Break doesn't compile if it would propagate outside closure +- [x] A recursion limit is implemented +- [x] Lots of tests + - [x] Loop / Break inside closure + - [x] Break doesn't compile if it would propagate outside closure - [ ] Closed Bindings - [ ] Change bindings (currently just variables) to be able to store any of the following: (nb we still restrict variables to be owned for now). ```rust diff --git a/src/expressions/closures.rs b/src/expressions/closures.rs index 3577fa24..952719be 100644 --- a/src/expressions/closures.rs +++ b/src/expressions/closures.rs @@ -65,7 +65,7 @@ impl ClosureValue { context .interpreter - .enter_function_boundary_scope(definition.scope_id, definition.frame_id, definition.span_range)?; + .enter_function_boundary_scope(definition.scope_id, definition.frame_id, context.output_span_range)?; for (pattern, Spanned(arg, arg_span)) in definition.argument_definitions.iter().zip(arguments) diff --git a/src/expressions/values/preinterpret.rs b/src/expressions/values/preinterpret.rs index 598de9da..bec7c554 100644 --- a/src/expressions/values/preinterpret.rs +++ b/src/expressions/values/preinterpret.rs @@ -20,8 +20,8 @@ define_type_features! { context.interpreter.set_iteration_limit(new_limit.0); } - [context] fn set_stack_depth_limit(new_limit: OptionalSuffix) { - context.interpreter.set_stack_depth_limit(new_limit.0); + [context] fn set_recursion_limit(new_limit: OptionalSuffix) { + context.interpreter.set_recursion_limit(new_limit.0); } } } diff --git a/src/interpretation/interpreter.rs b/src/interpretation/interpreter.rs index b280c6c9..774073da 100644 --- a/src/interpretation/interpreter.rs +++ b/src/interpretation/interpreter.rs @@ -3,7 +3,7 @@ use super::*; pub(crate) struct Interpreter { config: InterpreterConfig, scope_definitions: ScopeDefinitions, - frame_depth: usize, + call_depth: usize, scopes: Vec, no_mutation_above: Vec<(ScopeId, MutationBlockReason)>, output_handler: OutputHandler, @@ -17,7 +17,7 @@ impl Interpreter { config: Default::default(), scope_definitions, scopes: vec![], - frame_depth: 0, + call_depth: 0, no_mutation_above: vec![], output_handler: OutputHandler::new(OutputStream::new()), input_handler: InputHandler::new(), @@ -122,21 +122,22 @@ impl Interpreter { } } - fn enter_scope_inner(&mut self, id: ScopeId, expected_kind: ScopeKind) -> ExecutionResult<()> { + fn enter_scope_inner(&mut self, id: ScopeId, scope_kind: ScopeKind) -> ExecutionResult<()> { let new_scope = self.scope_definitions.scopes.get(id); - let frame = match expected_kind { + match &scope_kind { ScopeKind::Root { root_frame } => { assert!(new_scope.parent.is_none()); - assert_eq!(new_scope.frame, root_frame); - Some((root_frame, Span::call_site().span_range())) + assert_eq!(new_scope.frame, *root_frame); } - ScopeKind::FunctionBoundary { new_frame, span } => { + ScopeKind::FunctionBoundary { span, .. } => { assert!(new_scope.parent.is_none()); - Some((new_frame, span)) + self.call_depth += 1; + if self.call_depth > self.config.recursion_limit { + return span.control_flow_err(format!("Recursion limit of {} exceeded.\nIf needed, the limit can be reconfigured with preinterpret::set_recursion_limit(XXX)", self.config.recursion_limit)); + } } ScopeKind::Child => { assert_eq!(new_scope.parent, Some(self.current_scope_id())); - None } }; let variables = { @@ -146,13 +147,7 @@ impl Interpreter { } map }; - if let Some((_, span)) = &frame { - self.frame_depth += 1; - if self.frame_depth > self.config.stack_depth_limit { - return span.control_flow_err(format!("Stack depth limit of {} exceeded.\nIf needed, the limit can be reconfigured with preinterpret::set_stack_depth_limit(XXX)", self.config.stack_depth_limit)); - } - } - self.scopes.push(RuntimeScope { id, frame, variables }); + self.scopes.push(RuntimeScope { id, scope_kind, variables }); Ok(()) } @@ -165,8 +160,8 @@ impl Interpreter { ); let scope = self.scopes.pop() .expect("We've just asserted there's a scope to pop"); - if scope.frame.is_some() { - self.frame_depth -= 1; + if let ScopeKind::FunctionBoundary { .. } = scope.scope_kind { + self.call_depth -= 1; } } @@ -242,8 +237,8 @@ impl Interpreter { self.config.iteration_limit = limit; } - pub(crate) fn set_stack_depth_limit(&mut self, limit: usize) { - self.config.stack_depth_limit = limit; + pub(crate) fn set_recursion_limit(&mut self, limit: usize) { + self.config.recursion_limit = limit; } // Input @@ -430,8 +425,7 @@ pub(crate) enum AttemptOutcome { struct RuntimeScope { id: ScopeId, - /// Present if it's the start of a new frame - frame: Option<(FrameId, SpanRange)>, + scope_kind: ScopeKind, variables: HashMap, } @@ -480,7 +474,7 @@ impl IterationCounter<'_, S> { pub(crate) struct InterpreterConfig { iteration_limit: usize, - stack_depth_limit: usize, + recursion_limit: usize, } pub(crate) const DEFAULT_ITERATION_LIMIT: usize = 1000; @@ -490,7 +484,7 @@ impl Default for InterpreterConfig { fn default() -> Self { Self { iteration_limit: DEFAULT_ITERATION_LIMIT, - stack_depth_limit: STACK_DEPTH_LIMIT, + recursion_limit: STACK_DEPTH_LIMIT, } } } diff --git a/tests/compilation_failures/core/manual_iteration_limit.rs b/tests/compilation_failures/core/manual_iteration_limit.rs new file mode 100644 index 00000000..3621b522 --- /dev/null +++ b/tests/compilation_failures/core/manual_iteration_limit.rs @@ -0,0 +1,8 @@ +use preinterpret::*; + +fn main() { + run!{ + preinterpret::set_iteration_limit(4); + loop {} + }; +} \ No newline at end of file diff --git a/tests/compilation_failures/core/manual_iteration_limit.stderr b/tests/compilation_failures/core/manual_iteration_limit.stderr new file mode 100644 index 00000000..501e6647 --- /dev/null +++ b/tests/compilation_failures/core/manual_iteration_limit.stderr @@ -0,0 +1,6 @@ +error: Iteration limit of 4 exceeded. + If needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX) + --> tests/compilation_failures/core/manual_iteration_limit.rs:6:14 + | +6 | loop {} + | ^^ diff --git a/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.rs b/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.rs index 77fe7228..20724f28 100644 --- a/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.rs +++ b/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.rs @@ -2,7 +2,7 @@ use preinterpret::*; fn main() { run!{ - let arr = [1, 2, 3]; - array::push_me(arr, 4, 5); + preinterpret::set_iteration_limit(14); + for i in 0..15 {} } } \ No newline at end of file diff --git a/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.stderr b/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.stderr index 7cd55a08..92a9cf2e 100644 --- a/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.stderr +++ b/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.stderr @@ -1,5 +1,6 @@ -error: Type 'array' has no property, function or method named 'push_me' - --> tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.rs:6:9 +error: Iteration limit of 14 exceeded. + If needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX) + --> tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.rs:6:24 | -6 | array::push_me(arr, 4, 5); - | ^^^^^^^^^^^^^^ +6 | for i in 0..15 {} + | ^^ diff --git a/tests/compilation_failures/functions/abort_on_manual_recursion_limit_exceeded.rs b/tests/compilation_failures/functions/abort_on_manual_recursion_limit_exceeded.rs new file mode 100644 index 00000000..73e6dccb --- /dev/null +++ b/tests/compilation_failures/functions/abort_on_manual_recursion_limit_exceeded.rs @@ -0,0 +1,15 @@ +use preinterpret::*; + +fn main() { + run!{ + preinterpret::set_recursion_limit(4); + let factorial = |n, f| { + if n == 1 { + 1 + } else { + n * f(n - 1, f) + } + }; + %[_].assert_eq(factorial(5, factorial), 120); + } +} \ No newline at end of file diff --git a/tests/compilation_failures/functions/abort_on_manual_recursion_limit_exceeded.stderr b/tests/compilation_failures/functions/abort_on_manual_recursion_limit_exceeded.stderr new file mode 100644 index 00000000..0225d9f5 --- /dev/null +++ b/tests/compilation_failures/functions/abort_on_manual_recursion_limit_exceeded.stderr @@ -0,0 +1,6 @@ +error: Recursion limit of 4 exceeded. + If needed, the limit can be reconfigured with preinterpret::set_recursion_limit(XXX) + --> tests/compilation_failures/functions/abort_on_manual_recursion_limit_exceeded.rs:10:21 + | +10 | n * f(n - 1, f) + | ^^^^^^^^^^^ diff --git a/tests/compilation_failures/functions/abort_on_recursion_limit_exceeded.rs b/tests/compilation_failures/functions/abort_on_recursion_limit_exceeded.rs new file mode 100644 index 00000000..cad2902a --- /dev/null +++ b/tests/compilation_failures/functions/abort_on_recursion_limit_exceeded.rs @@ -0,0 +1,8 @@ +use preinterpret::*; + +fn main() { + run!{ + let f = |g| g(g); + f(f); + } +} \ No newline at end of file diff --git a/tests/compilation_failures/functions/abort_on_recursion_limit_exceeded.stderr b/tests/compilation_failures/functions/abort_on_recursion_limit_exceeded.stderr new file mode 100644 index 00000000..ad2a46f7 --- /dev/null +++ b/tests/compilation_failures/functions/abort_on_recursion_limit_exceeded.stderr @@ -0,0 +1,6 @@ +error: Recursion limit of 200 exceeded. + If needed, the limit can be reconfigured with preinterpret::set_recursion_limit(XXX) + --> tests/compilation_failures/functions/abort_on_recursion_limit_exceeded.rs:5:21 + | +5 | let f = |g| g(g); + | ^^^^ diff --git a/tests/compilation_failures/functions/cannot_break_outside_of_closure.rs b/tests/compilation_failures/functions/cannot_break_outside_of_closure.rs new file mode 100644 index 00000000..3ce25473 --- /dev/null +++ b/tests/compilation_failures/functions/cannot_break_outside_of_closure.rs @@ -0,0 +1,13 @@ +use preinterpret::*; + +fn main() { + run! { + loop { + let f = || { + break; + }; + f(); + break; + } + } +} diff --git a/tests/compilation_failures/functions/cannot_break_outside_of_closure.stderr b/tests/compilation_failures/functions/cannot_break_outside_of_closure.stderr new file mode 100644 index 00000000..5506d3d8 --- /dev/null +++ b/tests/compilation_failures/functions/cannot_break_outside_of_closure.stderr @@ -0,0 +1,5 @@ +error: A break must be used inside a loop + --> tests/compilation_failures/functions/cannot_break_outside_of_closure.rs:7:17 + | +7 | break; + | ^^^^^ diff --git a/tests/compilation_failures/functions/cannot_break_to_label_outside_of_closure.rs b/tests/compilation_failures/functions/cannot_break_to_label_outside_of_closure.rs new file mode 100644 index 00000000..dd0f44a6 --- /dev/null +++ b/tests/compilation_failures/functions/cannot_break_to_label_outside_of_closure.rs @@ -0,0 +1,13 @@ +use preinterpret::*; + +fn main() { + run! { + 'outer: loop { + let f = || { + break 'outer; + }; + f(); + break 'outer; + } + } +} diff --git a/tests/compilation_failures/functions/cannot_break_to_label_outside_of_closure.stderr b/tests/compilation_failures/functions/cannot_break_to_label_outside_of_closure.stderr new file mode 100644 index 00000000..e830aa4b --- /dev/null +++ b/tests/compilation_failures/functions/cannot_break_to_label_outside_of_closure.stderr @@ -0,0 +1,5 @@ +error: A labelled break must be used inside a loop or block with a matching label + --> tests/compilation_failures/functions/cannot_break_to_label_outside_of_closure.rs:7:23 + | +7 | break 'outer; + | ^^^^^^ diff --git a/tests/compilation_failures/functions/incorrect_type_function_name.rs b/tests/compilation_failures/functions/incorrect_type_function_name.rs index 20724f28..77fe7228 100644 --- a/tests/compilation_failures/functions/incorrect_type_function_name.rs +++ b/tests/compilation_failures/functions/incorrect_type_function_name.rs @@ -2,7 +2,7 @@ use preinterpret::*; fn main() { run!{ - preinterpret::set_iteration_limit(14); - for i in 0..15 {} + let arr = [1, 2, 3]; + array::push_me(arr, 4, 5); } } \ No newline at end of file diff --git a/tests/compilation_failures/functions/incorrect_type_function_name.stderr b/tests/compilation_failures/functions/incorrect_type_function_name.stderr index e65faf4b..37e8493c 100644 --- a/tests/compilation_failures/functions/incorrect_type_function_name.stderr +++ b/tests/compilation_failures/functions/incorrect_type_function_name.stderr @@ -1,6 +1,5 @@ -error: Iteration limit of 14 exceeded. - If needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX) - --> tests/compilation_failures/functions/incorrect_type_function_name.rs:6:24 +error: Type 'array' has no property, function or method named 'push_me' + --> tests/compilation_failures/functions/incorrect_type_function_name.rs:6:9 | -6 | for i in 0..15 {} - | ^^ +6 | array::push_me(arr, 4, 5); + | ^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/functions/return_shared_ref_cannot_mutate.stderr b/tests/compilation_failures/functions/return_shared_ref_cannot_mutate.stderr new file mode 100644 index 00000000..978d63d6 --- /dev/null +++ b/tests/compilation_failures/functions/return_shared_ref_cannot_mutate.stderr @@ -0,0 +1,5 @@ +error: 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. + --> tests/compilation_failures/functions/return_shared_ref_cannot_mutate.rs:6:9 + | +6 | convert_to_ref("hello").as_mut() + | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/functions.rs b/tests/functions.rs index 4fff1a15..e10fbee7 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -69,13 +69,32 @@ fn test_basic_closures() { #[test] fn test_recursion() { run! { + preinterpret::set_recursion_limit(5); let factorial = |n, f| { - if n == 0 { + if n == 1 { 1 } else { n * f(n - 1, f) } }; %[_].assert_eq(factorial(5, factorial), 120); + %[_].assert_eq(factorial(5, factorial), 120); + } +} + +#[test] +fn test_control_flow_inside_closure() { + run! { + let f = || { + let out; + for i in 0..10 { + if i == 5 { + out = i; + break; + } + } + out + }; + %[_].assert_eq(f(), 5); } } \ No newline at end of file From 601d5cc892a14a700e7a5947fb49e157e72cefd7 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 31 Jan 2026 14:26:27 +0000 Subject: [PATCH 13/49] refactor: Rename of static analysis files --- src/expressions/closures.rs | 8 ++-- src/extensions/parsing.rs | 4 +- src/internal_prelude.rs | 1 + src/interpretation/interpreter.rs | 37 ++++++++++++---- src/interpretation/mod.rs | 4 -- src/lib.rs | 1 + src/misc/parse_traits.rs | 42 +++++++++---------- .../control_flow_analyzer.rs} | 0 src/static_analysis/mod.rs | 7 ++++ .../static_analyzer.rs} | 10 ++--- tests/functions.rs | 4 +- 11 files changed, 75 insertions(+), 43 deletions(-) rename src/{interpretation/control_flow_pass.rs => static_analysis/control_flow_analyzer.rs} (100%) create mode 100644 src/static_analysis/mod.rs rename src/{interpretation/source_parsing.rs => static_analysis/static_analyzer.rs} (99%) diff --git a/src/expressions/closures.rs b/src/expressions/closures.rs index 952719be..191d3fc5 100644 --- a/src/expressions/closures.rs +++ b/src/expressions/closures.rs @@ -63,9 +63,11 @@ impl ClosureValue { ) -> ExecutionResult> { let definition = &*self.definition; - context - .interpreter - .enter_function_boundary_scope(definition.scope_id, definition.frame_id, context.output_span_range)?; + context.interpreter.enter_function_boundary_scope( + definition.scope_id, + definition.frame_id, + context.output_span_range, + )?; for (pattern, Spanned(arg, arg_span)) in definition.argument_definitions.iter().zip(arguments) diff --git a/src/extensions/parsing.rs b/src/extensions/parsing.rs index 291c672c..37722966 100644 --- a/src/extensions/parsing.rs +++ b/src/extensions/parsing.rs @@ -5,7 +5,7 @@ pub(crate) trait TokenStreamParseExt: Sized { self, parser: impl FnOnce(SourceParser) -> ParseResult, control_flow_analysis: impl FnOnce(&mut T, FlowCapturer) -> ParseResult<()>, - ) -> ParseResult<(T, ScopeDefinitions)>; + ) -> ParseResult<(T, StaticDefinitions)>; fn interpreted_parse_with>( self, @@ -18,7 +18,7 @@ impl TokenStreamParseExt for TokenStream { self, parser: impl FnOnce(SourceParser) -> ParseResult, control_flow_analysis: impl FnOnce(&mut T, FlowCapturer) -> ParseResult<()>, - ) -> ParseResult<(T, ScopeDefinitions)> { + ) -> ParseResult<(T, StaticDefinitions)> { let mut parsed = parse_with(self, parse_without_analysis(parser))?; let definitions = ControlFlowContext::analyze(&mut parsed, control_flow_analysis)?; Ok((parsed, definitions)) diff --git a/src/internal_prelude.rs b/src/internal_prelude.rs index c02b5ca4..b9df86e4 100644 --- a/src/internal_prelude.rs +++ b/src/internal_prelude.rs @@ -28,3 +28,4 @@ pub(crate) use crate::expressions::*; pub(crate) use crate::extensions::*; pub(crate) use crate::interpretation::*; pub(crate) use crate::misc::*; +pub(crate) use crate::static_analysis::*; diff --git a/src/interpretation/interpreter.rs b/src/interpretation/interpreter.rs index 774073da..394b9182 100644 --- a/src/interpretation/interpreter.rs +++ b/src/interpretation/interpreter.rs @@ -2,7 +2,7 @@ use super::*; pub(crate) struct Interpreter { config: InterpreterConfig, - scope_definitions: ScopeDefinitions, + scope_definitions: StaticDefinitions, call_depth: usize, scopes: Vec, no_mutation_above: Vec<(ScopeId, MutationBlockReason)>, @@ -11,7 +11,7 @@ pub(crate) struct Interpreter { } impl Interpreter { - pub(crate) fn new(scope_definitions: ScopeDefinitions) -> Self { + pub(crate) fn new(scope_definitions: StaticDefinitions) -> Self { let (root_frame_id, root_scope_id) = scope_definitions.root_frame; let mut interpreter = Self { config: Default::default(), @@ -22,7 +22,13 @@ impl Interpreter { output_handler: OutputHandler::new(OutputStream::new()), input_handler: InputHandler::new(), }; - interpreter.enter_scope_inner(root_scope_id, ScopeKind::Root { root_frame: root_frame_id }) + interpreter + .enter_scope_inner( + root_scope_id, + ScopeKind::Root { + root_frame: root_frame_id, + }, + ) .expect("The root scope can always be entered"); interpreter } @@ -43,8 +49,19 @@ impl Interpreter { self.enter_scope_inner(id, ScopeKind::Child) } - pub(crate) fn enter_function_boundary_scope(&mut self, id: ScopeId, frame_id: FrameId, span: SpanRange) -> ExecutionResult<()> { - self.enter_scope_inner(id, ScopeKind::FunctionBoundary { new_frame: frame_id, span }) + pub(crate) fn enter_function_boundary_scope( + &mut self, + id: ScopeId, + frame_id: FrameId, + span: SpanRange, + ) -> ExecutionResult<()> { + self.enter_scope_inner( + id, + ScopeKind::FunctionBoundary { + new_frame: frame_id, + span, + }, + ) } pub(crate) fn enter_scope_starting_with_revertible_segment( @@ -147,7 +164,11 @@ impl Interpreter { } map }; - self.scopes.push(RuntimeScope { id, scope_kind, variables }); + self.scopes.push(RuntimeScope { + id, + scope_kind, + variables, + }); Ok(()) } @@ -158,7 +179,9 @@ impl Interpreter { scope_id, self.current_scope_id() ); - let scope = self.scopes.pop() + let scope = self + .scopes + .pop() .expect("We've just asserted there's a scope to pop"); if let ScopeKind::FunctionBoundary { .. } = scope.scope_kind { self.call_depth -= 1; diff --git a/src/interpretation/mod.rs b/src/interpretation/mod.rs index adb537e7..23deb67d 100644 --- a/src/interpretation/mod.rs +++ b/src/interpretation/mod.rs @@ -1,5 +1,4 @@ mod bindings; -mod control_flow_pass; mod input_handler; mod interpret_traits; mod interpreter; @@ -8,14 +7,12 @@ mod output_parse_utilities; mod output_stream; mod parse_template_stream; mod refs; -mod source_parsing; mod source_stream; mod variable; // Marked as use for expression sub-modules to use with a `use super::*` statement use crate::internal_prelude::*; pub(crate) use bindings::*; -use control_flow_pass::*; pub(crate) use input_handler::*; pub(crate) use interpret_traits::*; pub(crate) use interpreter::*; @@ -24,6 +21,5 @@ pub(crate) use output_parse_utilities::*; pub(crate) use output_stream::*; pub(crate) use parse_template_stream::*; pub(crate) use refs::*; -pub(crate) use source_parsing::*; pub(crate) use source_stream::*; pub(crate) use variable::*; diff --git a/src/lib.rs b/src/lib.rs index aa43efe6..488559d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -406,6 +406,7 @@ mod extensions; mod internal_prelude; mod interpretation; mod misc; +mod static_analysis; use internal_prelude::*; diff --git a/src/misc/parse_traits.rs b/src/misc/parse_traits.rs index 94e03062..cb79a508 100644 --- a/src/misc/parse_traits.rs +++ b/src/misc/parse_traits.rs @@ -201,16 +201,16 @@ pub(crate) type SourceParser<'a> = &'a SourceParseBuffer<'a>; pub(crate) type FlowCapturer<'a> = &'a mut ControlFlowContext; pub(crate) struct ControlFlowContext { - state: FlowAnalysisState, + analyzer: StaticAnalyzer, } impl ControlFlowContext { pub(crate) fn analyze( parsed: &mut T, inner: impl FnOnce(&mut T, FlowCapturer) -> ParseResult<()>, - ) -> ParseResult { + ) -> ParseResult { let mut context = Self { - state: FlowAnalysisState::new_empty(), + analyzer: StaticAnalyzer::new_empty(), }; let mut root_frame = FrameId::new_placeholder(); let mut root_scope = ScopeId::new_placeholder(); @@ -219,17 +219,17 @@ impl ControlFlowContext { context.enter_frame(root_frame, root_scope); inner(parsed, &mut context)?; context.exit_frame(root_frame, root_scope); - context.state.finish((root_frame, root_scope)) + context.analyzer.finish((root_frame, root_scope)) } pub(crate) fn register_frame(&mut self, id: &mut FrameId) { assert!(id.is_placeholder()); - *id = self.state.allocate_frame(); + *id = self.analyzer.allocate_frame(); } pub(crate) fn register_scope(&mut self, id: &mut ScopeId) { assert!(id.is_placeholder()); - *id = self.state.allocate_scope(); + *id = self.analyzer.allocate_scope(); } pub(crate) fn register_variable_definition( @@ -238,7 +238,7 @@ impl ControlFlowContext { id: &mut VariableDefinitionId, ) { assert!(id.is_placeholder()); - *id = self.state.allocate_variable_definition(ident); + *id = self.analyzer.allocate_variable_definition(ident); } pub(crate) fn register_variable_reference( @@ -247,27 +247,27 @@ impl ControlFlowContext { id: &mut VariableReferenceId, ) { assert!(id.is_placeholder()); - *id = self.state.allocate_variable_reference(ident); + *id = self.analyzer.allocate_variable_reference(ident); } pub(crate) fn enter_frame(&mut self, frame: FrameId, scope: ScopeId) { - self.state.enter_frame(frame, scope); + self.analyzer.enter_frame(frame, scope); } pub(crate) fn exit_frame(&mut self, frame: FrameId, scope: ScopeId) { - self.state.exit_frame(frame, scope); + self.analyzer.exit_frame(frame, scope); } pub(crate) fn enter_scope(&mut self, scope: ScopeId) { - self.state.enter_scope(scope); + self.analyzer.enter_scope(scope); } pub(crate) fn exit_scope(&mut self, scope: ScopeId) { - self.state.exit_scope(scope); + self.analyzer.exit_scope(scope); } pub(crate) fn define_variable(&mut self, id: VariableDefinitionId) { - self.state.define_variable(id); + self.analyzer.define_variable(id); } pub(crate) fn reference_variable( @@ -275,7 +275,7 @@ impl ControlFlowContext { id: VariableReferenceId, #[cfg(feature = "debug")] assertion: FinalUseAssertion, ) -> ParseResult<()> { - self.state.reference_variable( + self.analyzer.reference_variable( id, #[cfg(feature = "debug")] assertion, @@ -283,7 +283,7 @@ impl ControlFlowContext { } pub(crate) fn enter_next_segment(&mut self, segment_kind: SegmentKind) -> ControlFlowSegmentId { - self.state.enter_next_segment(segment_kind) + self.analyzer.enter_next_segment(segment_kind) } pub(crate) fn enter_path_segment( @@ -291,31 +291,31 @@ impl ControlFlowContext { previous_sibling_id: Option, segment_kind: SegmentKind, ) -> ControlFlowSegmentId { - self.state + self.analyzer .enter_path_segment(previous_sibling_id, segment_kind) } pub(crate) fn exit_segment(&mut self, segment_id: ControlFlowSegmentId) { - self.state.exit_segment(segment_id); + self.analyzer.exit_segment(segment_id); } pub(crate) fn register_catch_location(&mut self, data: CatchLocationData) -> CatchLocationId { - self.state.register_catch_location(data) + self.analyzer.register_catch_location(data) } pub(crate) fn enter_catch(&mut self, catch_location_id: CatchLocationId) { - self.state.enter_catch(catch_location_id); + self.analyzer.enter_catch(catch_location_id); } pub(crate) fn exit_catch(&mut self, catch_location_id: CatchLocationId) { - self.state.exit_catch(catch_location_id); + self.analyzer.exit_catch(catch_location_id); } pub(crate) fn resolve_catch_for_interrupt( &self, interrupt_details: InterruptDetails, ) -> ParseResult { - self.state.resolve_catch_for_interrupt(interrupt_details) + self.analyzer.resolve_catch_for_interrupt(interrupt_details) } } diff --git a/src/interpretation/control_flow_pass.rs b/src/static_analysis/control_flow_analyzer.rs similarity index 100% rename from src/interpretation/control_flow_pass.rs rename to src/static_analysis/control_flow_analyzer.rs diff --git a/src/static_analysis/mod.rs b/src/static_analysis/mod.rs new file mode 100644 index 00000000..8f9b0fba --- /dev/null +++ b/src/static_analysis/mod.rs @@ -0,0 +1,7 @@ +mod control_flow_analyzer; +mod static_analyzer; + +pub(crate) use static_analyzer::*; + +use crate::internal_prelude::*; +use control_flow_analyzer::*; diff --git a/src/interpretation/source_parsing.rs b/src/static_analysis/static_analyzer.rs similarity index 99% rename from src/interpretation/source_parsing.rs rename to src/static_analysis/static_analyzer.rs index ecafbe28..f4db96f2 100644 --- a/src/interpretation/source_parsing.rs +++ b/src/static_analysis/static_analyzer.rs @@ -35,7 +35,7 @@ pub(crate) enum FinalUseAssertion { } #[derive(Debug)] -pub(crate) struct ScopeDefinitions { +pub(crate) struct StaticDefinitions { pub(crate) root_frame: (FrameId, ScopeId), pub(crate) frames: Arena, pub(crate) scopes: Arena, @@ -52,7 +52,7 @@ pub(crate) struct ScopeDefinitions { } #[allow(unused)] -pub(crate) struct FlowAnalysisState { +pub(crate) struct StaticAnalyzer { frames_stack: Vec, frames: Arena, scopes: Arena, @@ -70,7 +70,7 @@ pub(crate) struct FlowAnalysisState { current_segment_id: ControlFlowSegmentId, } -impl FlowAnalysisState { +impl StaticAnalyzer { pub(crate) fn new_empty() -> Self { Self { current_frame_id: FrameId::new_placeholder(), @@ -89,7 +89,7 @@ impl FlowAnalysisState { pub(crate) fn finish( mut self, root_frame: (FrameId, ScopeId), - ) -> ParseResult { + ) -> ParseResult { assert!( self.frames_stack.is_empty(), "Cannot finish - Unpopped frames remain" @@ -137,7 +137,7 @@ impl FlowAnalysisState { } } - Ok(ScopeDefinitions { + Ok(StaticDefinitions { root_frame, frames, scopes, diff --git a/tests/functions.rs b/tests/functions.rs index e10fbee7..5cc58342 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -31,6 +31,8 @@ fn test_method_function_has_receiver_bound() { push_me(4); %[_].assert_eq(arr.to_debug_string(), "[1, 2, 3, 4]"); } + // Note - the following differs from e.g. Javascript: + // Here, an object property is not classed as a method. run! { let arr = [1, 2, 3]; let x = %{ push: arr.push }; @@ -97,4 +99,4 @@ fn test_control_flow_inside_closure() { }; %[_].assert_eq(f(), 5); } -} \ No newline at end of file +} From f5d82d621d0b78b6aca216f4b705a3d1c614f117 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 1 Feb 2026 01:43:59 +0000 Subject: [PATCH 14/49] feat(ui): Closures can capture variable references --- plans/TODO.md | 22 +-- src/expressions/closures.rs | 12 +- src/expressions/values/any_value.rs | 22 ++- src/internal_prelude.rs | 1 + src/interpretation/bindings.rs | 33 +++- src/interpretation/interpreter.rs | 52 +++++- src/interpretation/variable.rs | 5 +- src/misc/parse_traits.rs | 8 +- src/static_analysis/static_analyzer.rs | 231 ++++++++++++++++++++----- tests/functions.rs | 58 ++++++- 10 files changed, 355 insertions(+), 89 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index fd8c4cc9..a9475b14 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -231,8 +231,9 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] Lots of tests - [x] Loop / Break inside closure - [x] Break doesn't compile if it would propagate outside closure -- [ ] Closed Bindings - - [ ] Change bindings (currently just variables) to be able to store any of the following: (nb we still restrict variables to be owned for now). +- [x] Close over variables in ancestor scopes +- [ ] Support shared/mutable arguments + We may need to change VariableContent (nb we still restrict variables to be owned for now). ```rust enum VariableContent { Owned(Referenceable), @@ -240,21 +241,16 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). Mutable(Mutable), } ``` - * A function may capture variable bindings from the parent scope, these are converted into a `VariableBinding::Closure()` - * The closure consists of a set of bindings attached to the function value, either: - - `ClosedVariable::Owned(Value)` if it's the last mention of the closed variable, so it can be moved in - - `ClosedVariable::Referenced(Rc>)` otherwise. - The nice thing about this is can pull shared/mutable at runtime, without needing to magically work out what to move. - - DAVID NOTE: This only works if we expect the variables to be passed by reference, not moved. There's often an expectation that value types are passed cloned... - - ... let's think about this a little more. Perhaps being able to support (move) expressions in future might be nice. `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). - * Invocation requests `CopyOnWrite`, and can be on a shared function or an owned function (if it is the last usage of that value, as per normal red/owned binding rules) - * If invocation is on an owned function, then owned values from the closure can be consumed by the invocation - * Otherwise, the values are only available as shared/mut -- [ ] Support optional arguments - [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` - [ ] Add `array.sort`, `array.sort_by` - [ ] Resolve all `TODO[functions]` +Possible punted: +- [ ] 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. + ## Parser - Methods using closures Future methods once we have closures: diff --git a/src/expressions/closures.rs b/src/expressions/closures.rs index 191d3fc5..d63d4b78 100644 --- a/src/expressions/closures.rs +++ b/src/expressions/closures.rs @@ -17,13 +17,13 @@ impl ParseSource for ClosureExpression { impl ClosureExpression { pub(crate) fn evaluate_spanned( &self, - _interpreter: &mut Interpreter, + interpreter: &mut Interpreter, ownership: RequestedOwnership, ) -> ExecutionResult> { - // TODO[functions]: Capture variables from the parent frame. let span_range = self.0.span_range; let value = ClosureValue { definition: Rc::clone(&self.0), + closed_references: interpreter.resolve_closed_references(self.0.frame_id), }; ownership.map_from_owned(value.into_any_value().spanned(span_range)) } @@ -32,7 +32,8 @@ impl ClosureExpression { #[derive(Clone)] pub(crate) struct ClosureValue { definition: Rc, - // TODO[functions]: Add closed_variables here + closed_references: Vec<(VariableDefinitionId, Referenceable)>, + // TODO[functions]: Add closed_values from moves here } impl PartialEq for ClosureValue { @@ -68,6 +69,9 @@ impl ClosureValue { definition.frame_id, context.output_span_range, )?; + for (definition, content) in self.closed_references { + context.interpreter.define_variable(definition, content); + } for (pattern, Spanned(arg, arg_span)) in definition.argument_definitions.iter().zip(arguments) @@ -186,7 +190,7 @@ impl ParseSource for ClosureDefinition { fn control_flow_pass(&mut self, context: FlowCapturer) -> ParseResult<()> { context.register_frame(&mut self.frame_id); context.register_scope(&mut self.scope_id); - context.enter_frame(self.frame_id, self.scope_id); + context.enter_closure_frame(self.frame_id, self.scope_id); for argument in &mut self.argument_definitions { argument.control_flow_pass(context)?; } diff --git a/src/expressions/values/any_value.rs b/src/expressions/values/any_value.rs index d320a340..1af6e259 100644 --- a/src/expressions/values/any_value.rs +++ b/src/expressions/values/any_value.rs @@ -449,15 +449,23 @@ impl<'a> AnyValueRef<'a> { AnyValueContent::Range(range) => { range.concat_recursive_into(output, behaviour)?; } - AnyValueContent::Parser(_) => { - return behaviour - .error_span_range - .type_err("Parsers cannot be output to a string"); + AnyValueContent::Parser(parser) => { + if behaviour.use_debug_literal_syntax { + write!(output, "parser[{:?}]", parser).unwrap(); + } else { + return behaviour + .error_span_range + .type_err("Parsers cannot be output to a string"); + } } AnyValueContent::Function(_) => { - return behaviour - .error_span_range - .type_err("Functions cannot be output to a string"); + if behaviour.use_debug_literal_syntax { + output.push_str("function[?]") + } else { + return behaviour + .error_span_range + .type_err("Functions cannot be output to a string"); + } } AnyValueContent::Integer(_) | AnyValueContent::Float(_) diff --git a/src/internal_prelude.rs b/src/internal_prelude.rs index b9df86e4..34a546b3 100644 --- a/src/internal_prelude.rs +++ b/src/internal_prelude.rs @@ -6,6 +6,7 @@ pub(crate) use std::{ cell::{Ref, RefCell, RefMut}, collections::{BTreeMap, HashMap, HashSet}, fmt::Debug, + fmt::Write, iter, marker::PhantomData, ops::{Deref, DerefMut}, diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index f263bbba..1c1d30df 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -6,21 +6,45 @@ use std::rc::Rc; pub(super) enum VariableState { Uninitialized, - Value(Rc>), + Value(Referenceable), Finished, } +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: AnyValue) { + pub(crate) fn define(&mut self, value: Referenceable) { match self { content @ VariableState::Uninitialized => { - *content = VariableState::Value(Rc::new(RefCell::new(value))); + *content = VariableState::Value(value); } VariableState::Value(_) => panic!("Cannot define existing variable"), VariableState::Finished => panic!("Cannot define finished variable"), } } + pub(crate) fn resolve_referenceable( + &mut self, + is_final: bool, + blocked_from_mutation: Option, + ) -> Referenceable { + 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(referencable) => referencable, + VariableState::Finished => panic!("{}", FINISHED_ERR), + } + } else { + match self { + VariableState::Uninitialized => panic!("{}", UNITIALIZED_ERR), + VariableState::Value(referencable) => Rc::clone(referencable), + VariableState::Finished => panic!("{}", FINISHED_ERR), + } + } + } + pub(crate) fn resolve( &mut self, variable_span: Span, @@ -28,9 +52,6 @@ impl VariableState { ownership: RequestedOwnership, blocked_from_mutation: Option, ) -> ExecutionResult> { - 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."; - let span_range = variable_span.span_range(); // If blocked from mutation, we technically could allow is_final to work and diff --git a/src/interpretation/interpreter.rs b/src/interpretation/interpreter.rs index 394b9182..4a9c7c79 100644 --- a/src/interpretation/interpreter.rs +++ b/src/interpretation/interpreter.rs @@ -211,12 +211,42 @@ impl Interpreter { } } - pub(crate) fn define_variable(&mut self, definition_id: VariableDefinitionId, value: AnyValue) { + pub(crate) fn define_variable( + &mut self, + definition_id: VariableDefinitionId, + value: Referenceable, + ) { let definition = self.scope_definitions.definitions.get(definition_id); let scope_data = self.scope_mut(definition.scope); scope_data.define_variable(definition_id, value) } + pub(crate) fn resolve_closed_references( + &mut self, + frame_id: FrameId, + ) -> Vec<(VariableDefinitionId, Referenceable)> { + let frame = self.scope_definitions.frames.get(frame_id); + frame + .closed_variables + .clone() + .into_iter() + .map(|(closure_definition_id, reference_id)| { + let reference_def = self.scope_definitions.references.get(reference_id); + let is_final_reference = reference_def.is_final_reference; + let definition = reference_def.definition; + let definition_scope = reference_def.definition_scope; + let is_blocked_from_mutation = self.is_blocked_from_mutating(definition_scope); + let reference = self + .scope_mut(definition_scope) + .variables + .get_mut(&definition) + .expect("Variable data not found in scope") + .resolve_referenceable(is_final_reference, is_blocked_from_mutation); + (closure_definition_id, reference) + }) + .collect() + } + pub(crate) fn resolve( &mut self, variable: &VariableReference, @@ -228,11 +258,17 @@ impl Interpreter { reference.reference_name_span, reference.is_final_reference, ); - let blocked_from_mutation = match self.no_mutation_above.last() { + let blocked_from_mutation = self.is_blocked_from_mutating(reference.definition_scope); + let scope_data = self.scope_mut(reference.definition_scope); + scope_data.resolve(definition, span, is_final, ownership, blocked_from_mutation) + } + + fn is_blocked_from_mutating(&self, definition_scope: ScopeId) -> Option { + match self.no_mutation_above.last() { Some(&(no_mutation_above_scope, reason)) => 'result: { for scope in self.scopes.iter().rev() { match scope.id { - id if id == reference.definition_scope => break 'result None, + id if id == definition_scope => break 'result None, id if id == no_mutation_above_scope => break 'result Some(reason), _ => {} } @@ -240,9 +276,7 @@ impl Interpreter { panic!("Definition scope expected in scope stack due to control flow analysis"); } None => None, - }; - let scope_data = self.scope_mut(reference.definition_scope); - scope_data.resolve(definition, span, is_final, ownership, blocked_from_mutation) + } } pub(crate) fn start_iteration_counter<'s, S: HasSpanRange>( @@ -453,7 +487,11 @@ struct RuntimeScope { } impl RuntimeScope { - fn define_variable(&mut self, definition_id: VariableDefinitionId, value: AnyValue) { + fn define_variable( + &mut self, + definition_id: VariableDefinitionId, + value: Referenceable, + ) { self.variables .get_mut(&definition_id) .expect("Variable data not found in scope") diff --git a/src/interpretation/variable.rs b/src/interpretation/variable.rs index 72ee8431..a915c652 100644 --- a/src/interpretation/variable.rs +++ b/src/interpretation/variable.rs @@ -66,7 +66,10 @@ impl ParseSource for VariableDefinition { impl VariableDefinition { pub(crate) fn define(&self, interpreter: &mut Interpreter, value_source: impl IntoAnyValue) { - interpreter.define_variable(self.id, value_source.into_any_value()); + interpreter.define_variable( + self.id, + Rc::new(RefCell::new(value_source.into_any_value())), + ); } } diff --git a/src/misc/parse_traits.rs b/src/misc/parse_traits.rs index cb79a508..6658102d 100644 --- a/src/misc/parse_traits.rs +++ b/src/misc/parse_traits.rs @@ -216,7 +216,9 @@ impl ControlFlowContext { let mut root_scope = ScopeId::new_placeholder(); context.register_frame(&mut root_frame); context.register_scope(&mut root_scope); - context.enter_frame(root_frame, root_scope); + context + .analyzer + .enter_frame(root_frame, root_scope, FrameKind::Root); inner(parsed, &mut context)?; context.exit_frame(root_frame, root_scope); context.analyzer.finish((root_frame, root_scope)) @@ -250,8 +252,8 @@ impl ControlFlowContext { *id = self.analyzer.allocate_variable_reference(ident); } - pub(crate) fn enter_frame(&mut self, frame: FrameId, scope: ScopeId) { - self.analyzer.enter_frame(frame, scope); + pub(crate) fn enter_closure_frame(&mut self, frame: FrameId, scope: ScopeId) { + self.analyzer.enter_frame(frame, scope, FrameKind::Closure); } pub(crate) fn exit_frame(&mut self, frame: FrameId, scope: ScopeId) { diff --git a/src/static_analysis/static_analyzer.rs b/src/static_analysis/static_analyzer.rs index f4db96f2..3e60a5fb 100644 --- a/src/static_analysis/static_analyzer.rs +++ b/src/static_analysis/static_analyzer.rs @@ -179,16 +179,16 @@ impl StaticAnalyzer { // FRAMES // ====== - pub(crate) fn enter_frame(&mut self, frame_id: FrameId, scope_id: ScopeId) { - let parent_scope = self.current_scope_id; + pub(crate) fn enter_frame(&mut self, frame_id: FrameId, scope_id: ScopeId, kind: FrameKind) { *self.frames.get_mut(frame_id) = AllocatedFrame::Defined(FrameData { - lexical_parent: if parent_scope.is_placeholder() { - None - } else { - Some(parent_scope) + kind_data: match kind { + FrameKind::Root => FrameKindData::Root, + FrameKind::Closure => FrameKindData::Closure { + lexical_parent: (self.current_frame_id, self.current_scope_id), + closed_variables: BTreeMap::new(), + }, }, root_segment: ControlFlowSegmentId::new_placeholder(), - closed_variables: BTreeMap::new(), scope_stack: Vec::new(), segment_stack: Vec::new(), catch_location_stack: Vec::new(), @@ -308,39 +308,153 @@ impl StaticAnalyzer { id: VariableReferenceId, #[cfg(feature = "debug")] assertion: FinalUseAssertion, ) -> ParseResult<()> { - let segment = self.current_segment_id; - let reference = self.references.get_mut(id); - let (name, reference_name_span) = reference.take_allocated(); - // self.scope_id_stack() but inlined so that the mutability checker is happy - let scope_stack = self - .frames - .get(self.current_frame_id) - .defined_ref() - .scope_stack - .as_slice(); + let (name, reference_name_span) = self.references.get_mut(id).take_allocated(); + match self.find_definition_or_create_from_parent(self.current_frame_id, name.as_str()) { + Some((def_id, def_scope_id, segment_id)) => { + let reference = self.references.get_mut(id); + *reference = AllocatedVariableReference::Defined(VariableReferenceData { + definition: def_id, + definition_scope: def_scope_id, + segment: segment_id, + reference_name_span, + is_final_reference: false, // Some will be set to true later + #[cfg(feature = "debug")] + assertion, + }); + self.definitions + .get_mut(def_id) + .defined_mut() + .references + .push(id); + self.segments + .get_mut(segment_id) + .children + .push(ControlFlowChild::VariableReference(id, def_id)); + Ok(()) + } + None => reference_name_span + .parse_err(format!("Cannot find variable `{}` in this scope", name)), + } + } + + // Returns: + // - The definition id from the given frame + // - The definition scope id from the given frame + // - The segment id of the reference in the given frame + fn find_definition_or_create_from_parent( + &mut self, + frame_id: FrameId, + name: &str, + ) -> Option<(VariableDefinitionId, ScopeId, ControlFlowSegmentId)> { + let frame = self.frames.get_mut(frame_id).defined_mut(); + let segment_id = *frame.segment_stack.last().unwrap(); + let scope_id = *frame.scope_stack.last().unwrap(); + if let Some((def_id, scope_id)) = + Self::find_definition_from_own_frame(&*frame, &self.scopes, &self.definitions, name) + { + return Some((def_id, scope_id, segment_id)); + } + // We didn't find a variable in the current frame - + // let's see if we can find it in the parent frame (recursively) + let parent_frame_id = match &frame.kind_data { + FrameKindData::Root => return None, + FrameKindData::Closure { + lexical_parent: (parent_frame_id, _), + .. + } => *parent_frame_id, + }; + // Closed variable definitions must live in the frame's root scope/segment, + // because at runtime they are defined during invoke() before the body + // (and any child scopes) are evaluated. + let frame_root_scope_id = frame.scope_stack[0]; + let frame_root_segment_id = frame.root_segment; + let (parent_definition, parent_definition_scope, parent_segment_id) = + self.find_definition_or_create_from_parent(parent_frame_id, name)?; + // If we find it in the parent frame, then we need to connect it up over 3 steps: + // 1. We need to create a reference for it in the parent frame + let parent_reference_id = + self.references + .add(AllocatedVariableReference::Defined(VariableReferenceData { + definition: parent_definition, + definition_scope: parent_definition_scope, + segment: parent_segment_id, + // This span is only used for ownership resolution errors, + // but not for referenced closures where we copy the Referencable into the closure. + reference_name_span: Span::call_site(), + is_final_reference: false, + #[cfg(feature = "debug")] + assertion: FinalUseAssertion::None, + })); + self.definitions + .get_mut(parent_definition) + .defined_mut() + .references + .push(parent_reference_id); + self.segments.get_mut(parent_segment_id).children.push( + ControlFlowChild::VariableReference(parent_reference_id, parent_definition), + ); + + // 2. We need to create a definition for it in this frame's root scope + let definition_id = self.definitions.add(AllocatedVariableDefinition::Defined( + VariableDefinitionData { + scope: frame_root_scope_id, + segment: frame_root_segment_id, + name: name.to_string(), + // This is unused, so can put in a placeholder + definition_name_span: Span::call_site(), + references: vec![], + }, + )); + self.scopes + .get_mut(frame_root_scope_id) + .defined_mut() + .definitions + .push(definition_id); + // TODO[functions]: This is incorrect - it should really be prepended before other control flow... + // -- in fact I'm slightly surprised we don't get issues with references before definitions. + self.segments + .get_mut(frame_root_segment_id) + .children + .push(ControlFlowChild::VariableDefinition(definition_id)); + + // 3. We need to add it to the closed variables of this frame + let frame = self.frames.get_mut(frame_id).defined_mut(); + match &mut frame.kind_data { + FrameKindData::Root => unreachable!("Already returned above"), + FrameKindData::Closure { + closed_variables, .. + } => { + closed_variables.insert(name.to_string(), (definition_id, parent_reference_id)); + } + } + + Some((definition_id, frame_root_scope_id, segment_id)) + } + + fn find_definition_from_own_frame( + frame: &FrameData, + scopes: &Arena, + definitions: &Arena, + name: &str, + ) -> Option<(VariableDefinitionId, ScopeId)> { + let scope_stack = frame.scope_stack.as_slice(); for scope_id in scope_stack.iter().rev() { - let scope = self.scopes.get(*scope_id).defined_ref(); + let scope = scopes.get(*scope_id).defined_ref(); for &def_id in scope.definitions.iter().rev() { - let def = self.definitions.get_mut(def_id).defined_mut(); + let def = definitions.get(def_id).defined_ref(); if def.name == name { - *reference = AllocatedVariableReference::Defined(VariableReferenceData { - definition: def_id, - definition_scope: def.scope, - segment, - reference_name_span, - is_final_reference: false, // Some will be set to true later - #[cfg(feature = "debug")] - assertion, - }); - def.references.push(id); - self.current_segment() - .children - .push(ControlFlowChild::VariableReference(id, def_id)); - return Ok(()); + return Some((def_id, def.scope)); } } } - reference_name_span.parse_err(format!("Cannot find variable `{}` in this scope", name)) + match &frame.kind_data { + FrameKindData::Root => None, + FrameKindData::Closure { + closed_variables, .. + } => closed_variables + .get(name) + .map(|(def_id, _)| (*def_id, frame.scope_stack[0])), + } } // SEGMENTS @@ -679,8 +793,20 @@ impl AllocatedFrame { fn into_runtime(self) -> RuntimeFrame { match self { - AllocatedFrame::Defined(data) => RuntimeFrame { - closed_variables: data.closed_variables, + AllocatedFrame::Defined(FrameData { + kind_data: + FrameKindData::Closure { + closed_variables, .. + }, + .. + }) => RuntimeFrame { + closed_variables: closed_variables.into_values().collect(), + }, + AllocatedFrame::Defined(FrameData { + kind_data: FrameKindData::Root, + .. + }) => RuntimeFrame { + closed_variables: Vec::new(), }, _ => panic!("Frame was not defined"), } @@ -688,25 +814,36 @@ impl AllocatedFrame { } struct FrameData { - // Only the actual root has no lexical parent - lexical_parent: Option, + kind_data: FrameKindData, root_segment: ControlFlowSegmentId, - // When a variable name matches to a variable defined in an ancestor scope, - // we need to close over that variable. - // - // To do this, at every function boundary between these, we: - // - Define a closed variable with the same name - // - Create a variable reference which we can use to capture the variable - // from the parent closure when the closure is created. - closed_variables: BTreeMap, scope_stack: Vec, catch_location_stack: Vec, segment_stack: Vec, } +pub(crate) enum FrameKind { + Root, + Closure, +} + +pub(crate) enum FrameKindData { + Root, + Closure { + lexical_parent: (FrameId, ScopeId), + // When a variable name matches to a variable defined in an ancestor scope, + // we need to close over that variable. + // + // To do this, at every function boundary between these, we: + // - Define a closed variable with the same name + // - Create a variable reference which we can use to capture the variable + // from the parent closure when the closure is created. + closed_variables: BTreeMap, + }, +} + #[derive(Debug)] pub(crate) struct RuntimeFrame { - pub(crate) closed_variables: BTreeMap, + pub(crate) closed_variables: Vec<(VariableDefinitionId, VariableReferenceId)>, } enum AllocatedScope { diff --git a/tests/functions.rs b/tests/functions.rs index 5cc58342..407d9bc7 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -52,7 +52,7 @@ fn test_preinterpret_api() { } #[test] -fn test_basic_closures() { +fn test_simple_closures() { run! { let double_me = |x| x * 2; %[_].assert_eq(double_me(4), 8); @@ -100,3 +100,59 @@ fn test_control_flow_inside_closure() { %[_].assert_eq(f(), 5); } } + +#[test] +fn test_closed_reference_closures() { + run! { + let multiplier = 3; + let multiply_me = |x| x * multiplier; + %[_].assert_eq(multiply_me(4), 12); + multiplier = 5; + %[_].assert_eq(multiply_me(4), 20); + } + run! { + let multiplier = 3; + let multiply_me = |x| { + x * multiplier + }; + %[_].assert_eq(multiply_me(4), 12); + multiplier = 5; + %[_].assert_eq(multiply_me(4), 20); + } + run! { + let multiplier = 3; + let multiply_me = |x| || x * multiplier; + %[_].assert_eq(multiply_me(4)(), 12); + multiplier = 5; + %[_].assert_eq(multiply_me(4)(), 20); + } + run! { + let multiplier = 3; + let multiply_me = |x| { + || x * multiplier + }; + %[_].assert_eq(multiply_me(4)(), 12); + multiplier = 5; + %[_].assert_eq(multiply_me(4)(), 20); + } +} + +#[test] +fn test_closed_reference_last_use_allows_transfer_of_ownership() { + // The destructuring requires an owned object. + // So this test demonstrates that obj (as last use) can be transferred into the closure... + // ... and then retrieved as a unique reference, and so an owned object. + // --- + // If either of the commented out lines is uncommented, then this test correctly (but slightly confusingly) + // breaks, because multiple copies of obj are still around (either in obj.inner or inside the closure). + run! { + let obj = %{ inner: 0 }; + let captured_obj = || { + obj + }; + let %{ inner } = captured_obj(); + // %[_].assert_eq(obj.inner, 0); + // %[_].assert_eq(captured_obj.to_debug_string(), "function[?]"); + %[_].assert_eq(inner, 0); + } +} From 3ee4816bbcc0224ada3631923beaf46bb7c070a5 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 1 Feb 2026 12:49:01 +0000 Subject: [PATCH 15/49] fix: Add a closure definition segment --- src/static_analysis/static_analyzer.rs | 55 ++++++++++++++++++++------ 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/src/static_analysis/static_analyzer.rs b/src/static_analysis/static_analyzer.rs index 3e60a5fb..bd5c2df9 100644 --- a/src/static_analysis/static_analyzer.rs +++ b/src/static_analysis/static_analyzer.rs @@ -186,6 +186,7 @@ impl StaticAnalyzer { FrameKind::Closure => FrameKindData::Closure { lexical_parent: (self.current_frame_id, self.current_scope_id), closed_variables: BTreeMap::new(), + closure_definition_segment: ControlFlowSegmentId::new_placeholder(), }, }, root_segment: ControlFlowSegmentId::new_placeholder(), @@ -197,8 +198,23 @@ impl StaticAnalyzer { self.current_frame_id = frame_id; self.enter_scope(scope_id); let segment = self.enter_segment_with_valid_previous(None, None, SegmentKind::Sequential); - let frame = self.current_frame_mut(); + let frame = self.frames.get_mut(frame_id).defined_mut(); frame.root_segment = segment; + match &mut frame.kind_data { + FrameKindData::Root => {} + FrameKindData::Closure { + closure_definition_segment, + .. + } => { + *closure_definition_segment = Self::create_segment_with_valid_previous( + &mut self.segments, + Some(segment), + None, + SegmentKind::Sequential, + scope_id, + ); + } + } } /// Updates `current_frame_id` from the frames stack. @@ -356,12 +372,13 @@ impl StaticAnalyzer { } // We didn't find a variable in the current frame - // let's see if we can find it in the parent frame (recursively) - let parent_frame_id = match &frame.kind_data { + let (parent_frame_id, closure_definition_segment) = match &frame.kind_data { FrameKindData::Root => return None, FrameKindData::Closure { lexical_parent: (parent_frame_id, _), + closure_definition_segment, .. - } => *parent_frame_id, + } => (*parent_frame_id, *closure_definition_segment), }; // Closed variable definitions must live in the frame's root scope/segment, // because at runtime they are defined during invoke() before the body @@ -410,10 +427,8 @@ impl StaticAnalyzer { .defined_mut() .definitions .push(definition_id); - // TODO[functions]: This is incorrect - it should really be prepended before other control flow... - // -- in fact I'm slightly surprised we don't get issues with references before definitions. self.segments - .get_mut(frame_root_segment_id) + .get_mut(closure_definition_segment) .children .push(ControlFlowChild::VariableDefinition(definition_id)); @@ -524,16 +539,33 @@ impl StaticAnalyzer { previous_sibling_id: Option, segment_kind: SegmentKind, ) -> ControlFlowSegmentId { - let child_id = self.segments.add(ControlFlowSegmentData { - scope: self.current_scope_id, + let child_id = Self::create_segment_with_valid_previous( + &mut self.segments, + parent_id, + previous_sibling_id, + segment_kind, + self.current_scope_id, + ); + self.segments_stack().push(child_id); + self.current_segment_id = child_id; + child_id + } + + fn create_segment_with_valid_previous( + segments: &mut Arena, + parent_id: Option, + previous_sibling_id: Option, + segment_kind: SegmentKind, + scope: ScopeId, + ) -> ControlFlowSegmentId { + let child_id = segments.add(ControlFlowSegmentData { + scope, parent: parent_id, children: segment_kind.new_children(), segment_kind, }); - self.segments_stack().push(child_id); - self.current_segment_id = child_id; if let Some(parent_id) = parent_id { - let parent = self.segments.get_mut(parent_id); + let parent = segments.get_mut(parent_id); match &mut parent.children { SegmentChildren::Sequential { ref mut children } => { children.push(ControlFlowChild::Segment(child_id)); @@ -838,6 +870,7 @@ pub(crate) enum FrameKindData { // - Create a variable reference which we can use to capture the variable // from the parent closure when the closure is created. closed_variables: BTreeMap, + closure_definition_segment: ControlFlowSegmentId, }, } From dbb366091b915e6712020c5f90d41d72d049e361 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 1 Feb 2026 15:53:26 +0000 Subject: [PATCH 16/49] feat: Arguments can be shared/mutable --- plans/TODO.md | 20 +-- src/expressions/closures.rs | 42 ++++-- src/interpretation/bindings.rs | 136 ++++++++++++------ src/interpretation/interpreter.rs | 16 +-- src/interpretation/variable.rs | 2 +- src/misc/errors.rs | 76 +++++----- src/misc/mut_rc_ref_cell.rs | 96 +++++++++---- .../cannot_pass_shared_to_mut_argument.rs | 8 ++ .../cannot_pass_shared_to_mut_argument.stderr | 5 + .../functions/mut_argument_cannot_be_owned.rs | 11 ++ .../mut_argument_cannot_be_owned.stderr | 5 + .../ref_argument_cannot_be_mutated.rs | 9 ++ .../ref_argument_cannot_be_mutated.stderr | 5 + tests/functions.rs | 42 ++++++ 14 files changed, 338 insertions(+), 135 deletions(-) create mode 100644 tests/compilation_failures/functions/cannot_pass_shared_to_mut_argument.rs create mode 100644 tests/compilation_failures/functions/cannot_pass_shared_to_mut_argument.stderr create mode 100644 tests/compilation_failures/functions/mut_argument_cannot_be_owned.rs create mode 100644 tests/compilation_failures/functions/mut_argument_cannot_be_owned.stderr create mode 100644 tests/compilation_failures/functions/ref_argument_cannot_be_mutated.rs create mode 100644 tests/compilation_failures/functions/ref_argument_cannot_be_mutated.stderr diff --git a/plans/TODO.md b/plans/TODO.md index a9475b14..120eb61d 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -232,20 +232,21 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] Loop / Break inside closure - [x] Break doesn't compile if it would propagate outside closure - [x] Close over variables in ancestor scopes -- [ ] Support shared/mutable arguments - We may need to change VariableContent (nb we still restrict variables to be owned for now). - ```rust - enum VariableContent { - Owned(Referenceable), - Shared(Shared), - Mutable(Mutable), - } - ``` +- [x] Support shared/mutable arguments + - [x] Allow variable content to hold shared / mutable + - [x] Allow shared / mutable arguments + - [x] Add tests for shared/mutable, including conversions working and not - [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` - [ ] Add `array.sort`, `array.sort_by` - [ ] Resolve all `TODO[functions]` Possible punted: +- [ ] Allow variables to be marked owned / shared / mutable + - [ ] ... and maybe no marking accepts any? + ... so e.g. push_one_and_return_mut can have `let y = arr; y` without erroring + - [ ] Else - improve the errors so that the conversion error has some hint from the target explaining that the target can be changed to allow + shared/mutable instead. +- [ ] Allow destructuring shared and mutable variables and arguments - [ ] Support optional arguments in closures - [ ] Support for `move()` expressions in closures. - `move(x)` / `move(a.b.as_ref())` / `move(a.b.as_mut())` => we hoist up the content into the previous frame (effectively temporarily change `current_frame_id` to be the parent in the `FlowAnalysisState` - pretty easy). @@ -449,6 +450,7 @@ preinterpret::run! { - [ ] Change the storage model for `OutputStream` - [ ] Either just use `TokenStream` directly(!) (...and ignore Rust analyzer's poor handling of none groups) - [ ] Or use an `Rc` model like https://github.com/dtolnay/proc-macro2/pull/341/files and Rust itself +- [ ] Investigate if we increase compile time perf by removing all the integer cast implementations, instead putting them on AnyInteger and via u128. - Address `TODO[performance]` ## Deferred diff --git a/src/expressions/closures.rs b/src/expressions/closures.rs index d63d4b78..faa522f9 100644 --- a/src/expressions/closures.rs +++ b/src/expressions/closures.rs @@ -32,7 +32,7 @@ impl ClosureExpression { #[derive(Clone)] pub(crate) struct ClosureValue { definition: Rc, - closed_references: Vec<(VariableDefinitionId, Referenceable)>, + closed_references: Vec<(VariableDefinitionId, VariableContent)>, // TODO[functions]: Add closed_values from moves here } @@ -76,15 +76,39 @@ impl ClosureValue { for (pattern, Spanned(arg, arg_span)) in definition.argument_definitions.iter().zip(arguments) { - let value = match arg { - ArgumentValue::Owned(owned) => owned, - _ => { - context.interpreter.exit_scope(definition.scope_id); - return arg_span - .type_err("Only owned arguments are currently supported for closures"); + match (pattern, arg) { + (pattern, ArgumentValue::Owned(owned)) => { + pattern.handle_destructure(context.interpreter, owned)?; } - }; - pattern.handle_destructure(context.interpreter, value)?; + (Pattern::Discarded(_), _) => {} + (Pattern::Variable(variable), ArgumentValue::Shared(shared)) => { + context.interpreter.define_variable( + variable.definition.id, + VariableContent::Shared(shared.disable()), + ); + } + (_, ArgumentValue::Shared(_)) => { + return arg_span.type_err( + "Destructuring patterns are not currently supported for & arguments", + ); + } + (Pattern::Variable(variable), ArgumentValue::Mutable(mutable)) => { + context.interpreter.define_variable( + variable.definition.id, + VariableContent::Mutable(mutable.disable()), + ); + } + (_, ArgumentValue::Mutable(_)) => { + return arg_span.type_err( + "Destructuring patterns are not currently supported for &mut arguments", + ); + } + (_, _) => { + return arg_span.type_err( + "Only owned, shared and mutable arguments are currently supported for closures", + ); + } + } } let Spanned(output, body_span) = definition.body.evaluate( diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 1c1d30df..7b673bcf 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -1,20 +1,43 @@ use super::*; use std::borrow::{Borrow, ToOwned}; -use std::cell::*; use std::rc::Rc; pub(super) enum VariableState { Uninitialized, - Value(Referenceable), + 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: Referenceable) { + pub(crate) fn define(&mut self, value: VariableContent) { match self { content @ VariableState::Uninitialized => { *content = VariableState::Value(value); @@ -24,22 +47,22 @@ impl VariableState { } } - pub(crate) fn resolve_referenceable( + pub(crate) fn resolve_content( &mut self, is_final: bool, blocked_from_mutation: Option, - ) -> Referenceable { + ) -> 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(referencable) => referencable, + VariableState::Value(content) => content, VariableState::Finished => panic!("{}", FINISHED_ERR), } } else { match self { VariableState::Uninitialized => panic!("{}", UNITIALIZED_ERR), - VariableState::Value(referencable) => Rc::clone(referencable), + VariableState::Value(content) => content.clone(), VariableState::Finished => panic!("{}", FINISHED_ERR), } } @@ -57,12 +80,12 @@ impl VariableState { // 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 value_rc = if is_final && blocked_from_mutation.is_none() { + 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(ref_cell) => match Rc::try_unwrap(ref_cell) { - Ok(ref_cell) => { + VariableState::Value(content) => match content.into_owned_as_only_owner() { + Ok(owned) => { if matches!( ownership, RequestedOwnership::Concrete(ArgumentOwnership::Assignee { .. }) @@ -71,29 +94,31 @@ impl VariableState { } return Ok(Spanned( LateBoundValue::Owned(LateBoundOwnedValue { - owned: ref_cell.into_inner(), + owned, is_from_last_use: true, }), span_range, )); } - // It's currently referenced, proceed with normal late-bound resolution. + // 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 - Err(rc) => rc, + // 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(ref_cell) => Rc::clone(ref_cell), + VariableState::Value(content) => content.clone(), VariableState::Finished => panic!("{}", FINISHED_ERR), } }; let binding = VariableBinding { - data: value_rc, + content, variable_span, }; let resolved = match ownership { @@ -140,7 +165,7 @@ impl VariableState { #[derive(Clone)] pub(crate) struct VariableBinding { - data: Rc>, + content: VariableContent, variable_span: Span, } @@ -156,23 +181,57 @@ impl VariableBinding { } fn into_mut(self) -> ExecutionResult { - MutableValue::new_from_variable(self).map_err(ExecutionInterrupt::ownership_error) + 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) -> ExecutionResult { - SharedValue::new_from_variable(self).map_err(ExecutionInterrupt::ownership_error) + 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) -> ExecutionResult { - match MutableValue::new_from_variable(self.clone()) { - Ok(value) => Ok(LateBoundValue::Mutable(value)), - Err(reason_not_mutable) => { - // If we get an error with a mutable and shared reference, a mutable reference must already exist. - // We can just propagate the error from taking the shared reference, it should be good enough. - let shared = self.into_shared()?; + 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, - reason_not_mutable, + shared.enable(self.variable_span.span_range())?, + self.variable_span + .syn_error(SHARED_TO_MUTABLE_ERROR_MESSAGE), ))) } } @@ -290,7 +349,6 @@ impl Deref for LateBoundValue { } } -pub(crate) type MutableValue = AnyValueMutable; pub(crate) type AssigneeValue = AnyValueAssignee; /// A binding of a unique (mutable) reference to a value. @@ -394,10 +452,16 @@ impl DisabledMutable { .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) -> ExecutionResult { @@ -408,14 +472,7 @@ impl Spanned { impl AnyValueMutable { pub(crate) fn new_from_owned(value: AnyValue) -> Self { - // Unwrap is safe because it's a new refcell - Mutable(MutableSubRcRefCell::new(Rc::new(RefCell::new(value))).unwrap()) - } - - fn new_from_variable(reference: VariableBinding) -> syn::Result { - Ok(Mutable(MutableSubRcRefCell::new(reference.data).map_err( - |_| reference.variable_span.syn_error(MUTABLE_ERROR_MESSAGE), - )?)) + Mutable(MutableSubRcRefCell::new_from_owned(value)) } } @@ -532,19 +589,12 @@ impl Spanned { impl AnyValueShared { pub(crate) fn new_from_owned(value: AnyValue) -> Self { - // Unwrap is safe because it's a new refcell - Shared(SharedSubRcRefCell::new(Rc::new(RefCell::new(value))).unwrap()) + Shared(SharedSubRcRefCell::new_from_owned(value)) } pub(crate) fn infallible_clone(&self) -> AnyValue { self.0.clone() } - - fn new_from_variable(reference: VariableBinding) -> syn::Result { - Ok(Shared(SharedSubRcRefCell::new(reference.data).map_err( - |_| reference.variable_span.syn_error(SHARED_ERROR_MESSAGE), - )?)) - } } impl IsValueContent for Shared { diff --git a/src/interpretation/interpreter.rs b/src/interpretation/interpreter.rs index 4a9c7c79..b1a07cd2 100644 --- a/src/interpretation/interpreter.rs +++ b/src/interpretation/interpreter.rs @@ -214,17 +214,17 @@ impl Interpreter { pub(crate) fn define_variable( &mut self, definition_id: VariableDefinitionId, - value: Referenceable, + content: VariableContent, ) { let definition = self.scope_definitions.definitions.get(definition_id); let scope_data = self.scope_mut(definition.scope); - scope_data.define_variable(definition_id, value) + scope_data.define_variable(definition_id, content) } pub(crate) fn resolve_closed_references( &mut self, frame_id: FrameId, - ) -> Vec<(VariableDefinitionId, Referenceable)> { + ) -> Vec<(VariableDefinitionId, VariableContent)> { let frame = self.scope_definitions.frames.get(frame_id); frame .closed_variables @@ -241,7 +241,7 @@ impl Interpreter { .variables .get_mut(&definition) .expect("Variable data not found in scope") - .resolve_referenceable(is_final_reference, is_blocked_from_mutation); + .resolve_content(is_final_reference, is_blocked_from_mutation); (closure_definition_id, reference) }) .collect() @@ -487,15 +487,11 @@ struct RuntimeScope { } impl RuntimeScope { - fn define_variable( - &mut self, - definition_id: VariableDefinitionId, - value: Referenceable, - ) { + fn define_variable(&mut self, definition_id: VariableDefinitionId, content: VariableContent) { self.variables .get_mut(&definition_id) .expect("Variable data not found in scope") - .define(value); + .define(content); } fn resolve( diff --git a/src/interpretation/variable.rs b/src/interpretation/variable.rs index a915c652..221f6c4d 100644 --- a/src/interpretation/variable.rs +++ b/src/interpretation/variable.rs @@ -68,7 +68,7 @@ impl VariableDefinition { pub(crate) fn define(&self, interpreter: &mut Interpreter, value_source: impl IntoAnyValue) { interpreter.define_variable( self.id, - Rc::new(RefCell::new(value_source.into_any_value())), + VariableContent::Referenceable(Rc::new(RefCell::new(value_source.into_any_value()))), ); } } diff --git a/src/misc/errors.rs b/src/misc/errors.rs index b53bb732..187d3445 100644 --- a/src/misc/errors.rs +++ b/src/misc/errors.rs @@ -45,7 +45,7 @@ impl HasSpan for DetailedError { impl DetailedError { /// This is not a `From` because it wants to be explicit - pub(crate) fn convert_to_final_error(self) -> syn::Error { + pub(crate) fn convert_to_syn_error(self) -> syn::Error { match self { DetailedError::Standard(e) => e, DetailedError::Contextual(e, message) => e.concat(&format!("\n{}", message)), @@ -86,7 +86,7 @@ impl ParseError { /// This is not a `From` because it wants to be explicit pub(crate) fn convert_to_final_error(self) -> syn::Error { - self.0.convert_to_final_error() + self.0.convert_to_syn_error() } } @@ -124,11 +124,9 @@ impl ExecutionInterrupt { } fn new_error(kind: ErrorKind, error: syn::Error) -> Self { + let error = ExecutionError(kind, DetailedError::Standard(error)); ExecutionInterrupt { - inner: Box::new(ExecutionInterruptInner::Error( - kind, - DetailedError::Standard(error), - )), + inner: Box::new(ExecutionInterruptInner::Error(error)), } } @@ -154,14 +152,14 @@ impl ExecutionInterrupt { /// it encounters. pub(crate) fn is_catchable_by_attempt_block(&self, catch_location_id: CatchLocationId) -> bool { match self.inner.as_ref() { - ExecutionInterruptInner::Error(ErrorKind::Syntax, _) => false, - ExecutionInterruptInner::Error(ErrorKind::Type, _) => false, - ExecutionInterruptInner::Error(ErrorKind::Ownership, _) => false, - ExecutionInterruptInner::Error(ErrorKind::Debug, _) => false, - ExecutionInterruptInner::Error(ErrorKind::Assertion, _) => true, - ExecutionInterruptInner::Error(ErrorKind::Value, _) => true, - ExecutionInterruptInner::Error(ErrorKind::ControlFlow, _) => false, - ExecutionInterruptInner::Error(ErrorKind::Parse, _) => true, + ExecutionInterruptInner::Error(ExecutionError(ErrorKind::Syntax, _)) => false, + ExecutionInterruptInner::Error(ExecutionError(ErrorKind::Type, _)) => false, + ExecutionInterruptInner::Error(ExecutionError(ErrorKind::Ownership, _)) => false, + ExecutionInterruptInner::Error(ExecutionError(ErrorKind::Debug, _)) => false, + ExecutionInterruptInner::Error(ExecutionError(ErrorKind::Assertion, _)) => true, + ExecutionInterruptInner::Error(ExecutionError(ErrorKind::Value, _)) => true, + ExecutionInterruptInner::Error(ExecutionError(ErrorKind::ControlFlow, _)) => false, + ExecutionInterruptInner::Error(ExecutionError(ErrorKind::Parse, _)) => true, ExecutionInterruptInner::ControlFlowInterrupt(interrupt) => { interrupt.catch_location_id() == catch_location_id } @@ -170,7 +168,7 @@ impl ExecutionInterrupt { pub(crate) fn error_mut(&mut self) -> Option<(ErrorKind, &mut DetailedError)> { Some(match self.inner.as_mut() { - ExecutionInterruptInner::Error(kind, error) => (*kind, error), + ExecutionInterruptInner::Error(ExecutionError(kind, error)) => (*kind, error), ExecutionInterruptInner::ControlFlowInterrupt { .. } => return None, }) } @@ -196,7 +194,10 @@ impl ExecutionInterrupt { } pub(crate) fn parse_error(error: ParseError) -> Self { - Self::new(ExecutionInterruptInner::Error(ErrorKind::Parse, error.0)) + Self::new(ExecutionInterruptInner::Error(ExecutionError( + ErrorKind::Parse, + error.0, + ))) } pub(crate) fn value_error(error: syn::Error) -> Self { @@ -215,7 +216,7 @@ impl ExecutionInterrupt { /// mutable value, to retry as a shared value instead. pub(crate) fn into_caught_mutable_map_attempt_error(self) -> Result { match self.inner.as_ref() { - ExecutionInterruptInner::Error(ErrorKind::Value, _) => { + ExecutionInterruptInner::Error(ExecutionError(ErrorKind::Value, _)) => { Ok(self.convert_to_final_error()) } _ => Err(self), @@ -268,11 +269,20 @@ impl ErrorKind { #[derive(Debug)] enum ExecutionInterruptInner { /// Some runtime error - Error(ErrorKind, DetailedError), + Error(ExecutionError), /// Indicates unwinding due to control flow (break/continue) ControlFlowInterrupt(ControlFlowInterrupt), } +#[derive(Debug)] +pub(crate) struct ExecutionError(ErrorKind, DetailedError); + +impl ExecutionError { + pub(crate) fn convert_to_syn_error(self) -> syn::Error { + self.1.convert_to_syn_error() + } +} + pub(crate) enum ControlFlowInterrupt { Break(BreakInterrupt), Continue(ContinueInterrupt), @@ -355,27 +365,17 @@ pub(crate) struct RevertInterrupt { } impl ExecutionInterrupt { - pub(crate) fn convert_to_final_error(self) -> syn::Error { + pub(crate) fn expect_error(self) -> ExecutionError { match *self.inner { - ExecutionInterruptInner::Error(_, e) => e.convert_to_final_error(), - ExecutionInterruptInner::ControlFlowInterrupt(ControlFlowInterrupt::Break(_)) => { - panic!( - "Internal error: break escaped to root (should be caught at parse time). \ - Please report this bug at https://github.com/dhedey/preinterpret/issues" - ) - } - ExecutionInterruptInner::ControlFlowInterrupt(ControlFlowInterrupt::Continue(_)) => { - panic!( - "Internal error: continue escaped to root (should be caught at parse time). \ - Please report this bug at https://github.com/dhedey/preinterpret/issues" - ) - } - ExecutionInterruptInner::ControlFlowInterrupt(ControlFlowInterrupt::Revert(_)) => { - panic!( - "Internal error: revert escaped to root (should be caught at parse time). \ - Please report this bug at https://github.com/dhedey/preinterpret/issues" - ) - } + ExecutionInterruptInner::Error(error) => error, + ExecutionInterruptInner::ControlFlowInterrupt(_) => panic!( + "Internal error: expected error but got control flow interrupt. \ + Please report this bug at https://github.com/dhedey/preinterpret/issues" + ), } } + + pub(crate) fn convert_to_final_error(self) -> syn::Error { + self.expect_error().convert_to_syn_error() + } } diff --git a/src/misc/mut_rc_ref_cell.rs b/src/misc/mut_rc_ref_cell.rs index d9a11805..8bf147a1 100644 --- a/src/misc/mut_rc_ref_cell.rs +++ b/src/misc/mut_rc_ref_cell.rs @@ -14,20 +14,37 @@ pub(crate) struct MutableSubRcRefCell } impl MutableSubRcRefCell { - pub(crate) fn new(pointed_at: Rc>) -> Result { - let ref_mut = pointed_at.try_borrow_mut()?; - Ok(Self { - // 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. - ref_mut: unsafe { - less_buggy_transmute::, std::cell::RefMut<'static, T>>( - ref_mut, - ) - }, - pointed_at, - }) + 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")) } } @@ -42,7 +59,9 @@ impl MutableSubRcRefCell { 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() + .unwrap_or_else(|_| { + unreachable!("Must be able to create shared after holding mutable") + }) .map(|_| &*ptr) } } @@ -210,6 +229,13 @@ impl DisabledMutableSubRcRefCell 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>`]. @@ -226,16 +252,36 @@ pub(crate) struct SharedSubRcRefCell { } impl SharedSubRcRefCell { - pub(crate) fn new(pointed_at: Rc>) -> Result { - let shared_ref = pointed_at.try_borrow()?; - Ok(Self { - // 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. - shared_ref: unsafe { less_buggy_transmute::, Ref<'static, T>>(shared_ref) }, - pointed_at, - }) + 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")) } } diff --git a/tests/compilation_failures/functions/cannot_pass_shared_to_mut_argument.rs b/tests/compilation_failures/functions/cannot_pass_shared_to_mut_argument.rs new file mode 100644 index 00000000..02f2494d --- /dev/null +++ b/tests/compilation_failures/functions/cannot_pass_shared_to_mut_argument.rs @@ -0,0 +1,8 @@ +use preinterpret::*; + +fn main() { + run! { + let my_array_mut_fn = |arr: &mut any| { None }; + my_array_mut_fn([1, 2, 3].as_ref()); + } +} \ No newline at end of file diff --git a/tests/compilation_failures/functions/cannot_pass_shared_to_mut_argument.stderr b/tests/compilation_failures/functions/cannot_pass_shared_to_mut_argument.stderr new file mode 100644 index 00000000..0d9ef723 --- /dev/null +++ b/tests/compilation_failures/functions/cannot_pass_shared_to_mut_argument.stderr @@ -0,0 +1,5 @@ +error: 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().as_mut()` to get a mutable reference. + --> tests/compilation_failures/functions/cannot_pass_shared_to_mut_argument.rs:6:25 + | +6 | my_array_mut_fn([1, 2, 3].as_ref()); + | ^^^^^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/functions/mut_argument_cannot_be_owned.rs b/tests/compilation_failures/functions/mut_argument_cannot_be_owned.rs new file mode 100644 index 00000000..1d8d0139 --- /dev/null +++ b/tests/compilation_failures/functions/mut_argument_cannot_be_owned.rs @@ -0,0 +1,11 @@ +use preinterpret::*; + +fn main() { + run! { + let my_array_destructure = |arr: &mut any, value| { + let [first, second] = arr; + first + second + }; + my_array_destructure([1, 2, 3].as_mut(), 4); + } +} \ No newline at end of file diff --git a/tests/compilation_failures/functions/mut_argument_cannot_be_owned.stderr b/tests/compilation_failures/functions/mut_argument_cannot_be_owned.stderr new file mode 100644 index 00000000..ca22399c --- /dev/null +++ b/tests/compilation_failures/functions/mut_argument_cannot_be_owned.stderr @@ -0,0 +1,5 @@ +error: An owned value is required, but a reference was received, and an array value does not support transparent cloning. You may wish to use .clone() explicitly. + --> tests/compilation_failures/functions/mut_argument_cannot_be_owned.rs:6:35 + | +6 | let [first, second] = arr; + | ^^^ diff --git a/tests/compilation_failures/functions/ref_argument_cannot_be_mutated.rs b/tests/compilation_failures/functions/ref_argument_cannot_be_mutated.rs new file mode 100644 index 00000000..6a0b5465 --- /dev/null +++ b/tests/compilation_failures/functions/ref_argument_cannot_be_mutated.rs @@ -0,0 +1,9 @@ +use preinterpret::*; + +fn main() { + run! { + let my_array_push = |arr: &any, value| arr.push(value); + let arr = [1, 2, 3]; + my_array_push(arr, 4); + } +} \ No newline at end of file diff --git a/tests/compilation_failures/functions/ref_argument_cannot_be_mutated.stderr b/tests/compilation_failures/functions/ref_argument_cannot_be_mutated.stderr new file mode 100644 index 00000000..185a9ee0 --- /dev/null +++ b/tests/compilation_failures/functions/ref_argument_cannot_be_mutated.stderr @@ -0,0 +1,5 @@ +error: The variable cannot be modified as it is a shared 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/functions.rs b/tests/functions.rs index 407d9bc7..aa58fd3e 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -68,6 +68,48 @@ fn test_simple_closures() { } } +#[test] +fn test_closures_with_ref_arguments() { + run! { + let my_array_len = |arr: &any| arr.len(); + let arr = [1, 2, 3]; + %[_].assert_eq(my_array_len(arr), 3); + } + run! { + let my_array_push = |arr: &mut any, value| arr.push(value); + let arr = [1, 2, 3]; + my_array_push(arr, 4); + %[_].assert_eq(arr.to_debug_string(), "[1, 2, 3, 4]"); + } +} + +#[test] +fn can_pass_owned_to_shared_argument() { + run! { + let my_array_len = |arr: &any| arr.len(); + %[_].assert_eq(my_array_len([1, 2, 3]), 3); + } +} + +#[test] +fn can_pass_owned_to_mutable_argument() { + run! { + let push_one_and_return_mut = |arr: &mut any| { + arr.push(0); + arr + }; + %[_].assert_eq(push_one_and_return_mut([1, 2, 3]).len(), 4); + } +} + +#[test] +fn can_pass_mutable_to_shared_argument() { + run! { + let my_array_len = |arr: &any| arr.len(); + %[_].assert_eq(my_array_len([1, 2, 3].as_mut()), 3); + } +} + #[test] fn test_recursion() { run! { From ca54587d1f703c0fd8c96ae2e2847526d037d0cc Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 1 Feb 2026 23:49:11 +0000 Subject: [PATCH 17/49] docs: Update TODOs --- plans/TODO.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/plans/TODO.md b/plans/TODO.md index 120eb61d..411f2cb4 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -236,7 +236,26 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] Allow variable content to hold shared / mutable - [x] Allow shared / mutable arguments - [x] Add tests for shared/mutable, including conversions working and not -- [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` +- [ ] Iterable methods: + - [ ] Unpick `feat/iterable-map` and work our what we want to keep: + * Half-baked `FunctionValue` changes to allow invocation + * Separation of `ExecutionInterrupt` and `FunctionError` - do we actually want / need it? + - Perhaps `Result` -> `core::Result` + - `ExecutionResult` -> `ExpressionResult` + - And `FunctionResult` -> `Result` ? + * `Iterator` returns `Result` change -> do we want it? + * `Iterator` comparison can return errors -> maybe we just have `iterator != iterator` unless `std::ptr::eq`? + * To implement `.map()`, we have a few things we need first: + - 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[?]` + - [ ] Create new iterator trait `PreinterpretIterator` and `IntoPreinterpretIterator` with `.next(&mut Interpreter)` + - [ ] Blanket implement it for iterator + - [ ] Then replace e.g. for loop with it. + - [ ] Create `Map` and `Filter` types on top of it, to be able to + implement `map` and `filter` + - [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` - [ ] Add `array.sort`, `array.sort_by` - [ ] Resolve all `TODO[functions]` From 44b1a72260a898ea3edc292dffc2d08231fe56ea Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 7 Feb 2026 16:20:07 +0000 Subject: [PATCH 18/49] refactor: Add non-interrupt results, simplify iterator comparison, fix MSRV --- src/expressions/closures.rs | 32 +++--- src/expressions/control_flow.rs | 12 +-- src/expressions/equality.rs | 31 ------ src/expressions/evaluation/node_conversion.rs | 7 +- src/expressions/evaluation/value_frames.rs | 59 +++++------ src/expressions/expression_block.rs | 2 +- src/expressions/operations.rs | 2 +- src/expressions/type_resolution/type_data.rs | 6 +- src/expressions/type_resolution/type_kinds.rs | 2 +- src/expressions/values/any_value.rs | 2 +- src/expressions/values/function.rs | 2 +- src/expressions/values/integer_untyped.rs | 16 +-- src/expressions/values/iterator.rs | 60 ++++-------- src/expressions/values/none.rs | 18 ++++ src/expressions/values/object.rs | 4 +- src/expressions/values/parser.rs | 12 +-- src/expressions/values/string.rs | 8 +- src/extensions/errors_and_spans.rs | 63 ++++++++---- src/interpretation/bindings.rs | 39 ++++---- src/interpretation/interpreter.rs | 13 ++- src/interpretation/variable.rs | 6 +- src/lib.rs | 3 + src/misc/errors.rs | 98 ++++++++++++------- .../core/assert_eq_long_iterator.stderr | 2 +- .../expressions/large_range_to_string.rs | 7 -- .../expressions/large_range_to_string.stderr | 5 - .../iteration/long_iterable_to_string.rs | 5 - .../iteration/long_iterable_to_string.stderr | 5 - tests/expressions.rs | 2 +- tests/iteration.rs | 23 +++-- 30 files changed, 279 insertions(+), 267 deletions(-) delete mode 100644 tests/compilation_failures/expressions/large_range_to_string.rs delete mode 100644 tests/compilation_failures/expressions/large_range_to_string.stderr delete mode 100644 tests/compilation_failures/iteration/long_iterable_to_string.rs delete mode 100644 tests/compilation_failures/iteration/long_iterable_to_string.stderr diff --git a/src/expressions/closures.rs b/src/expressions/closures.rs index faa522f9..5c1da71a 100644 --- a/src/expressions/closures.rs +++ b/src/expressions/closures.rs @@ -19,7 +19,7 @@ impl ClosureExpression { &self, interpreter: &mut Interpreter, ownership: RequestedOwnership, - ) -> ExecutionResult> { + ) -> FunctionResult> { let span_range = self.0.span_range; let value = ClosureValue { definition: Rc::clone(&self.0), @@ -61,14 +61,17 @@ impl ClosureValue { self, arguments: Vec>, context: &mut FunctionCallContext, - ) -> ExecutionResult> { + ) -> FunctionResult> { let definition = &*self.definition; - context.interpreter.enter_function_boundary_scope( - definition.scope_id, - definition.frame_id, - context.output_span_range, - )?; + context + .interpreter + .enter_function_boundary_scope( + definition.scope_id, + definition.frame_id, + context.output_span_range, + ) + .expect_no_interrupts()?; for (definition, content) in self.closed_references { context.interpreter.define_variable(definition, content); } @@ -78,7 +81,9 @@ impl ClosureValue { { match (pattern, arg) { (pattern, ArgumentValue::Owned(owned)) => { - pattern.handle_destructure(context.interpreter, owned)?; + pattern + .handle_destructure(context.interpreter, owned) + .expect_no_interrupts()?; } (Pattern::Discarded(_), _) => {} (Pattern::Variable(variable), ArgumentValue::Shared(shared)) => { @@ -111,10 +116,13 @@ impl ClosureValue { } } - let Spanned(output, body_span) = definition.body.evaluate( - context.interpreter, - RequestedOwnership::Concrete(ArgumentOwnership::AsIs), - )?; + let Spanned(output, body_span) = definition + .body + .evaluate( + context.interpreter, + RequestedOwnership::Concrete(ArgumentOwnership::AsIs), + ) + .expect_no_interrupts()?; let returned_value = match output { RequestedValue::Owned(any_value) => ReturnedValue::Owned(any_value), diff --git a/src/expressions/control_flow.rs b/src/expressions/control_flow.rs index 3e941f1d..9e3a0c62 100644 --- a/src/expressions/control_flow.rs +++ b/src/expressions/control_flow.rs @@ -125,9 +125,7 @@ impl Evaluate for IfExpression { return else_code.evaluate(interpreter, requested_ownership); } - requested_ownership - .map_from_owned(Spanned(().into_any_value(), self.span_range())) - .map(|spanned| spanned.0) + Ok(requested_ownership.map_none(self.span_range())?.0) } } @@ -216,9 +214,7 @@ impl Evaluate for WhileExpression { } } } - ownership - .map_none(self.span_range()) - .map(|spanned| spanned.0) + Ok(ownership.map_none(self.span_range())?.0) } } @@ -407,9 +403,7 @@ impl Evaluate for ForExpression { } interpreter.exit_scope(self.iteration_scope); } - ownership - .map_none(self.span_range()) - .map(|spanned| spanned.0) + Ok(ownership.map_none(self.span_range())?.0) } } diff --git a/src/expressions/equality.rs b/src/expressions/equality.rs index 59662f54..2d7050a8 100644 --- a/src/expressions/equality.rs +++ b/src/expressions/equality.rs @@ -9,7 +9,6 @@ use super::*; pub(crate) enum PathSegment { ArrayIndex(usize), ObjectKey(String), - IteratorIndex(usize), RangeStart, RangeEnd, } @@ -27,7 +26,6 @@ impl PathSegment { result.push_str(&format!("[{:?}]", k)) } } - PathSegment::IteratorIndex(i) => result.push_str(&format!("[{}]", i)), PathSegment::RangeStart => result.push_str(".start"), PathSegment::RangeEnd => result.push_str(".end"), } @@ -68,20 +66,12 @@ pub(crate) trait EqualityContext { /// Object is missing a key that the other has. fn missing_key(&mut self, key: &str, missing_on: MissingSide) -> Self::Result; - fn iteration_limit_exceeded(&mut self, limit: usize) -> Self::Result { - let message = format!("iteration limit {} exceeded", limit); - self.leaf_values_not_equal(&message, &message) - } - /// Wrap a comparison within an array index context. fn with_array_index(&mut self, index: usize, f: impl FnOnce(&mut Self) -> R) -> R; /// Wrap a comparison within an object key context. fn with_object_key(&mut self, key: &str, f: impl FnOnce(&mut Self) -> R) -> R; - /// Wrap a comparison within an iterator index context. - fn with_iterator_index(&mut self, index: usize, f: impl FnOnce(&mut Self) -> R) -> R; - /// Wrap a comparison within a range start context. fn with_range_start(&mut self, f: impl FnOnce(&mut Self) -> R) -> R; @@ -140,11 +130,6 @@ impl EqualityContext for SimpleEquality { f(self) } - #[inline] - fn with_iterator_index(&mut self, _index: usize, f: impl FnOnce(&mut Self) -> R) -> R { - f(self) - } - #[inline] fn with_range_start(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { f(self) @@ -253,14 +238,6 @@ impl EqualityContext for TypedEquality { result } - #[inline] - fn with_iterator_index(&mut self, index: usize, f: impl FnOnce(&mut Self) -> R) -> R { - self.path.push(PathSegment::IteratorIndex(index)); - let result = f(self); - self.path.pop(); - result - } - #[inline] fn with_range_start(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { self.path.push(PathSegment::RangeStart); @@ -513,14 +490,6 @@ impl EqualityContext for DebugEquality { result } - #[inline] - fn with_iterator_index(&mut self, index: usize, f: impl FnOnce(&mut Self) -> R) -> R { - self.path.push(PathSegment::IteratorIndex(index)); - let result = f(self); - self.path.pop(); - result - } - #[inline] fn with_range_start(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { self.path.push(PathSegment::RangeStart); diff --git a/src/expressions/evaluation/node_conversion.rs b/src/expressions/evaluation/node_conversion.rs index 443f9e33..53afdc87 100644 --- a/src/expressions/evaluation/node_conversion.rs +++ b/src/expressions/evaluation/node_conversion.rs @@ -23,9 +23,8 @@ impl ExpressionNode { .return_argument_value(Spanned(resolved, variable.span_range()))? } }, - Leaf::TypeProperty(type_property) => { - context.evaluate(|_, ownership| type_property.resolve_spanned(ownership))? - } + Leaf::TypeProperty(type_property) => context + .evaluate(|_, ownership| Ok(type_property.resolve_spanned(ownership)?))?, Leaf::Block(block) => context.evaluate(|interpreter, ownership| { block.evaluate_spanned(interpreter, ownership) })?, @@ -83,7 +82,7 @@ impl ExpressionNode { } Leaf::ClosureExpression(closure_expression) => { context.evaluate(|interpreter, ownership| { - closure_expression.evaluate_spanned(interpreter, ownership) + Ok(closure_expression.evaluate_spanned(interpreter, ownership)?) })? } } diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index 66a6c82c..2a7a7a06 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -95,7 +95,7 @@ impl Clone for DisabledArgumentValue { impl DisabledArgumentValue { /// Re-enables this disabled argument value by re-acquiring any borrow. - pub(crate) fn enable(self, span: SpanRange) -> ExecutionResult { + pub(crate) fn enable(self, span: SpanRange) -> FunctionResult { match self { DisabledArgumentValue::Owned(owned) => Ok(ArgumentValue::Owned(owned)), DisabledArgumentValue::CopyOnWrite(copy_on_write) => { @@ -206,14 +206,14 @@ impl RequestedOwnership { ) } - pub(crate) fn map_none(self, span: SpanRange) -> ExecutionResult> { + pub(crate) fn map_none(self, span: SpanRange) -> FunctionResult> { self.map_from_owned(Spanned(().into_any_value(), span)) } pub(crate) fn map_from_late_bound( &self, Spanned(late_bound, span): Spanned, - ) -> ExecutionResult> { + ) -> FunctionResult> { Ok(match self { RequestedOwnership::LateBound => RequestedValue::LateBound(late_bound), RequestedOwnership::Concrete(argument_ownership) => { @@ -227,7 +227,7 @@ impl RequestedOwnership { pub(crate) fn map_from_argument( &self, Spanned(value, span): Spanned, - ) -> ExecutionResult> { + ) -> FunctionResult> { match value { ArgumentValue::Owned(owned) => self.map_from_owned(Spanned(owned, span)), ArgumentValue::Mutable(mutable) => self.map_from_mutable(Spanned(mutable, span)), @@ -242,7 +242,7 @@ impl RequestedOwnership { pub(crate) fn map_from_returned( &self, Spanned(value, span): Spanned, - ) -> ExecutionResult> { + ) -> FunctionResult> { match value { ReturnedValue::Owned(owned) => self.map_from_owned(Spanned(owned, span)), ReturnedValue::Mutable(mutable) => self.map_from_mutable(Spanned(mutable, span)), @@ -257,7 +257,7 @@ impl RequestedOwnership { pub(crate) fn map_from_requested( &self, Spanned(requested, span): Spanned, - ) -> ExecutionResult> { + ) -> FunctionResult> { match requested { RequestedValue::Owned(owned) => self.map_from_owned(Spanned(owned, span)), RequestedValue::Shared(shared) => self.map_from_shared(Spanned(shared, span)), @@ -278,7 +278,7 @@ impl RequestedOwnership { pub(crate) fn map_from_owned( &self, Spanned(value, span): Spanned, - ) -> ExecutionResult> { + ) -> FunctionResult> { Ok(Spanned( match self { RequestedOwnership::LateBound => { @@ -298,7 +298,7 @@ impl RequestedOwnership { pub(crate) fn map_from_copy_on_write( &self, Spanned(cow, span): Spanned, - ) -> ExecutionResult> { + ) -> FunctionResult> { Ok(Spanned( match self { RequestedOwnership::LateBound => { @@ -315,7 +315,7 @@ impl RequestedOwnership { pub(crate) fn map_from_mutable( &self, Spanned(mutable, span): Spanned, - ) -> ExecutionResult> { + ) -> FunctionResult> { Ok(Spanned( match self { RequestedOwnership::LateBound => { @@ -332,7 +332,7 @@ impl RequestedOwnership { pub(crate) fn map_from_assignee( &self, Spanned(assignee, span): Spanned, - ) -> ExecutionResult> { + ) -> FunctionResult> { Ok(Spanned( match self { RequestedOwnership::LateBound => { @@ -349,7 +349,7 @@ impl RequestedOwnership { pub(crate) fn map_from_shared( &self, Spanned(shared, span): Spanned, - ) -> ExecutionResult> { + ) -> FunctionResult> { Ok(Spanned( match self { RequestedOwnership::LateBound => RequestedValue::LateBound( @@ -421,7 +421,7 @@ impl ArgumentOwnership { pub(crate) fn map_from_late_bound( &self, Spanned(late_bound, span): Spanned, - ) -> ExecutionResult { + ) -> FunctionResult { match late_bound { LateBoundValue::Owned(owned) => self.map_from_owned_with_is_last_use( Spanned(owned.owned, span), @@ -433,17 +433,21 @@ impl ArgumentOwnership { LateBoundValue::Mutable(mutable) => { self.map_from_mutable_inner(Spanned(mutable, span), true) } - LateBoundValue::Shared(late_bound_shared) => self - .map_from_shared_with_error_reason(Spanned(late_bound_shared.shared, span), |_| { - ExecutionInterrupt::ownership_error(late_bound_shared.reason_not_mutable) - }), + LateBoundValue::Shared(late_bound_shared) => self.map_from_shared_with_error_reason( + Spanned(late_bound_shared.shared, span), + |_| { + FunctionError::new(ExecutionInterrupt::ownership_error( + late_bound_shared.reason_not_mutable, + )) + }, + ), } } pub(crate) fn map_from_copy_on_write( &self, Spanned(copy_on_write, span): Spanned, - ) -> ExecutionResult { + ) -> FunctionResult { match self { ArgumentOwnership::Owned => Ok(ArgumentValue::Owned( copy_on_write.clone_to_owned_transparently(span)?, @@ -474,7 +478,7 @@ impl ArgumentOwnership { pub(crate) fn map_from_shared( &self, shared: Spanned, - ) -> ExecutionResult { + ) -> FunctionResult { self.map_from_shared_with_error_reason( shared, |span| span.ownership_error("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().as_mut()` to get a mutable reference."), @@ -484,8 +488,8 @@ impl ArgumentOwnership { fn map_from_shared_with_error_reason( &self, Spanned(shared, span): Spanned, - mutable_error: impl FnOnce(SpanRange) -> ExecutionInterrupt, - ) -> ExecutionResult { + mutable_error: impl FnOnce(SpanRange) -> FunctionError, + ) -> FunctionResult { match self { ArgumentOwnership::Owned => Ok(ArgumentValue::Owned( Spanned(shared, span).transparent_clone()?, @@ -504,14 +508,14 @@ impl ArgumentOwnership { pub(crate) fn map_from_mutable( &self, spanned_mutable: Spanned, - ) -> ExecutionResult { + ) -> FunctionResult { self.map_from_mutable_inner(spanned_mutable, false) } pub(crate) fn map_from_assignee( &self, Spanned(assignee, span): Spanned, - ) -> ExecutionResult { + ) -> FunctionResult { self.map_from_mutable_inner(Spanned(assignee.0, span), false) } @@ -519,7 +523,7 @@ impl ArgumentOwnership { &self, Spanned(mutable, span): Spanned, is_late_bound: bool, - ) -> ExecutionResult { + ) -> FunctionResult { match self { ArgumentOwnership::Owned => { if is_late_bound { @@ -541,10 +545,7 @@ impl ArgumentOwnership { } } - pub(crate) fn map_from_owned( - &self, - owned: Spanned, - ) -> ExecutionResult { + pub(crate) fn map_from_owned(&self, owned: Spanned) -> FunctionResult { self.map_from_owned_with_is_last_use(owned, false) } @@ -552,7 +553,7 @@ impl ArgumentOwnership { &self, Spanned(owned, span): Spanned, is_from_last_use: bool, - ) -> ExecutionResult { + ) -> FunctionResult { match self { ArgumentOwnership::Owned | ArgumentOwnership::AsIs => Ok(ArgumentValue::Owned(owned)), ArgumentOwnership::CopyOnWrite => { @@ -1488,7 +1489,7 @@ impl EvaluationFrame for InvocationBuilder { let span = arg.1; arg.try_map(|v| v.enable(span)) }) - .collect::>>()?; + .collect::>>()?; (arguments, invokable, function_span) } }; diff --git a/src/expressions/expression_block.rs b/src/expressions/expression_block.rs index 91ee3c9c..44327146 100644 --- a/src/expressions/expression_block.rs +++ b/src/expressions/expression_block.rs @@ -332,6 +332,6 @@ impl ExpressionBlockContent { statement.evaluate_as_statement(interpreter)?; } } - ownership.map_from_owned(Spanned(().into_any_value(), output_span)) + Ok(ownership.map_none(output_span)?) } } diff --git a/src/expressions/operations.rs b/src/expressions/operations.rs index 13c30884..8f323cb6 100644 --- a/src/expressions/operations.rs +++ b/src/expressions/operations.rs @@ -91,7 +91,7 @@ impl UnaryOperation { .kind() .feature_resolver() .resolve_unary_operation(self) - .ok_or_else(|| { + .ok_or_else(|| -> ExecutionInterrupt { self.type_error(format!( "The {} operator is not supported for {} operand", self, diff --git a/src/expressions/type_resolution/type_data.rs b/src/expressions/type_resolution/type_data.rs index 9ebd2726..f89118af 100644 --- a/src/expressions/type_resolution/type_data.rs +++ b/src/expressions/type_resolution/type_data.rs @@ -153,7 +153,7 @@ impl FunctionInterface { &self, arguments: Vec>, context: &mut FunctionCallContext, - ) -> ExecutionResult> { + ) -> FunctionResult> { let output_value = match self { FunctionInterface::Arity0 { method, .. } => { if !arguments.is_empty() { @@ -232,7 +232,9 @@ impl FunctionInterface { }, FunctionInterface::ArityAny { method, .. } => method(context, arguments), }; - output_value.map(|v| v.spanned(context.output_span_range)) + output_value + .map(|v| v.spanned(context.output_span_range)) + .expect_no_interrupts() } /// Returns (argument_ownerships, required_argument_count) diff --git a/src/expressions/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index e750baf8..df4d25ce 100644 --- a/src/expressions/type_resolution/type_kinds.rs +++ b/src/expressions/type_resolution/type_kinds.rs @@ -237,7 +237,7 @@ impl TypeProperty { pub(crate) fn resolve_spanned( &self, ownership: RequestedOwnership, - ) -> ExecutionResult> { + ) -> FunctionResult> { let resolver = self.source_type.kind.feature_resolver(); // TODO[performance] - lazily initialize properties as Shared let property_name = &self.property.to_string(); diff --git a/src/expressions/values/any_value.rs b/src/expressions/values/any_value.rs index 1af6e259..6be7bf3d 100644 --- a/src/expressions/values/any_value.rs +++ b/src/expressions/values/any_value.rs @@ -251,7 +251,7 @@ impl AnyValue { pub(crate) fn try_transparent_clone( &self, error_span_range: SpanRange, - ) -> ExecutionResult { + ) -> FunctionResult { if !self.value_kind().supports_transparent_cloning() { return error_span_range.ownership_err(format!( "An owned value is required, but a reference was received, and {} does not support transparent cloning. You may wish to use .clone() explicitly.", diff --git a/src/expressions/values/function.rs b/src/expressions/values/function.rs index df7f3563..3e40a6ab 100644 --- a/src/expressions/values/function.rs +++ b/src/expressions/values/function.rs @@ -52,7 +52,7 @@ impl InvokableFunction { self, arguments: Vec>, context: &mut FunctionCallContext, - ) -> ExecutionResult> { + ) -> FunctionResult> { match self { InvokableFunction::Native(interface) => interface.invoke(arguments, context), InvokableFunction::Closure(closure) => closure.invoke(arguments, context), diff --git a/src/expressions/values/integer_untyped.rs b/src/expressions/values/integer_untyped.rs index 44f9c6ae..8ec83388 100644 --- a/src/expressions/values/integer_untyped.rs +++ b/src/expressions/values/integer_untyped.rs @@ -111,13 +111,15 @@ impl UntypedInteger { impl Spanned { pub(crate) fn into_kind(self, kind: IntegerLeafKind) -> ExecutionResult { let Spanned(value, span_range) = self; - value.try_into_kind(kind).ok_or_else(|| { - span_range.value_error(format!( - "The integer value {} does not fit into {}", - value.0, - kind.articled_value_name() - )) - }) + value + .try_into_kind(kind) + .ok_or_else(|| -> ExecutionInterrupt { + span_range.value_error(format!( + "The integer value {} does not fit into {}", + value.0, + kind.articled_value_name() + )) + }) } } diff --git a/src/expressions/values/iterator.rs b/src/expressions/values/iterator.rs index 7e1f7b84..cef36bea 100644 --- a/src/expressions/values/iterator.rs +++ b/src/expressions/values/iterator.rs @@ -118,15 +118,20 @@ impl IteratorValue { output: &mut String, behaviour: &ConcatBehaviour, ) -> ExecutionResult<()> { - Self::any_iterator_to_string( - self.clone(), - output, - behaviour, - "[]", - "[ ", - "]", - true, - ) + if behaviour.use_debug_literal_syntax { + Self::any_iterator_to_string( + self.clone(), + output, + behaviour, + "[]", + "[ ", + "]", + true, + ) + } else { + output.push_str("Iterator[?]"); + Ok(()) + } } pub(crate) fn any_iterator_to_string>( @@ -216,42 +221,13 @@ impl_resolvable_argument_for! { } } -fn definite_size_hint(size_hint: (usize, Option)) -> Option { - let (min, max) = size_hint; - if let Some(max) = max { - if min == max { - Some(min) - } else { - None - } - } else { - None - } -} - impl ValuesEqual for IteratorValue { - /// Compares two iterators by cloning and comparing element-by-element. fn test_equality(&self, other: &Self, ctx: &mut C) -> C::Result { - let mut lhs_iter = self.clone(); - let mut rhs_iter = other.clone(); - let mut index = 0; - let lhs_size = definite_size_hint(lhs_iter.size_hint()); - let rhs_size = definite_size_hint(rhs_iter.size_hint()); - const MAX_ITERATIONS: usize = 1000; - while index < MAX_ITERATIONS { - match (lhs_iter.next(), rhs_iter.next()) { - (Some(l), Some(r)) => { - let result = ctx.with_iterator_index(index, |ctx| l.test_equality(&r, ctx)); - if ctx.should_short_circuit(&result) { - return result; - } - index += 1; - } - (None, None) => return ctx.values_equal(), - _ => return ctx.lengths_unequal(lhs_size, rhs_size), - } + if std::ptr::eq(self, other) { + ctx.values_equal() + } else { + ctx.leaf_values_not_equal(&"Iterator[?]", &"Iterator[?]") } - ctx.iteration_limit_exceeded(MAX_ITERATIONS) } } diff --git a/src/expressions/values/none.rs b/src/expressions/values/none.rs index b336aebe..143c4002 100644 --- a/src/expressions/values/none.rs +++ b/src/expressions/values/none.rs @@ -22,6 +22,24 @@ impl ResolvableOwned for () { } } +/// Returns a reference to a `None` value with the `'static` lifetime. +/// MSRV: On Rust 1.68, `&AnyValue::None(())` can't be promoted to `'static` directly. +#[allow(clippy::missing_const_for_thread_local)] +pub(crate) fn static_none_ref() -> &'static AnyValue { + use std::cell::Cell; + thread_local! { + static NONE: Cell> = Cell::new(None); + } + NONE.with(|cell| match cell.get() { + Some(val) => val, + None => { + let val: &'static AnyValue = Box::leak(Box::new(AnyValue::None(()))); + cell.set(Some(val)); + val + } + }) +} + define_type_features! { impl NoneType, pub(crate) mod none_interface {} diff --git a/src/expressions/values/object.rs b/src/expressions/values/object.rs index d689523c..4ab307d5 100644 --- a/src/expressions/values/object.rs +++ b/src/expressions/values/object.rs @@ -85,7 +85,7 @@ impl ObjectValue { let key: Spanned<&str> = index.downcast_resolve("An object key")?; match self.entries.get(*key) { Some(entry) => Ok(&entry.value), - None => Ok(&AnyValue::None(())), + None => Ok(static_none_ref()), } } @@ -104,7 +104,7 @@ impl ObjectValue { let key = access.property.to_string(); match self.entries.get(&key) { Some(entry) => Ok(&entry.value), - None => Ok(&AnyValue::None(())), + None => Ok(static_none_ref()), } } diff --git a/src/expressions/values/parser.rs b/src/expressions/values/parser.rs index 1b40d0bb..b5175d1c 100644 --- a/src/expressions/values/parser.rs +++ b/src/expressions/values/parser.rs @@ -55,7 +55,7 @@ impl Spanned> { interpreter: &'i mut Interpreter, ) -> ExecutionResult> { let Spanned(handle, span) = self; - interpreter.parser(**handle, *span) + Ok(interpreter.parser(**handle, *span)?) } pub(crate) fn parse_with( @@ -145,7 +145,7 @@ define_type_features! { // Must be paired with `close`. [context] fn open(this: Spanned>, Spanned(delimiter_char, char_span): Spanned) -> ExecutionResult<()> { let delimiter = delimiter_from_open_char(delimiter_char) - .ok_or_else(|| char_span.value_error(format!( + .ok_or_else(|| char_span.value_error::(format!( "Invalid open delimiter '{}'. Expected '(', '{{', or '['", delimiter_char )))?; this.parse_with(context.interpreter, |interpreter| { @@ -158,13 +158,13 @@ define_type_features! { // The close character must match: ')' for '(', '}' for '{', ']' for '[' [context] fn close(this: Spanned>, Spanned(delimiter_char, char_span): Spanned) -> ExecutionResult<()> { let expected_delimiter = delimiter_from_close_char(delimiter_char) - .ok_or_else(|| char_span.value_error(format!( + .ok_or_else(|| char_span.value_error::(format!( "Invalid close delimiter '{}'. Expected ')', '}}', or ']'", delimiter_char )))?; this.parse_with(context.interpreter, |interpreter| { // Check if there's a group to close first if !interpreter.has_active_input_group() { - return Err(char_span.value_error(format!( + return Err(char_span.value_error::(format!( "attempting to close '{}' isn't valid, because there is no open group", expected_delimiter.description_of_close() ))); @@ -355,9 +355,7 @@ impl Evaluate for ParseTemplateLiteral { parser.parse_with(interpreter, |interpreter| self.content.consume(interpreter))?; - ownership - .map_from_owned(Spanned(().into_any_value(), self.span_range())) - .map(|spanned| spanned.0) + Ok(ownership.map_none(self.span_range())?.0) } } diff --git a/src/expressions/values/string.rs b/src/expressions/values/string.rs index 5f32a666..03409410 100644 --- a/src/expressions/values/string.rs +++ b/src/expressions/values/string.rs @@ -48,7 +48,8 @@ pub(crate) fn string_to_ident( span: Span, ) -> ExecutionResult { let ident = parse_str::(str).map_err(|err| { - error_source.value_error(format!("`{}` is not a valid ident: {:?}", str, err)) + error_source + .value_error::(format!("`{}` is not a valid ident: {:?}", str, err)) })?; Ok(ident.with_span(span)) } @@ -59,7 +60,10 @@ pub(crate) fn string_to_literal( span: Span, ) -> ExecutionResult { let literal = Literal::from_str(str).map_err(|err| { - error_source.value_error(format!("`{}` is not a valid literal: {:?}", str, err)) + error_source.value_error::(format!( + "`{}` is not a valid literal: {:?}", + str, err + )) })?; Ok(literal.with_span(span)) } diff --git a/src/extensions/errors_and_spans.rs b/src/extensions/errors_and_spans.rs index 9a1c04df..05ae24b8 100644 --- a/src/extensions/errors_and_spans.rs +++ b/src/extensions/errors_and_spans.rs @@ -23,60 +23,81 @@ pub(crate) trait SpanErrorExt { Err(self.syn_error(message).into()) } - fn syntax_err(&self, message: impl std::fmt::Display) -> ExecutionResult { + fn syntax_err>( + &self, + message: impl std::fmt::Display, + ) -> Result { Err(self.syntax_error(message)) } - fn syntax_error(&self, message: impl std::fmt::Display) -> ExecutionInterrupt { - ExecutionInterrupt::syntax_error(self.syn_error(message)) + fn syntax_error>(&self, message: impl std::fmt::Display) -> E { + ExecutionError::new(ErrorKind::Syntax, self.syn_error(message)).into() } - fn type_err(&self, message: impl std::fmt::Display) -> ExecutionResult { + fn type_err>( + &self, + message: impl std::fmt::Display, + ) -> Result { Err(self.type_error(message)) } - fn type_error(&self, message: impl std::fmt::Display) -> ExecutionInterrupt { - ExecutionInterrupt::type_error(self.syn_error(message)) + fn type_error>(&self, message: impl std::fmt::Display) -> E { + ExecutionError::new(ErrorKind::Type, self.syn_error(message)).into() } - fn control_flow_err(&self, message: impl std::fmt::Display) -> ExecutionResult { + fn control_flow_err>( + &self, + message: impl std::fmt::Display, + ) -> Result { Err(self.control_flow_error(message)) } - fn control_flow_error(&self, message: impl std::fmt::Display) -> ExecutionInterrupt { - ExecutionInterrupt::control_flow_error(self.syn_error(message)) + fn control_flow_error>(&self, message: impl std::fmt::Display) -> E { + ExecutionError::new(ErrorKind::ControlFlow, self.syn_error(message)).into() } - fn ownership_err(&self, message: impl std::fmt::Display) -> ExecutionResult { + fn ownership_err>( + &self, + message: impl std::fmt::Display, + ) -> Result { Err(self.ownership_error(message)) } - fn ownership_error(&self, message: impl std::fmt::Display) -> ExecutionInterrupt { - ExecutionInterrupt::ownership_error(self.syn_error(message)) + fn ownership_error>(&self, message: impl std::fmt::Display) -> E { + ExecutionError::new(ErrorKind::Ownership, self.syn_error(message)).into() } - fn assertion_err(&self, message: impl std::fmt::Display) -> ExecutionResult { + fn assertion_err>( + &self, + message: impl std::fmt::Display, + ) -> Result { Err(self.assertion_error(message)) } - fn debug_error(&self, message: impl std::fmt::Display) -> ExecutionInterrupt { - ExecutionInterrupt::debug_error(self.syn_error(message)) + fn assertion_error>(&self, message: impl std::fmt::Display) -> E { + ExecutionError::new(ErrorKind::Assertion, self.syn_error(message)).into() } - fn debug_err(&self, message: impl std::fmt::Display) -> ExecutionResult { + fn debug_err>( + &self, + message: impl std::fmt::Display, + ) -> Result { Err(self.debug_error(message)) } - fn assertion_error(&self, message: impl std::fmt::Display) -> ExecutionInterrupt { - ExecutionInterrupt::assertion_error(self.syn_error(message)) + fn debug_error>(&self, message: impl std::fmt::Display) -> E { + ExecutionError::new(ErrorKind::Debug, self.syn_error(message)).into() } - fn value_err(&self, message: impl std::fmt::Display) -> ExecutionResult { + fn value_err>( + &self, + message: impl std::fmt::Display, + ) -> Result { Err(self.value_error(message)) } - fn value_error(&self, message: impl std::fmt::Display) -> ExecutionInterrupt { - ExecutionInterrupt::value_error(self.syn_error(message)) + fn value_error>(&self, message: impl std::fmt::Display) -> E { + ExecutionError::new(ErrorKind::Value, self.syn_error(message)).into() } } diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 7b673bcf..92581209 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -74,7 +74,7 @@ impl VariableState { is_final: bool, ownership: RequestedOwnership, blocked_from_mutation: Option, - ) -> ExecutionResult> { + ) -> FunctionResult> { let span_range = variable_span.span_range(); // If blocked from mutation, we technically could allow is_final to work and @@ -173,18 +173,20 @@ pub(crate) struct VariableBinding { impl VariableBinding { /// Gets the cloned expression value /// This only works if the value can be transparently cloned - pub(crate) fn into_transparently_cloned(self) -> ExecutionResult { + 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) -> ExecutionResult { + 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))?; + 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()), @@ -194,11 +196,13 @@ impl VariableBinding { } } - fn into_shared(self) -> ExecutionResult { + 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))?; + let inner = SharedSubRcRefCell::new(referenceable).map_err(|_| { + self.variable_span + .ownership_error::(SHARED_ERROR_MESSAGE) + })?; Ok(Shared(inner)) } VariableContent::Mutable(mutable) => mutable @@ -208,14 +212,15 @@ impl VariableBinding { } } - fn into_late_bound(self) -> ExecutionResult { + 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) + self.variable_span + .ownership_error::(SHARED_ERROR_MESSAGE) })?; Ok(LateBoundValue::Shared(LateBoundSharedValue::new( Shared(shared), @@ -280,7 +285,7 @@ pub(crate) enum LateBoundValue { } impl Spanned { - pub(crate) fn resolve(self, ownership: ArgumentOwnership) -> ExecutionResult { + pub(crate) fn resolve(self, ownership: ArgumentOwnership) -> FunctionResult { ownership.map_from_late_bound(self) } } @@ -446,7 +451,7 @@ impl Clone for DisabledMutable { impl DisabledMutable { /// Re-enables this disabled mutable reference by re-acquiring the borrow. - pub(crate) fn enable(self, span: SpanRange) -> ExecutionResult> { + pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { self.0 .enable() .map(Mutable) @@ -464,7 +469,7 @@ 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) -> ExecutionResult { + pub(crate) fn transparent_clone(&self) -> FunctionResult { let value = self.0.as_ref().try_transparent_clone(self.1)?; Ok(value) } @@ -569,7 +574,7 @@ impl Clone for DisabledShared { impl DisabledShared { /// Re-enables this disabled shared reference by re-acquiring the borrow. - pub(crate) fn enable(self, span: SpanRange) -> ExecutionResult> { + pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { self.0 .enable() .map(Shared) @@ -581,7 +586,7 @@ 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) -> ExecutionResult { + pub(crate) fn transparent_clone(&self) -> FunctionResult { let value = self.0.as_ref().try_transparent_clone(self.1)?; Ok(value) } @@ -765,7 +770,7 @@ where impl DisabledCopyOnWrite { /// Re-enables this disabled copy-on-write value by re-acquiring any borrow. - pub(crate) fn enable(self, span: SpanRange) -> ExecutionResult> { + pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { let inner = match self.inner { DisabledCopyOnWriteInner::Owned(owned) => CopyOnWriteInner::Owned(owned), DisabledCopyOnWriteInner::SharedWithInfallibleCloning(shared) => { @@ -838,7 +843,7 @@ impl CopyOnWrite { pub(crate) fn clone_to_owned_transparently( self, span: SpanRange, - ) -> ExecutionResult { + ) -> FunctionResult { match self.inner { CopyOnWriteInner::Owned(owned) => Ok(owned), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => Ok(shared.infallible_clone()), diff --git a/src/interpretation/interpreter.rs b/src/interpretation/interpreter.rs index b1a07cd2..42f2226f 100644 --- a/src/interpretation/interpreter.rs +++ b/src/interpretation/interpreter.rs @@ -251,7 +251,7 @@ impl Interpreter { &mut self, variable: &VariableReference, ownership: RequestedOwnership, - ) -> ExecutionResult> { + ) -> FunctionResult> { let reference = self.scope_definitions.references.get(variable.id); let (definition, span, is_final) = ( reference.definition, @@ -347,11 +347,10 @@ impl Interpreter { &mut self, handle: ParserHandle, error_span_range: SpanRange, - ) -> ExecutionResult> { - let stack = self - .input_handler - .get(handle) - .ok_or_else(|| error_span_range.value_error("This parser is no longer available"))?; + ) -> FunctionResult> { + let stack = self.input_handler.get(handle).ok_or_else(|| { + error_span_range.value_error::("This parser is no longer available") + })?; Ok(stack.current()) } @@ -501,7 +500,7 @@ impl RuntimeScope { is_final: bool, ownership: RequestedOwnership, blocked_from_mutation: Option, - ) -> ExecutionResult> { + ) -> FunctionResult> { self.variables .get_mut(&definition_id) .expect("Variable data not found in scope") diff --git a/src/interpretation/variable.rs b/src/interpretation/variable.rs index 221f6c4d..2d2dca38 100644 --- a/src/interpretation/variable.rs +++ b/src/interpretation/variable.rs @@ -137,7 +137,7 @@ impl VariableReference { pub(crate) fn resolve_late_bound( &self, interpreter: &mut Interpreter, - ) -> ExecutionResult> { + ) -> FunctionResult> { interpreter.resolve(self, RequestedOwnership::LateBound) } @@ -145,7 +145,7 @@ impl VariableReference { &self, interpreter: &mut Interpreter, ownership: ArgumentOwnership, - ) -> ExecutionResult { + ) -> FunctionResult { interpreter .resolve(self, RequestedOwnership::Concrete(ownership))? .resolve(ownership) @@ -154,7 +154,7 @@ impl VariableReference { pub(crate) fn resolve_shared( &self, interpreter: &mut Interpreter, - ) -> ExecutionResult { + ) -> FunctionResult { Ok(self .resolve_concrete(interpreter, ArgumentOwnership::Shared)? .expect_shared()) diff --git a/src/lib.rs b/src/lib.rs index 488559d8..967fd5cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -432,6 +432,7 @@ fn preinterpret_stream_internal(input: TokenStream) -> SynResult { stream .interpret(&mut interpreter) + .expect_no_interrupts() .convert_to_final_result()?; let output_stream = interpreter.complete(); @@ -463,6 +464,7 @@ fn preinterpret_run_internal(input: TokenStream) -> SynResult { let returned_stream = content .evaluate_spanned(&mut interpreter, entry_span, RequestedOwnership::owned()) .and_then(|x| x.expect_owned().into_stream()) + .expect_no_interrupts() .convert_to_final_result()?; let mut output_stream = interpreter.complete(); @@ -586,6 +588,7 @@ mod benchmarking { let returned_stream = parsed .evaluate_spanned(&mut interpreter, entry_span, RequestedOwnership::owned()) .and_then(|x| x.expect_owned().into_stream()) + .expect_no_interrupts() .convert_to_final_result()?; let mut output_stream = interpreter.complete(); diff --git a/src/misc/errors.rs b/src/misc/errors.rs index 187d3445..80da0460 100644 --- a/src/misc/errors.rs +++ b/src/misc/errors.rs @@ -11,7 +11,7 @@ pub(crate) trait ParseResultExt { impl ParseResultExt for ParseResult { fn convert_to_final_result(self) -> syn::Result { - self.map_err(|error| error.convert_to_final_error()) + self.map_err(|error| error.convert_to_syn_error()) } fn into_execution_result(self) -> ExecutionResult { @@ -85,7 +85,7 @@ impl ParseError { } /// This is not a `From` because it wants to be explicit - pub(crate) fn convert_to_final_error(self) -> syn::Error { + pub(crate) fn convert_to_syn_error(self) -> syn::Error { self.0.convert_to_syn_error() } } @@ -101,13 +101,53 @@ pub(crate) enum ExecutionOutcome { } pub(crate) trait ExecutionResultExt { + /// Asserts that the result contains no control flow interrupts (only errors). + /// This is used at boundaries where control flow should have already been caught. + fn expect_no_interrupts(self) -> FunctionResult; +} + +impl ExecutionResultExt for ExecutionResult { + fn expect_no_interrupts(self) -> FunctionResult { + self.map_err(FunctionError::new) + } +} + +/// A result type for functions that can produce errors but NOT control-flow interrupts. +pub(crate) type FunctionResult = core::result::Result; + +/// A newtype wrapping `ExecutionInterrupt` that asserts it is not a control flow interrupt. +/// This is used for functions that cannot produce control flow (break/continue/revert). +#[derive(Debug)] +pub(crate) struct FunctionError(ExecutionInterrupt); + +impl FunctionError { + pub(crate) fn new(interrupt: ExecutionInterrupt) -> Self { + debug_assert!( + !matches!( + interrupt.inner.as_ref(), + ExecutionInterruptInner::ControlFlowInterrupt(_) + ), + "FunctionError should not wrap a control flow interrupt. \ + Please report this bug at https://github.com/dhedey/preinterpret/issues" + ); + FunctionError(interrupt) + } +} + +pub(crate) trait FunctionResultExt { /// This is not a `From` because it wants to be explicit fn convert_to_final_result(self) -> syn::Result; } -impl ExecutionResultExt for ExecutionResult { +impl FunctionResultExt for FunctionResult { fn convert_to_final_result(self) -> syn::Result { - self.map_err(|error| error.convert_to_final_error()) + self.map_err(|error| error.0.expect_error().convert_to_syn_error()) + } +} + +impl From for ExecutionInterrupt { + fn from(error: FunctionError) -> Self { + error.0 } } @@ -173,26 +213,10 @@ impl ExecutionInterrupt { }) } - pub(crate) fn syntax_error(error: syn::Error) -> Self { - Self::new_error(ErrorKind::Syntax, error) - } - - pub(crate) fn type_error(error: syn::Error) -> Self { - Self::new_error(ErrorKind::Type, error) - } - pub(crate) fn ownership_error(error: syn::Error) -> Self { Self::new_error(ErrorKind::Ownership, error) } - pub(crate) fn debug_error(error: syn::Error) -> Self { - Self::new_error(ErrorKind::Debug, error) - } - - pub(crate) fn assertion_error(error: syn::Error) -> Self { - Self::new_error(ErrorKind::Assertion, error) - } - pub(crate) fn parse_error(error: ParseError) -> Self { Self::new(ExecutionInterruptInner::Error(ExecutionError( ErrorKind::Parse, @@ -200,14 +224,6 @@ impl ExecutionInterrupt { ))) } - pub(crate) fn value_error(error: syn::Error) -> Self { - Self::new_error(ErrorKind::Value, error) - } - - pub(crate) fn control_flow_error(error: syn::Error) -> Self { - Self::new_error(ErrorKind::ControlFlow, error) - } - pub(crate) fn control_flow(control_flow: ControlFlowInterrupt) -> Self { Self::new(ExecutionInterruptInner::ControlFlowInterrupt(control_flow)) } @@ -217,7 +233,7 @@ impl ExecutionInterrupt { pub(crate) fn into_caught_mutable_map_attempt_error(self) -> Result { match self.inner.as_ref() { ExecutionInterruptInner::Error(ExecutionError(ErrorKind::Value, _)) => { - Ok(self.convert_to_final_error()) + Ok(self.expect_error().convert_to_syn_error()) } _ => Err(self), } @@ -278,11 +294,27 @@ enum ExecutionInterruptInner { pub(crate) struct ExecutionError(ErrorKind, DetailedError); impl ExecutionError { + pub(crate) fn new(kind: ErrorKind, error: syn::Error) -> Self { + ExecutionError(kind, DetailedError::Standard(error)) + } + pub(crate) fn convert_to_syn_error(self) -> syn::Error { self.1.convert_to_syn_error() } } +impl From for ExecutionInterrupt { + fn from(error: ExecutionError) -> Self { + ExecutionInterrupt::new(ExecutionInterruptInner::Error(error)) + } +} + +impl From for FunctionError { + fn from(error: ExecutionError) -> Self { + FunctionError(ExecutionInterrupt::from(error)) + } +} + pub(crate) enum ControlFlowInterrupt { Break(BreakInterrupt), Continue(ContinueInterrupt), @@ -350,9 +382,7 @@ impl BreakInterrupt { Some(value) => value, None => ().into_any_value(), }; - ownership - .map_from_owned(Spanned(value, span_range)) - .map(|spanned| spanned.0) + Ok(ownership.map_from_owned(Spanned(value, span_range))?.0) } } @@ -374,8 +404,4 @@ impl ExecutionInterrupt { ), } } - - pub(crate) fn convert_to_final_error(self) -> syn::Error { - self.expect_error().convert_to_syn_error() - } } diff --git a/tests/compilation_failures/core/assert_eq_long_iterator.stderr b/tests/compilation_failures/core/assert_eq_long_iterator.stderr index 16002692..fb66783c 100644 --- a/tests/compilation_failures/core/assert_eq_long_iterator.stderr +++ b/tests/compilation_failures/core/assert_eq_long_iterator.stderr @@ -1,4 +1,4 @@ -error: Assertion failed: lhs != rhs: "iteration limit 1000 exceeded" != "iteration limit 1000 exceeded" +error: Assertion failed: lhs != rhs: "Iterator[?]" != "Iterator[?]" lhs = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ..<1980 further items>] rhs = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ..<1980 further items>] --> tests/compilation_failures/core/assert_eq_long_iterator.rs:4:12 diff --git a/tests/compilation_failures/expressions/large_range_to_string.rs b/tests/compilation_failures/expressions/large_range_to_string.rs deleted file mode 100644 index 37e36f5c..00000000 --- a/tests/compilation_failures/expressions/large_range_to_string.rs +++ /dev/null @@ -1,7 +0,0 @@ -use preinterpret::*; - -fn main() { - let _ = stream!{ - #((0..10000) as iterator as string) - }; -} \ No newline at end of file diff --git a/tests/compilation_failures/expressions/large_range_to_string.stderr b/tests/compilation_failures/expressions/large_range_to_string.stderr deleted file mode 100644 index 1e971e34..00000000 --- a/tests/compilation_failures/expressions/large_range_to_string.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: To protect against infinite loops, only a maximum of 1000 items can be output to a string from an iterator. You can use .to_vec() to avoid this limit. This can't currently be reconfigured with the iteration limit. - --> tests/compilation_failures/expressions/large_range_to_string.rs:5:11 - | -5 | #((0..10000) as iterator as string) - | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/compilation_failures/iteration/long_iterable_to_string.rs b/tests/compilation_failures/iteration/long_iterable_to_string.rs deleted file mode 100644 index 1bb1d921..00000000 --- a/tests/compilation_failures/iteration/long_iterable_to_string.rs +++ /dev/null @@ -1,5 +0,0 @@ -use preinterpret::*; - -fn main() { - run!((0..10000).into_iter().to_string()) -} \ No newline at end of file diff --git a/tests/compilation_failures/iteration/long_iterable_to_string.stderr b/tests/compilation_failures/iteration/long_iterable_to_string.stderr deleted file mode 100644 index ccf86dff..00000000 --- a/tests/compilation_failures/iteration/long_iterable_to_string.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: To protect against infinite loops, only a maximum of 1000 items can be output to a string from an iterator. You can use .to_vec() to avoid this limit. This can't currently be reconfigured with the iteration limit. - --> tests/compilation_failures/iteration/long_iterable_to_string.rs:4:10 - | -4 | run!((0..10000).into_iter().to_string()) - | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/expressions.rs b/tests/expressions.rs index 72a9c09f..effbae4e 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -297,7 +297,7 @@ fn test_range() { 5 ); run! { - %[_].assert_eq('A'.. .into_iter().take(5).to_string(), "ABCDE"); + %[_].assert_eq('A'.. .into_iter().take(5).to_vec().to_string(), "ABCDE"); } } diff --git a/tests/iteration.rs b/tests/iteration.rs index 743c0f52..46c3c6cc 100644 --- a/tests/iteration.rs +++ b/tests/iteration.rs @@ -81,12 +81,21 @@ fn iterator_to_debug_string() { #[test] fn iterator_to_string() { - assert_eq!(run!([1, 2, 3].into_iter().to_string()), "123"); - assert_eq!(run!(%[%group[a b] c d].into_iter().to_string()), "abcd"); - assert_eq!(run!((3..=5).into_iter().to_string()), "345"); - assert_eq!(run!(%{ a: 1 }.into_iter().to_string()), r#"a1"#); - assert_eq!(run!("Hello World".into_iter().to_string()), "Hello World"); - assert_eq!(run!((0..10).into_iter().to_string()), "0123456789"); + // Iterators display as "Iterator[?]" to avoid consuming them + assert_eq!(run!([1, 2, 3].into_iter().to_string()), "Iterator[?]"); + // Use .to_vec().to_string() to get the actual concatenated values + assert_eq!(run!([1, 2, 3].into_iter().to_vec().to_string()), "123"); + assert_eq!( + run!(%[%group[a b] c d].into_iter().to_vec().to_string()), + "abcd" + ); + assert_eq!(run!((3..=5).into_iter().to_vec().to_string()), "345"); + assert_eq!(run!(%{ a: 1 }.into_iter().to_vec().to_string()), r#"a1"#); + assert_eq!( + run!("Hello World".into_iter().to_vec().to_string()), + "Hello World" + ); + assert_eq!(run!((0..10).into_iter().to_vec().to_string()), "0123456789"); } #[test] @@ -109,7 +118,7 @@ fn iterator_next() { fn iterator_skip_and_take() { run! { let iterator = ('A'..).into_iter(); - %[].assert_eq(iterator.skip(1).take(4).to_string(), "BCDE"); + %[].assert_eq(iterator.skip(1).take(4).to_vec().to_string(), "BCDE"); } } From 3242e461e9fea273657ee5be47e43dbb88ad9baa Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 7 Feb 2026 18:34:34 +0000 Subject: [PATCH 19/49] fix: Fix MSRV --- plans/TODO.md | 17 ++++++----------- src/expressions/values/object.rs | 1 - 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index 411f2cb4..1f9590f4 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -238,23 +238,18 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] Add tests for shared/mutable, including conversions working and not - [ ] Iterable methods: - [ ] Unpick `feat/iterable-map` and work our what we want to keep: - * Half-baked `FunctionValue` changes to allow invocation - * Separation of `ExecutionInterrupt` and `FunctionError` - do we actually want / need it? - - Perhaps `Result` -> `core::Result` - - `ExecutionResult` -> `ExpressionResult` - - And `FunctionResult` -> `Result` ? - * `Iterator` returns `Result` change -> do we want it? - * `Iterator` comparison can return errors -> maybe we just have `iterator != iterator` unless `std::ptr::eq`? + * Salvage half-baked `FunctionValue` changes to allow invocation + * Separation of `ExecutionInterrupt` and `FunctionError` - incorporated below + * `Iterator` returns `Result` change -> replaced with below * To implement `.map()`, we have a few things we need first: - 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[?]` - [ ] Create new iterator trait `PreinterpretIterator` and `IntoPreinterpretIterator` with `.next(&mut Interpreter)` - - [ ] Blanket implement it for iterator - - [ ] Then replace e.g. for loop with it. - - [ ] Create `Map` and `Filter` types on top of it, to be able to - implement `map` and `filter` + - [ ] Blanket implement it for `Iterator + Clone` + - [ ] Then replace e.g. for loop impl with it. + - [ ] Create `Map` and `Filter` types on top of it, to be able to implement `map` and `filter` - [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` - [ ] Add `array.sort`, `array.sort_by` - [ ] Resolve all `TODO[functions]` diff --git a/src/expressions/values/object.rs b/src/expressions/values/object.rs index 4ab307d5..d51c4841 100644 --- a/src/expressions/values/object.rs +++ b/src/expressions/values/object.rs @@ -342,7 +342,6 @@ pub(crate) trait ObjectValidate { } fn describe_object(&self) -> String { - use std::fmt::Write; let mut buffer = String::new(); buffer.write_str("%{\n").unwrap(); for (key, definition) in self.all_fields() { From 704e5a36a91a95aa7249cebc2d18e8038c609605 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 8 Feb 2026 20:47:50 +0000 Subject: [PATCH 20/49] refactor: Create cleaner iterator abstraction --- plans/TODO.md | 6 +- src/expressions/concepts/content.rs | 10 +- src/expressions/concepts/form.rs | 4 +- src/expressions/concepts/forms/any_mut.rs | 2 +- src/expressions/concepts/forms/any_ref.rs | 2 +- src/expressions/concepts/forms/argument.rs | 2 +- src/expressions/concepts/forms/assignee.rs | 2 +- .../concepts/forms/copy_on_write.rs | 2 +- src/expressions/concepts/forms/mutable.rs | 2 +- src/expressions/concepts/forms/owned.rs | 4 +- src/expressions/concepts/forms/shared.rs | 4 +- src/expressions/concepts/type_traits.rs | 10 +- src/expressions/control_flow.rs | 13 +- src/expressions/equality.rs | 16 +- src/expressions/evaluation/evaluator.rs | 8 +- src/expressions/evaluation/node_conversion.rs | 2 +- src/expressions/evaluation/value_frames.rs | 9 +- src/expressions/expression_block.rs | 26 +- src/expressions/operations.rs | 19 +- src/expressions/statements.rs | 5 +- src/expressions/type_resolution/arguments.rs | 74 +-- .../type_resolution/interface_macros.rs | 60 +-- src/expressions/type_resolution/outputs.rs | 43 +- src/expressions/type_resolution/type_data.rs | 43 +- src/expressions/values/any_value.rs | 166 ++++--- src/expressions/values/array.rs | 36 +- src/expressions/values/float.rs | 36 +- src/expressions/values/float_subtypes.rs | 14 +- src/expressions/values/float_untyped.rs | 10 +- src/expressions/values/function.rs | 31 ++ src/expressions/values/integer.rs | 64 +-- src/expressions/values/integer_subtypes.rs | 16 +- src/expressions/values/integer_untyped.rs | 20 +- src/expressions/values/iterable.rs | 40 +- src/expressions/values/iterator.rs | 282 +++++------- src/expressions/values/none.rs | 2 +- src/expressions/values/object.rs | 29 +- src/expressions/values/parser.rs | 70 +-- src/expressions/values/range.rs | 95 ++-- src/expressions/values/stream.rs | 92 ++-- src/expressions/values/string.rs | 24 +- src/internal_prelude.rs | 3 +- src/interpretation/bindings.rs | 14 +- src/interpretation/interpret_traits.rs | 85 +++- src/interpretation/interpreter.rs | 46 +- src/interpretation/output_handler.rs | 16 + src/interpretation/output_parse_utilities.rs | 12 +- src/interpretation/output_stream.rs | 14 +- src/interpretation/source_stream.rs | 36 +- src/interpretation/variable.rs | 14 +- src/lib.rs | 16 +- src/misc/errors.rs | 34 +- src/misc/field_inputs.rs | 6 +- src/misc/iterators.rs | 420 ++++++++++++++++-- 54 files changed, 1304 insertions(+), 807 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index 1f9590f4..3c1b5835 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -252,6 +252,7 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [ ] Create `Map` and `Filter` types on top of it, to be able to implement `map` and `filter` - [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` - [ ] 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 - [ ] Resolve all `TODO[functions]` Possible punted: @@ -453,8 +454,9 @@ preinterpret::run! { - [ ] Look at benchmarks and if anything should be sped up - [ ] Speeding up stream literal processing - - [ ] When interpreting a stream literal, we can avoid having to go through error handling pathways to get an `output` from the intepreter by storing a `OutputInterpreter<'a>` which wraps an `&mut OutputStream` and a pointer to an Intepreter, and can be converted back into/from an `Interpreter` easily - - [ ] Possibly similarly for an `InputInterpreter<'a>` when processing a `ConsumeStream` + - [ ] Avoid a little overhead by having `OutputInterpreter<'a>` store a pointer to interpreter and output stream and a `PhantomData<&'a Interpreter>` / `PhantomData<&'a OutputStream>`, and recreate output stream after a call to with_interpreter. + ... and get rid of `output_stack_height`, `current_output_unchecked`, `current_output_mut_unchecked` + - [ ] Create an `InputInterpreter<'a>` when processing a `ConsumeStream` - [ ] Speeding up scopes at runtime: - [ ] In the interpreter, store a flattened stack of variable values - [ ] `no_mutation_above` can be a stack offset diff --git a/src/expressions/concepts/content.rs b/src/expressions/concepts/content.rs index d33e97c7..641cbcd0 100644 --- a/src/expressions/concepts/content.rs +++ b/src/expressions/concepts/content.rs @@ -92,7 +92,7 @@ where pub(crate) fn downcast_resolve>( self, resolution_target: &str, - ) -> ExecutionResult + ) -> FunctionResult where ::Type: DowncastFrom, { @@ -107,11 +107,11 @@ where } // TODO[concepts]: Change to use a FromSpannedDynContent trait, - // so that it can return a ExecutionResult and avoid needing to specify D. + // so that it can return a FunctionResult and avoid needing to specify D. pub(crate) fn dyn_resolve( self, resolution_target: &str, - ) -> ExecutionResult> + ) -> FunctionResult> where ::Type: DynResolveFrom, C::Form: IsDynCompatibleForm, @@ -225,7 +225,7 @@ impl< { type ValueType = T; const OWNERSHIP: ArgumentOwnership = F::ARGUMENT_OWNERSHIP; - fn from_argument(Spanned(value, span_range): Spanned) -> ExecutionResult { + fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { let ownership_mapped = F::from_argument_value(value)?; let type_mapped = T::resolve(ownership_mapped, span_range, "This argument")?; Ok(X::from_content(type_mapped)) @@ -240,7 +240,7 @@ impl< T: UpcastTo, > IsReturnable for X { - fn to_returned_value(self) -> ExecutionResult { + fn to_returned_value(self) -> FunctionResult { let type_mapped = self.into_content().upcast::(); BeOwned::into_returned_value(type_mapped) } diff --git a/src/expressions/concepts/form.rs b/src/expressions/concepts/form.rs index 6e86fb15..5d3837a9 100644 --- a/src/expressions/concepts/form.rs +++ b/src/expressions/concepts/form.rs @@ -67,11 +67,11 @@ pub(crate) trait MapFromArgument: IsHierarchicalForm { fn from_argument_value( value: ArgumentValue, - ) -> ExecutionResult>; + ) -> FunctionResult>; } pub(crate) trait MapIntoReturned: IsHierarchicalForm { fn into_returned_value( value: Content<'static, AnyType, Self>, - ) -> ExecutionResult; + ) -> FunctionResult; } diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index 5bbaf984..43f623e9 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -57,7 +57,7 @@ impl MapFromArgument for BeAnyMut { fn from_argument_value( value: ArgumentValue, - ) -> ExecutionResult> { + ) -> FunctionResult> { Ok(value .expect_mutable() .0 diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index 3e9220cd..b813264b 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -52,7 +52,7 @@ impl MapFromArgument for BeAnyRef { fn from_argument_value( value: ArgumentValue, - ) -> ExecutionResult> { + ) -> FunctionResult> { Ok(value .expect_shared() .0 diff --git a/src/expressions/concepts/forms/argument.rs b/src/expressions/concepts/forms/argument.rs index a2ac0e4d..f7cafdfc 100644 --- a/src/expressions/concepts/forms/argument.rs +++ b/src/expressions/concepts/forms/argument.rs @@ -38,7 +38,7 @@ impl MapFromArgument for BeArgument { fn from_argument_value( value: ArgumentValue, - ) -> ExecutionResult> { + ) -> FunctionResult> { todo!("Argument") } } diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index 1a20eef6..489bb712 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -62,7 +62,7 @@ impl MapFromArgument for BeAssignee { fn from_argument_value( value: ArgumentValue, - ) -> ExecutionResult> { + ) -> 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 2e6ea9a0..8a644ddb 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -76,7 +76,7 @@ impl MapFromArgument for BeCopyOnWrite { fn from_argument_value( value: ArgumentValue, - ) -> ExecutionResult> { + ) -> FunctionResult> { match value.expect_copy_on_write().inner { CopyOnWriteInner::Owned(owned) => Ok(BeCopyOnWrite::new_owned(owned)), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index b17e1c43..d8c79110 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -60,7 +60,7 @@ impl MapFromArgument for BeMutable { fn from_argument_value( value: ArgumentValue, - ) -> ExecutionResult> { + ) -> FunctionResult> { Ok(value.expect_mutable().into_content()) } } diff --git a/src/expressions/concepts/forms/owned.rs b/src/expressions/concepts/forms/owned.rs index 5095ac42..09873161 100644 --- a/src/expressions/concepts/forms/owned.rs +++ b/src/expressions/concepts/forms/owned.rs @@ -50,7 +50,7 @@ impl MapFromArgument for BeOwned { fn from_argument_value( value: ArgumentValue, - ) -> ExecutionResult> { + ) -> FunctionResult> { Ok(value.expect_owned()) } } @@ -58,7 +58,7 @@ impl MapFromArgument for BeOwned { impl MapIntoReturned for BeOwned { fn into_returned_value( content: Content<'static, AnyType, Self>, - ) -> ExecutionResult { + ) -> FunctionResult { Ok(ReturnedValue::Owned(content)) } } diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index 106cf736..e9a21302 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -54,7 +54,7 @@ impl MapFromArgument for BeShared { fn from_argument_value( value: ArgumentValue, - ) -> ExecutionResult> { + ) -> FunctionResult> { Ok(value.expect_shared().into_content()) } } @@ -62,7 +62,7 @@ impl MapFromArgument for BeShared { // impl MapIntoReturned for BeShared { // fn into_returned_value( // content: Content<'static, AnyType, Self>, -// ) -> ExecutionResult { +// ) -> FunctionResult { // todo!("Return shared") // } // } diff --git a/src/expressions/concepts/type_traits.rs b/src/expressions/concepts/type_traits.rs index d5111b6d..d267a660 100644 --- a/src/expressions/concepts/type_traits.rs +++ b/src/expressions/concepts/type_traits.rs @@ -86,7 +86,7 @@ pub(crate) trait DowncastFrom: IsHierarchicalType { content: Content<'a, T, F>, span_range: SpanRange, resolution_target: &str, - ) -> ExecutionResult> { + ) -> FunctionResult> { let content = match Self::downcast_from(content) { Ok(c) => c, Err(existing) => { @@ -112,7 +112,7 @@ pub(crate) trait DynResolveFrom: IsDynType { content: Content<'a, T, F>, span_range: SpanRange, resolution_target: &str, - ) -> ExecutionResult> { + ) -> FunctionResult> { let content = match Self::downcast_from(content) { Ok(c) => c, Err(existing) => { @@ -816,7 +816,7 @@ macro_rules! define_dyn_type { impl IsArgument for Box<$dyn_type> { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Owned; - fn from_argument(Spanned(value, span_range): Spanned) -> ExecutionResult { + fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { let form_mapped = BeOwned::from_argument_value(value)?; <$type_def as DynResolveFrom>::resolve(form_mapped, span_range, "This argument") } @@ -825,7 +825,7 @@ macro_rules! define_dyn_type { impl<'a> IsArgument for AnyRef<'a, $dyn_type> { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; - fn from_argument(Spanned(value, span_range): Spanned) -> ExecutionResult { + fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { let form_mapped = BeAnyRef::from_argument_value(value)?; <$type_def as DynResolveFrom>::resolve(form_mapped, span_range, "This argument") } @@ -834,7 +834,7 @@ macro_rules! define_dyn_type { impl<'a> IsArgument for AnyMut<'a, $dyn_type> { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; - fn from_argument(Spanned(value, span_range): Spanned) -> ExecutionResult { + fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { let form_mapped = BeAnyMut::from_argument_value(value)?; <$type_def as DynResolveFrom>::resolve(form_mapped, span_range, "This argument") } diff --git a/src/expressions/control_flow.rs b/src/expressions/control_flow.rs index 9e3a0c62..dd0f67f4 100644 --- a/src/expressions/control_flow.rs +++ b/src/expressions/control_flow.rs @@ -375,7 +375,12 @@ impl Evaluate for ForExpression { let scope = interpreter.current_scope_id(); let mut iteration_counter = interpreter.start_iteration_counter(&span); - for item in iterable.into_iterator()? { + let mut iterator = iterable.into_iterator()?; + loop { + let item = match iterator.do_next(interpreter)? { + Some(item) => item, + None => break, + }; iteration_counter.increment_and_check()?; interpreter.enter_child_scope(self.iteration_scope)?; @@ -521,7 +526,8 @@ impl Evaluate for AttemptExpression { move |interpreter: &mut Interpreter| -> ExecutionResult { guard_expression .evaluate_owned(interpreter)? - .resolve_as("The guard condition of an attempt arm") + .downcast_resolve::("The guard condition of an attempt arm") + .into_execution_result() } }) } @@ -532,7 +538,8 @@ impl Evaluate for AttemptExpression { |interpreter| -> ExecutionResult<()> { arm.lhs .evaluate_owned(interpreter)? - .resolve_as("The returned value from the left half of an attempt arm") + .downcast_resolve::<()>("The returned value from the left half of an attempt arm") + .into_execution_result() }, guard_clause(arm.guard.as_ref()), MutationBlockReason::AttemptRevertibleSegment, diff --git a/src/expressions/equality.rs b/src/expressions/equality.rs index 2d7050a8..5b095550 100644 --- a/src/expressions/equality.rs +++ b/src/expressions/equality.rs @@ -162,10 +162,10 @@ impl TypedEquality { } impl EqualityContext for TypedEquality { - type Result = ExecutionResult; + type Result = FunctionResult; #[inline] - fn values_equal(&mut self) -> ExecutionResult { + fn values_equal(&mut self) -> FunctionResult { Ok(true) } @@ -174,7 +174,7 @@ impl EqualityContext for TypedEquality { &mut self, _lhs: &T, _rhs: &T, - ) -> ExecutionResult { + ) -> FunctionResult { Ok(false) } @@ -182,7 +182,7 @@ impl EqualityContext for TypedEquality { &mut self, lhs: &L, rhs: &R, - ) -> ExecutionResult { + ) -> FunctionResult { let path_str = PathSegment::fmt_path(&self.path); Err(self.error_span.type_error(format!( "lhs{} is {}, but rhs{} is {}", @@ -213,12 +213,12 @@ impl EqualityContext for TypedEquality { &mut self, _lhs_len: Option, _rhs_len: Option, - ) -> ExecutionResult { + ) -> FunctionResult { Ok(false) } #[inline] - fn missing_key(&mut self, _key: &str, _missing_on: MissingSide) -> ExecutionResult { + fn missing_key(&mut self, _key: &str, _missing_on: MissingSide) -> FunctionResult { Ok(false) } @@ -255,7 +255,7 @@ impl EqualityContext for TypedEquality { } #[inline] - fn should_short_circuit(&self, result: &ExecutionResult) -> bool { + fn should_short_circuit(&self, result: &FunctionResult) -> bool { // Short-circuit on Ok(false) or Err(_) !matches!(result, Ok(true)) } @@ -539,7 +539,7 @@ pub(crate) trait ValuesEqual: Sized + HasLeafKind { } /// Strict equality check that errors on incompatible types. - fn typed_eq(&self, other: &Self, error_span: SpanRange) -> ExecutionResult { + fn typed_eq(&self, other: &Self, error_span: SpanRange) -> FunctionResult { self.test_equality(other, &mut TypedEquality::new(error_span)) } diff --git a/src/expressions/evaluation/evaluator.rs b/src/expressions/evaluation/evaluator.rs index db95133e..75fca49f 100644 --- a/src/expressions/evaluation/evaluator.rs +++ b/src/expressions/evaluation/evaluator.rs @@ -237,13 +237,13 @@ impl RequestedValue { pub(crate) fn expect_any_value_and_map( self, - map_shared: impl FnOnce(AnyValueShared) -> ExecutionResult, + map_shared: impl FnOnce(AnyValueShared) -> FunctionResult, map_mutable: impl FnOnce( AnyValueMutable, ) - -> Result, - map_owned: impl FnOnce(AnyValueOwned) -> ExecutionResult, - ) -> ExecutionResult { + -> Result, + map_owned: impl FnOnce(AnyValueOwned) -> FunctionResult, + ) -> FunctionResult { Ok(match self { RequestedValue::LateBound(late_bound) => { RequestedValue::LateBound(late_bound.map_any(map_shared, map_mutable, map_owned)?) diff --git a/src/expressions/evaluation/node_conversion.rs b/src/expressions/evaluation/node_conversion.rs index 53afdc87..68333d37 100644 --- a/src/expressions/evaluation/node_conversion.rs +++ b/src/expressions/evaluation/node_conversion.rs @@ -42,7 +42,7 @@ impl ExpressionNode { let span = stream_literal.span_range(); let value = context .interpreter() - .capture_output(|interpreter| stream_literal.interpret(interpreter))?; + .capture_output(|output| stream_literal.output_to_stream(output))?; context.return_value(Spanned(value, span))? } Leaf::ParseTemplateLiteral(consume_literal) => { diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index 2a7a7a06..f32972a8 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -832,7 +832,7 @@ impl EvaluationFrame for UnaryOperationBuilder { fn handle_next( self, - context: ValueContext, + mut context: ValueContext, operand: Spanned, ) -> ExecutionResult { let operand = operand.expect_late_bound(); @@ -845,8 +845,11 @@ impl EvaluationFrame for UnaryOperationBuilder { { let operand_span = operand.span_range(); let resolved_value = operand.resolve(interface.argument_ownership())?; - let result = - interface.execute(Spanned(resolved_value, operand_span), &self.operation)?; + let result = interface.execute( + Spanned(resolved_value, operand_span), + &self.operation, + context.interpreter(), + )?; return context.return_returned_value(result); } self.operation.type_err(format!( diff --git a/src/expressions/expression_block.rs b/src/expressions/expression_block.rs index 44327146..5e4d4582 100644 --- a/src/expressions/expression_block.rs +++ b/src/expressions/expression_block.rs @@ -29,13 +29,13 @@ impl HasSpanRange for EmbeddedExpression { } } -impl Interpret for EmbeddedExpression { - fn interpret(&self, interpreter: &mut Interpreter) -> ExecutionResult<()> { - let value = self.content.evaluate_shared(interpreter)?; +impl OutputToStream for EmbeddedExpression { + fn output_to_stream(&self, output: &mut OutputInterpreter) -> ExecutionResult<()> { + let value = output.with_interpreter(|i| self.content.evaluate_shared(i))?; value.as_ref_value().output_to( Grouping::Flattened, - &mut ToStreamContext::new(interpreter.output(self)?, self.span_range()), - ) + &mut ToStreamContext::new(output, self.span_range()), + ).into_execution_result() } } @@ -68,17 +68,19 @@ impl HasSpanRange for EmbeddedStatements { } } -impl Interpret for EmbeddedStatements { - fn interpret(&self, interpreter: &mut Interpreter) -> ExecutionResult<()> { - let value = self - .content - .evaluate_spanned(interpreter, self.span_range(), RequestedOwnership::shared())? +impl OutputToStream for EmbeddedStatements { + fn output_to_stream(&self, output: &mut OutputInterpreter) -> ExecutionResult<()> { + let value = output + .with_interpreter(|i| { + self.content + .evaluate_spanned(i, self.span_range(), RequestedOwnership::shared()) + })? .0 .expect_shared(); value.as_ref_value().output_to( Grouping::Flattened, - &mut ToStreamContext::new(interpreter.output(self)?, self.span_range()), - ) + &mut ToStreamContext::new(output, self.span_range()), + ).into_execution_result() } } diff --git a/src/expressions/operations.rs b/src/expressions/operations.rs index 8f323cb6..3c7433e6 100644 --- a/src/expressions/operations.rs +++ b/src/expressions/operations.rs @@ -85,13 +85,14 @@ impl UnaryOperation { pub(super) fn evaluate( &self, Spanned(input, input_span): Spanned, - ) -> ExecutionResult> { + interpreter: &mut Interpreter, + ) -> FunctionResult> { let input = input.into_any_value(); let method = input .kind() .feature_resolver() .resolve_unary_operation(self) - .ok_or_else(|| -> ExecutionInterrupt { + .ok_or_else(|| -> FunctionError { self.type_error(format!( "The {} operator is not supported for {} operand", self, @@ -101,7 +102,7 @@ impl UnaryOperation { let input = method .argument_ownership .map_from_owned(Spanned(input, input_span))?; - method.execute(Spanned(input, input_span), self) + method.execute(Spanned(input, input_span), self, interpreter) } } @@ -305,7 +306,7 @@ impl BinaryOperation { &self, Spanned(left, left_span): Spanned, Spanned(right, right_span): Spanned, - ) -> ExecutionResult> { + ) -> FunctionResult> { let left = left.into_any_value(); let right = right.into_any_value(); match left @@ -418,7 +419,7 @@ pub(super) trait HandleBinaryOperation: Sized + std::fmt::Display + Copy { context: BinaryOperationCallContext, lhs: Self, rhs: impl std::fmt::Display, - ) -> ExecutionInterrupt { + ) -> FunctionError { context.error(format!( "The {} operation {} {} {} overflowed", Self::type_name(), @@ -433,7 +434,7 @@ pub(super) trait HandleBinaryOperation: Sized + std::fmt::Display + Copy { rhs: impl ResolveAs>, context: BinaryOperationCallContext, perform_fn: fn(Self, Self) -> Option, - ) -> ExecutionResult { + ) -> FunctionResult { let lhs = self; let rhs = rhs.resolve_as("This operand")?; perform_fn(lhs, rhs.0) @@ -445,7 +446,7 @@ pub(super) trait HandleBinaryOperation: Sized + std::fmt::Display + Copy { self, rhs: impl ResolveAs>, perform_fn: fn(Self, Self) -> Self, - ) -> ExecutionResult { + ) -> FunctionResult { let lhs = self; let rhs = rhs.resolve_as("This operand")?; Ok(perform_fn(lhs, rhs.0).into()) @@ -455,7 +456,7 @@ pub(super) trait HandleBinaryOperation: Sized + std::fmt::Display + Copy { self, rhs: impl ResolveAs>, compare_fn: fn(Self, Self) -> bool, - ) -> ExecutionResult { + ) -> FunctionResult { let lhs = self; let rhs = rhs.resolve_as("This operand")?; Ok(compare_fn(lhs, rhs.0)) @@ -466,7 +467,7 @@ pub(super) trait HandleBinaryOperation: Sized + std::fmt::Display + Copy { rhs: u32, context: BinaryOperationCallContext, perform_fn: impl FnOnce(Self, u32) -> Option, - ) -> ExecutionResult { + ) -> FunctionResult { let lhs = self; perform_fn(lhs, rhs) .map(|r| r.into()) diff --git a/src/expressions/statements.rs b/src/expressions/statements.rs index e0d4fca3..595ce92d 100644 --- a/src/expressions/statements.rs +++ b/src/expressions/statements.rs @@ -369,9 +369,10 @@ impl EmitStatement { interpreter: &mut Interpreter, ) -> ExecutionResult<()> { let Spanned(value, span) = self.expression.evaluate_owned(interpreter)?; + let mut output = OutputInterpreter::new_checked(interpreter, &self.emit)?; value.as_ref_value().output_to( Grouping::Flattened, - &mut ToStreamContext::new(interpreter.output(&self.emit)?, span), - ) + &mut ToStreamContext::new(&mut output, span), + ).into_execution_result() } } diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index ff442295..57f97a28 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -33,7 +33,7 @@ impl<'a> ResolutionContext<'a> { &self, articled_expected_value_kind: &str, value: V, - ) -> ExecutionResult { + ) -> FunctionResult { self.span_range.type_err(format!( "{} is expected to be {}, but it is {}", self.resolution_target, @@ -46,14 +46,14 @@ impl<'a> ResolutionContext<'a> { pub(crate) trait IsArgument: Sized { type ValueType: TypeData; const OWNERSHIP: ArgumentOwnership; - fn from_argument(value: Spanned) -> ExecutionResult; + fn from_argument(value: Spanned) -> FunctionResult; } impl IsArgument for ArgumentValue { type ValueType = AnyType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::AsIs; - fn from_argument(Spanned(value, _): Spanned) -> ExecutionResult { + fn from_argument(Spanned(value, _): Spanned) -> FunctionResult { Ok(value) } } @@ -62,7 +62,7 @@ impl + ResolvableArgumentTarget + ?Sized> IsArgume type ValueType = T::ValueType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; - fn from_argument(argument: Spanned) -> ExecutionResult { + fn from_argument(argument: Spanned) -> FunctionResult { T::resolve_shared(argument.expect_shared(), "This argument") } } @@ -85,7 +85,7 @@ impl + ResolvableArgumentTarget + ?Sized> IsArgum type ValueType = T::ValueType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Assignee { auto_create: false }; - fn from_argument(argument: Spanned) -> ExecutionResult { + fn from_argument(argument: Spanned) -> FunctionResult { T::resolve_assignee(argument.expect_assignee(), "This argument") } } @@ -94,7 +94,7 @@ impl + ResolvableArgumentTarget + ?Sized> IsArgum type ValueType = T::ValueType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; - fn from_argument(argument: Spanned) -> ExecutionResult { + fn from_argument(argument: Spanned) -> FunctionResult { T::resolve_mutable(argument.expect_mutable(), "This argument") } } @@ -128,7 +128,7 @@ where type ValueType = T::ValueType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::CopyOnWrite; - fn from_argument(Spanned(value, span): Spanned) -> ExecutionResult { + fn from_argument(Spanned(value, span): Spanned) -> FunctionResult { value.expect_copy_on_write().map( |v| T::resolve_shared(v.spanned(span), "This argument"), |v| { @@ -145,7 +145,7 @@ impl IsArgument for Spanned { type ValueType = ::ValueType; const OWNERSHIP: ArgumentOwnership = ::OWNERSHIP; - fn from_argument(argument: Spanned) -> ExecutionResult { + fn from_argument(argument: Spanned) -> FunctionResult { let span = argument.1; Ok(Spanned(T::from_argument(argument)?, span)) } @@ -153,26 +153,26 @@ impl IsArgument for Spanned { pub(crate) trait ResolveAs { /// The `resolution_target` should be capitalized, e.g. "This argument" or "The value destructed with an object pattern" - fn resolve_as(self, resolution_target: &str) -> ExecutionResult; + fn resolve_as(self, resolution_target: &str) -> FunctionResult; } // Sadly this can't be changed Value => V because of spurious issues with // https://github.com/rust-lang/rust/issues/48869 // Instead, we could introduce a different trait ResolveAs2 if needed. impl> ResolveAs for Spanned { - fn resolve_as(self, resolution_target: &str) -> ExecutionResult { + fn resolve_as(self, resolution_target: &str) -> FunctionResult { T::resolve_value(self, resolution_target) } } impl + ?Sized> ResolveAs> for Spanned { - fn resolve_as(self, resolution_target: &str) -> ExecutionResult> { + fn resolve_as(self, resolution_target: &str) -> FunctionResult> { T::resolve_shared(self, resolution_target) } } impl<'a, T: ResolvableShared + ?Sized> ResolveAs<&'a T> for Spanned<&'a AnyValue> { - fn resolve_as(self, resolution_target: &str) -> ExecutionResult<&'a T> { + fn resolve_as(self, resolution_target: &str) -> FunctionResult<&'a T> { T::resolve_ref(self, resolution_target) } } @@ -180,13 +180,13 @@ impl<'a, T: ResolvableShared + ?Sized> ResolveAs<&'a T> for Spanned<&' impl<'a, T: ResolvableShared + ?Sized> ResolveAs> for Spanned<&'a AnyValue> { - fn resolve_as(self, resolution_target: &str) -> ExecutionResult> { + fn resolve_as(self, resolution_target: &str) -> FunctionResult> { T::resolve_spanned_ref(self, resolution_target) } } impl + ?Sized> ResolveAs> for Spanned { - fn resolve_as(self, resolution_target: &str) -> ExecutionResult> { + fn resolve_as(self, resolution_target: &str) -> FunctionResult> { T::resolve_mutable(self, resolution_target) } } @@ -194,7 +194,7 @@ impl + ?Sized> ResolveAs> for Spanned< impl<'a, T: ResolvableMutable + ?Sized> ResolveAs<&'a mut T> for Spanned<&'a mut AnyValue> { - fn resolve_as(self, resolution_target: &str) -> ExecutionResult<&'a mut T> { + fn resolve_as(self, resolution_target: &str) -> FunctionResult<&'a mut T> { T::resolve_ref_mut(self, resolution_target) } } @@ -202,7 +202,7 @@ impl<'a, T: ResolvableMutable + ?Sized> ResolveAs<&'a mut T> impl<'a, T: ResolvableMutable + ?Sized> ResolveAs> for Spanned<&'a mut AnyValue> { - fn resolve_as(self, resolution_target: &str) -> ExecutionResult> { + fn resolve_as(self, resolution_target: &str) -> FunctionResult> { T::resolve_spanned_ref_mut(self, resolution_target) } } @@ -212,12 +212,12 @@ pub(crate) trait ResolvableArgumentTarget { } pub(crate) trait ResolvableOwned: Sized { - fn resolve_from_value(value: T, context: ResolutionContext) -> ExecutionResult; + fn resolve_from_value(value: T, context: ResolutionContext) -> FunctionResult; fn resolve_spanned_from_value( value: T, context: ResolutionContext, - ) -> ExecutionResult>> { + ) -> FunctionResult>> { let span_range = context.span_range; Self::resolve_from_value(value, context).map(|x| x.spanned(*span_range)) } @@ -226,7 +226,7 @@ pub(crate) trait ResolvableOwned: Sized { fn resolve_value( Spanned(value, span): Spanned, resolution_target: &str, - ) -> ExecutionResult { + ) -> FunctionResult { let context = ResolutionContext { span_range: &span, resolution_target, @@ -236,13 +236,13 @@ pub(crate) trait ResolvableOwned: Sized { } pub(crate) trait ResolvableShared { - fn resolve_from_ref<'a>(value: &'a T, context: ResolutionContext) -> ExecutionResult<&'a Self>; + 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, - ) -> ExecutionResult> { + ) -> FunctionResult> { value .try_map(|v| { Self::resolve_from_ref( @@ -259,7 +259,7 @@ pub(crate) trait ResolvableShared { fn resolve_ref<'a>( Spanned(value, span): Spanned<&'a T>, resolution_target: &str, - ) -> ExecutionResult<&'a Self> { + ) -> FunctionResult<&'a Self> { Self::resolve_from_ref( value, ResolutionContext { @@ -272,7 +272,7 @@ pub(crate) trait ResolvableShared { fn resolve_spanned_ref<'a>( Spanned(value, span): Spanned<&'a T>, resolution_target: &str, - ) -> ExecutionResult> { + ) -> FunctionResult> { Spanned(value, span).try_map(|v| { Self::resolve_from_ref( v, @@ -289,12 +289,12 @@ pub(crate) trait ResolvableMutable { fn resolve_from_mut<'a>( value: &'a mut T, context: ResolutionContext, - ) -> ExecutionResult<&'a mut Self>; + ) -> FunctionResult<&'a mut Self>; fn resolve_assignee( Spanned(value, span): Spanned>, resolution_target: &str, - ) -> ExecutionResult> { + ) -> FunctionResult> { Ok(Assignee(Self::resolve_mutable( Spanned(value.0, span), resolution_target, @@ -304,7 +304,7 @@ pub(crate) trait ResolvableMutable { fn resolve_mutable( Spanned(value, span): Spanned>, resolution_target: &str, - ) -> ExecutionResult> { + ) -> FunctionResult> { value .try_map(|v| { Self::resolve_from_mut( @@ -321,7 +321,7 @@ pub(crate) trait ResolvableMutable { fn resolve_ref_mut<'a>( Spanned(value, span): Spanned<&'a mut T>, resolution_target: &str, - ) -> ExecutionResult<&'a mut Self> { + ) -> FunctionResult<&'a mut Self> { Self::resolve_from_mut( value, ResolutionContext { @@ -334,7 +334,7 @@ pub(crate) trait ResolvableMutable { fn resolve_spanned_ref_mut<'a>( Spanned(value, span): Spanned<&'a mut T>, resolution_target: &str, - ) -> ExecutionResult> { + ) -> FunctionResult> { Spanned(value, span).try_map(|value| { Self::resolve_from_mut( value, @@ -352,7 +352,7 @@ impl ResolvableArgumentTarget for AnyValue { } impl ResolvableOwned for AnyValue { - fn resolve_from_value(value: AnyValue, _context: ResolutionContext) -> ExecutionResult { + fn resolve_from_value(value: AnyValue, _context: ResolutionContext) -> FunctionResult { Ok(value) } } @@ -360,7 +360,7 @@ impl ResolvableShared for AnyValue { fn resolve_from_ref<'a>( value: &'a AnyValue, _context: ResolutionContext, - ) -> ExecutionResult<&'a Self> { + ) -> FunctionResult<&'a Self> { Ok(value) } } @@ -368,7 +368,7 @@ impl ResolvableMutable for AnyValue { fn resolve_from_mut<'a>( value: &'a mut AnyValue, _context: ResolutionContext, - ) -> ExecutionResult<&'a mut Self> { + ) -> FunctionResult<&'a mut Self> { Ok(value) } } @@ -383,7 +383,7 @@ macro_rules! impl_resolvable_argument_for { fn resolve_from_value( $value: AnyValue, $context: ResolutionContext, - ) -> ExecutionResult { + ) -> FunctionResult { $body } } @@ -392,7 +392,7 @@ macro_rules! impl_resolvable_argument_for { fn resolve_from_ref<'a>( $value: &'a AnyValue, $context: ResolutionContext, - ) -> ExecutionResult<&'a Self> { + ) -> FunctionResult<&'a Self> { $body } } @@ -401,7 +401,7 @@ macro_rules! impl_resolvable_argument_for { fn resolve_from_mut<'a>( $value: &'a mut AnyValue, $context: ResolutionContext, - ) -> ExecutionResult<&'a mut Self> { + ) -> FunctionResult<&'a mut Self> { $body } } @@ -420,7 +420,7 @@ macro_rules! impl_delegated_resolvable_argument_for { fn resolve_from_value( input_value: Value, context: ResolutionContext, - ) -> ExecutionResult { + ) -> FunctionResult { let $value: $delegate = ResolvableOwned::::resolve_from_value(input_value, context)?; Ok($expr) @@ -431,7 +431,7 @@ macro_rules! impl_delegated_resolvable_argument_for { fn resolve_from_ref<'a>( input_value: &'a Value, context: ResolutionContext, - ) -> ExecutionResult<&'a Self> { + ) -> FunctionResult<&'a Self> { let $value: &$delegate = ResolvableShared::::resolve_from_ref(input_value, context)?; Ok(&$expr) @@ -442,7 +442,7 @@ macro_rules! impl_delegated_resolvable_argument_for { fn resolve_from_mut<'a>( input_value: &'a mut Value, context: ResolutionContext, - ) -> ExecutionResult<&'a mut Self> { + ) -> FunctionResult<&'a mut Self> { let $value: &mut $delegate = ResolvableMutable::::resolve_from_mut(input_value, context)?; Ok(&mut $expr) diff --git a/src/expressions/type_resolution/interface_macros.rs b/src/expressions/type_resolution/interface_macros.rs index 54f7490d..245b0bb0 100644 --- a/src/expressions/type_resolution/interface_macros.rs +++ b/src/expressions/type_resolution/interface_macros.rs @@ -157,7 +157,7 @@ macro_rules! parse_arg_types { pub(crate) fn apply_fn0( f: fn(&mut FunctionCallContext) -> R, context: &mut FunctionCallContext, -) -> ExecutionResult +) -> FunctionResult where R: IsReturnable, { @@ -169,7 +169,7 @@ pub(crate) fn apply_fn1( f: fn(&mut FunctionCallContext, A) -> R, a: Spanned, context: &mut FunctionCallContext, -) -> ExecutionResult +) -> FunctionResult where A: IsArgument, R: IsReturnable, @@ -183,7 +183,7 @@ pub(crate) fn apply_fn1_optional1( a: Spanned, b: Option>, context: &mut FunctionCallContext, -) -> ExecutionResult +) -> FunctionResult where A: IsArgument, B: IsArgument, @@ -203,7 +203,7 @@ pub(crate) fn apply_fn2( a: Spanned, b: Spanned, context: &mut FunctionCallContext, -) -> ExecutionResult +) -> FunctionResult where A: IsArgument, B: IsArgument, @@ -218,7 +218,7 @@ pub(crate) fn apply_fn2_optional1( b: Spanned, c: Option>, context: &mut FunctionCallContext, -) -> ExecutionResult +) -> FunctionResult where A: IsArgument, B: IsArgument, @@ -241,7 +241,7 @@ pub(crate) fn apply_fn3( b: Spanned, c: Spanned, context: &mut FunctionCallContext, -) -> ExecutionResult +) -> FunctionResult where A: IsArgument, B: IsArgument, @@ -264,7 +264,7 @@ pub(crate) fn apply_fn3_optional1( c: Spanned, d: Option>, context: &mut FunctionCallContext, -) -> ExecutionResult +) -> FunctionResult where A: IsArgument, B: IsArgument, @@ -286,7 +286,7 @@ pub(crate) fn apply_unary_fn( f: fn(UnaryOperationCallContext, A) -> R, a: Spanned, context: UnaryOperationCallContext, -) -> ExecutionResult +) -> FunctionResult where A: IsArgument, R: IsReturnable, @@ -299,7 +299,7 @@ pub(crate) fn apply_binary_fn( lhs: Spanned, rhs: Spanned, context: BinaryOperationCallContext, -) -> ExecutionResult +) -> FunctionResult where A: IsArgument, B: IsArgument, @@ -313,10 +313,10 @@ where // ============================================================================ pub(crate) fn apply_property_shared<'a, S: ResolvableShared + ?Sized + 'a>( - f: for<'b> fn(PropertyAccessCallContext, &'b S) -> ExecutionResult<&'b AnyValue>, + f: for<'b> fn(PropertyAccessCallContext, &'b S) -> FunctionResult<&'b AnyValue>, ctx: PropertyAccessCallContext, source: &'a AnyValue, -) -> ExecutionResult<&'a AnyValue> { +) -> FunctionResult<&'a AnyValue> { let source = S::resolve_from_ref( source, ResolutionContext::new(&ctx.property.span_range(), "The property access source"), @@ -325,11 +325,11 @@ 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) -> ExecutionResult<&'b mut AnyValue>, + f: for<'b> fn(PropertyAccessCallContext, &'b mut S, bool) -> FunctionResult<&'b mut AnyValue>, ctx: PropertyAccessCallContext, source: &'a mut AnyValue, auto_create: bool, -) -> ExecutionResult<&'a mut AnyValue> { +) -> FunctionResult<&'a mut AnyValue> { let source = S::resolve_from_mut( source, ResolutionContext::new(&ctx.property.span_range(), "The property access source"), @@ -338,10 +338,10 @@ pub(crate) fn apply_property_mutable<'a, S: ResolvableMutable + ?Sized } pub(crate) fn apply_property_owned>( - f: fn(PropertyAccessCallContext, S) -> ExecutionResult, + f: fn(PropertyAccessCallContext, S) -> FunctionResult, ctx: PropertyAccessCallContext, source: AnyValue, -) -> ExecutionResult { +) -> FunctionResult { let source = S::resolve_from_value( source, ResolutionContext::new(&ctx.property.span_range(), "The property access source"), @@ -358,11 +358,11 @@ pub(crate) fn apply_index_shared<'a, S: ResolvableShared + ?Sized + 'a IndexAccessCallContext, &'b S, Spanned, - ) -> ExecutionResult<&'b AnyValue>, + ) -> FunctionResult<&'b AnyValue>, ctx: IndexAccessCallContext, source: &'a AnyValue, index: Spanned, -) -> ExecutionResult<&'a AnyValue> { +) -> FunctionResult<&'a AnyValue> { let source = S::resolve_from_ref( source, ResolutionContext::new(&ctx.access.span_range(), "The index access source"), @@ -376,12 +376,12 @@ pub(crate) fn apply_index_mutable<'a, S: ResolvableMutable + ?Sized + &'b mut S, Spanned, bool, - ) -> ExecutionResult<&'b mut AnyValue>, + ) -> FunctionResult<&'b mut AnyValue>, ctx: IndexAccessCallContext, source: &'a mut AnyValue, index: Spanned, auto_create: bool, -) -> ExecutionResult<&'a mut AnyValue> { +) -> FunctionResult<&'a mut AnyValue> { let source = S::resolve_from_mut( source, ResolutionContext::new(&ctx.access.span_range(), "The index access source"), @@ -390,11 +390,11 @@ pub(crate) fn apply_index_mutable<'a, S: ResolvableMutable + ?Sized + } pub(crate) fn apply_index_owned>( - f: fn(IndexAccessCallContext, S, Spanned) -> ExecutionResult, + f: fn(IndexAccessCallContext, S, Spanned) -> FunctionResult, ctx: IndexAccessCallContext, source: AnyValue, index: Spanned, -) -> ExecutionResult { +) -> FunctionResult { let source = S::resolve_from_value( source, ResolutionContext::new(&ctx.access.span_range(), "The index access source"), @@ -413,9 +413,9 @@ impl<'a> HasSpanRange for FunctionCallContext<'a> { } } -#[derive(Clone, Copy)] pub(crate) struct UnaryOperationCallContext<'a> { pub operation: &'a UnaryOperation, + pub interpreter: &'a mut Interpreter, } #[derive(Clone, Copy)] @@ -425,11 +425,11 @@ pub(crate) struct BinaryOperationCallContext<'a> { impl<'a> BinaryOperationCallContext<'a> { #[allow(unused)] - pub(crate) fn err(&self, message: impl std::fmt::Display) -> ExecutionResult { + pub(crate) fn err(&self, message: impl std::fmt::Display) -> FunctionResult { self.operation.value_err(message) } - pub(crate) fn error(&self, message: impl std::fmt::Display) -> ExecutionInterrupt { + pub(crate) fn error(&self, message: impl std::fmt::Display) -> FunctionError { self.operation.value_error(message) } } @@ -601,11 +601,11 @@ macro_rules! define_type_features { #[allow(unused)] use super::*; - pub(crate) fn shared<'a>(if_empty!([$($property_shared_context)?][_ctx]): PropertyAccessCallContext, $($property_shared_args)*) -> ExecutionResult<&'a AnyValue> $property_shared_body + pub(crate) fn shared<'a>(if_empty!([$($property_shared_context)?][_ctx]): PropertyAccessCallContext, $($property_shared_args)*) -> FunctionResult<&'a AnyValue> $property_shared_body - pub(crate) fn mutable<'a>(if_empty!([$($property_mutable_context)?][_ctx]): PropertyAccessCallContext, $($property_mutable_args)*) -> ExecutionResult<&'a mut AnyValue> $property_mutable_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 owned(if_empty!([$($property_owned_context)?][_ctx]): PropertyAccessCallContext, $($property_owned_args)*) -> ExecutionResult $property_owned_body + pub(crate) fn owned(if_empty!([$($property_owned_context)?][_ctx]): PropertyAccessCallContext, $($property_owned_args)*) -> FunctionResult $property_owned_body } pub(crate) fn property_access_interface() -> PropertyAccessInterface { @@ -622,11 +622,11 @@ macro_rules! define_type_features { #[allow(unused)] use super::*; - pub(crate) fn shared<'a>(if_empty!([$($index_shared_context)?][_ctx]): IndexAccessCallContext, $($index_shared_args)*) -> ExecutionResult<&'a AnyValue> $index_shared_body + pub(crate) fn shared<'a>(if_empty!([$($index_shared_context)?][_ctx]): IndexAccessCallContext, $($index_shared_args)*) -> FunctionResult<&'a AnyValue> $index_shared_body - pub(crate) fn mutable<'a>(if_empty!([$($index_mutable_context)?][_ctx]): IndexAccessCallContext, $($index_mutable_args)*) -> ExecutionResult<&'a mut AnyValue> $index_mutable_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 owned(if_empty!([$($index_owned_context)?][_ctx]): IndexAccessCallContext, $($index_owned_args)*) -> ExecutionResult $index_owned_body + pub(crate) fn owned(if_empty!([$($index_owned_context)?][_ctx]): IndexAccessCallContext, $($index_owned_args)*) -> FunctionResult $index_owned_body } pub(crate) fn index_access_interface() -> IndexAccessInterface { diff --git a/src/expressions/type_resolution/outputs.rs b/src/expressions/type_resolution/outputs.rs index cd823619..e53943ed 100644 --- a/src/expressions/type_resolution/outputs.rs +++ b/src/expressions/type_resolution/outputs.rs @@ -17,58 +17,29 @@ pub(crate) enum ReturnedValue { // note = "`ResolvableOutput` is not implemented for `Shared` or `Mutable` unless `X` is `Value`. If we wish to change this, we'd need to have some way to represent some kind of `ExpressionReference`, i.e. a `Typed>` rather than a `Shared>`" // )] pub(crate) trait IsReturnable { - fn to_returned_value(self) -> ExecutionResult; + fn to_returned_value(self) -> FunctionResult; } impl IsReturnable for ReturnedValue { - fn to_returned_value(self) -> ExecutionResult { + fn to_returned_value(self) -> FunctionResult { Ok(self) } } impl IsReturnable for AnyValueShared { - fn to_returned_value(self) -> ExecutionResult { + fn to_returned_value(self) -> FunctionResult { Ok(ReturnedValue::Shared(self)) } } impl IsReturnable for AnyValueMutable { - fn to_returned_value(self) -> ExecutionResult { + fn to_returned_value(self) -> FunctionResult { Ok(ReturnedValue::Mutable(self)) } } -impl IsReturnable for ExecutionResult { - fn to_returned_value(self) -> ExecutionResult { +impl IsReturnable for FunctionResult { + fn to_returned_value(self) -> FunctionResult { self?.to_returned_value() } -} - -pub(crate) trait StreamAppender { - fn append(self, output: &mut OutputStream) -> ExecutionResult<()>; -} - -impl ExecutionResult<()>> StreamAppender for F { - fn append(self, output: &mut OutputStream) -> ExecutionResult<()> { - self(output) - } -} - -pub(crate) struct StreamOutput(T); -impl ExecutionResult<()>> StreamOutput { - pub fn new(appender: F) -> Self { - Self(appender) - } -} -impl From for StreamOutput { - fn from(value: T) -> Self { - Self(value) - } -} -impl IsReturnable for StreamOutput { - fn to_returned_value(self) -> ExecutionResult { - let mut output = OutputStream::new(); - self.0.append(&mut output)?; - output.to_returned_value() - } -} +} \ No newline at end of file diff --git a/src/expressions/type_resolution/type_data.rs b/src/expressions/type_resolution/type_data.rs index f89118af..002b7e85 100644 --- a/src/expressions/type_resolution/type_data.rs +++ b/src/expressions/type_resolution/type_data.rs @@ -86,12 +86,12 @@ pub(crate) trait TypeData { #[derive(Clone)] pub(crate) enum FunctionInterface { Arity0 { - method: fn(&mut FunctionCallContext) -> ExecutionResult, + method: fn(&mut FunctionCallContext) -> FunctionResult, argument_ownership: [ArgumentOwnership; 0], }, Arity1 { method: - fn(&mut FunctionCallContext, Spanned) -> ExecutionResult, + fn(&mut FunctionCallContext, Spanned) -> FunctionResult, argument_ownership: [ArgumentOwnership; 1], }, /// 1 argument, 1 optional argument @@ -100,7 +100,7 @@ pub(crate) enum FunctionInterface { &mut FunctionCallContext, Spanned, Option>, - ) -> ExecutionResult, + ) -> FunctionResult, argument_ownership: [ArgumentOwnership; 2], }, Arity2 { @@ -108,7 +108,7 @@ pub(crate) enum FunctionInterface { &mut FunctionCallContext, Spanned, Spanned, - ) -> ExecutionResult, + ) -> FunctionResult, argument_ownership: [ArgumentOwnership; 2], }, Arity2PlusOptional1 { @@ -117,7 +117,7 @@ pub(crate) enum FunctionInterface { Spanned, Spanned, Option>, - ) -> ExecutionResult, + ) -> FunctionResult, argument_ownership: [ArgumentOwnership; 3], }, Arity3 { @@ -126,7 +126,7 @@ pub(crate) enum FunctionInterface { Spanned, Spanned, Spanned, - ) -> ExecutionResult, + ) -> FunctionResult, argument_ownership: [ArgumentOwnership; 3], }, Arity3PlusOptional1 { @@ -136,14 +136,14 @@ pub(crate) enum FunctionInterface { Spanned, Spanned, Option>, - ) -> ExecutionResult, + ) -> FunctionResult, argument_ownership: [ArgumentOwnership; 4], }, ArityAny { method: fn( &mut FunctionCallContext, Vec>, - ) -> ExecutionResult, + ) -> FunctionResult, argument_ownership: Vec, }, } @@ -234,7 +234,6 @@ impl FunctionInterface { }; output_value .map(|v| v.spanned(context.output_span_range)) - .expect_no_interrupts() } /// Returns (argument_ownerships, required_argument_count) @@ -270,7 +269,7 @@ impl FunctionInterface { pub(crate) struct UnaryOperationInterface { pub method: - fn(UnaryOperationCallContext, Spanned) -> ExecutionResult, + fn(UnaryOperationCallContext, Spanned) -> FunctionResult, pub argument_ownership: ArgumentOwnership, } @@ -279,10 +278,14 @@ impl UnaryOperationInterface { &self, Spanned(input, input_span): Spanned, operation: &UnaryOperation, - ) -> ExecutionResult> { + interpreter: &mut Interpreter, + ) -> FunctionResult> { let output_span_range = operation.output_span_range(input_span); Ok((self.method)( - UnaryOperationCallContext { operation }, + UnaryOperationCallContext { + operation, + interpreter, + }, Spanned(input, input_span), )? .spanned(output_span_range)) @@ -298,7 +301,7 @@ pub(crate) struct BinaryOperationInterface { BinaryOperationCallContext, Spanned, Spanned, - ) -> ExecutionResult, + ) -> FunctionResult, pub lhs_ownership: ArgumentOwnership, pub rhs_ownership: ArgumentOwnership, } @@ -309,7 +312,7 @@ impl BinaryOperationInterface { Spanned(lhs, lhs_span): Spanned, Spanned(rhs, rhs_span): Spanned, operation: &BinaryOperation, - ) -> ExecutionResult> { + ) -> FunctionResult> { let output_span_range = SpanRange::new_between(lhs_span, rhs_span); Ok((self.method)( BinaryOperationCallContext { operation }, @@ -346,15 +349,15 @@ pub(crate) struct PropertyAccessCallContext<'a> { pub(crate) struct PropertyAccessInterface { /// Access a property by shared reference. pub shared_access: - for<'a> fn(PropertyAccessCallContext, &'a AnyValue) -> ExecutionResult<&'a AnyValue>, + for<'a> fn(PropertyAccessCallContext, &'a AnyValue) -> FunctionResult<&'a AnyValue>, /// Access a property by mutable reference, optionally auto-creating if missing. pub mutable_access: for<'a> fn( PropertyAccessCallContext, &'a mut AnyValue, bool, - ) -> ExecutionResult<&'a mut AnyValue>, + ) -> FunctionResult<&'a mut AnyValue>, /// Extract a property from an owned value. - pub owned_access: fn(PropertyAccessCallContext, AnyValue) -> ExecutionResult, + pub owned_access: fn(PropertyAccessCallContext, AnyValue) -> FunctionResult, } // ============================================================================ @@ -379,15 +382,15 @@ pub(crate) struct IndexAccessInterface { IndexAccessCallContext, &'a AnyValue, Spanned, - ) -> ExecutionResult<&'a AnyValue>, + ) -> FunctionResult<&'a AnyValue>, /// Access an element by mutable reference, optionally auto-creating if missing. pub mutable_access: for<'a> fn( IndexAccessCallContext, &'a mut AnyValue, Spanned, bool, - ) -> ExecutionResult<&'a mut AnyValue>, + ) -> FunctionResult<&'a mut AnyValue>, /// Extract an element from an owned value. pub owned_access: - fn(IndexAccessCallContext, AnyValue, Spanned) -> ExecutionResult, + fn(IndexAccessCallContext, AnyValue, Spanned) -> FunctionResult, } diff --git a/src/expressions/values/any_value.rs b/src/expressions/values/any_value.rs index 6be7bf3d..a7d57056 100644 --- a/src/expressions/values/any_value.rs +++ b/src/expressions/values/any_value.rs @@ -48,7 +48,7 @@ define_type_features! { this.clone_to_owned_infallible() } - fn as_mut(Spanned(this, span): Spanned) -> ExecutionResult { + fn as_mut(Spanned(this, span): Spanned) -> FunctionResult { Ok(match this { ArgumentValue::Owned(owned) => Mutable::new_from_owned(owned), ArgumentValue::CopyOnWrite(copy_on_write) => ArgumentOwnership::Mutable @@ -76,34 +76,40 @@ define_type_features! { core::mem::replace(a.0.deref_mut(), b) } - fn debug(Spanned(this, span_range): Spanned) -> ExecutionResult<()> { - let message = this.as_ref_value().concat_recursive(&ConcatBehaviour::debug(span_range))?; + [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) } - fn to_debug_string(Spanned(this, span_range): Spanned) -> ExecutionResult { - this.as_ref_value().concat_recursive(&ConcatBehaviour::debug(span_range)) + [context] fn to_debug_string(Spanned(this, span_range): Spanned) -> FunctionResult { + this.as_ref_value().concat_recursive(&ConcatBehaviour::debug(span_range), context.interpreter) } - fn to_stream(Spanned(input, span_range): Spanned) -> ExecutionResult { + [context] fn to_stream(Spanned(input, span_range): Spanned) -> FunctionResult { + let interpreter_ptr = context.interpreter as *mut Interpreter; input.map_into( - |shared| shared.as_ref_value().output_to_new_stream(Grouping::Flattened, span_range), - |owned| owned.into_stream(Grouping::Flattened, span_range), + // SAFETY: map_into only calls one of these two closures, + // so only one mutable reference is active at a time. + |shared| shared.as_ref_value().output_to_new_stream(Grouping::Flattened, span_range, unsafe { &mut *interpreter_ptr }), + |owned| owned.into_stream(Grouping::Flattened, span_range, unsafe { &mut *interpreter_ptr }), ) } - fn to_group(Spanned(input, span_range): Spanned) -> ExecutionResult { + [context] fn to_group(Spanned(input, span_range): Spanned) -> FunctionResult { + let interpreter_ptr = context.interpreter as *mut Interpreter; input.map_into( - |shared| shared.as_ref_value().output_to_new_stream(Grouping::Grouped, span_range), - |owned| owned.into_stream(Grouping::Grouped, span_range), + // SAFETY: map_into only calls one of these two closures, + // so only one mutable reference is active at a time. + |shared| shared.as_ref_value().output_to_new_stream(Grouping::Grouped, span_range, unsafe { &mut *interpreter_ptr }), + |owned| owned.into_stream(Grouping::Grouped, span_range, unsafe { &mut *interpreter_ptr }), ) } - fn to_string(Spanned(input, span_range): Spanned) -> ExecutionResult { - input.as_ref_value().concat_recursive(&ConcatBehaviour::standard(span_range)) + [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) -> ExecutionResult { + [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(), @@ -122,51 +128,51 @@ define_type_features! { // EQUALITY METHODS // =============================== // Compare values with strict type checking - errors on value kind mismatch. - [context] fn typed_eq(this: AnyValueAnyRef, other: AnyValueAnyRef) -> ExecutionResult { + [context] fn typed_eq(this: AnyValueAnyRef, other: AnyValueAnyRef) -> FunctionResult { this.as_ref_value().typed_eq(&other.as_ref_value(), context.span_range()) } // STRING-BASED CONVERSION METHODS // =============================== - [context] fn to_ident(this: Spanned) -> ExecutionResult { - let stream = this.into_stream()?; + [context] fn to_ident(this: Spanned) -> FunctionResult { + let stream = this.into_stream(context.interpreter)?; let spanned = stream.into_spanned_ref(context.output_span_range); stream_interface::methods::to_ident(context, spanned) } - [context] fn to_ident_camel(this: Spanned) -> ExecutionResult { - let stream = this.into_stream()?; + [context] fn to_ident_camel(this: Spanned) -> FunctionResult { + let stream = this.into_stream(context.interpreter)?; let spanned = stream.into_spanned_ref(context.output_span_range); stream_interface::methods::to_ident_camel(context, spanned) } - [context] fn to_ident_snake(this: Spanned) -> ExecutionResult { - let stream = this.into_stream()?; + [context] fn to_ident_snake(this: Spanned) -> FunctionResult { + let stream = this.into_stream(context.interpreter)?; let spanned = stream.into_spanned_ref(context.output_span_range); stream_interface::methods::to_ident_snake(context, spanned) } - [context] fn to_ident_upper_snake(this: Spanned) -> ExecutionResult { - let stream = this.into_stream()?; + [context] fn to_ident_upper_snake(this: Spanned) -> FunctionResult { + let stream = this.into_stream(context.interpreter)?; let spanned = stream.into_spanned_ref(context.output_span_range); stream_interface::methods::to_ident_upper_snake(context, spanned) } // Some literals become Value::UnsupportedLiteral but can still be round-tripped back to a stream - [context] fn to_literal(this: Spanned) -> ExecutionResult { - let stream = this.into_stream()?; + [context] fn to_literal(this: Spanned) -> FunctionResult { + let stream = this.into_stream(context.interpreter)?; let spanned = stream.into_spanned_ref(context.output_span_range); stream_interface::methods::to_literal(context, spanned) } } unary_operations { - fn cast_to_string(Spanned(input, span_range): Spanned) -> ExecutionResult { - input.as_ref_value().concat_recursive(&ConcatBehaviour::standard(span_range)) + [context] fn cast_to_string(Spanned(input, span_range): Spanned) -> FunctionResult { + input.as_ref_value().concat_recursive(&ConcatBehaviour::standard(span_range), context.interpreter) } - fn cast_to_stream(input: Spanned) -> ExecutionResult { - input.into_stream() + [context] fn cast_to_stream(Spanned(input, span_range): Spanned) -> FunctionResult { + input.into_stream(Grouping::Flattened, span_range, context.interpreter) } } binary_operations { @@ -317,18 +323,21 @@ impl AnyValue { self, grouping: Grouping, error_span_range: SpanRange, - ) -> ExecutionResult { + interpreter: &mut Interpreter, + ) -> FunctionResult { match (self, grouping) { (AnyValueContent::Stream(value), Grouping::Flattened) => Ok(value), (AnyValueContent::Stream(value), Grouping::Grouped) => { let mut output: OutputStream = OutputStream::new(); - let span = ToStreamContext::new(&mut output, error_span_range).new_token_span(); + let span = Span::call_site(); output.push_new_group(value, Delimiter::None, span); Ok(output) } - (other, grouping) => other - .as_ref_value() - .output_to_new_stream(grouping, error_span_range), + (other, grouping) => { + other + .as_ref_value() + .output_to_new_stream(grouping, error_span_range, interpreter) + } } } } @@ -338,20 +347,21 @@ impl<'a> AnyValueRef<'a> { self, grouping: Grouping, error_span_range: SpanRange, - ) -> ExecutionResult { - let mut output = OutputStream::new(); - self.output_to( - grouping, - &mut ToStreamContext::new(&mut output, error_span_range), - )?; - Ok(output) + interpreter: &mut Interpreter, + ) -> FunctionResult { + interpreter.capture_output(|output| { + self.output_to( + grouping, + &mut ToStreamContext::new(output, error_span_range), + ) + }) } pub(crate) fn output_to( self, grouping: Grouping, output: &mut ToStreamContext, - ) -> ExecutionResult<()> { + ) -> FunctionResult<()> { match grouping { Grouping::Grouped => { // Grouping can be important for different values, to ensure they're read atomically @@ -367,7 +377,7 @@ impl<'a> AnyValueRef<'a> { Ok(()) } - fn output_flattened_to(self, output: &mut ToStreamContext) -> ExecutionResult<()> { + fn output_flattened_to(self, output: &mut ToStreamContext) -> FunctionResult<()> { match self { AnyValueContent::None(_) => {} AnyValueContent::Integer(value) => { @@ -398,7 +408,7 @@ impl<'a> AnyValueRef<'a> { return output.type_err("Objects cannot be output to a stream"); } AnyValueContent::Array(array) => array.output_items_to(output, Grouping::Flattened)?, - AnyValueContent::Stream(value) => value.append_cloned_into(output.output_stream), + AnyValueContent::Stream(value) => value.append_cloned_into(output), AnyValueContent::Iterator(iterator) => iterator .clone() .output_items_to(output, Grouping::Flattened)?, @@ -417,9 +427,13 @@ impl<'a> AnyValueRef<'a> { Ok(()) } - pub(crate) fn concat_recursive(self, behaviour: &ConcatBehaviour) -> ExecutionResult { + pub(crate) fn concat_recursive( + self, + behaviour: &ConcatBehaviour, + interpreter: &mut Interpreter, + ) -> FunctionResult { let mut output = String::new(); - self.concat_recursive_into(&mut output, behaviour)?; + self.concat_recursive_into(&mut output, behaviour, interpreter)?; Ok(output) } @@ -427,7 +441,8 @@ impl<'a> AnyValueRef<'a> { self, output: &mut String, behaviour: &ConcatBehaviour, - ) -> ExecutionResult<()> { + interpreter: &mut Interpreter, + ) -> FunctionResult<()> { match self { AnyValueContent::None(_) => { if behaviour.show_none_values { @@ -438,16 +453,16 @@ impl<'a> AnyValueRef<'a> { stream.concat_as_literal_into(output, behaviour); } AnyValueContent::Array(array) => { - array.concat_recursive_into(output, behaviour)?; + array.concat_recursive_into(output, behaviour, interpreter)?; } AnyValueContent::Object(object) => { - object.concat_recursive_into(output, behaviour)?; + object.concat_recursive_into(output, behaviour, interpreter)?; } AnyValueContent::Iterator(iterator) => { - iterator.concat_recursive_into(output, behaviour)?; + iterator.concat_recursive_into(output, behaviour, interpreter)?; } AnyValueContent::Range(range) => { - range.concat_recursive_into(output, behaviour)?; + range.concat_recursive_into(output, behaviour, interpreter)?; } AnyValueContent::Parser(parser) => { if behaviour.use_debug_literal_syntax { @@ -475,7 +490,11 @@ impl<'a> AnyValueRef<'a> { | AnyValueContent::String(_) => { // This isn't the most efficient, but it's less code and debug doesn't need to be super efficient. let stream = self - .output_to_new_stream(Grouping::Flattened, behaviour.error_span_range) + .output_to_new_stream( + Grouping::Flattened, + behaviour.error_span_range, + interpreter, + ) .expect("Non-composite values should all be able to be outputted to a stream"); stream.concat_content_into(output, behaviour); } @@ -486,48 +505,50 @@ impl<'a> AnyValueRef<'a> { } pub(crate) struct ToStreamContext<'a> { - output_stream: &'a mut OutputStream, + inner: OutputInterpreter<'a>, error_span_range: SpanRange, } impl<'a> ToStreamContext<'a> { - pub(crate) fn new(output_stream: &'a mut OutputStream, error_span_range: SpanRange) -> Self { + pub(crate) fn new(output: &'a mut OutputInterpreter, error_span_range: SpanRange) -> Self { Self { - output_stream, + inner: output.reborrow(), error_span_range, } } - pub(crate) fn push_grouped( + pub(crate) fn push_grouped( &mut self, - f: impl FnOnce(&mut ToStreamContext) -> ExecutionResult<()>, + f: impl FnOnce(&mut ToStreamContext) -> Result<(), E>, delimiter: Delimiter, - ) -> ExecutionResult<()> { + ) -> Result<(), E> { let span = self.new_token_span(); - self.output_stream.push_grouped( - |inner| f(&mut ToStreamContext::new(inner, self.error_span_range)), - delimiter, - span, - ) + let error_span_range = self.error_span_range; + self.inner.in_output_group(delimiter, span, |inner| { + let mut ctx = ToStreamContext { + inner: inner.reborrow(), + error_span_range, + }; + f(&mut ctx) + }) } pub(crate) fn new_token_span(&self) -> Span { - // By default, we use call_site span for generated tokens Span::call_site() } } -impl Deref for ToStreamContext<'_> { - type Target = OutputStream; +impl<'a> Deref for ToStreamContext<'a> { + type Target = OutputInterpreter<'a>; fn deref(&self) -> &Self::Target { - self.output_stream + &self.inner } } impl DerefMut for ToStreamContext<'_> { fn deref_mut(&mut self) -> &mut Self::Target { - self.output_stream + &mut self.inner } } @@ -546,15 +567,18 @@ impl Spanned { } } - pub(crate) fn into_stream(self) -> ExecutionResult { + pub(crate) fn into_stream( + self, + interpreter: &mut Interpreter, + ) -> FunctionResult { let Spanned(value, span_range) = self; - value.into_stream(Grouping::Flattened, span_range) + value.into_stream(Grouping::Flattened, span_range, interpreter) } pub(crate) fn resolve_any_iterator( self, resolution_target: &str, - ) -> ExecutionResult { + ) -> FunctionResult { self.dyn_resolve::(resolution_target)? .into_iterator() } diff --git a/src/expressions/values/array.rs b/src/expressions/values/array.rs index e6dc4a77..daf4b5ea 100644 --- a/src/expressions/values/array.rs +++ b/src/expressions/values/array.rs @@ -8,11 +8,11 @@ define_leaf_type! { articled_value_name: "an array", dyn_impls: { IterableType: impl IsIterable { - fn into_iterator(self: Box) -> ExecutionResult { + fn into_iterator(self: Box) -> FunctionResult { Ok(IteratorValue::new_for_array(*self)) } - fn len(&self, _error_span_range: SpanRange) -> ExecutionResult { + fn iterable_len(&self, _error_span_range: SpanRange) -> FunctionResult { Ok(self.items.len()) } } @@ -33,7 +33,7 @@ impl ArrayValue { &self, output: &mut ToStreamContext, grouping: Grouping, - ) -> ExecutionResult<()> { + ) -> FunctionResult<()> { for item in &self.items { item.as_ref_value().output_to(grouping, output)?; } @@ -43,7 +43,7 @@ impl ArrayValue { pub(super) fn into_indexed( mut self, Spanned(index, span_range): Spanned, - ) -> ExecutionResult { + ) -> FunctionResult { Ok(match index { AnyValueContent::Integer(integer) => { let index = @@ -62,7 +62,7 @@ impl ArrayValue { pub(super) fn index_mut( &mut self, Spanned(index, span_range): Spanned, - ) -> ExecutionResult<&mut AnyValue> { + ) -> FunctionResult<&mut AnyValue> { Ok(match index { AnyValueContent::Integer(integer) => { let index = @@ -80,7 +80,7 @@ impl ArrayValue { pub(super) fn index_ref( &self, Spanned(index, span_range): Spanned, - ) -> ExecutionResult<&AnyValue> { + ) -> FunctionResult<&AnyValue> { Ok(match index { AnyValueContent::Integer(integer) => { let index = @@ -99,7 +99,7 @@ impl ArrayValue { &self, Spanned(index, span_range): Spanned, is_exclusive: bool, - ) -> ExecutionResult { + ) -> FunctionResult { match index { AnyValueContent::Integer(int) => { self.resolve_valid_index_from_integer(Spanned(int, span_range), is_exclusive) @@ -112,7 +112,7 @@ impl ArrayValue { &self, Spanned(integer, span): Spanned, is_exclusive: bool, - ) -> ExecutionResult { + ) -> FunctionResult { let index: OptionalSuffix = Spanned(integer.clone_to_owned_infallible(), span).resolve_as("An array index")?; let index = index.0; @@ -141,15 +141,17 @@ impl ArrayValue { &self, output: &mut String, behaviour: &ConcatBehaviour, - ) -> ExecutionResult<()> { - IteratorValue::any_iterator_to_string( - self.items.iter(), + interpreter: &mut Interpreter, + ) -> FunctionResult<()> { + any_items_to_string( + &mut self.items.iter(), output, behaviour, "[]", "[", "]", false, // Output all the vec because it's already in memory + interpreter, ) } } @@ -195,21 +197,23 @@ define_type_features! { impl ArrayType, pub(crate) mod array_interface { methods { - fn push(mut this: Mutable, item: AnyValue) -> ExecutionResult<()> { + fn push(mut this: Mutable, item: AnyValue) -> FunctionResult<()> { this.items.push(item); Ok(()) } - [context] fn to_stream_grouped(this: ArrayValue) -> StreamOutput [ignore_type_assertion!] { + [context] fn to_stream_grouped(this: ArrayValue) -> FunctionResult { let error_span_range = context.span_range(); - StreamOutput::new(move |stream| this.output_items_to(&mut ToStreamContext::new(stream, error_span_range), Grouping::Grouped)) + context.interpreter.capture_output(|output| { + this.output_items_to(&mut ToStreamContext::new(output, error_span_range), Grouping::Grouped) + }) } } unary_operations { - [context] fn cast_singleton_to_value(Spanned(mut this, span): Spanned) -> ExecutionResult { + [context] fn cast_singleton_to_value(Spanned(mut this, span): Spanned) -> FunctionResult { let length = this.items.len(); if length == 1 { - Ok(context.operation.evaluate(this.items.pop().unwrap().spanned(span))?.0) + Ok(context.operation.evaluate(this.items.pop().unwrap().spanned(span), context.interpreter)?.0) } else { context.operation.value_err(format!( "Only a singleton array can be cast to this value but the array has {} elements", diff --git a/src/expressions/values/float.rs b/src/expressions/values/float.rs index 899af2bf..974c8a50 100644 --- a/src/expressions/values/float.rs +++ b/src/expressions/values/float.rs @@ -103,8 +103,8 @@ fn assign_op( mut left: Assignee, right: R, context: BinaryOperationCallContext, - op: fn(BinaryOperationCallContext, FloatValue, R) -> ExecutionResult, -) -> ExecutionResult<()> { + op: fn(BinaryOperationCallContext, FloatValue, R) -> FunctionResult, +) -> FunctionResult<()> { let left_value = core::mem::replace(&mut *left, FloatContent::F32(0.0)); let result = op(context, left_value, right)?; *left = result; @@ -212,7 +212,7 @@ define_type_features! { } } binary_operations { - fn add(left: FloatValue, right: Spanned) -> ExecutionResult { + fn add(left: FloatValue, right: Spanned) -> FunctionResult { match left.resolve_untyped_to_match(right.as_ref_value()) { FloatContent::Untyped(left) => left.paired_operation(right, |a, b| a + b), FloatContent::F32(left) => left.paired_operation_no_overflow(right, |a, b| a + b), @@ -220,11 +220,11 @@ define_type_features! { } } - [context] fn add_assign(left: Assignee, right: Spanned) -> ExecutionResult<()> { + [context] fn add_assign(left: Assignee, right: Spanned) -> FunctionResult<()> { assign_op(left, right, context, add) } - fn sub(left: FloatValue, right: Spanned) -> ExecutionResult { + fn sub(left: FloatValue, right: Spanned) -> FunctionResult { match left.resolve_untyped_to_match(right.as_ref_value()) { FloatContent::Untyped(left) => left.paired_operation(right, |a, b| a - b), FloatContent::F32(left) => left.paired_operation_no_overflow(right, |a, b| a - b), @@ -232,11 +232,11 @@ define_type_features! { } } - [context] fn sub_assign(left: Assignee, right: Spanned) -> ExecutionResult<()> { + [context] fn sub_assign(left: Assignee, right: Spanned) -> FunctionResult<()> { assign_op(left, right, context, sub) } - fn mul(left: FloatValue, right: Spanned) -> ExecutionResult { + fn mul(left: FloatValue, right: Spanned) -> FunctionResult { match left.resolve_untyped_to_match(right.as_ref_value()) { FloatContent::Untyped(left) => left.paired_operation(right, |a, b| a * b), FloatContent::F32(left) => left.paired_operation_no_overflow(right, |a, b| a * b), @@ -244,11 +244,11 @@ define_type_features! { } } - [context] fn mul_assign(left: Assignee, right: Spanned) -> ExecutionResult<()> { + [context] fn mul_assign(left: Assignee, right: Spanned) -> FunctionResult<()> { assign_op(left, right, context, mul) } - fn div(left: FloatValue, right: Spanned) -> ExecutionResult { + fn div(left: FloatValue, right: Spanned) -> FunctionResult { match left.resolve_untyped_to_match(right.as_ref_value()) { FloatContent::Untyped(left) => left.paired_operation(right, |a, b| a / b), FloatContent::F32(left) => left.paired_operation_no_overflow(right, |a, b| a / b), @@ -256,11 +256,11 @@ define_type_features! { } } - [context] fn div_assign(left: Assignee, right: Spanned) -> ExecutionResult<()> { + [context] fn div_assign(left: Assignee, right: Spanned) -> FunctionResult<()> { assign_op(left, right, context, div) } - fn rem(left: FloatValue, right: Spanned) -> ExecutionResult { + fn rem(left: FloatValue, right: Spanned) -> FunctionResult { match left.resolve_untyped_to_match(right.as_ref_value()) { FloatContent::Untyped(left) => left.paired_operation(right, |a, b| a % b), FloatContent::F32(left) => left.paired_operation_no_overflow(right, |a, b| a % b), @@ -268,11 +268,11 @@ define_type_features! { } } - [context] fn rem_assign(left: Assignee, right: Spanned) -> ExecutionResult<()> { + [context] fn rem_assign(left: Assignee, right: Spanned) -> FunctionResult<()> { assign_op(left, right, context, rem) } - fn lt(left: FloatValue, right: Spanned) -> ExecutionResult { + fn lt(left: FloatValue, right: Spanned) -> FunctionResult { match left.resolve_untyped_to_match(right.as_ref_value()) { FloatContent::Untyped(left) => left.paired_comparison(right, |a, b| a < b), FloatContent::F32(left) => left.paired_comparison(right, |a, b| a < b), @@ -280,7 +280,7 @@ define_type_features! { } } - fn le(left: FloatValue, right: Spanned) -> ExecutionResult { + fn le(left: FloatValue, right: Spanned) -> FunctionResult { match left.resolve_untyped_to_match(right.as_ref_value()) { FloatContent::Untyped(left) => left.paired_comparison(right, |a, b| a <= b), FloatContent::F32(left) => left.paired_comparison(right, |a, b| a <= b), @@ -288,7 +288,7 @@ define_type_features! { } } - fn gt(left: FloatValue, right: Spanned) -> ExecutionResult { + fn gt(left: FloatValue, right: Spanned) -> FunctionResult { match left.resolve_untyped_to_match(right.as_ref_value()) { FloatContent::Untyped(left) => left.paired_comparison(right, |a, b| a > b), FloatContent::F32(left) => left.paired_comparison(right, |a, b| a > b), @@ -296,7 +296,7 @@ define_type_features! { } } - fn ge(left: FloatValue, right: Spanned) -> ExecutionResult { + fn ge(left: FloatValue, right: Spanned) -> FunctionResult { match left.resolve_untyped_to_match(right.as_ref_value()) { FloatContent::Untyped(left) => left.paired_comparison(right, |a, b| a >= b), FloatContent::F32(left) => left.paired_comparison(right, |a, b| a >= b), @@ -304,7 +304,7 @@ define_type_features! { } } - fn eq(left: FloatValue, right: Spanned) -> ExecutionResult { + fn eq(left: FloatValue, right: Spanned) -> FunctionResult { match left.resolve_untyped_to_match(right.as_ref_value()) { FloatContent::Untyped(left) => left.paired_comparison(right, |a, b| a == b), FloatContent::F32(left) => left.paired_comparison(right, |a, b| a == b), @@ -312,7 +312,7 @@ define_type_features! { } } - fn ne(left: FloatValue, right: Spanned) -> ExecutionResult { + fn ne(left: FloatValue, right: Spanned) -> FunctionResult { match left.resolve_untyped_to_match(right.as_ref_value()) { FloatContent::Untyped(left) => left.paired_comparison(right, |a, b| a != b), FloatContent::F32(left) => left.paired_comparison(right, |a, b| a != b), diff --git a/src/expressions/values/float_subtypes.rs b/src/expressions/values/float_subtypes.rs index 7ec2dd58..df31f779 100644 --- a/src/expressions/values/float_subtypes.rs +++ b/src/expressions/values/float_subtypes.rs @@ -158,13 +158,13 @@ macro_rules! impl_resolvable_float_subtype { type ValueType = FloatType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Owned; - fn from_argument(argument: Spanned) -> ExecutionResult { + fn from_argument(argument: Spanned) -> FunctionResult { argument.expect_owned().resolve_as("This argument") } } impl ResolveAs> for Spanned { - fn resolve_as(self, resolution_target: &str) -> ExecutionResult> { + fn resolve_as(self, resolution_target: &str) -> FunctionResult> { let span = self.span_range(); let float_value: FloatValue = self.resolve_as(resolution_target)?; Spanned(float_value, span).resolve_as(resolution_target) @@ -172,7 +172,7 @@ macro_rules! impl_resolvable_float_subtype { } impl ResolveAs> for Spanned { - fn resolve_as(self, resolution_target: &str) -> ExecutionResult> { + fn resolve_as(self, resolution_target: &str) -> FunctionResult> { let Spanned(value, span) = self; match value { FloatContent::Untyped(v) => Ok(OptionalSuffix(v.into_fallback() as $type)), @@ -201,7 +201,7 @@ macro_rules! impl_resolvable_float_subtype { fn resolve_from_value( value: FloatValue, context: ResolutionContext, - ) -> ExecutionResult { + ) -> FunctionResult { match value { FloatContent::Untyped(x) => Ok(x.into_fallback() as $type), FloatContent::$variant(x) => Ok(x), @@ -214,7 +214,7 @@ macro_rules! impl_resolvable_float_subtype { fn resolve_from_value( value: AnyValue, context: ResolutionContext, - ) -> ExecutionResult { + ) -> FunctionResult { match value { AnyValue::Float(x) => <$type>::resolve_from_value(x, context), other => context.err($type_def::ARTICLED_VALUE_NAME, other), @@ -226,7 +226,7 @@ macro_rules! impl_resolvable_float_subtype { fn resolve_from_ref<'a>( value: &'a AnyValue, context: ResolutionContext, - ) -> ExecutionResult<&'a Self> { + ) -> FunctionResult<&'a Self> { match value { AnyValueContent::Float(FloatContent::$variant(x)) => Ok(x), other => context.err($type_def::ARTICLED_VALUE_NAME, other), @@ -238,7 +238,7 @@ macro_rules! impl_resolvable_float_subtype { fn resolve_from_mut<'a>( value: &'a mut AnyValue, context: ResolutionContext, - ) -> ExecutionResult<&'a mut Self> { + ) -> FunctionResult<&'a mut Self> { match value { AnyValueContent::Float(FloatContent::$variant(x)) => Ok(x), other => context.err($type_def::ARTICLED_VALUE_NAME, other), diff --git a/src/expressions/values/float_untyped.rs b/src/expressions/values/float_untyped.rs index 0b4bde7f..a4cf3608 100644 --- a/src/expressions/values/float_untyped.rs +++ b/src/expressions/values/float_untyped.rs @@ -38,7 +38,7 @@ impl UntypedFloat { self, rhs: Spanned, perform_fn: fn(FallbackFloat, FallbackFloat) -> FallbackFloat, - ) -> ExecutionResult { + ) -> FunctionResult { let lhs = self.0; let rhs: UntypedFloat = rhs.downcast_resolve("This operand")?; let rhs = rhs.0; @@ -50,7 +50,7 @@ impl UntypedFloat { self, rhs: Spanned, compare_fn: fn(FallbackFloat, FallbackFloat) -> bool, - ) -> ExecutionResult { + ) -> FunctionResult { let lhs = self.0; let rhs: UntypedFloat = rhs.downcast_resolve("This operand")?; let rhs = rhs.0; @@ -189,7 +189,7 @@ pub(crate) struct UntypedFloatFallback(pub FallbackFloat); impl IsArgument for UntypedFloatFallback { type ValueType = UntypedFloatType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Owned; - fn from_argument(value: Spanned) -> ExecutionResult { + fn from_argument(value: Spanned) -> FunctionResult { Self::resolve_value(value.expect_owned(), "This argument") } } @@ -202,14 +202,14 @@ impl ResolvableOwned for UntypedFloatFallback { fn resolve_from_value( input_value: AnyValue, context: ResolutionContext, - ) -> ExecutionResult { + ) -> FunctionResult { let value = UntypedFloat::resolve_from_value(input_value, context)?; Ok(UntypedFloatFallback(value.0)) } } impl ResolvableOwned for UntypedFloat { - fn resolve_from_value(value: FloatValue, context: ResolutionContext) -> ExecutionResult { + fn resolve_from_value(value: FloatValue, context: ResolutionContext) -> FunctionResult { match value { FloatContent::Untyped(value) => Ok(value), _ => context.err("an untyped float", value), diff --git a/src/expressions/values/function.rs b/src/expressions/values/function.rs index 3e40a6ab..ce37e42e 100644 --- a/src/expressions/values/function.rs +++ b/src/expressions/values/function.rs @@ -60,6 +60,37 @@ impl InvokableFunction { } } +impl FunctionValue { + /// Convenience method to invoke a `FunctionValue`, handling bound arguments. + /// Consumes `self` because `InvokableFunction::invoke` takes `self`. + pub(crate) fn invoke( + self, + mut extra_arguments: Vec>, + context: &mut FunctionCallContext, + ) -> FunctionResult> { + let mut arguments: Vec> = self + .disabled_bound_arguments + .into_iter() + .map(|arg| { + let span = arg.span_range(); + arg.try_map(|v| v.enable(span)) + }) + .collect::>()?; + arguments.append(&mut extra_arguments); + self.invokable.invoke(arguments, context) + } +} + +impl_resolvable_argument_for! { + FunctionType, + (value, context) -> FunctionValue { + match value { + AnyValue::Function(value) => Ok(value), + _ => context.err("a function", value), + } + } +} + impl ValuesEqual for FunctionValue { fn test_equality(&self, other: &Self, ctx: &mut C) -> C::Result { if self.invokable == other.invokable { diff --git a/src/expressions/values/integer.rs b/src/expressions/values/integer.rs index 90de463e..f4fa7ca3 100644 --- a/src/expressions/values/integer.rs +++ b/src/expressions/values/integer.rs @@ -58,7 +58,7 @@ impl IntegerValue { pub(crate) fn resolve_untyped_to_match_other( Spanned(value, span): Spanned, other: &AnyValue, - ) -> ExecutionResult { + ) -> FunctionResult { match (value, other) { (IntegerValue::Untyped(this), AnyValue::Integer(other)) => { this.spanned(span).into_kind(other.kind()) @@ -70,7 +70,7 @@ impl IntegerValue { pub(crate) fn resolve_untyped_to_match( Spanned(value, span): Spanned, target: &IntegerValue, - ) -> ExecutionResult { + ) -> FunctionResult { match value { IntegerValue::Untyped(this) => this.spanned(span).into_kind(target.kind()), value => Ok(value), @@ -85,8 +85,8 @@ impl IntegerValue { BinaryOperationCallContext, Spanned, R, - ) -> ExecutionResult, - ) -> ExecutionResult<()> { + ) -> FunctionResult, + ) -> FunctionResult<()> { let left_value = core::mem::replace(&mut *left, IntegerValue::U32(0)); let result = op(context, Spanned(left_value, left_span), right)?; *left = result; @@ -207,7 +207,7 @@ define_type_features! { impl IntegerType, pub(crate) mod integer_interface { binary_operations { - [context] fn add(left: Spanned, right: Spanned) -> ExecutionResult { + [context] fn add(left: Spanned, right: Spanned) -> FunctionResult { match IntegerValue::resolve_untyped_to_match(left, &right)? { IntegerValue::Untyped(left) => left.paired_operation(right, context, FallbackInteger::checked_add), IntegerValue::U8(left) => left.paired_operation(right, context, u8::checked_add), @@ -225,11 +225,11 @@ define_type_features! { } } - [context] fn add_assign(lhs: Spanned>, rhs: Spanned) -> ExecutionResult<()> { + [context] fn add_assign(lhs: Spanned>, rhs: Spanned) -> FunctionResult<()> { IntegerValue::assign_op(lhs, rhs, context, add) } - [context] fn sub(left: Spanned, right: Spanned) -> ExecutionResult { + [context] fn sub(left: Spanned, right: Spanned) -> FunctionResult { match IntegerValue::resolve_untyped_to_match(left, &right)? { IntegerValue::Untyped(left) => left.paired_operation(right, context, FallbackInteger::checked_sub), IntegerValue::U8(left) => left.paired_operation(right, context, u8::checked_sub), @@ -247,11 +247,11 @@ define_type_features! { } } - [context] fn sub_assign(lhs: Spanned>, rhs: Spanned) -> ExecutionResult<()> { + [context] fn sub_assign(lhs: Spanned>, rhs: Spanned) -> FunctionResult<()> { IntegerValue::assign_op(lhs, rhs, context, sub) } - [context] fn mul(left: Spanned, right: Spanned) -> ExecutionResult { + [context] fn mul(left: Spanned, right: Spanned) -> FunctionResult { match IntegerValue::resolve_untyped_to_match(left, &right)? { IntegerValue::Untyped(left) => left.paired_operation(right, context, FallbackInteger::checked_mul), IntegerValue::U8(left) => left.paired_operation(right, context, u8::checked_mul), @@ -269,11 +269,11 @@ define_type_features! { } } - [context] fn mul_assign(lhs: Spanned>, rhs: Spanned) -> ExecutionResult<()> { + [context] fn mul_assign(lhs: Spanned>, rhs: Spanned) -> FunctionResult<()> { IntegerValue::assign_op(lhs, rhs, context, mul) } - [context] fn div(left: Spanned, right: Spanned) -> ExecutionResult { + [context] fn div(left: Spanned, right: Spanned) -> FunctionResult { match IntegerValue::resolve_untyped_to_match(left, &right)? { IntegerValue::Untyped(left) => left.paired_operation(right, context, FallbackInteger::checked_div), IntegerValue::U8(left) => left.paired_operation(right, context, u8::checked_div), @@ -291,11 +291,11 @@ define_type_features! { } } - [context] fn div_assign(lhs: Spanned>, rhs: Spanned) -> ExecutionResult<()> { + [context] fn div_assign(lhs: Spanned>, rhs: Spanned) -> FunctionResult<()> { IntegerValue::assign_op(lhs, rhs, context, div) } - [context] fn rem(left: Spanned, right: Spanned) -> ExecutionResult { + [context] fn rem(left: Spanned, right: Spanned) -> FunctionResult { match IntegerValue::resolve_untyped_to_match(left, &right)? { IntegerValue::Untyped(left) => left.paired_operation(right, context, FallbackInteger::checked_rem), IntegerValue::U8(left) => left.paired_operation(right, context, u8::checked_rem), @@ -313,11 +313,11 @@ define_type_features! { } } - [context] fn rem_assign(lhs: Spanned>, rhs: Spanned) -> ExecutionResult<()> { + [context] fn rem_assign(lhs: Spanned>, rhs: Spanned) -> FunctionResult<()> { IntegerValue::assign_op(lhs, rhs, context, rem) } - [context] fn bitxor(left: Spanned, right: Spanned) -> ExecutionResult { + [context] fn bitxor(left: Spanned, right: Spanned) -> FunctionResult { match IntegerValue::resolve_untyped_to_match(left, &right)? { IntegerValue::Untyped(left) => left.paired_operation(right, context, |a, b| Some(a ^ b)), IntegerValue::U8(left) => left.paired_operation(right, context, |a, b| Some(a ^ b)), @@ -335,11 +335,11 @@ define_type_features! { } } - [context] fn bitxor_assign(lhs: Spanned>, rhs: Spanned) -> ExecutionResult<()> { + [context] fn bitxor_assign(lhs: Spanned>, rhs: Spanned) -> FunctionResult<()> { IntegerValue::assign_op(lhs, rhs, context, bitxor) } - [context] fn bitand(left: Spanned, right: Spanned) -> ExecutionResult { + [context] fn bitand(left: Spanned, right: Spanned) -> FunctionResult { match IntegerValue::resolve_untyped_to_match(left, &right)? { IntegerValue::Untyped(left) => left.paired_operation(right, context, |a, b| Some(a & b)), IntegerValue::U8(left) => left.paired_operation(right, context, |a, b| Some(a & b)), @@ -357,11 +357,11 @@ define_type_features! { } } - [context] fn bitand_assign(lhs: Spanned>, rhs: Spanned) -> ExecutionResult<()> { + [context] fn bitand_assign(lhs: Spanned>, rhs: Spanned) -> FunctionResult<()> { IntegerValue::assign_op(lhs, rhs, context, bitand) } - [context] fn bitor(left: Spanned, right: Spanned) -> ExecutionResult { + [context] fn bitor(left: Spanned, right: Spanned) -> FunctionResult { match IntegerValue::resolve_untyped_to_match(left, &right)? { IntegerValue::Untyped(left) => left.paired_operation(right, context, |a, b| Some(a | b)), IntegerValue::U8(left) => left.paired_operation(right, context, |a, b| Some(a | b)), @@ -379,11 +379,11 @@ define_type_features! { } } - [context] fn bitor_assign(lhs: Spanned>, rhs: Spanned) -> ExecutionResult<()> { + [context] fn bitor_assign(lhs: Spanned>, rhs: Spanned) -> FunctionResult<()> { IntegerValue::assign_op(lhs, rhs, context, bitor) } - [context] fn shift_left(lhs: Spanned, CoercedToU32(right): CoercedToU32) -> ExecutionResult { + [context] fn shift_left(lhs: Spanned, CoercedToU32(right): CoercedToU32) -> FunctionResult { match lhs.0 { IntegerValue::Untyped(left) => left.shift_operation(right, context, FallbackInteger::checked_shl), IntegerValue::U8(left) => left.shift_operation(right, context, u8::checked_shl), @@ -401,11 +401,11 @@ define_type_features! { } } - [context] fn shift_left_assign(lhs: Spanned>, rhs: CoercedToU32) -> ExecutionResult<()> { + [context] fn shift_left_assign(lhs: Spanned>, rhs: CoercedToU32) -> FunctionResult<()> { IntegerValue::assign_op(lhs, rhs, context, shift_left) } - [context] fn shift_right(lhs: Spanned, CoercedToU32(right): CoercedToU32) -> ExecutionResult { + [context] fn shift_right(lhs: Spanned, CoercedToU32(right): CoercedToU32) -> FunctionResult { match lhs.0 { IntegerValue::Untyped(left) => left.shift_operation(right, context, FallbackInteger::checked_shr), IntegerValue::U8(left) => left.shift_operation(right, context, u8::checked_shr), @@ -423,11 +423,11 @@ define_type_features! { } } - [context] fn shift_right_assign(lhs: Spanned>, rhs: CoercedToU32) -> ExecutionResult<()> { + [context] fn shift_right_assign(lhs: Spanned>, rhs: CoercedToU32) -> FunctionResult<()> { IntegerValue::assign_op(lhs, rhs, context, shift_right) } - fn lt(left: Spanned, right: Spanned) -> ExecutionResult { + fn lt(left: Spanned, right: Spanned) -> FunctionResult { match IntegerValue::resolve_untyped_to_match(left, &right)? { IntegerValue::Untyped(left) => left.paired_comparison(right, |a, b| a < b), IntegerValue::U8(left) => left.paired_comparison(right, |a, b| a < b), @@ -445,7 +445,7 @@ define_type_features! { } } - fn le(left: Spanned, right: Spanned) -> ExecutionResult { + fn le(left: Spanned, right: Spanned) -> FunctionResult { match IntegerValue::resolve_untyped_to_match(left, &right)? { IntegerValue::Untyped(left) => left.paired_comparison(right, |a, b| a <= b), IntegerValue::U8(left) => left.paired_comparison(right, |a, b| a <= b), @@ -463,7 +463,7 @@ define_type_features! { } } - fn gt(left: Spanned, right: Spanned) -> ExecutionResult { + fn gt(left: Spanned, right: Spanned) -> FunctionResult { match IntegerValue::resolve_untyped_to_match(left, &right)? { IntegerValue::Untyped(left) => left.paired_comparison(right, |a, b| a > b), IntegerValue::U8(left) => left.paired_comparison(right, |a, b| a > b), @@ -481,7 +481,7 @@ define_type_features! { } } - fn ge(left: Spanned, right: Spanned) -> ExecutionResult { + fn ge(left: Spanned, right: Spanned) -> FunctionResult { match IntegerValue::resolve_untyped_to_match(left, &right)? { IntegerValue::Untyped(left) => left.paired_comparison(right, |a, b| a >= b), IntegerValue::U8(left) => left.paired_comparison(right, |a, b| a >= b), @@ -499,7 +499,7 @@ define_type_features! { } } - fn eq(left: Spanned, right: Spanned) -> ExecutionResult { + fn eq(left: Spanned, right: Spanned) -> FunctionResult { match IntegerValue::resolve_untyped_to_match(left, &right)? { IntegerValue::Untyped(left) => left.paired_comparison(right, |a, b| a == b), IntegerValue::U8(left) => left.paired_comparison(right, |a, b| a == b), @@ -517,7 +517,7 @@ define_type_features! { } } - fn ne(left: Spanned, right: Spanned) -> ExecutionResult { + fn ne(left: Spanned, right: Spanned) -> FunctionResult { match IntegerValue::resolve_untyped_to_match(left, &right)? { IntegerValue::Untyped(left) => left.paired_comparison(right, |a, b| a != b), IntegerValue::U8(left) => left.paired_comparison(right, |a, b| a != b), @@ -592,7 +592,7 @@ pub(crate) struct CoercedToU32(pub(crate) u32); impl IsArgument for CoercedToU32 { type ValueType = IntegerType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Owned; - fn from_argument(value: Spanned) -> ExecutionResult { + fn from_argument(value: Spanned) -> FunctionResult { Self::resolve_value(value.expect_owned(), "This argument") } } @@ -605,7 +605,7 @@ impl ResolvableOwned for CoercedToU32 { fn resolve_from_value( input_value: AnyValue, context: ResolutionContext, - ) -> ExecutionResult { + ) -> FunctionResult { let integer = match input_value { AnyValue::Integer(value) => value, other => return context.err("an integer", other), diff --git a/src/expressions/values/integer_subtypes.rs b/src/expressions/values/integer_subtypes.rs index 4be03512..ed848ce4 100644 --- a/src/expressions/values/integer_subtypes.rs +++ b/src/expressions/values/integer_subtypes.rs @@ -10,7 +10,7 @@ macro_rules! impl_int_operations { pub(crate) mod $mod_name { unary_operations { $( - fn neg(Spanned(value, span): Spanned<$integer_type>) -> ExecutionResult<$integer_type> { + fn neg(Spanned(value, span): Spanned<$integer_type>) -> FunctionResult<$integer_type> { ignore_all!($signed); // Include only for signed types match value.checked_neg() { Some(negated) => Ok(negated), @@ -191,13 +191,13 @@ macro_rules! impl_resolvable_integer_subtype { type ValueType = IntegerType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Owned; - fn from_argument(argument: Spanned) -> ExecutionResult { + fn from_argument(argument: Spanned) -> FunctionResult { argument.expect_owned().resolve_as("This argument") } } impl ResolveAs> for Spanned { - fn resolve_as(self, resolution_target: &str) -> ExecutionResult> { + fn resolve_as(self, resolution_target: &str) -> FunctionResult> { let span = self.span_range(); let integer_value: IntegerValue = self.resolve_as(resolution_target)?; Spanned(integer_value, span).resolve_as(resolution_target) @@ -205,7 +205,7 @@ macro_rules! impl_resolvable_integer_subtype { } impl ResolveAs> for Spanned { - fn resolve_as(self, resolution_target: &str) -> ExecutionResult> { + fn resolve_as(self, resolution_target: &str) -> FunctionResult> { let Spanned(value, span) = self; match value { IntegerValue::Untyped(v) => Ok(OptionalSuffix(v.into_fallback() as $type)), @@ -234,7 +234,7 @@ macro_rules! impl_resolvable_integer_subtype { fn resolve_from_value( value: IntegerValue, context: ResolutionContext, - ) -> ExecutionResult { + ) -> FunctionResult { match value { IntegerValue::Untyped(x) => Ok(x.into_fallback() as $type), IntegerValue::$variant(x) => Ok(x), @@ -247,7 +247,7 @@ macro_rules! impl_resolvable_integer_subtype { fn resolve_from_value( value: AnyValue, context: ResolutionContext, - ) -> ExecutionResult { + ) -> FunctionResult { match value { AnyValue::Integer(x) => <$type>::resolve_from_value(x, context), other => context.err($type_def::ARTICLED_VALUE_NAME, other), @@ -259,7 +259,7 @@ macro_rules! impl_resolvable_integer_subtype { fn resolve_from_ref<'a>( value: &'a AnyValue, context: ResolutionContext, - ) -> ExecutionResult<&'a Self> { + ) -> FunctionResult<&'a Self> { match value { AnyValue::Integer(IntegerValue::$variant(x)) => Ok(x), other => context.err($type_def::ARTICLED_VALUE_NAME, other), @@ -271,7 +271,7 @@ macro_rules! impl_resolvable_integer_subtype { fn resolve_from_mut<'a>( value: &'a mut AnyValue, context: ResolutionContext, - ) -> ExecutionResult<&'a mut Self> { + ) -> FunctionResult<&'a mut Self> { match value { AnyValue::Integer(IntegerValue::$variant(x)) => Ok(x), other => context.err($type_def::ARTICLED_VALUE_NAME, other), diff --git a/src/expressions/values/integer_untyped.rs b/src/expressions/values/integer_untyped.rs index 8ec83388..a28de72c 100644 --- a/src/expressions/values/integer_untyped.rs +++ b/src/expressions/values/integer_untyped.rs @@ -28,7 +28,7 @@ impl UntypedInteger { context: BinaryOperationCallContext, lhs: impl std::fmt::Display, rhs: impl std::fmt::Display, - ) -> ExecutionInterrupt { + ) -> FunctionError { context.error(format!( "The untyped integer operation {} {} {} overflowed in {} space", lhs, @@ -63,7 +63,7 @@ impl UntypedInteger { rhs: Spanned, context: BinaryOperationCallContext, perform_fn: fn(FallbackInteger, FallbackInteger) -> Option, - ) -> ExecutionResult { + ) -> FunctionResult { let lhs = self.0; let rhs: UntypedInteger = rhs.downcast_resolve("This operand")?; let rhs = rhs.0; @@ -76,7 +76,7 @@ impl UntypedInteger { self, rhs: Spanned, compare_fn: fn(FallbackInteger, FallbackInteger) -> bool, - ) -> ExecutionResult { + ) -> FunctionResult { let lhs = self.0; let rhs: UntypedInteger = rhs.downcast_resolve("This operand")?; let rhs = rhs.0; @@ -88,7 +88,7 @@ impl UntypedInteger { rhs: u32, context: BinaryOperationCallContext, perform_fn: fn(FallbackInteger, u32) -> Option, - ) -> ExecutionResult { + ) -> FunctionResult { let lhs = self.0; let output = perform_fn(lhs, rhs) .ok_or_else(|| UntypedInteger::binary_overflow_error(context, lhs, rhs))?; @@ -109,11 +109,11 @@ impl UntypedInteger { } impl Spanned { - pub(crate) fn into_kind(self, kind: IntegerLeafKind) -> ExecutionResult { + pub(crate) fn into_kind(self, kind: IntegerLeafKind) -> FunctionResult { let Spanned(value, span_range) = self; value .try_into_kind(kind) - .ok_or_else(|| -> ExecutionInterrupt { + .ok_or_else(|| -> FunctionError { span_range.value_error(format!( "The integer value {} does not fit into {}", value.0, @@ -127,7 +127,7 @@ define_type_features! { impl UntypedIntegerType, pub(crate) mod untyped_integer_interface { unary_operations { - fn neg(Spanned(value, span): Spanned) -> ExecutionResult { + fn neg(Spanned(value, span): Spanned) -> FunctionResult { let input = value.into_fallback(); match input.checked_neg() { Some(negated) => Ok(UntypedInteger::from_fallback(negated)), @@ -251,7 +251,7 @@ impl IsValueContent for UntypedIntegerFallback { impl IsArgument for UntypedIntegerFallback { type ValueType = UntypedIntegerType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Owned; - fn from_argument(value: Spanned) -> ExecutionResult { + fn from_argument(value: Spanned) -> FunctionResult { Self::resolve_value(value.expect_owned(), "This argument") } } @@ -264,7 +264,7 @@ impl ResolvableOwned for UntypedIntegerFallback { fn resolve_from_value( input_value: AnyValue, context: ResolutionContext, - ) -> ExecutionResult { + ) -> FunctionResult { let value: UntypedInteger = ResolvableOwned::::resolve_from_value(input_value, context)?; Ok(UntypedIntegerFallback(value.into_fallback())) @@ -275,7 +275,7 @@ impl ResolvableOwned for UntypedInteger { fn resolve_from_value( value: IntegerValue, context: ResolutionContext, - ) -> ExecutionResult { + ) -> FunctionResult { match value { IntegerValue::Untyped(value) => Ok(value), _ => context.err("an untyped integer", value), diff --git a/src/expressions/values/iterable.rs b/src/expressions/values/iterable.rs index 2a8186c3..b46a4362 100644 --- a/src/expressions/values/iterable.rs +++ b/src/expressions/values/iterable.rs @@ -1,8 +1,8 @@ use super::*; pub(crate) trait IsIterable: 'static { - fn into_iterator(self: Box) -> ExecutionResult; - fn len(&self, error_span_range: SpanRange) -> ExecutionResult; + fn into_iterator(self: Box) -> FunctionResult; + fn iterable_len(&self, error_span_range: SpanRange) -> FunctionResult; } define_dyn_type!( @@ -20,43 +20,47 @@ define_type_features! { impl IterableType, pub(crate) mod iterable_interface { methods { - fn into_iter(this: IterableValue) -> ExecutionResult { + fn into_iter(this: IterableValue) -> FunctionResult { this.into_iterator() } - fn len(Spanned(this, span_range): Spanned) -> ExecutionResult { - this.len(span_range) + fn len(Spanned(this, span_range): Spanned) -> FunctionResult { + this.iterable_len(span_range) } - fn is_empty(Spanned(this, span_range): Spanned) -> ExecutionResult { - Ok(this.len(span_range)? == 0) + fn is_empty(Spanned(this, span_range): Spanned) -> FunctionResult { + Ok(this.iterable_len(span_range)? == 0) } - [context] fn zip(this: IterableValue) -> ExecutionResult { + [context] fn zip(this: IterableValue) -> FunctionResult { let iterator = this.into_iterator()?; - ZipIterators::new_from_iterator(iterator, context.span_range())?.run_zip(context.interpreter, true) + ZipIterators::new_from_iterator(iterator, context.span_range(), context.interpreter)?.run_zip(context.interpreter, true) } - [context] fn zip_truncated(this: IterableValue) -> ExecutionResult { + [context] fn zip_truncated(this: IterableValue) -> FunctionResult { let iterator = this.into_iterator()?; - ZipIterators::new_from_iterator(iterator, context.span_range())?.run_zip(context.interpreter, false) + ZipIterators::new_from_iterator(iterator, context.span_range(), context.interpreter)?.run_zip(context.interpreter, false) } - fn intersperse(this: IterableValue, separator: AnyValue, settings: Option) -> ExecutionResult { - run_intersperse(this, separator, settings.unwrap_or_default()) + [context] fn intersperse(this: IterableValue, separator: AnyValue, settings: Option) -> FunctionResult { + run_intersperse(this, separator, settings.unwrap_or_default(), context.interpreter) } - [context] fn to_vec(this: IterableValue) -> ExecutionResult> { + [context] fn to_vec(this: IterableValue) -> FunctionResult> { let error_span_range = context.span_range(); let mut counter = context.interpreter.start_iteration_counter(&error_span_range); - let iterator = this.into_iterator()?; - let max_hint = iterator.size_hint().1; + let mut iterator = this.into_iterator()?; + let max_hint = iterator.do_size_hint().1; let mut vec = if let Some(max) = max_hint { Vec::with_capacity(max) } else { Vec::new() }; - for item in iterator { + loop { + let item = match iterator.do_next(context.interpreter)? { + Some(item) => item, + None => break, + }; counter.increment_and_check()?; vec.push(item); } @@ -64,7 +68,7 @@ define_type_features! { } } unary_operations { - fn cast_into_iterator(this: IterableValue) -> ExecutionResult { + fn cast_into_iterator(this: IterableValue) -> FunctionResult { this.into_iterator() } } diff --git a/src/expressions/values/iterator.rs b/src/expressions/values/iterator.rs index cef36bea..81283eba 100644 --- a/src/expressions/values/iterator.rs +++ b/src/expressions/values/iterator.rs @@ -8,43 +8,45 @@ define_leaf_type! { articled_value_name: "an iterator", dyn_impls: { IterableType: impl IsIterable { - fn into_iterator(self: Box) -> ExecutionResult { + fn into_iterator(self: Box) -> FunctionResult { Ok(*self) } - fn len(&self, error_span_range: SpanRange) -> ExecutionResult { - self.len(error_span_range) + fn iterable_len(&self, error_span_range: SpanRange) -> FunctionResult { + self.do_len(error_span_range) } } }, } +pub(crate) type ValueIterator = Box>; + #[derive(Clone)] pub(crate) struct IteratorValue { - iterator: IteratorValueInner, + inner: ValueIterator, } impl IteratorValue { - fn new(iterator: IteratorValueInner) -> Self { - Self { iterator } + pub(crate) fn new(iterator: ValueIterator) -> Self { + Self { inner: iterator } } #[allow(unused)] pub(crate) fn new_any(iterator: impl Iterator + 'static + Clone) -> Self { - Self::new_custom(Box::new(iterator)) + Self::new(Box::new(iterator)) } pub(crate) fn new_for_array(array: ArrayValue) -> Self { - Self::new_vec(array.items.into_iter()) + Self::new(Box::new(array.items.into_iter())) } pub(crate) fn new_for_stream(stream: OutputStream) -> Self { - Self::new(IteratorValueInner::Stream(Box::new(stream.into_iter()))) + Self::new(Box::new(StreamValueIterator(stream.into_iter()))) } - pub(crate) fn new_for_range(range: RangeValue) -> ExecutionResult { + pub(crate) fn new_for_range(range: RangeValue) -> FunctionResult { let iterator = range.inner.into_iterable()?.resolve_iterator()?; - Ok(Self::new_custom(iterator)) + Ok(Self::new(iterator)) } pub(crate) fn new_for_object(object: ObjectValue) -> Self { @@ -55,7 +57,7 @@ impl IteratorValue { .map(|(k, v)| vec![k.into_any_value(), v.value].into_any_value()) .collect::>() .into_iter(); - Self::new_vec(iterator) + Self::new(Box::new(iterator)) } pub(crate) fn new_for_string_over_chars(string: String) -> Self { @@ -69,46 +71,46 @@ impl IteratorValue { .map(|c| c.into_any_value()) .collect::>() .into_iter(); - Self::new_vec(iterator) - } - - fn new_vec(iterator: std::vec::IntoIter) -> Self { - Self::new(IteratorValueInner::Vec(Box::new(iterator))) - } - - pub(crate) fn new_custom(iterator: Box>) -> Self { - Self::new(IteratorValueInner::Other(iterator)) + Self::new(Box::new(iterator)) } - pub(crate) fn len(&self, error_span_range: SpanRange) -> ExecutionResult { - let (min, max) = self.size_hint(); - if max == Some(min) { - Ok(min) - } else { - error_span_range.value_err("Iterator has an inexact length") - } + /// Returns the inner BoxedIterator box (for creating Map/Filter wrappers). + pub(crate) fn into_inner(self) -> ValueIterator { + self.inner } - pub(crate) fn singleton_value(mut self) -> Option { - let first = self.next()?; - if self.next().is_none() { - Some(first) + pub(crate) fn singleton_value( + mut self, + interpreter: &mut Interpreter, + ) -> FunctionResult> { + let first = match self.do_next(interpreter)? { + Some(v) => v, + None => return Ok(None), + }; + if self.do_next(interpreter)?.is_none() { + Ok(Some(first)) } else { - None + Ok(None) } } pub(super) fn output_items_to( - self, + mut self, output: &mut ToStreamContext, grouping: Grouping, - ) -> ExecutionResult<()> { + ) -> FunctionResult<()> { const LIMIT: usize = 10_000; - for (i, item) in self.enumerate() { + let mut i = 0; + loop { + let item = match output.with_interpreter(|interpreter| self.do_next(interpreter))? { + Some(item) => item, + None => break, + }; if i > LIMIT { return output.debug_err(format!("Only a maximum of {} items can be output to a stream from an iterator, to protect you from infinite loops. This can't currently be reconfigured with the iteration limit.", LIMIT)); } item.as_ref_value().output_to(grouping, output)?; + i += 1; } Ok(()) } @@ -117,97 +119,60 @@ impl IteratorValue { &self, output: &mut String, behaviour: &ConcatBehaviour, - ) -> ExecutionResult<()> { + interpreter: &mut Interpreter, + ) -> FunctionResult<()> { if behaviour.use_debug_literal_syntax { - Self::any_iterator_to_string( - self.clone(), + any_items_to_string( + &mut self.clone(), output, behaviour, "[]", "[ ", "]", true, + interpreter, ) } else { output.push_str("Iterator[?]"); Ok(()) } } - - pub(crate) fn any_iterator_to_string>( - iterator: impl Iterator, - output: &mut String, - behaviour: &ConcatBehaviour, - literal_empty: &str, - literal_start: &str, - literal_end: &str, - possibly_unbounded: bool, - ) -> ExecutionResult<()> { - let mut is_empty = true; - let max = iterator.size_hint().1; - for (i, item) in iterator.enumerate() { - if i == 0 { - if behaviour.output_literal_structure { - output.push_str(literal_start); - } - is_empty = false; - } - if possibly_unbounded && i >= behaviour.iterator_limit { - if behaviour.error_after_iterator_limit { - return behaviour.error_span_range.debug_err(format!("To protect against infinite loops, only a maximum of {} items can be output to a string from an iterator. You can use .to_vec() to avoid this limit. This can't currently be reconfigured with the iteration limit.", behaviour.iterator_limit)); - } else { - if behaviour.output_literal_structure { - match max { - Some(max) => output.push_str(&format!( - ", ..<{} further items>", - max.saturating_sub(i) - )), - None => output.push_str(", .."), - } - } - break; - } - } - let item = item.borrow(); - if i != 0 && behaviour.output_literal_structure { - output.push(','); - } - if i != 0 && behaviour.add_space_between_token_trees { - output.push(' '); - } - item.as_ref_value() - .concat_recursive_into(output, behaviour)?; - } - if behaviour.output_literal_structure { - if is_empty { - output.push_str(literal_empty); - } else { - output.push_str(literal_end); - } - } - Ok(()) - } } -impl IsValueContent for IteratorValueInner { - type Type = IteratorType; - type Form = BeOwned; +#[derive(Clone)] +struct StreamValueIterator(OutputStreamIntoIter); + +impl PreinterpretIterator for StreamValueIterator { + type Item = AnyValue; + fn do_next(&mut self, _: &mut Interpreter) -> FunctionResult> { + Ok(Iterator::next(&mut self.0).map(|segment| { + let stream: OutputStream = segment.into(); + stream.coerce_into_value() + })) + } + fn do_size_hint(&self) -> (usize, Option) { + Iterator::size_hint(&self.0) + } } -impl IntoValueContent<'static> for IteratorValueInner { - fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - IteratorValue::new(self) +impl PreinterpretIterator for IteratorValue { + type Item = AnyValue; + fn do_next(&mut self, interpreter: &mut Interpreter) -> FunctionResult> { + self.inner.do_next(interpreter) + } + fn do_size_hint(&self) -> (usize, Option) { + self.inner.do_size_hint() } } -impl IsValueContent for Box> { +impl IsValueContent for ValueIterator { type Type = IteratorType; type Form = BeOwned; } -impl IntoValueContent<'static> for Box> { +impl IntoValueContent<'static> for ValueIterator { fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - IteratorValue::new_custom(self) + IteratorValue::new(self) } } @@ -231,85 +196,64 @@ impl ValuesEqual for IteratorValue { } } -#[derive(Clone)] -enum IteratorValueInner { - // We Box these so that Value is smaller on the stack - Vec(Box< as IntoIterator>::IntoIter>), - Stream(Box<::IntoIter>), - Other(Box>), -} - -impl Iterator for IteratorValue { - type Item = AnyValue; - - fn next(&mut self) -> Option { - match &mut self.iterator { - IteratorValueInner::Vec(iter) => iter.next(), - IteratorValueInner::Stream(iter) => { - let item = iter.next()?; - let stream: OutputStream = item.into(); - Some(stream.coerce_into_value()) - } - IteratorValueInner::Other(iter) => iter.next(), - } - } - - fn size_hint(&self) -> (usize, Option) { - match &self.iterator { - IteratorValueInner::Vec(iter) => iter.size_hint(), - IteratorValueInner::Stream(iter) => iter.size_hint(), - IteratorValueInner::Other(iter) => iter.size_hint(), - } - } -} - -impl Iterator for Mutable { - type Item = AnyValue; - - fn next(&mut self) -> Option { - let this: &mut IteratorValue = &mut *self; - this.next() - } - - fn size_hint(&self) -> (usize, Option) { - let this: &IteratorValue = self; - this.size_hint() - } -} - define_type_features! { impl IteratorType, pub(crate) mod iterator_interface { methods { - fn next(mut this: Mutable) -> AnyValue { - match this.next() { - Some(value) => value, - None => ().into_any_value(), + [context] fn next(mut this: Mutable) -> FunctionResult { + match this.do_next(context.interpreter)? { + Some(value) => Ok(value), + None => Ok(().into_any_value()), } } - fn skip(mut this: IteratorValue, n: OptionalSuffix) -> IteratorValue { - // We make this greedy instead of lazy because the Skip iterator is not clonable. - // We return an iterator for forwards compatibility in case we change it. - for _ in 0..n.0 { - if this.next().is_none() { - break; - } - } - this + fn skip(this: IteratorValue, n: OptionalSuffix) -> ValueIterator { + this.do_skip(n.0).boxed() + } + + fn take(this: IteratorValue, n: OptionalSuffix) -> ValueIterator { + this.do_take(n.0).boxed() + } + + [context] fn map(this: IteratorValue, function: FunctionValue) -> IteratorValue { + let inner = this.into_inner(); + let span = context.output_span_range; + let f = move |item: AnyValue, interpreter: &mut Interpreter| -> FunctionResult { + let mut ctx = FunctionCallContext { interpreter, output_span_range: span }; + let argument = Spanned(ArgumentValue::Owned(item), span); + let result = function.clone().invoke(vec![argument], &mut ctx)?; + let owned = RequestedOwnership::owned() + .map_from_returned(result)? + .0 + .expect_owned(); + Ok(owned) + }; + IteratorValue::new(Box::new(MapIterator::new(inner, f))) } - fn take(this: IteratorValue, n: OptionalSuffix) -> IteratorValue { - // We collect to a vec to satisfy the clonability requirement, - // but only return an iterator for forwards compatibility in case we change it. - let taken = this.take(n.0).collect::>(); - IteratorValue::new_for_array(ArrayValue::new(taken)) + [context] fn filter(this: IteratorValue, function: FunctionValue) -> IteratorValue { + let inner = this.into_inner(); + let span = context.output_span_range; + let f = move |item: &AnyValue, interpreter: &mut Interpreter| -> FunctionResult { + let mut ctx = FunctionCallContext { interpreter, output_span_range: span }; + let argument = Spanned(ArgumentValue::Owned(item.clone()), span); + let result = function.clone().invoke(vec![argument], &mut ctx)?; + let owned = RequestedOwnership::owned() + .map_from_returned(result)? + .0 + .expect_owned(); + let keep: bool = owned + .spanned(span) + .resolve_as("The result of a filter predicate")?; + Ok(keep) + }; + IteratorValue::new(Box::new(FilterIterator::new(inner, f))) } } unary_operations { - [context] fn cast_singleton_to_value(Spanned(this, span): Spanned) -> ExecutionResult { - match this.singleton_value() { - Some(value) => Ok(context.operation.evaluate(Spanned(value, span))?.0), + [context] fn cast_singleton_to_value(Spanned(this, span): Spanned) -> FunctionResult { + match this.singleton_value(context.interpreter)? { + Some(value) => Ok(context.operation.evaluate(Spanned(value, span), context.interpreter)?.0), None => span.value_err("Only an iterator with one item can be cast to this value"), } } diff --git a/src/expressions/values/none.rs b/src/expressions/values/none.rs index 143c4002..0f79e10a 100644 --- a/src/expressions/values/none.rs +++ b/src/expressions/values/none.rs @@ -14,7 +14,7 @@ impl ResolvableArgumentTarget for () { } impl ResolvableOwned for () { - fn resolve_from_value(value: AnyValue, context: ResolutionContext) -> ExecutionResult { + fn resolve_from_value(value: AnyValue, context: ResolutionContext) -> FunctionResult { match value { AnyValue::None(_) => Ok(()), other => context.err("None", other), diff --git a/src/expressions/values/object.rs b/src/expressions/values/object.rs index d51c4841..aa8dd59b 100644 --- a/src/expressions/values/object.rs +++ b/src/expressions/values/object.rs @@ -8,11 +8,11 @@ define_leaf_type! { articled_value_name: "an object", dyn_impls: { IterableType: impl IsIterable { - fn into_iterator(self: Box) -> ExecutionResult { + fn into_iterator(self: Box) -> FunctionResult { Ok(IteratorValue::new_for_object(*self)) } - fn len(&self, _error_span_range: SpanRange) -> ExecutionResult { + fn iterable_len(&self, _error_span_range: SpanRange) -> FunctionResult { Ok(self.entries.len()) } } @@ -42,12 +42,12 @@ pub(crate) struct ObjectEntry { } impl ObjectValue { - pub(super) fn into_indexed(mut self, index: Spanned) -> ExecutionResult { + pub(super) fn into_indexed(mut self, index: Spanned) -> FunctionResult { let key = index.downcast_resolve("An object key")?; Ok(self.remove_or_none(key)) } - pub(super) fn into_property(mut self, access: &PropertyAccess) -> ExecutionResult { + pub(super) fn into_property(mut self, access: &PropertyAccess) -> FunctionResult { let key = access.property.to_string(); Ok(self.remove_or_none(&key)) } @@ -76,12 +76,12 @@ impl ObjectValue { &mut self, index: Spanned, auto_create: bool, - ) -> ExecutionResult<&mut AnyValue> { + ) -> 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) -> ExecutionResult<&AnyValue> { + 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), @@ -93,14 +93,14 @@ impl ObjectValue { &mut self, access: &PropertyAccess, auto_create: bool, - ) -> ExecutionResult<&mut AnyValue> { + ) -> FunctionResult<&mut AnyValue> { self.mut_entry( access.property.to_string().spanned(access.property.span()), auto_create, ) } - pub(super) fn property_ref(&self, access: &PropertyAccess) -> ExecutionResult<&AnyValue> { + pub(super) fn property_ref(&self, access: &PropertyAccess) -> FunctionResult<&AnyValue> { let key = access.property.to_string(); match self.entries.get(&key) { Some(entry) => Ok(&entry.value), @@ -112,7 +112,7 @@ impl ObjectValue { &mut self, Spanned(key, key_span): Spanned, auto_create: bool, - ) -> ExecutionResult<&mut AnyValue> { + ) -> FunctionResult<&mut AnyValue> { use std::collections::btree_map::*; Ok(match self.entries.entry(key) { Entry::Occupied(entry) => &mut entry.into_mut().value, @@ -138,7 +138,8 @@ impl ObjectValue { &self, output: &mut String, behaviour: &ConcatBehaviour, - ) -> ExecutionResult<()> { + interpreter: &mut Interpreter, + ) -> FunctionResult<()> { if !behaviour.use_debug_literal_syntax { return behaviour .error_span_range @@ -176,7 +177,7 @@ impl ObjectValue { entry .value .as_ref_value() - .concat_recursive_into(output, behaviour)?; + .concat_recursive_into(output, behaviour, interpreter)?; is_first = false; } if behaviour.output_literal_structure { @@ -214,7 +215,7 @@ impl ValuesEqual for ObjectValue { } impl Spanned<&ObjectValue> { - pub(crate) fn validate(&self, validation: &impl ObjectValidate) -> ExecutionResult<()> { + pub(crate) fn validate(&self, validation: &impl ObjectValidate) -> FunctionResult<()> { let mut missing_fields = Vec::new(); for (field_name, _) in validation.required_fields() { match self.entries.get(field_name) { @@ -275,11 +276,11 @@ define_type_features! { impl ObjectType, pub(crate) mod object_interface { methods { - [context] fn zip(this: ObjectValue) -> ExecutionResult { + [context] fn zip(this: ObjectValue) -> FunctionResult { ZipIterators::new_from_object(this, context.span_range())?.run_zip(context.interpreter, true) } - [context] fn zip_truncated(this: ObjectValue) -> ExecutionResult { + [context] fn zip_truncated(this: ObjectValue) -> FunctionResult { ZipIterators::new_from_object(this, context.span_range())?.run_zip(context.interpreter, false) } } diff --git a/src/expressions/values/parser.rs b/src/expressions/values/parser.rs index b5175d1c..1ad47e6f 100644 --- a/src/expressions/values/parser.rs +++ b/src/expressions/values/parser.rs @@ -53,16 +53,16 @@ impl Spanned> { pub(crate) fn parser<'i>( &self, interpreter: &'i mut Interpreter, - ) -> ExecutionResult> { + ) -> FunctionResult> { let Spanned(handle, span) = self; Ok(interpreter.parser(**handle, *span)?) } - pub(crate) fn parse_with( + pub(crate) fn parse_with( &self, interpreter: &mut Interpreter, - f: impl FnOnce(&mut Interpreter) -> ExecutionResult, - ) -> ExecutionResult { + f: impl FnOnce(&mut Interpreter) -> Result, + ) -> Result { let Spanned(handle, _) = self; interpreter.parse_with(**handle, f) } @@ -71,7 +71,7 @@ impl Spanned> { fn parser<'a>( this: Spanned>, context: &'a mut FunctionCallContext, -) -> ExecutionResult> { +) -> FunctionResult> { this.parser(context.interpreter) } @@ -82,12 +82,12 @@ define_type_features! { // GENERAL // ======= - [context] fn is_end(this: Spanned>) -> ExecutionResult { + [context] fn is_end(this: Spanned>) -> FunctionResult { Ok(parser(this, context)?.is_empty()) } // Asserts that the parser has reached the end of input - [context] fn end(this: Spanned>) -> ExecutionResult<()> { + [context] fn end(this: Spanned>) -> FunctionResult<()> { let parser = parser(this, context)?; match parser.is_empty() { true => Ok(()), @@ -95,37 +95,37 @@ define_type_features! { } } - [context] fn token_tree(this: Spanned>) -> ExecutionResult { + [context] fn token_tree(this: Spanned>) -> FunctionResult { Ok(parser(this, context)?.parse()?) } - [context] fn ident(this: Spanned>) -> ExecutionResult { + [context] fn ident(this: Spanned>) -> FunctionResult { Ok(parser(this, context)?.parse()?) } - [context] fn any_ident(this: Spanned>) -> ExecutionResult { + [context] fn any_ident(this: Spanned>) -> FunctionResult { Ok(parser(this, context)?.parse_any_ident()?) } - [context] fn punct(this: Spanned>) -> ExecutionResult { + [context] fn punct(this: Spanned>) -> FunctionResult { Ok(parser(this, context)?.parse()?) } - [context] fn read(this: Spanned>, parse_template: AnyRef) -> ExecutionResult { + [context] fn read(this: Spanned>, parse_template: AnyRef) -> FunctionResult { let this = parser(this, context)?; let mut output = OutputStream::new(); parse_template.parse_exact_match(this, &mut output)?; Ok(output) } - [context] fn rest(this: Spanned>) -> ExecutionResult { + [context] fn rest(this: Spanned>) -> FunctionResult { let input = parser(this, context)?; let mut output = OutputStream::new(); ParseUntil::End.handle_parse_into(input, &mut output)?; Ok(output) } - [context] fn until(this: Spanned>, until: OutputStream) -> ExecutionResult { + [context] fn until(this: Spanned>, until: OutputStream) -> FunctionResult { let input = parser(this, context)?; let until: ParseUntil = until.parse_as()?; let mut output = OutputStream::new(); @@ -133,7 +133,7 @@ define_type_features! { Ok(output) } - [context] fn error(this: Spanned>, message: String) -> ExecutionResult<()> { + [context] fn error(this: Spanned>, message: String) -> FunctionResult<()> { let parser = parser(this, context)?; parser.parse_err(message).map_err(|e| e.into()) } @@ -143,9 +143,9 @@ define_type_features! { // Opens a group with the specified delimiter character ('(', '{', or '['). // Must be paired with `close`. - [context] fn open(this: Spanned>, Spanned(delimiter_char, char_span): Spanned) -> ExecutionResult<()> { + [context] fn open(this: Spanned>, Spanned(delimiter_char, char_span): Spanned) -> FunctionResult<()> { let delimiter = delimiter_from_open_char(delimiter_char) - .ok_or_else(|| char_span.value_error::(format!( + .ok_or_else(|| char_span.value_error::(format!( "Invalid open delimiter '{}'. Expected '(', '{{', or '['", delimiter_char )))?; this.parse_with(context.interpreter, |interpreter| { @@ -156,15 +156,15 @@ define_type_features! { // Closes the current group. Must be paired with a prior `open`. // The close character must match: ')' for '(', '}' for '{', ']' for '[' - [context] fn close(this: Spanned>, Spanned(delimiter_char, char_span): Spanned) -> ExecutionResult<()> { + [context] fn close(this: Spanned>, Spanned(delimiter_char, char_span): Spanned) -> FunctionResult<()> { let expected_delimiter = delimiter_from_close_char(delimiter_char) - .ok_or_else(|| char_span.value_error::(format!( + .ok_or_else(|| char_span.value_error::(format!( "Invalid close delimiter '{}'. Expected ')', '}}', or ']'", delimiter_char )))?; this.parse_with(context.interpreter, |interpreter| { // Check if there's a group to close first if !interpreter.has_active_input_group() { - return Err(char_span.value_error::(format!( + return Err(char_span.value_error::(format!( "attempting to close '{}' isn't valid, because there is no open group", expected_delimiter.description_of_close() ))); @@ -182,72 +182,72 @@ define_type_features! { // LITERALS // ======== - [context] fn is_literal(this: Spanned>) -> ExecutionResult { + [context] fn is_literal(this: Spanned>) -> FunctionResult { Ok(parser(this, context)?.cursor().literal().is_some()) } - [context] fn literal(this: Spanned>) -> ExecutionResult { + [context] fn literal(this: Spanned>) -> FunctionResult { let literal = parser(this, context)?.parse()?; Ok(OutputStream::new_with(|s| s.push_literal(literal))) } - [context] fn inferred_literal(this: Spanned>) -> ExecutionResult { + [context] fn inferred_literal(this: Spanned>) -> FunctionResult { let literal = parser(this, context)?.parse()?; Ok(AnyValue::for_literal(literal).into_any_value()) } - [context] fn is_char(this: Spanned>) -> ExecutionResult { + [context] fn is_char(this: Spanned>) -> FunctionResult { Ok(parser(this, context)?.peek(syn::LitChar)) } - [context] fn char_literal(this: Spanned>) -> ExecutionResult { + [context] fn char_literal(this: Spanned>) -> FunctionResult { let char: syn::LitChar = parser(this, context)?.parse()?; Ok(OutputStream::new_with(|s| s.push_tokens(char))) } - [context] fn char(this: Spanned>) -> ExecutionResult { + [context] fn char(this: Spanned>) -> FunctionResult { let char: syn::LitChar = parser(this, context)?.parse()?; Ok(char.value()) } - [context] fn is_string(this: Spanned>) -> ExecutionResult { + [context] fn is_string(this: Spanned>) -> FunctionResult { Ok(parser(this, context)?.peek(syn::LitStr)) } - [context] fn string_literal(this: Spanned>) -> ExecutionResult { + [context] fn string_literal(this: Spanned>) -> FunctionResult { let string: syn::LitStr = parser(this, context)?.parse()?; Ok(OutputStream::new_with(|s| s.push_tokens(string))) } - [context] fn string(this: Spanned>) -> ExecutionResult { + [context] fn string(this: Spanned>) -> FunctionResult { let string: syn::LitStr = parser(this, context)?.parse()?; Ok(string.value()) } - [context] fn is_integer(this: Spanned>) -> ExecutionResult { + [context] fn is_integer(this: Spanned>) -> FunctionResult { Ok(parser(this, context)?.peek(syn::LitInt)) } - [context] fn integer_literal(this: Spanned>) -> ExecutionResult { + [context] fn integer_literal(this: Spanned>) -> FunctionResult { let integer: syn::LitInt = parser(this, context)?.parse()?; Ok(OutputStream::new_with(|s| s.push_tokens(integer))) } - [context] fn integer(this: Spanned>) -> ExecutionResult { + [context] fn integer(this: Spanned>) -> FunctionResult { let integer: syn::LitInt = parser(this, context)?.parse()?; Ok(IntegerValue::for_litint(&integer)?) } - [context] fn is_float(this: Spanned>) -> ExecutionResult { + [context] fn is_float(this: Spanned>) -> FunctionResult { Ok(parser(this, context)?.peek(syn::LitFloat)) } - [context] fn float_literal(this: Spanned>) -> ExecutionResult { + [context] fn float_literal(this: Spanned>) -> FunctionResult { let float: syn::LitFloat = parser(this, context)?.parse()?; Ok(OutputStream::new_with(|s| s.push_tokens(float))) } - [context] fn float(this: Spanned>) -> ExecutionResult { + [context] fn float(this: Spanned>) -> FunctionResult { let float: syn::LitFloat = parser(this, context)?.parse()?; Ok(FloatValue::for_litfloat(&float)?) } diff --git a/src/expressions/values/range.rs b/src/expressions/values/range.rs index 656238aa..c9179601 100644 --- a/src/expressions/values/range.rs +++ b/src/expressions/values/range.rs @@ -10,11 +10,11 @@ define_leaf_type! { articled_value_name: "a range", dyn_impls: { IterableType: impl IsIterable { - fn into_iterator(self: Box) -> ExecutionResult { + fn into_iterator(self: Box) -> FunctionResult { IteratorValue::new_for_range(*self) } - fn len(&self, error_span_range: SpanRange) -> ExecutionResult { + fn iterable_len(&self, error_span_range: SpanRange) -> FunctionResult { self.len(error_span_range) } } @@ -27,24 +27,27 @@ pub(crate) struct RangeValue { } impl RangeValue { - pub(crate) fn len(&self, error_span_range: SpanRange) -> ExecutionResult { - IteratorValue::new_for_range(self.clone())?.len(error_span_range) + pub(crate) fn len(&self, error_span_range: SpanRange) -> FunctionResult { + IteratorValue::new_for_range(self.clone())?.do_len(error_span_range) } pub(crate) fn concat_recursive_into( &self, output: &mut String, behaviour: &ConcatBehaviour, - ) -> ExecutionResult<()> { + interpreter: &mut Interpreter, + ) -> FunctionResult<()> { if !behaviour.use_debug_literal_syntax { - return IteratorValue::any_iterator_to_string( - self.clone().inner.into_iterable()?.resolve_iterator()?, + let mut iter = IteratorValue::new_for_range(self.clone())?; + return any_items_to_string( + &mut iter, output, behaviour, "[]", "[ ", "]", true, + interpreter, ); } match &*self.inner { @@ -53,27 +56,35 @@ impl RangeValue { end_exclusive, .. } => { - start_inclusive - .as_ref_value() - .concat_recursive_into(output, behaviour)?; + start_inclusive.as_ref_value().concat_recursive_into( + output, + behaviour, + interpreter, + )?; output.push_str(".."); - end_exclusive - .as_ref_value() - .concat_recursive_into(output, behaviour)?; + end_exclusive.as_ref_value().concat_recursive_into( + output, + behaviour, + interpreter, + )?; } RangeValueInner::RangeFrom { start_inclusive, .. } => { - start_inclusive - .as_ref_value() - .concat_recursive_into(output, behaviour)?; + start_inclusive.as_ref_value().concat_recursive_into( + output, + behaviour, + interpreter, + )?; output.push_str(".."); } RangeValueInner::RangeTo { end_exclusive, .. } => { output.push_str(".."); - end_exclusive - .as_ref_value() - .concat_recursive_into(output, behaviour)?; + end_exclusive.as_ref_value().concat_recursive_into( + output, + behaviour, + interpreter, + )?; } RangeValueInner::RangeFull { .. } => { output.push_str(".."); @@ -83,19 +94,25 @@ impl RangeValue { end_inclusive, .. } => { - start_inclusive - .as_ref_value() - .concat_recursive_into(output, behaviour)?; + start_inclusive.as_ref_value().concat_recursive_into( + output, + behaviour, + interpreter, + )?; output.push_str("..="); - end_inclusive - .as_ref_value() - .concat_recursive_into(output, behaviour)?; + end_inclusive.as_ref_value().concat_recursive_into( + output, + behaviour, + interpreter, + )?; } RangeValueInner::RangeToInclusive { end_inclusive, .. } => { output.push_str("..="); - end_inclusive - .as_ref_value() - .concat_recursive_into(output, behaviour)?; + end_inclusive.as_ref_value().concat_recursive_into( + output, + behaviour, + interpreter, + )?; } } Ok(()) @@ -106,7 +123,7 @@ impl Spanned<&RangeValue> { pub(crate) fn resolve_to_index_range( self, array: &ArrayValue, - ) -> ExecutionResult> { + ) -> FunctionResult> { let Spanned(value, span_range) = self; let mut start = 0; let mut end = array.items.len(); @@ -325,7 +342,7 @@ impl RangeValueInner { } } - pub(super) fn into_iterable(self) -> ExecutionResult> { + pub(super) fn into_iterable(self) -> FunctionResult> { Ok(match self { Self::Range { start_inclusive, @@ -399,9 +416,9 @@ define_type_features! { impl RangeType, pub(crate) mod range_interface { unary_operations { - [context] fn cast_via_iterator(Spanned(this, span): Spanned) -> ExecutionResult { + [context] fn cast_via_iterator(Spanned(this, span): Spanned) -> FunctionResult { let this_iterator = IteratorValue::new_for_range(this)?; - Ok(context.operation.evaluate(Spanned(this_iterator, span))?.0) + Ok(context.operation.evaluate(Spanned(this_iterator, span), context.interpreter)?.0) } } interface_items { @@ -437,7 +454,7 @@ fn resolve_range + ResolvableRange>( start: T, dots: syn::RangeLimits, end: Option>, -) -> ExecutionResult>> { +) -> FunctionResult { let definition = match (end, dots) { (Some(end), dots) => { let end = end.resolve_as("The end of this range bound")?; @@ -452,15 +469,11 @@ fn resolve_range + ResolvableRange>( } trait ResolvableRange: Sized { - fn resolve( - definition: IterableRangeOf, - ) -> ExecutionResult>>; + fn resolve(definition: IterableRangeOf) -> FunctionResult; } impl IterableRangeOf { - pub(super) fn resolve_iterator( - self, - ) -> ExecutionResult>> { + pub(super) fn resolve_iterator(self) -> FunctionResult { let (start, dots, end) = match self { Self::RangeFromTo { start, dots, end } => { (start, dots, Some(end.spanned(dots.span_range()))) @@ -500,7 +513,7 @@ impl IterableRangeOf { impl ResolvableRange for UntypedInteger { fn resolve( definition: IterableRangeOf, - ) -> ExecutionResult>> { + ) -> FunctionResult { match definition { IterableRangeOf::RangeFromTo { start, dots, end } => { let start = start.into_fallback(); @@ -531,7 +544,7 @@ macro_rules! define_range_resolvers { $($the_type:ident),* $(,)? ) => {$( impl ResolvableRange for $the_type { - fn resolve(definition: IterableRangeOf) -> ExecutionResult>> { + fn resolve(definition: IterableRangeOf) -> FunctionResult { match definition { IterableRangeOf::RangeFromTo { start, dots, end } => { Ok(match dots { diff --git a/src/expressions/values/stream.rs b/src/expressions/values/stream.rs index 8076f66e..4fa466c3 100644 --- a/src/expressions/values/stream.rs +++ b/src/expressions/values/stream.rs @@ -8,11 +8,11 @@ define_leaf_type! { articled_value_name: "a stream", dyn_impls: { IterableType: impl IsIterable { - fn into_iterator(self: Box) -> ExecutionResult { + fn into_iterator(self: Box) -> FunctionResult { Ok(IteratorValue::new_for_stream(*self)) } - fn len(&self, _error_span_range: SpanRange) -> ExecutionResult { + fn iterable_len(&self, _error_span_range: SpanRange) -> FunctionResult { Ok(self.len()) } } @@ -132,7 +132,7 @@ define_type_features! { this.is_empty() } - fn flatten(this: OutputStream) -> ExecutionResult { + fn flatten(this: OutputStream) -> FunctionResult { Ok(this.to_token_stream_removing_any_transparent_groups()) } @@ -142,39 +142,39 @@ define_type_features! { OutputStream::raw(this.to_token_stream_removing_any_transparent_groups()) } - fn infer(this: OutputStream) -> ExecutionResult { + fn infer(this: OutputStream) -> FunctionResult { Ok(this.coerce_into_value()) } - fn split(this: OutputStream, separator: AnyRef, settings: Option) -> ExecutionResult { + fn split(this: OutputStream, separator: AnyRef, settings: Option) -> FunctionResult { handle_split(this, &separator, settings.unwrap_or_default()) } // STRING-BASED CONVERSION METHODS // =============================== - [context] fn to_ident(this: Spanned>) -> ExecutionResult { + [context] fn to_ident(this: Spanned>) -> FunctionResult { let string = this.concat_content(&ConcatBehaviour::standard(this.span_range())); string_interface::methods::to_ident(context, string.into_spanned_ref(this.span_range())) } - [context] fn to_ident_camel(this: Spanned>) -> ExecutionResult { + [context] fn to_ident_camel(this: Spanned>) -> FunctionResult { let string = this.concat_content(&ConcatBehaviour::standard(this.span_range())); string_interface::methods::to_ident_camel(context, string.into_spanned_ref(this.span_range())) } - [context] fn to_ident_snake(this: Spanned>) -> ExecutionResult { + [context] fn to_ident_snake(this: Spanned>) -> FunctionResult { let string = this.concat_content(&ConcatBehaviour::standard(this.span_range())); string_interface::methods::to_ident_snake(context, string.into_spanned_ref(this.span_range())) } - [context] fn to_ident_upper_snake(this: Spanned>) -> ExecutionResult { + [context] fn to_ident_upper_snake(this: Spanned>) -> FunctionResult { let string = this.concat_content(&ConcatBehaviour::standard(this.span_range())); string_interface::methods::to_ident_upper_snake(context, string.into_spanned_ref(this.span_range())) } // Some literals become Value::UnsupportedLiteral but can still be round-tripped back to a stream - [context] fn to_literal(this: Spanned>) -> ExecutionResult { + [context] fn to_literal(this: Spanned>) -> FunctionResult { let string = this.concat_content(&ConcatBehaviour::literal(this.span_range())); let literal = string_interface::methods::to_literal(context, string.into_spanned_ref(this.span_range()))?; Ok(AnyValue::for_literal(literal).into_any_value()) @@ -184,18 +184,18 @@ define_type_features! { // ============ // NOTE: with_span() exists on all values, this is just a specialized mutable version for streams - fn set_span(mut this: Mutable, span_source: AnyRef) -> ExecutionResult<()> { + fn set_span(mut this: Mutable, span_source: AnyRef) -> FunctionResult<()> { let span_range = span_source.resolve_content_span_range().unwrap_or(Span::call_site().span_range()); this.replace_first_level_spans(span_range.join_into_span_else_start()); Ok(()) } - fn error(this: AnyRef, message: AnyRef) -> ExecutionResult { + fn error(this: AnyRef, message: AnyRef) -> FunctionResult { let error_span_range = this.resolve_content_span_range().unwrap_or(Span::call_site().span_range()); error_span_range.assertion_err(message.as_str()) } - fn assert(this: AnyRef, condition: bool, message: Option>) -> ExecutionResult<()> { + fn assert(this: AnyRef, condition: bool, message: Option>) -> FunctionResult<()> { if condition { Ok(()) } else { @@ -208,7 +208,7 @@ define_type_features! { } } - fn assert_eq(this: AnyRef, lhs: Spanned, rhs: Spanned, message: Option>) -> ExecutionResult<()> { + [context] fn assert_eq(this: AnyRef, lhs: Spanned, rhs: Spanned, message: Option>) -> FunctionResult<()> { match AnyValueRef::debug_eq(&lhs.as_ref_value(), &rhs.as_ref_value()) { Ok(()) => Ok(()), Err(debug_error) => { @@ -218,8 +218,8 @@ define_type_features! { None => format!( "Assertion failed: {}\n lhs = {}\n rhs = {}", debug_error.format_message(), - lhs.as_ref_value().concat_recursive(&ConcatBehaviour::debug(lhs.span_range()))?, - rhs.as_ref_value().concat_recursive(&ConcatBehaviour::debug(rhs.span_range()))?, + lhs.as_ref_value().concat_recursive(&ConcatBehaviour::debug(lhs.span_range()), context.interpreter)?, + rhs.as_ref_value().concat_recursive(&ConcatBehaviour::debug(rhs.span_range()), context.interpreter)?, ), }; error_span_range.assertion_err(message) @@ -227,36 +227,39 @@ define_type_features! { } } - [context] fn reinterpret_as_run(Spanned(this, span_range): Spanned) -> ExecutionResult { + [context] fn reinterpret_as_run(Spanned(this, span_range): Spanned) -> FunctionResult { let source = this.into_token_stream(); let (reparsed, scope_definitions) = source.source_parse_and_analyze(ExpressionBlockContent::parse, ExpressionBlockContent::control_flow_pass)?; let mut inner_interpreter = Interpreter::new(scope_definitions); - let return_value = reparsed.evaluate_spanned(&mut inner_interpreter, span_range, RequestedOwnership::owned())?.expect_owned(); + let return_value = reparsed.evaluate_spanned(&mut inner_interpreter, span_range, RequestedOwnership::owned()) + .expect_no_interrupts()? + .expect_owned(); if !inner_interpreter.complete().is_empty() { return context.control_flow_err("reinterpret_as_run does not allow non-empty emit output") } Ok(return_value.0) } - fn reinterpret_as_stream(Spanned(this, span_range): Spanned) -> ExecutionResult { + fn reinterpret_as_stream(Spanned(this, span_range): Spanned) -> FunctionResult { let source = this.into_token_stream(); let (reparsed, scope_definitions) = source.source_parse_and_analyze( |input| SourceStream::parse_with_span(input, span_range.span_from_join_else_start()), SourceStream::control_flow_pass, )?; let mut inner_interpreter = Interpreter::new(scope_definitions); - reparsed.interpret(&mut inner_interpreter)?; + reparsed.output_to_stream(&mut OutputInterpreter::new_unchecked(&mut inner_interpreter)) + .expect_no_interrupts()?; Ok(inner_interpreter.complete()) } } unary_operations { - [context] fn cast_coerced_to_value(Spanned(this, span): Spanned) -> ExecutionResult { + [context] fn cast_coerced_to_value(Spanned(this, span): Spanned) -> FunctionResult { let coerced = this.coerce_into_value(); if let AnyValue::Stream(_) = &coerced { return span.value_err("The stream could not be coerced into a single value"); } // Re-run the cast operation on the coerced value - Ok(context.operation.evaluate(Spanned(coerced, span))?.0) + Ok(context.operation.evaluate(Spanned(coerced, span), context.interpreter)?.0) } } binary_operations { @@ -329,13 +332,13 @@ impl ParseSource for StreamLiteral { } } -impl Interpret for StreamLiteral { - fn interpret(&self, interpreter: &mut Interpreter) -> ExecutionResult<()> { +impl OutputToStream for StreamLiteral { + fn output_to_stream(&self, output: &mut OutputInterpreter) -> ExecutionResult<()> { match self { - StreamLiteral::Regular(lit) => lit.interpret(interpreter), - StreamLiteral::Raw(lit) => lit.interpret(interpreter), - StreamLiteral::Grouped(lit) => lit.interpret(interpreter), - StreamLiteral::Concatenated(lit) => lit.interpret(interpreter), + StreamLiteral::Regular(lit) => lit.output_to_stream(output), + StreamLiteral::Raw(lit) => lit.output_to_stream(output), + StreamLiteral::Grouped(lit) => lit.output_to_stream(output), + StreamLiteral::Concatenated(lit) => lit.output_to_stream(output), } } } @@ -374,9 +377,9 @@ impl ParseSource for RegularStreamLiteral { } } -impl Interpret for RegularStreamLiteral { - fn interpret(&self, interpreter: &mut Interpreter) -> ExecutionResult<()> { - self.content.interpret(interpreter) +impl OutputToStream for RegularStreamLiteral { + fn output_to_stream(&self, output: &mut OutputInterpreter) -> ExecutionResult<()> { + self.content.output_to_stream(output) } } @@ -412,11 +415,9 @@ impl ParseSource for RawStreamLiteral { } } -impl Interpret for RawStreamLiteral { - fn interpret(&self, interpreter: &mut Interpreter) -> ExecutionResult<()> { - interpreter - .output(self)? - .extend_raw_tokens(self.content.clone()); +impl OutputToStream for RawStreamLiteral { + fn output_to_stream(&self, output: &mut OutputInterpreter) -> ExecutionResult<()> { + output.extend_raw_tokens(self.content.clone()); Ok(()) } } @@ -453,10 +454,10 @@ impl ParseSource for GroupedStreamLiteral { } } -impl Interpret for GroupedStreamLiteral { - fn interpret(&self, interpreter: &mut Interpreter) -> ExecutionResult<()> { - interpreter.in_output_group(Delimiter::None, self.brackets.span(), |interpreter| { - self.content.interpret(interpreter) +impl OutputToStream for GroupedStreamLiteral { + fn output_to_stream(&self, output: &mut OutputInterpreter) -> ExecutionResult<()> { + output.in_output_group(Delimiter::None, self.brackets.span(), |output| { + self.content.output_to_stream(output) }) } } @@ -516,10 +517,9 @@ impl ParseSource for ConcatenatedStreamLiteral { } } -impl Interpret for ConcatenatedStreamLiteral { - fn interpret(&self, interpreter: &mut Interpreter) -> ExecutionResult<()> { - let stream = - interpreter.capture_output(|interpreter| self.content.interpret(interpreter))?; +impl OutputToStream for ConcatenatedStreamLiteral { + fn output_to_stream(&self, output: &mut OutputInterpreter) -> ExecutionResult<()> { + let stream = output.capture_output(|output| self.content.output_to_stream(output))?; let string = stream.concat_content(&ConcatBehaviour::standard(self.span_range())); let ident_span = stream .resolve_content_span_range() @@ -550,8 +550,8 @@ impl Interpret for ConcatenatedStreamLiteral { }; value.as_ref_value().output_to( Grouping::Flattened, - &mut ToStreamContext::new(interpreter.output(self)?, self.span_range()), - ) + &mut ToStreamContext::new(output, self.span_range()), + ).into_execution_result() } } diff --git a/src/expressions/values/string.rs b/src/expressions/values/string.rs index 03409410..3b562d57 100644 --- a/src/expressions/values/string.rs +++ b/src/expressions/values/string.rs @@ -8,11 +8,11 @@ define_leaf_type! { articled_value_name: "a string", dyn_impls: { IterableType: impl IsIterable { - fn into_iterator(self: Box) -> ExecutionResult { + fn into_iterator(self: Box) -> FunctionResult { Ok(IteratorValue::new_for_string_over_chars(*self)) } - fn len(&self, _error_span_range: SpanRange) -> ExecutionResult { + fn iterable_len(&self, _error_span_range: SpanRange) -> FunctionResult { // The iterator is over chars, so this must count chars. // But contrast, string.len() counts bytes Ok(self.chars().count()) @@ -46,10 +46,10 @@ pub(crate) fn string_to_ident( str: &str, error_source: &impl HasSpanRange, span: Span, -) -> ExecutionResult { +) -> FunctionResult { let ident = parse_str::(str).map_err(|err| { error_source - .value_error::(format!("`{}` is not a valid ident: {:?}", str, err)) + .value_error::(format!("`{}` is not a valid ident: {:?}", str, err)) })?; Ok(ident.with_span(span)) } @@ -58,9 +58,9 @@ pub(crate) fn string_to_literal( str: &str, error_source: &impl HasSpanRange, span: Span, -) -> ExecutionResult { +) -> FunctionResult { let literal = Literal::from_str(str).map_err(|err| { - error_source.value_error::(format!( + error_source.value_error::(format!( "`{}` is not a valid literal: {:?}", str, err )) @@ -75,26 +75,26 @@ define_type_features! { // ================== // CONVERSION METHODS // ================== - [context] fn to_ident(this: Spanned>) -> ExecutionResult { + [context] fn to_ident(this: Spanned>) -> FunctionResult { string_to_ident(&this, &this, context.span_from_join_else_start()) } - [context] fn to_ident_camel(this: Spanned>) -> ExecutionResult { + [context] fn to_ident_camel(this: Spanned>) -> FunctionResult { let str = string_conversion::to_upper_camel_case(&this); string_to_ident(&str, &this, context.span_from_join_else_start()) } - [context] fn to_ident_snake(this: Spanned>) -> ExecutionResult { + [context] fn to_ident_snake(this: Spanned>) -> FunctionResult { let str = string_conversion::to_lower_snake_case(&this); string_to_ident(&str, &this, context.span_from_join_else_start()) } - [context] fn to_ident_upper_snake(this: Spanned>) -> ExecutionResult { + [context] fn to_ident_upper_snake(this: Spanned>) -> FunctionResult { let str = string_conversion::to_upper_snake_case(&this); string_to_ident(&str, &this, context.span_from_join_else_start()) } - [context] fn to_literal(this: Spanned>) -> ExecutionResult { + [context] fn to_literal(this: Spanned>) -> FunctionResult { string_to_literal(&this, &this, context.span_from_join_else_start()) } @@ -232,7 +232,7 @@ impl ResolvableShared for str { fn resolve_from_ref<'a>( value: &'a AnyValue, context: ResolutionContext, - ) -> ExecutionResult<&'a Self> { + ) -> FunctionResult<&'a Self> { match value { AnyValueContent::String(s) => Ok(s.as_str()), _ => context.err("a string", value), diff --git a/src/internal_prelude.rs b/src/internal_prelude.rs index 34a546b3..7fb94530 100644 --- a/src/internal_prelude.rs +++ b/src/internal_prelude.rs @@ -1,8 +1,7 @@ pub(crate) use proc_macro2::{extra::*, *}; pub(crate) use quote::ToTokens; pub(crate) use std::{ - borrow::Borrow, - borrow::Cow, + borrow::{Borrow, Cow}, cell::{Ref, RefCell, RefMut}, collections::{BTreeMap, HashMap, HashSet}, fmt::Debug, diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 92581209..efa92819 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -298,13 +298,13 @@ impl LateBoundValue { /// 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) -> ExecutionResult, + map_shared: impl FnOnce(AnyValueShared) -> FunctionResult, map_mutable: impl FnOnce( AnyValueMutable, ) - -> Result, - map_owned: impl FnOnce(AnyValueOwned) -> ExecutionResult, - ) -> ExecutionResult { + -> Result, + map_owned: impl FnOnce(AnyValueOwned) -> FunctionResult, + ) -> FunctionResult { Ok(match self { LateBoundValue::Owned(owned) => LateBoundValue::Owned(LateBoundOwnedValue { owned: map_owned(owned.owned)?, @@ -694,9 +694,9 @@ impl CopyOnWrite { pub(crate) fn map( self, - map_shared: impl FnOnce(Shared) -> ExecutionResult>, - map_owned: impl FnOnce(Owned) -> ExecutionResult>, - ) -> ExecutionResult> { + 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) => { diff --git a/src/interpretation/interpret_traits.rs b/src/interpretation/interpret_traits.rs index ccae002a..db4feaa1 100644 --- a/src/interpretation/interpret_traits.rs +++ b/src/interpretation/interpret_traits.rs @@ -1,8 +1,83 @@ use crate::internal_prelude::*; -// This trait isn't so important any more. It can probably be removed in future. -// It is typically used for things which output directly to the interpreter's output -// stream, rather than returning values. -pub(crate) trait Interpret: Sized { - fn interpret(&self, interpreter: &mut Interpreter) -> ExecutionResult<()>; +pub(crate) trait OutputToStream: Sized { + fn output_to_stream(&self, output: &mut OutputInterpreter) -> ExecutionResult<()>; +} + +/// A wrapper around `Interpreter` that is intended to make outputting to the +/// top-most output stream more convenient, performant and less error-prone. +pub(crate) struct OutputInterpreter<'a> { + interpreter: &'a mut Interpreter, +} + +impl<'a> OutputInterpreter<'a> { + /// Should only be used with a new Interpreter + pub(crate) fn new_unchecked(interpreter: &'a mut Interpreter) -> Self { + Self { interpreter } + } + + pub(crate) fn new_checked( + interpreter: &'a mut Interpreter, + span: &impl HasSpanRange, + ) -> ExecutionResult { + // Validate that output is not frozen, then drop the temporary reference + let _ = interpreter.output(span)?; + Ok(Self { interpreter }) + } + + /// Creates a new `OutputInterpreter` by reborrowing from an existing one. + pub(crate) fn reborrow(&mut self) -> OutputInterpreter<'_> { + OutputInterpreter { + interpreter: self.interpreter, + } + } + + /// Executes `f` with mutable access to the underlying `Interpreter`. + /// + /// The executed code must not change the height of the output stack or + /// leave the output in a frozen state — i.e. it should evaluate expressions + /// or resolve variables, not manipulate output buffers directly. + pub(crate) fn with_interpreter( + &mut self, + f: impl FnOnce(&mut Interpreter) -> Result, + ) -> Result { + let height_before = self.interpreter.output_stack_height(); + let result = f(self.interpreter); + assert_eq!( + self.interpreter.output_stack_height(), + height_before, + "OutputInterpreter::with_interpreter: closure must not change the output stack height" + ); + result + } + + pub(crate) fn capture_output( + &mut self, + f: impl FnOnce(&mut OutputInterpreter) -> Result<(), E>, + ) -> Result { + self.interpreter.capture_output(f) + } + + pub(crate) fn in_output_group( + &mut self, + delimiter: Delimiter, + span: Span, + f: impl FnOnce(&mut OutputInterpreter) -> Result<(), E>, + ) -> Result<(), E> { + self.interpreter.in_output_group(delimiter, span, f) + } +} + +impl Deref for OutputInterpreter<'_> { + type Target = OutputStream; + + fn deref(&self) -> &Self::Target { + self.interpreter.current_output_unchecked() + } +} + +impl DerefMut for OutputInterpreter<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.interpreter.current_output_mut_unchecked() + } } diff --git a/src/interpretation/interpreter.rs b/src/interpretation/interpreter.rs index 42f2226f..19868f66 100644 --- a/src/interpretation/interpreter.rs +++ b/src/interpretation/interpreter.rs @@ -299,11 +299,11 @@ impl Interpreter { } // Input - pub(crate) fn start_parse( + pub(crate) fn start_parse>( &mut self, stream: OutputStream, - f: impl FnOnce(&mut Interpreter, ParserHandle) -> ExecutionResult, - ) -> ExecutionResult { + f: impl FnOnce(&mut Interpreter, ParserHandle) -> Result, + ) -> Result { stream.parse_with(|input| { let handle = unsafe { // SAFETY: This is paired with `finish_parse` below, @@ -324,11 +324,11 @@ impl Interpreter { }) } - pub(crate) fn parse_with( + pub(crate) fn parse_with( &mut self, handle: ParserHandle, - f: impl FnOnce(&mut Interpreter) -> ExecutionResult, - ) -> ExecutionResult { + f: impl FnOnce(&mut Interpreter) -> Result, + ) -> Result { unsafe { // SAFETY: This is paired with `pop_current_handle` below, // without any early returns in the middle @@ -376,7 +376,7 @@ impl Interpreter { pub(crate) fn enter_input_group( &mut self, required_delimiter: Option, - ) -> ExecutionResult<(Delimiter, DelimSpan)> { + ) -> FunctionResult<(Delimiter, DelimSpan)> { self.input_handler .current_stack() .parse_and_enter_group(required_delimiter) @@ -388,7 +388,7 @@ impl Interpreter { pub(crate) fn exit_input_group( &mut self, expected_delimiter: Option, - ) -> ExecutionResult<()> { + ) -> FunctionResult<()> { self.input_handler .current_stack() .exit_group(expected_delimiter) @@ -405,20 +405,20 @@ impl Interpreter { } // Output - pub(crate) fn in_output_group( + pub(crate) fn in_output_group( &mut self, delimiter: Delimiter, span: Span, f: F, - ) -> ExecutionResult + ) -> Result where - F: FnOnce(&mut Interpreter) -> ExecutionResult, + F: FnOnce(&mut OutputInterpreter) -> Result, { unsafe { // SAFETY: This is paired with `finish_inner_buffer_as_group` self.output_handler.start_inner_buffer(); } - let result = f(self); + let result = f(&mut OutputInterpreter::new_unchecked(self)); unsafe { // SAFETY: This is paired with `start_inner_buffer`, // even if `f` returns an Err propogating a control flow interrupt. @@ -428,15 +428,15 @@ impl Interpreter { result } - pub(crate) fn capture_output(&mut self, f: F) -> ExecutionResult + pub(crate) fn capture_output(&mut self, f: F) -> Result where - F: FnOnce(&mut Interpreter) -> ExecutionResult<()>, + F: FnOnce(&mut OutputInterpreter) -> Result<(), E>, { unsafe { // SAFETY: This is paired with `finish_inner_buffer_as_separate_stream` self.output_handler.start_inner_buffer(); } - let result = f(self); + let result = f(&mut OutputInterpreter::new_unchecked(self)); let output = unsafe { // SAFETY: This is paired with `start_inner_buffer`, // even if `f` returns an Err propogating a control flow interrupt. @@ -446,6 +446,18 @@ impl Interpreter { Ok(output) } + pub(crate) fn output_stack_height(&self) -> usize { + self.output_handler.output_stack_height() + } + + pub(crate) fn current_output_unchecked(&self) -> &OutputStream { + self.output_handler.current_output_unchecked() + } + + pub(crate) fn current_output_mut_unchecked(&mut self) -> &mut OutputStream { + self.output_handler.current_output_mut_unchecked() + } + pub(crate) fn output( &mut self, span_source: &impl HasSpanRange, @@ -515,12 +527,12 @@ pub(crate) struct IterationCounter<'a, S: HasSpanRange> { } impl IterationCounter<'_, S> { - pub(crate) fn increment_and_check(&mut self) -> ExecutionResult<()> { + pub(crate) fn increment_and_check(&mut self) -> FunctionResult<()> { self.count += 1; self.check() } - pub(crate) fn check(&self) -> ExecutionResult<()> { + pub(crate) fn check(&self) -> FunctionResult<()> { if self.count > self.iteration_limit { return self.span_source.control_flow_err(format!("Iteration limit of {} exceeded.\nIf needed, the limit can be reconfigured with preinterpret::set_iteration_limit(XXX)", self.iteration_limit)); } diff --git a/src/interpretation/output_handler.rs b/src/interpretation/output_handler.rs index f10cdfa2..036f9842 100644 --- a/src/interpretation/output_handler.rs +++ b/src/interpretation/output_handler.rs @@ -94,6 +94,22 @@ impl OutputHandler { ); } + pub(super) fn output_stack_height(&self) -> usize { + self.output_stack.len() + } + + pub(super) fn current_output_unchecked(&self) -> &OutputStream { + self.output_stack + .last() + .expect("Output stack should never be empty") + } + + pub(super) fn current_output_mut_unchecked(&mut self) -> &mut OutputStream { + self.output_stack + .last_mut() + .expect("Output stack should never be empty") + } + fn validate_index(&self, index: usize) -> Result<(), OutputHandlerError> { if let Some(&(freeze_at_or_below_depth, reason)) = self.freeze_stack_indices_at_or_below.last() diff --git a/src/interpretation/output_parse_utilities.rs b/src/interpretation/output_parse_utilities.rs index cc03fdba..16da5a32 100644 --- a/src/interpretation/output_parse_utilities.rs +++ b/src/interpretation/output_parse_utilities.rs @@ -44,7 +44,7 @@ impl ParseUntil { &self, input: OutputParseStream, output: &mut OutputStream, - ) -> ExecutionResult<()> { + ) -> FunctionResult<()> { match self { ParseUntil::End => { let remaining = input.parse::()?; @@ -98,7 +98,7 @@ pub(crate) fn handle_parsing_exact_output_match( input: ParseStream, expected: &OutputStream, output: &mut OutputStream, -) -> ExecutionResult<()> { +) -> FunctionResult<()> { for item in expected.iter() { match item { OutputTokenTreeRef::TokenTree(token_tree) => { @@ -123,7 +123,7 @@ fn handle_parsing_exact_stream_match( input: ParseStream, expected: TokenStream, output: &mut OutputStream, -) -> ExecutionResult<()> { +) -> FunctionResult<()> { for item in expected.into_iter() { handle_parsing_exact_token_tree(input, &item, output)?; } @@ -134,7 +134,7 @@ fn handle_parsing_exact_token_tree( input: ParseStream, expected: &TokenTree, output: &mut OutputStream, -) -> ExecutionResult<()> { +) -> FunctionResult<()> { match expected { TokenTree::Group(group) => { handle_parsing_exact_group( @@ -162,9 +162,9 @@ fn handle_parsing_exact_token_tree( fn handle_parsing_exact_group( input: ParseStream, delimiter: Delimiter, - parse_inner: impl FnOnce(ParseStream, &mut OutputStream) -> ExecutionResult<()>, + parse_inner: impl FnOnce(ParseStream, &mut OutputStream) -> FunctionResult<()>, output: &mut OutputStream, -) -> ExecutionResult<()> { +) -> FunctionResult<()> { // Because `None` is ignored by Syn at parsing time, we can effectively be most permissive by ignoring them. // This removes a bit of a footgun for users. // If they really want to check for a None group, they can embed `@[GROUP @[EXACT ...]]` transformer. diff --git a/src/interpretation/output_stream.rs b/src/interpretation/output_stream.rs index aedc470c..26040093 100644 --- a/src/interpretation/output_stream.rs +++ b/src/interpretation/output_stream.rs @@ -61,10 +61,10 @@ impl OutputStream { pub(crate) fn push_grouped( &mut self, - appender: impl FnOnce(&mut Self) -> ExecutionResult<()>, + appender: impl FnOnce(&mut Self) -> FunctionResult<()>, delimiter: Delimiter, span: Span, - ) -> ExecutionResult<()> { + ) -> FunctionResult<()> { let mut inner = Self::new(); appender(&mut inner)?; self.push_new_group(inner, delimiter, span); @@ -131,10 +131,10 @@ impl OutputStream { /// WARNING: With rust-analyzer, this loses transparent groups which have been inserted. /// Use only where that doesn't matter: https://github.com/rust-lang/rust-analyzer/issues/18211#issuecomment-2604547032 - pub(crate) fn parse_with( + pub(crate) fn parse_with>( self, - parser: impl FnOnce(ParseStream) -> ExecutionResult, - ) -> ExecutionResult { + parser: impl FnOnce(ParseStream) -> Result, + ) -> Result { self.into_token_stream().interpreted_parse_with(parser) } @@ -274,7 +274,7 @@ impl OutputStream { } } - fn concat_recursive_token_stream>( + fn concat_recursive_token_stream>( behaviour: &ConcatBehaviour, output: &mut String, prefix_spacing: Spacing, @@ -340,7 +340,7 @@ impl OutputStream { &self, input: ParseStream, output: &mut OutputStream, - ) -> ExecutionResult<()> { + ) -> FunctionResult<()> { handle_parsing_exact_output_match(input, self, output) } diff --git a/src/interpretation/source_stream.rs b/src/interpretation/source_stream.rs index da680076..323d17aa 100644 --- a/src/interpretation/source_stream.rs +++ b/src/interpretation/source_stream.rs @@ -23,10 +23,10 @@ impl SourceStream { } } -impl Interpret for SourceStream { - fn interpret(&self, interpreter: &mut Interpreter) -> ExecutionResult<()> { +impl OutputToStream for SourceStream { + fn output_to_stream(&self, output: &mut OutputInterpreter) -> ExecutionResult<()> { for item in self.items.iter() { - item.interpret(interpreter)?; + item.output_to_stream(output)?; } Ok(()) } @@ -83,27 +83,25 @@ impl ParseSource for SourceItem { } } -impl Interpret for SourceItem { - fn interpret(&self, interpreter: &mut Interpreter) -> ExecutionResult<()> { +impl OutputToStream for SourceItem { + fn output_to_stream(&self, output: &mut OutputInterpreter) -> ExecutionResult<()> { match self { SourceItem::Variable(variable) => { - variable.interpret(interpreter)?; + variable.output_to_stream(output)?; } SourceItem::EmbeddedExpression(block) => { - block.interpret(interpreter)?; + block.output_to_stream(output)?; } SourceItem::EmbeddedStatements(statements) => { - statements.interpret(interpreter)?; + statements.output_to_stream(output)?; } SourceItem::SourceGroup(group) => { - group.interpret(interpreter)?; + group.output_to_stream(output)?; } - SourceItem::Punct(punct) => interpreter.output(punct)?.push_punct(punct.clone()), - SourceItem::Ident(ident) => interpreter.output(ident)?.push_ident(ident.clone()), - SourceItem::Literal(literal) => { - interpreter.output(literal)?.push_literal(literal.clone()) - } - SourceItem::StreamLiteral(stream_literal) => stream_literal.interpret(interpreter)?, + SourceItem::Punct(punct) => output.push_punct(punct.clone()), + SourceItem::Ident(ident) => output.push_ident(ident.clone()), + SourceItem::Literal(literal) => output.push_literal(literal.clone()), + SourceItem::StreamLiteral(stream_literal) => stream_literal.output_to_stream(output)?, } Ok(()) } @@ -147,12 +145,12 @@ impl ParseSource for SourceGroup { } } -impl Interpret for SourceGroup { - fn interpret(&self, interpreter: &mut Interpreter) -> ExecutionResult<()> { - interpreter.in_output_group( +impl OutputToStream for SourceGroup { + fn output_to_stream(&self, output: &mut OutputInterpreter) -> ExecutionResult<()> { + output.in_output_group( self.source_delimiter, self.source_delim_span.join(), - |interpreter| self.content.interpret(interpreter), + |output| self.content.output_to_stream(output), ) } } diff --git a/src/interpretation/variable.rs b/src/interpretation/variable.rs index 2d2dca38..a7800883 100644 --- a/src/interpretation/variable.rs +++ b/src/interpretation/variable.rs @@ -19,10 +19,10 @@ impl ParseSource for EmbeddedVariable { } } -impl Interpret for EmbeddedVariable { - fn interpret(&self, interpreter: &mut Interpreter) -> ExecutionResult<()> { +impl OutputToStream for EmbeddedVariable { + fn output_to_stream(&self, output: &mut OutputInterpreter) -> ExecutionResult<()> { self.reference - .substitute_into_output(interpreter, Grouping::Flattened) + .substitute_into_output(output, Grouping::Flattened) } } @@ -124,14 +124,14 @@ impl ParseSource for VariableReference { impl VariableReference { fn substitute_into_output( &self, - interpreter: &mut Interpreter, + output: &mut OutputInterpreter, grouping: Grouping, ) -> ExecutionResult<()> { - let value = self.resolve_shared(interpreter)?; + let value = output.with_interpreter(|i| self.resolve_shared(i))?; value.as_ref_value().output_to( grouping, - &mut ToStreamContext::new(interpreter.output(self)?, self.span_range()), - ) + &mut ToStreamContext::new(output, self.span_range()), + ).into_execution_result() } pub(crate) fn resolve_late_bound( diff --git a/src/lib.rs b/src/lib.rs index 967fd5cc..0278afe6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -431,7 +431,7 @@ fn preinterpret_stream_internal(input: TokenStream) -> SynResult { let mut interpreter = Interpreter::new(parse_state); stream - .interpret(&mut interpreter) + .output_to_stream(&mut OutputInterpreter::new_unchecked(&mut interpreter)) .expect_no_interrupts() .convert_to_final_result()?; @@ -461,11 +461,14 @@ fn preinterpret_run_internal(input: TokenStream) -> SynResult { let mut interpreter = Interpreter::new(parse_state); let entry_span = Span::call_site().span_range(); - let returned_stream = content + let returned_value = content .evaluate_spanned(&mut interpreter, entry_span, RequestedOwnership::owned()) - .and_then(|x| x.expect_owned().into_stream()) .expect_no_interrupts() .convert_to_final_result()?; + let returned_stream = returned_value + .expect_owned() + .into_stream(&mut interpreter) + .convert_to_final_result()?; let mut output_stream = interpreter.complete(); @@ -585,11 +588,14 @@ mod benchmarking { let output = context.time("evaluation", move || -> SynResult { let mut interpreter = Interpreter::new(scopes); let entry_span = Span::call_site().span_range(); - let returned_stream = parsed + let returned_value = parsed .evaluate_spanned(&mut interpreter, entry_span, RequestedOwnership::owned()) - .and_then(|x| x.expect_owned().into_stream()) .expect_no_interrupts() .convert_to_final_result()?; + let returned_stream = returned_value + .expect_owned() + .into_stream(&mut interpreter) + .convert_to_final_result()?; let mut output_stream = interpreter.complete(); diff --git a/src/misc/errors.rs b/src/misc/errors.rs index 80da0460..27c04977 100644 --- a/src/misc/errors.rs +++ b/src/misc/errors.rs @@ -132,14 +132,31 @@ impl FunctionError { ); FunctionError(interrupt) } + + /// Determines if the error can be caught when attempting to map a late-bound + /// mutable value, to retry as a shared value instead. + pub(crate) fn into_caught_mutable_map_attempt_error(self) -> Result { + match self.0.inner.as_ref() { + ExecutionInterruptInner::Error(ExecutionError(ErrorKind::Value, _)) => { + Ok(self.0.expect_error().convert_to_syn_error()) + } + _ => Err(self), + } + } } pub(crate) trait FunctionResultExt { + fn into_execution_result(self) -> ExecutionResult; + /// This is not a `From` because it wants to be explicit fn convert_to_final_result(self) -> syn::Result; } impl FunctionResultExt for FunctionResult { + fn into_execution_result(self) -> ExecutionResult { + self.map_err(|error| error.into()) + } + fn convert_to_final_result(self) -> syn::Result { self.map_err(|error| error.0.expect_error().convert_to_syn_error()) } @@ -227,17 +244,6 @@ impl ExecutionInterrupt { pub(crate) fn control_flow(control_flow: ControlFlowInterrupt) -> Self { Self::new(ExecutionInterruptInner::ControlFlowInterrupt(control_flow)) } - - /// Determines if the error can be caught when attempting to map a late-bound - /// mutable value, to retry as a shared value instead. - pub(crate) fn into_caught_mutable_map_attempt_error(self) -> Result { - match self.inner.as_ref() { - ExecutionInterruptInner::Error(ExecutionError(ErrorKind::Value, _)) => { - Ok(self.expect_error().convert_to_syn_error()) - } - _ => Err(self), - } - } } impl From for ExecutionInterrupt { @@ -246,6 +252,12 @@ impl From for ExecutionInterrupt { } } +impl From for FunctionError { + fn from(e: ParseError) -> Self { + FunctionError::new(ExecutionInterrupt::parse_error(e)) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(crate) enum ErrorKind { /// Some error with preinterpret syntax diff --git a/src/misc/field_inputs.rs b/src/misc/field_inputs.rs index 9d19385a..cdd9bb37 100644 --- a/src/misc/field_inputs.rs +++ b/src/misc/field_inputs.rs @@ -66,7 +66,7 @@ macro_rules! define_typed_object { impl IsArgument for $model { type ValueType = ObjectType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Owned; - fn from_argument(value: Spanned) -> ExecutionResult { + fn from_argument(value: Spanned) -> FunctionResult { Self::resolve_value( value.expect_owned(), "This argument", @@ -79,7 +79,7 @@ macro_rules! define_typed_object { } impl ResolvableOwned for $model { - fn resolve_from_value(value: AnyValue, context: ResolutionContext) -> ExecutionResult { + fn resolve_from_value(value: AnyValue, context: ResolutionContext) -> FunctionResult { Self::from_object_value(ObjectValue::resolve_spanned_from_value(value, context)?) } } @@ -105,7 +105,7 @@ macro_rules! define_typed_object { } impl $model { - fn from_object_value(Spanned(mut object, span_range): Spanned) -> ExecutionResult { + fn from_object_value(Spanned(mut object, span_range): Spanned) -> FunctionResult { (&object).spanned(span_range).validate(&Self::validation())?; Ok($model { $( diff --git a/src/misc/iterators.rs b/src/misc/iterators.rs index 2925a367..d369f4b6 100644 --- a/src/misc/iterators.rs +++ b/src/misc/iterators.rs @@ -1,21 +1,381 @@ use super::*; -impl ClonableIterator for I { - fn clone_box(&self) -> Box> { - Box::new(self.clone()) +// ============================================================================ +// PreinterpretIterator trait — associated Item, no Clone/'static required +// ============================================================================ + +/// An iterator protocol that takes `&mut Interpreter` on each `next()` call. +/// This allows iterators like `Map` and `Filter` to evaluate closures during iteration. +/// +/// Unlike `Iterator`, this has an associated `Item` type and passes the interpreter +/// through on each call. Types that don't need the interpreter (like `StdIteratorAdapter`) +/// simply ignore it. +pub(crate) trait PreinterpretIterator { + type Item; + fn do_next(&mut self, interpreter: &mut Interpreter) -> FunctionResult>; + fn do_size_hint(&self) -> (usize, Option); + + fn do_len(&self, error_span_range: SpanRange) -> FunctionResult { + let (min, max) = self.do_size_hint(); + if max == Some(min) { + Ok(min) + } else { + error_span_range.value_err("Iterator has an inexact length") + } + } + + fn do_map(self, f: F) -> MapIterator + where + Self: Sized, + F: FnMut(Self::Item, &mut Interpreter) -> FunctionResult, + { + MapIterator::new(self, f) + } + + fn do_take(self, count: usize) -> TakeIterator + where + Self: Sized, + { + TakeIterator::new(self, count) + } + + fn do_skip(self, count: usize) -> SkipIterator + where + Self: Sized, + { + SkipIterator::new(self, count) + } + + fn boxed(self) -> Box> + where + Self: Sized + 'static + Clone, + { + Box::new(self) + } + + fn do_into_iter<'i>(self, interpreter: &'i mut Interpreter) -> PreinterpretToIterator<'i, Self> + where + Self: Sized + 'i, + { + PreinterpretToIterator { + inner: self, + errored: false, + interpreter, + } + } + + fn do_collect>(self, interpreter: &mut Interpreter) -> FunctionResult + where + Self: Sized, + { + self.do_into_iter(interpreter).collect() + } +} + +impl PreinterpretIterator for I { + type Item = I::Item; + fn do_next(&mut self, _: &mut Interpreter) -> FunctionResult> { + Ok(Iterator::next(self)) + } + fn do_size_hint(&self) -> (usize, Option) { + Iterator::size_hint(self) + } +} + +impl PreinterpretIterator for Box> { + type Item = Item; + + fn do_next(&mut self, interpreter: &mut Interpreter) -> FunctionResult> { + (**self).do_next(interpreter) + } + + fn do_size_hint(&self) -> (usize, Option) { + (**self).do_size_hint() + } +} + +impl PreinterpretIterator for Box> { + type Item = Item; + + fn do_next(&mut self, interpreter: &mut Interpreter) -> FunctionResult> { + (**self).do_next(interpreter) + } + + fn do_size_hint(&self) -> (usize, Option) { + (**self).do_size_hint() } } -pub(crate) trait ClonableIterator: Iterator { - fn clone_box(&self) -> Box>; +pub(crate) struct PreinterpretToIterator<'a, I> { + inner: I, + errored: bool, + interpreter: &'a mut Interpreter, } -impl Clone for Box> { +impl Iterator for PreinterpretToIterator<'_, I> { + type Item = FunctionResult; + + fn next(&mut self) -> Option { + if self.errored { + return None; + } + match self.inner.do_next(self.interpreter) { + Ok(Some(item)) => Some(Ok(item)), + Ok(None) => None, + Err(e) => { + self.errored = true; + Some(Err(e)) + } + } + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.do_size_hint() + } +} + +// ============================================================================ +// BoxedIterator — object-safe sub-trait with clone_box, stored in IteratorValue +// ============================================================================ + +/// An object-safe sub-trait with clone_box, stored in IteratorValue. +pub(crate) trait BoxedIterator: 'static + PreinterpretIterator { + fn clone_box(&self) -> Box>; +} + +impl BoxedIterator for T { + fn clone_box(&self) -> Box> { + Box::new(self.clone()) + } +} + +impl Clone for Box> { fn clone(&self) -> Self { (**self).clone_box() } } +// ============================================================================ +// Generic MapIterator +// ============================================================================ + +/// An iterator that applies a function to each item of the inner iterator. +#[derive(Clone)] +pub(crate) struct MapIterator { + inner: I, + f: F, +} + +impl MapIterator { + pub(crate) fn new(inner: I, f: F) -> Self { + Self { inner, f } + } +} + +impl PreinterpretIterator for MapIterator +where + I: PreinterpretIterator, + F: FnMut(I::Item, &mut Interpreter) -> FunctionResult, +{ + type Item = O; + fn do_next(&mut self, interpreter: &mut Interpreter) -> FunctionResult> { + match self.inner.do_next(interpreter)? { + Some(item) => Ok(Some((self.f)(item, interpreter)?)), + None => Ok(None), + } + } + fn do_size_hint(&self) -> (usize, Option) { + self.inner.do_size_hint() + } +} + +// ============================================================================ +// Generic FilterIterator +// ============================================================================ + +/// An iterator that filters items using a predicate function. +#[derive(Clone)] +pub(crate) struct FilterIterator { + inner: I, + f: F, +} + +impl FilterIterator { + pub(crate) fn new(inner: I, f: F) -> Self { + Self { inner, f } + } +} + +impl PreinterpretIterator for FilterIterator +where + I: PreinterpretIterator, + I::Item: Clone, + F: FnMut(&I::Item, &mut Interpreter) -> FunctionResult, +{ + type Item = I::Item; + fn do_next(&mut self, interpreter: &mut Interpreter) -> FunctionResult> { + loop { + let item = match self.inner.do_next(interpreter)? { + Some(item) => item, + None => return Ok(None), + }; + if (self.f)(&item, interpreter)? { + return Ok(Some(item)); + } + } + } + fn do_size_hint(&self) -> (usize, Option) { + (0, self.inner.do_size_hint().1) + } +} + +// ============================================================================ +// SkipIterator — lazy skip +// ============================================================================ + +/// An iterator that lazily skips the first `remaining` items. +#[derive(Clone)] +pub(crate) struct SkipIterator { + inner: I, + remaining: usize, +} + +impl SkipIterator { + pub(crate) fn new(inner: I, count: usize) -> Self { + Self { + inner, + remaining: count, + } + } +} + +impl PreinterpretIterator for SkipIterator { + type Item = I::Item; + fn do_next(&mut self, interpreter: &mut Interpreter) -> FunctionResult> { + while self.remaining > 0 { + self.remaining -= 1; + if self.inner.do_next(interpreter)?.is_none() { + return Ok(None); + } + } + self.inner.do_next(interpreter) + } + fn do_size_hint(&self) -> (usize, Option) { + let (lo, hi) = self.inner.do_size_hint(); + ( + lo.saturating_sub(self.remaining), + hi.map(|h| h.saturating_sub(self.remaining)), + ) + } +} + +// ============================================================================ +// TakeIterator — lazy take +// ============================================================================ + +/// An iterator that yields at most `remaining` items from the inner iterator. +#[derive(Clone)] +pub(crate) struct TakeIterator { + inner: I, + remaining: usize, +} + +impl TakeIterator { + pub(crate) fn new(inner: I, count: usize) -> Self { + Self { + inner, + remaining: count, + } + } +} + +impl PreinterpretIterator for TakeIterator { + type Item = I::Item; + fn do_next(&mut self, interpreter: &mut Interpreter) -> FunctionResult> { + if self.remaining == 0 { + return Ok(None); + } + self.remaining -= 1; + self.inner.do_next(interpreter) + } + fn do_size_hint(&self) -> (usize, Option) { + let (lo, hi) = self.inner.do_size_hint(); + ( + lo.min(self.remaining), + Some(match hi { + Some(h) => h.min(self.remaining), + None => self.remaining, + }), + ) + } +} + +// ============================================================================ +// Unified to_string for any PreinterpretIterator +// ============================================================================ + +#[allow(clippy::too_many_arguments)] +pub(crate) fn any_items_to_string>( + iterator: &mut impl PreinterpretIterator, + output: &mut String, + behaviour: &ConcatBehaviour, + literal_empty: &str, + literal_start: &str, + literal_end: &str, + possibly_unbounded: bool, + interpreter: &mut Interpreter, +) -> FunctionResult<()> { + let mut is_empty = true; + let max = iterator.do_size_hint().1; + let mut i = 0; + loop { + let item = match iterator.do_next(interpreter)? { + Some(item) => item, + None => break, + }; + if i == 0 { + if behaviour.output_literal_structure { + output.push_str(literal_start); + } + is_empty = false; + } + if possibly_unbounded && i >= behaviour.iterator_limit { + if behaviour.error_after_iterator_limit { + return behaviour.error_span_range.debug_err(format!("To protect against infinite loops, only a maximum of {} items can be output to a string from an iterator. You can use .to_vec() to avoid this limit. This can't currently be reconfigured with the iteration limit.", behaviour.iterator_limit)); + } else { + if behaviour.output_literal_structure { + match max { + Some(max) => output.push_str(&format!( + ", ..<{} further items>", + max.saturating_sub(i) + )), + None => output.push_str(", .."), + } + } + break; + } + } + let item = item.borrow(); + if i != 0 && behaviour.output_literal_structure { + output.push(','); + } + if i != 0 && behaviour.add_space_between_token_trees { + output.push(' '); + } + item.as_ref_value() + .concat_recursive_into(output, behaviour, interpreter)?; + i += 1; + } + if behaviour.output_literal_structure { + if is_empty { + output.push_str(literal_empty); + } else { + output.push_str(literal_end); + } + } + Ok(()) +} + #[derive(Clone)] pub(crate) enum EitherIterator { Left(L), @@ -46,12 +406,12 @@ impl ZipIterators { pub(crate) fn new_from_object( object: ObjectValue, span_range: SpanRange, - ) -> ExecutionResult { + ) -> FunctionResult { let entries = object .entries .into_iter() .take(101) - .map(|(k, v)| -> ExecutionResult<_> { + .map(|(k, v)| -> FunctionResult<_> { Ok(( k, v.key_span, @@ -70,11 +430,11 @@ impl ZipIterators { pub(crate) fn new_from_iterator( iterator: IteratorValue, span_range: SpanRange, - ) -> ExecutionResult { - let vec = iterator - .take(101) - .map(|x| x.spanned(span_range).resolve_any_iterator("Each zip input")) - .collect::, _>>()?; + interpreter: &mut Interpreter, + ) -> FunctionResult { + let vec: Vec<_> = iterator.do_take(101) + .do_map(|x, _| x.spanned(span_range).resolve_any_iterator("Each zip input")) + .do_collect(interpreter)?; if vec.len() == 101 { return span_range.value_err("A maximum of 100 iterators are allowed"); } @@ -85,7 +445,7 @@ impl ZipIterators { self, interpreter: &mut Interpreter, error_on_length_mismatch: bool, - ) -> ExecutionResult { + ) -> FunctionResult { let mut iterators = self; let error_span_range = match &iterators { ZipIterators::Array(_, span_range) => *span_range, @@ -123,8 +483,8 @@ impl ZipIterators { /// Panics if called on an empty list of iterators fn size_hint_range(&self) -> (usize, Option) { let size_hints: Vec<_> = match self { - ZipIterators::Array(inner, _) => inner.iter().map(|x| x.size_hint()).collect(), - ZipIterators::Object(inner, _) => inner.iter().map(|x| x.2.size_hint()).collect(), + ZipIterators::Array(inner, _) => inner.iter().map(|x| x.do_size_hint()).collect(), + ZipIterators::Object(inner, _) => inner.iter().map(|x| x.2.do_size_hint()).collect(), }; let min_min = size_hints.iter().map(|s| s.0).min().unwrap(); let max_max = size_hints @@ -154,7 +514,7 @@ impl ZipIterators { interpreter: &mut Interpreter, error_span_range: SpanRange, output: &mut Vec, - ) -> ExecutionResult<()> { + ) -> FunctionResult<()> { let mut counter = interpreter.start_iteration_counter(&error_span_range); match self { @@ -163,7 +523,7 @@ impl ZipIterators { counter.increment_and_check()?; let mut inner = Vec::with_capacity(iterators.len()); for iter in iterators.iter_mut() { - inner.push(iter.next().unwrap()); + inner.push(iter.do_next(interpreter)?.unwrap()); } output.push(inner.into_any_value()); } @@ -177,7 +537,7 @@ impl ZipIterators { key.clone(), ObjectEntry { key_span: *key_span, - value: iter.next().unwrap(), + value: iter.do_next(interpreter)?.unwrap(), }, ); } @@ -201,12 +561,20 @@ pub(crate) fn run_intersperse( items: Box, separator: AnyValue, settings: IntersperseSettings, -) -> ExecutionResult { + interpreter: &mut Interpreter, +) -> FunctionResult { let mut output = Vec::new(); - let mut items = items.into_iterator()?.peekable(); + // Collect items eagerly since we need lookahead for separator logic. + let mut iterator = items.into_iterator()?; + let mut collected = Vec::new(); + while let Some(item) = iterator.do_next(interpreter)? { + collected.push(item); + } + + let mut collected_iter = collected.into_iter().peekable(); - let mut this_item = match items.next() { + let mut this_item = match collected_iter.next() { Some(next) => next, None => return Ok(ArrayValue { items: output }), }; @@ -219,10 +587,10 @@ pub(crate) fn run_intersperse( loop { output.push(this_item); - let next_item = items.next(); + let next_item = collected_iter.next(); match next_item { Some(next_item) => { - let remaining = if items.peek().is_some() { + let remaining = if collected_iter.peek().is_some() { RemainingItemCount::MoreThanOne } else { RemainingItemCount::ExactlyOne @@ -251,7 +619,7 @@ impl SeparatorAppender { &mut self, remaining: RemainingItemCount, output: &mut Vec, - ) -> ExecutionResult<()> { + ) -> FunctionResult<()> { match self.separator(remaining) { TrailingSeparator::Normal => output.push(self.separator.clone()), TrailingSeparator::Final => match self.final_separator.take() { @@ -308,7 +676,7 @@ pub(crate) fn handle_split( input: OutputStream, separator: &OutputStream, settings: SplitSettings, -) -> ExecutionResult { +) -> FunctionResult { input.parse_with(move |input| { let mut output = Vec::new(); let mut current_item = OutputStream::new(); From a90152b4a6d16c9246e61d4d20b14d9fd8403245 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 8 Feb 2026 20:53:02 +0000 Subject: [PATCH 21/49] docs: Update TODO --- plans/TODO.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index 3c1b5835..94fb0b8b 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -237,20 +237,23 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] Allow shared / mutable arguments - [x] Add tests for shared/mutable, including conversions working and not - [ ] Iterable methods: - - [ ] Unpick `feat/iterable-map` and work our what we want to keep: - * Salvage half-baked `FunctionValue` changes to allow invocation - * Separation of `ExecutionInterrupt` and `FunctionError` - incorporated below - * `Iterator` returns `Result` change -> replaced with below + - [x] Unpick `feat/iterable-map` and work our what we want to keep: + - [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: - - An iterator trait where Interpreter is passed at next time. - - Possibly - not require `Clone` on iterators: + -[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[?]` - - [ ] Create new iterator trait `PreinterpretIterator` and `IntoPreinterpretIterator` with `.next(&mut Interpreter)` - - [ ] Blanket implement it for `Iterator + Clone` - - [ ] Then replace e.g. for loop impl with it. - - [ ] Create `Map` and `Filter` types on top of it, to be able to implement `map` and `filter` + - [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 - [ ] Resolve all `TODO[functions]` From 7419be48e5ea0ba6cdfe72d8548dd319c98de6cc Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 8 Feb 2026 20:58:17 +0000 Subject: [PATCH 22/49] fix: Fix style --- src/expressions/concepts/form.rs | 10 ++++----- src/expressions/control_flow.rs | 4 +++- src/expressions/evaluation/evaluator.rs | 3 +-- src/expressions/expression_block.rs | 22 +++++++++++++------- src/expressions/statements.rs | 11 ++++++---- src/expressions/type_resolution/outputs.rs | 2 +- src/expressions/type_resolution/type_data.rs | 3 +-- src/expressions/values/any_value.rs | 5 +---- src/expressions/values/integer_untyped.rs | 21 +++++++------------ src/expressions/values/parser.rs | 2 +- src/expressions/values/range.rs | 4 +--- src/expressions/values/stream.rs | 11 ++++++---- src/expressions/values/string.rs | 6 ++---- src/interpretation/bindings.rs | 3 +-- src/interpretation/variable.rs | 11 ++++++---- src/misc/iterators.rs | 16 +++++++------- 16 files changed, 68 insertions(+), 66 deletions(-) diff --git a/src/expressions/concepts/form.rs b/src/expressions/concepts/form.rs index 5d3837a9..6337ce19 100644 --- a/src/expressions/concepts/form.rs +++ b/src/expressions/concepts/form.rs @@ -65,13 +65,11 @@ pub(crate) trait IsDynCompatibleForm: IsHierarchicalForm { pub(crate) trait MapFromArgument: IsHierarchicalForm { const ARGUMENT_OWNERSHIP: ArgumentOwnership; - fn from_argument_value( - value: ArgumentValue, - ) -> FunctionResult>; + fn from_argument_value(value: ArgumentValue) + -> FunctionResult>; } pub(crate) trait MapIntoReturned: IsHierarchicalForm { - fn into_returned_value( - value: Content<'static, AnyType, Self>, - ) -> FunctionResult; + fn into_returned_value(value: Content<'static, AnyType, Self>) + -> FunctionResult; } diff --git a/src/expressions/control_flow.rs b/src/expressions/control_flow.rs index dd0f67f4..ee1f22d1 100644 --- a/src/expressions/control_flow.rs +++ b/src/expressions/control_flow.rs @@ -538,7 +538,9 @@ impl Evaluate for AttemptExpression { |interpreter| -> ExecutionResult<()> { arm.lhs .evaluate_owned(interpreter)? - .downcast_resolve::<()>("The returned value from the left half of an attempt arm") + .downcast_resolve::<()>( + "The returned value from the left half of an attempt arm", + ) .into_execution_result() }, guard_clause(arm.guard.as_ref()), diff --git a/src/expressions/evaluation/evaluator.rs b/src/expressions/evaluation/evaluator.rs index 75fca49f..1576b1ad 100644 --- a/src/expressions/evaluation/evaluator.rs +++ b/src/expressions/evaluation/evaluator.rs @@ -240,8 +240,7 @@ impl RequestedValue { map_shared: impl FnOnce(AnyValueShared) -> FunctionResult, map_mutable: impl FnOnce( AnyValueMutable, - ) - -> Result, + ) -> Result, map_owned: impl FnOnce(AnyValueOwned) -> FunctionResult, ) -> FunctionResult { Ok(match self { diff --git a/src/expressions/expression_block.rs b/src/expressions/expression_block.rs index 5e4d4582..a46c22e7 100644 --- a/src/expressions/expression_block.rs +++ b/src/expressions/expression_block.rs @@ -32,10 +32,13 @@ impl HasSpanRange for EmbeddedExpression { impl OutputToStream for EmbeddedExpression { fn output_to_stream(&self, output: &mut OutputInterpreter) -> ExecutionResult<()> { let value = output.with_interpreter(|i| self.content.evaluate_shared(i))?; - value.as_ref_value().output_to( - Grouping::Flattened, - &mut ToStreamContext::new(output, self.span_range()), - ).into_execution_result() + value + .as_ref_value() + .output_to( + Grouping::Flattened, + &mut ToStreamContext::new(output, self.span_range()), + ) + .into_execution_result() } } @@ -77,10 +80,13 @@ impl OutputToStream for EmbeddedStatements { })? .0 .expect_shared(); - value.as_ref_value().output_to( - Grouping::Flattened, - &mut ToStreamContext::new(output, self.span_range()), - ).into_execution_result() + value + .as_ref_value() + .output_to( + Grouping::Flattened, + &mut ToStreamContext::new(output, self.span_range()), + ) + .into_execution_result() } } diff --git a/src/expressions/statements.rs b/src/expressions/statements.rs index 595ce92d..55187bcf 100644 --- a/src/expressions/statements.rs +++ b/src/expressions/statements.rs @@ -370,9 +370,12 @@ impl EmitStatement { ) -> ExecutionResult<()> { let Spanned(value, span) = self.expression.evaluate_owned(interpreter)?; let mut output = OutputInterpreter::new_checked(interpreter, &self.emit)?; - value.as_ref_value().output_to( - Grouping::Flattened, - &mut ToStreamContext::new(&mut output, span), - ).into_execution_result() + value + .as_ref_value() + .output_to( + Grouping::Flattened, + &mut ToStreamContext::new(&mut output, span), + ) + .into_execution_result() } } diff --git a/src/expressions/type_resolution/outputs.rs b/src/expressions/type_resolution/outputs.rs index e53943ed..74a66561 100644 --- a/src/expressions/type_resolution/outputs.rs +++ b/src/expressions/type_resolution/outputs.rs @@ -42,4 +42,4 @@ impl IsReturnable for FunctionResult { fn to_returned_value(self) -> FunctionResult { self?.to_returned_value() } -} \ No newline at end of file +} diff --git a/src/expressions/type_resolution/type_data.rs b/src/expressions/type_resolution/type_data.rs index 002b7e85..c31cd275 100644 --- a/src/expressions/type_resolution/type_data.rs +++ b/src/expressions/type_resolution/type_data.rs @@ -232,8 +232,7 @@ impl FunctionInterface { }, FunctionInterface::ArityAny { method, .. } => method(context, arguments), }; - output_value - .map(|v| v.spanned(context.output_span_range)) + output_value.map(|v| v.spanned(context.output_span_range)) } /// Returns (argument_ownerships, required_argument_count) diff --git a/src/expressions/values/any_value.rs b/src/expressions/values/any_value.rs index a7d57056..d3799c35 100644 --- a/src/expressions/values/any_value.rs +++ b/src/expressions/values/any_value.rs @@ -567,10 +567,7 @@ impl Spanned { } } - pub(crate) fn into_stream( - self, - interpreter: &mut Interpreter, - ) -> FunctionResult { + pub(crate) fn into_stream(self, interpreter: &mut Interpreter) -> FunctionResult { let Spanned(value, span_range) = self; value.into_stream(Grouping::Flattened, span_range, interpreter) } diff --git a/src/expressions/values/integer_untyped.rs b/src/expressions/values/integer_untyped.rs index a28de72c..da3f162b 100644 --- a/src/expressions/values/integer_untyped.rs +++ b/src/expressions/values/integer_untyped.rs @@ -111,15 +111,13 @@ impl UntypedInteger { impl Spanned { pub(crate) fn into_kind(self, kind: IntegerLeafKind) -> FunctionResult { let Spanned(value, span_range) = self; - value - .try_into_kind(kind) - .ok_or_else(|| -> FunctionError { - span_range.value_error(format!( - "The integer value {} does not fit into {}", - value.0, - kind.articled_value_name() - )) - }) + value.try_into_kind(kind).ok_or_else(|| -> FunctionError { + span_range.value_error(format!( + "The integer value {} does not fit into {}", + value.0, + kind.articled_value_name() + )) + }) } } @@ -272,10 +270,7 @@ impl ResolvableOwned for UntypedIntegerFallback { } impl ResolvableOwned for UntypedInteger { - fn resolve_from_value( - value: IntegerValue, - context: ResolutionContext, - ) -> FunctionResult { + fn resolve_from_value(value: IntegerValue, context: ResolutionContext) -> FunctionResult { match value { IntegerValue::Untyped(value) => Ok(value), _ => context.err("an untyped integer", value), diff --git a/src/expressions/values/parser.rs b/src/expressions/values/parser.rs index 1ad47e6f..5a6f4497 100644 --- a/src/expressions/values/parser.rs +++ b/src/expressions/values/parser.rs @@ -55,7 +55,7 @@ impl Spanned> { interpreter: &'i mut Interpreter, ) -> FunctionResult> { let Spanned(handle, span) = self; - Ok(interpreter.parser(**handle, *span)?) + interpreter.parser(**handle, *span) } pub(crate) fn parse_with( diff --git a/src/expressions/values/range.rs b/src/expressions/values/range.rs index c9179601..9ef583d8 100644 --- a/src/expressions/values/range.rs +++ b/src/expressions/values/range.rs @@ -511,9 +511,7 @@ impl IterableRangeOf { } impl ResolvableRange for UntypedInteger { - fn resolve( - definition: IterableRangeOf, - ) -> FunctionResult { + fn resolve(definition: IterableRangeOf) -> FunctionResult { match definition { IterableRangeOf::RangeFromTo { start, dots, end } => { let start = start.into_fallback(); diff --git a/src/expressions/values/stream.rs b/src/expressions/values/stream.rs index 4fa466c3..e4d3dc3e 100644 --- a/src/expressions/values/stream.rs +++ b/src/expressions/values/stream.rs @@ -548,10 +548,13 @@ impl OutputToStream for ConcatenatedStreamLiteral { string_to_literal(str, self, ident_span)?.into_any_value() } }; - value.as_ref_value().output_to( - Grouping::Flattened, - &mut ToStreamContext::new(output, self.span_range()), - ).into_execution_result() + value + .as_ref_value() + .output_to( + Grouping::Flattened, + &mut ToStreamContext::new(output, self.span_range()), + ) + .into_execution_result() } } diff --git a/src/expressions/values/string.rs b/src/expressions/values/string.rs index 3b562d57..4be261a4 100644 --- a/src/expressions/values/string.rs +++ b/src/expressions/values/string.rs @@ -60,10 +60,8 @@ pub(crate) fn string_to_literal( span: Span, ) -> FunctionResult { let literal = Literal::from_str(str).map_err(|err| { - error_source.value_error::(format!( - "`{}` is not a valid literal: {:?}", - str, err - )) + error_source + .value_error::(format!("`{}` is not a valid literal: {:?}", str, err)) })?; Ok(literal.with_span(span)) } diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index efa92819..c83c0355 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -301,8 +301,7 @@ impl LateBoundValue { map_shared: impl FnOnce(AnyValueShared) -> FunctionResult, map_mutable: impl FnOnce( AnyValueMutable, - ) - -> Result, + ) -> Result, map_owned: impl FnOnce(AnyValueOwned) -> FunctionResult, ) -> FunctionResult { Ok(match self { diff --git a/src/interpretation/variable.rs b/src/interpretation/variable.rs index a7800883..dd67fc24 100644 --- a/src/interpretation/variable.rs +++ b/src/interpretation/variable.rs @@ -128,10 +128,13 @@ impl VariableReference { grouping: Grouping, ) -> ExecutionResult<()> { let value = output.with_interpreter(|i| self.resolve_shared(i))?; - value.as_ref_value().output_to( - grouping, - &mut ToStreamContext::new(output, self.span_range()), - ).into_execution_result() + value + .as_ref_value() + .output_to( + grouping, + &mut ToStreamContext::new(output, self.span_range()), + ) + .into_execution_result() } pub(crate) fn resolve_late_bound( diff --git a/src/misc/iterators.rs b/src/misc/iterators.rs index d369f4b6..80558621 100644 --- a/src/misc/iterators.rs +++ b/src/misc/iterators.rs @@ -64,7 +64,10 @@ pub(crate) trait PreinterpretIterator { } } - fn do_collect>(self, interpreter: &mut Interpreter) -> FunctionResult + fn do_collect>( + self, + interpreter: &mut Interpreter, + ) -> FunctionResult where Self: Sized, { @@ -114,7 +117,7 @@ pub(crate) struct PreinterpretToIterator<'a, I> { impl Iterator for PreinterpretToIterator<'_, I> { type Item = FunctionResult; - + fn next(&mut self) -> Option { if self.errored { return None; @@ -345,10 +348,8 @@ pub(crate) fn any_items_to_string>( } else { if behaviour.output_literal_structure { match max { - Some(max) => output.push_str(&format!( - ", ..<{} further items>", - max.saturating_sub(i) - )), + Some(max) => output + .push_str(&format!(", ..<{} further items>", max.saturating_sub(i))), None => output.push_str(", .."), } } @@ -432,7 +433,8 @@ impl ZipIterators { span_range: SpanRange, interpreter: &mut Interpreter, ) -> FunctionResult { - let vec: Vec<_> = iterator.do_take(101) + let vec: Vec<_> = iterator + .do_take(101) .do_map(|x, _| x.spanned(span_range).resolve_any_iterator("Each zip input")) .do_collect(interpreter)?; if vec.len() == 101 { From 1c41b54ec2afc0531242a10292934941bef44094 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 8 Feb 2026 20:58:30 +0000 Subject: [PATCH 23/49] fix: Update MSRV in line with latest dependencies --- .github/workflows/ci.yml | 4 ++-- Cargo.toml | 3 ++- local-check-msrv.sh | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7740c6a5..f0672f0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,14 +41,14 @@ jobs: path: Cargo.lock msrv: - name: MSRV (1.68) Compiles + name: MSRV (1.71) Compiles runs-on: ubuntu-latest timeout-minutes: 45 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.68 + toolchain: 1.71 - run: cargo check style-check: diff --git a/Cargo.toml b/Cargo.toml index 94855c4f..b1c35f9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,9 @@ categories = ["development-tools", "compilers"] # MSRV 1.61.0 is the old MSRV of syn # MRSV 1.63.0 is needed to support RefMut::filter_map # MRSV 1.68.0 is the new latest MSRV of syn (as of syn 2.0.107 released on 19th October 2025) +# MRSV 1.71.0 is the new latest MSRV of unicode-ident (released on 8th February 2026) # If changing this, also update the local-check-msrv.sh script and ci.yml -rust-version = "1.68" +rust-version = "1.71" [lib] proc-macro = true diff --git a/local-check-msrv.sh b/local-check-msrv.sh index ac001e6f..60909ae6 100755 --- a/local-check-msrv.sh +++ b/local-check-msrv.sh @@ -4,5 +4,5 @@ set -e cd "$(dirname "$0")" -rustup install 1.68 -rm Cargo.lock && rustup run 1.68 cargo check +rustup install 1.71 +rm Cargo.lock && rustup run 1.71 cargo check From a105f3c969be24bc8a9e01d9698d3d036d87c70d Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 8 Feb 2026 22:07:03 +0000 Subject: [PATCH 24/49] fix: Fix MSRV compilation --- src/misc/iterators.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc/iterators.rs b/src/misc/iterators.rs index 80558621..71277a1c 100644 --- a/src/misc/iterators.rs +++ b/src/misc/iterators.rs @@ -146,7 +146,7 @@ pub(crate) trait BoxedIterator: 'static + PreinterpretIterator { fn clone_box(&self) -> Box>; } -impl BoxedIterator for T { +impl BoxedIterator for T { fn clone_box(&self) -> Box> { Box::new(self.clone()) } From 11a49ed44627a36a8b7b5ff23cad6433c8f7bedc Mon Sep 17 00:00:00 2001 From: David Edey Date: Mon, 9 Feb 2026 13:04:47 +0000 Subject: [PATCH 25/49] docs: Document the disabled abstraction issue --- plans/TODO.md | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/plans/TODO.md b/plans/TODO.md index 94fb0b8b..a79f44f0 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -251,7 +251,6 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] Create `Map` and `Filter` types on top of it, to be able to implement `map` and `filter` - [ ] See if new iterators on iterator value can be fixed to be lazy - [ ] Salvage half-baked `FunctionValue` changes to allow invocation - - [ ] Consider if IteratorValue should have `Item = ReturnedValue` - [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` - [ ] Add tests for iterable methods - [ ] Add `array.sort`, `array.sort_by` @@ -270,6 +269,51 @@ Possible punted: - `move(x)` / `move(a.b.as_ref())` / `move(a.b.as_mut())` => we hoist up the content into the previous frame (effectively temporarily change `current_frame_id` to be the parent in the `FlowAnalysisState` - pretty easy). - These can be an anonymous definition in the root frame of the closure, which is referenced inline. +## Fix broken "Disabled" abstraction + +Suddenly dawned on me - my Disabled arguments might not be safe. +* Imagine if I get `my_arr = [[]]` a Shared `my_arr[0]` then do `my_arr.pop()` +* Then we enable `my_arr[0]` and get a use after free(!!). e.g. `my_arr[0].push(my_arr.pop())`. + +Instead, as per Rust two-phase borrows https://rustc-dev-guide.rust-lang.org/borrow-check/two-phase-borrows.html - +- We shouldn't be able to disable a `Shared` +- A `Disabled` becomes a `Shared`... + +I have a branch `spike/fix-disabled-abstraction` to explore this... Which converts a Shared back to a Mutable (which I think is technically UB and can't be done without it). Regardless, it works for a spike, and we get a few failures: +* `stream_append_can_use_self_in_appender` - these are OK to fail! +* This test is *not* OK to fail: +```rust +#[test] +fn can_pass_owned_to_mutable_argument() { + run! { + let push_twice_and_return_mut = |arr: &mut any| { + arr.push(0); + arr.push(0); + arr + }; + %[_].assert_eq(push_twice_and_return_mut([1, 2, 3]).len(), 5); + } +} +``` + +The issue is that we get a variable stored as `arr := DisabledMutable` and then when we do `arr.push()` we clone the DisabledMutable, and then enable it... +What we actually need is a sense of "Delegating" the mut-ness to a new `Mutable`, which then on drop re-enables its parent. + +... actually in our model, really the bug only appears if: +* A *parent* gets modified, which breaks our pointer +* We have a `&mut T` for some `T != any` and someone assigns to `self as &mut any` a different type + +We could imagine a world where we are more clever over our mutation: +* From a referenceable root (wrapping an `UnsafeCell`), we store paths => pointers +* It is illegal to mutate if there is an existing pointer further along a path that you in either direction: + * A value + * A more specific type of the current value +* We allow x.a and x.b to both be read/mutated independently + * I can reference `x.a` and `x.b` separately, or `x[0]` and `x[1]` but in that case, can't mutate `x` itself to create new fields. +* We track a path to which pointers are active; and don't allow mutations which could break existing pointers. + +- [ ] Consider if IteratorValue, Object and Array should have `Item = DisabledReturnedValue` + ## Parser - Methods using closures Future methods once we have closures: From 9dbaeb1d25fe4b7aa62e87b082daaa3c9f96b329 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 22 Feb 2026 10:59:47 +0000 Subject: [PATCH 26/49] feat: Begin references rework --- plans/TODO.md | 55 ++-- src/expressions/type_resolution/type_kinds.rs | 33 +++ .../dynamic_references/inactive_reference.rs | 36 +++ src/misc/dynamic_references/mod.rs | 85 ++++++ .../dynamic_references/mutable_reference.rs | 34 +++ src/misc/dynamic_references/reference_core.rs | 42 +++ src/misc/dynamic_references/referenceable.rs | 246 ++++++++++++++++++ .../dynamic_references/shared_reference.rs | 24 ++ src/misc/mod.rs | 2 + 9 files changed, 535 insertions(+), 22 deletions(-) create mode 100644 src/misc/dynamic_references/inactive_reference.rs create mode 100644 src/misc/dynamic_references/mod.rs create mode 100644 src/misc/dynamic_references/mutable_reference.rs create mode 100644 src/misc/dynamic_references/reference_core.rs create mode 100644 src/misc/dynamic_references/referenceable.rs create mode 100644 src/misc/dynamic_references/shared_reference.rs diff --git a/plans/TODO.md b/plans/TODO.md index a79f44f0..18aa09e2 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -241,10 +241,8 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [x] Separation of `ExecutionInterrupt` and `FunctionError` - incorporated below - [x] `Iterator` returns `Result` change -> replaced with below * To implement `.map()`, we have a few things we need first: - -[x] An iterator trait where Interpreter is passed at next time. - -[ ] Possibly - not require `Clone` on iterators: - - Make `TryClone -> Result` - - Make `to_string` for iterator return `Iterator[?]` + - [x] An iterator trait where Interpreter is passed at next time. + - [x] Make `to_string` for iterator return `Iterator[?]` - [x] Create new iterator trait `PreinterpretIterator` and `IntoPreinterpretIterator` with `.next(&mut Interpreter)` - [x] Blanket implement it for `Iterator + Clone` - [x] Then replace e.g. for loop impl with it. @@ -254,23 +252,22 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` - [ ] Add tests for iterable methods - [ ] Add `array.sort`, `array.sort_by` -- [ ] Look into if a closure like `|| { }()` can evade `attempt` block statue mutation checks. Maybe lean into it as a way to avoid htem, and mention it in the error message +- [ ] Look into if a closure like `|| { }()` can evade `attempt` block statue mutation checks. Maybe lean into it as a way to avoid them, and mention it in the error message - [ ] Resolve all `TODO[functions]` Possible punted: -- [ ] Allow variables to be marked owned / shared / mutable - - [ ] ... and maybe no marking accepts any? - ... so e.g. push_one_and_return_mut can have `let y = arr; y` without erroring - - [ ] Else - improve the errors so that the conversion error has some hint from the target explaining that the target can be changed to allow - shared/mutable instead. - [ ] Allow destructuring shared and mutable variables and arguments - [ ] Support optional arguments in closures - [ ] Support for `move()` expressions in closures. - `move(x)` / `move(a.b.as_ref())` / `move(a.b.as_mut())` => we hoist up the content into the previous frame (effectively temporarily change `current_frame_id` to be the parent in the `FlowAnalysisState` - pretty easy). - These can be an anonymous definition in the root frame of the closure, which is referenced inline. +-[ ] Possibly - not require `Clone` on iterators: + - Make `TryClone -> Result` ## Fix broken "Disabled" abstraction +### Background + Suddenly dawned on me - my Disabled arguments might not be safe. * Imagine if I get `my_arr = [[]]` a Shared `my_arr[0]` then do `my_arr.pop()` * Then we enable `my_arr[0]` and get a use after free(!!). e.g. `my_arr[0].push(my_arr.pop())`. @@ -297,22 +294,36 @@ fn can_pass_owned_to_mutable_argument() { ``` The issue is that we get a variable stored as `arr := DisabledMutable` and then when we do `arr.push()` we clone the DisabledMutable, and then enable it... -What we actually need is a sense of "Delegating" the mut-ness to a new `Mutable`, which then on drop re-enables its parent. -... actually in our model, really the bug only appears if: -* A *parent* gets modified, which breaks our pointer -* We have a `&mut T` for some `T != any` and someone assigns to `self as &mut any` a different type +### Task list + +- [x] Write up model in `dynamic_references/mod.rs` +- [x] Add structure for `dynamic_references` +- [x] Create Referenceable, and Reference types +- [ ] Create error messages for rule breaks in `referenceable.rs` +- [ ] Add the following to `ReferenceCore` and maybe others: + - [ ] Ability to map deeper. Should take a `PathExtension` and a new span. + See rule (c) from `dynamic_references/mod.rs` + - [ ] Various mapping functions (e.g. array offset; object key) + - [ ] Try map + - [ ] Emplacing +- [ ] Check for missing features from `Shared` / `SharedSubRcRefCell` and `Mutable` / `MutableSubRcRefCell` + + +### Other ideas We could imagine a world where we are more clever over our mutation: -* From a referenceable root (wrapping an `UnsafeCell`), we store paths => pointers -* It is illegal to mutate if there is an existing pointer further along a path that you in either direction: - * A value - * A more specific type of the current value -* We allow x.a and x.b to both be read/mutated independently - * I can reference `x.a` and `x.b` separately, or `x[0]` and `x[1]` but in that case, can't mutate `x` itself to create new fields. -* We track a path to which pointers are active; and don't allow mutations which could break existing pointers. +* Note that I can reference `x.a` and `x.b` separately, or `x[0]` and `x[1]` but in that case, can't mutate `x` itself to create new fields. + * Or we use a `ExpandableVec` which allocates a `Vec>` and doesn't move the inner chunks; allowing us to add new fields without breaking pointers to existing ones. + Maybe some SmallVec or SegVec like library has this feature? Stores an initial chunk `[_; N]` and then a `Vec>` - [ ] Consider if IteratorValue, Object and Array should have `Item = DisabledReturnedValue` + - [ ] And add `iter()` and `iter_mut()` methods +- [ ] Allow variables to be marked owned / shared / mutable + - [ ] ... and maybe no marking accepts any? + ... so e.g. push_one_and_return_mut can have `let y = arr; y` without erroring + - [ ] Else - improve the errors so that the conversion error has some hint from the target explaining that the target can be changed to allow + shared/mutable instead. ## Parser - Methods using closures @@ -345,7 +356,7 @@ input.repeated( ) ``` - [ ] `input.any_group(|inner| { })` -- [ ] `input.group('()', |inner| { })` +- [ ] `input.group("()", |inner| { })` - [ ] `input.transparent_group(|inner| { })` ## Parser - Better Types for Tokens diff --git a/src/expressions/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index df4d25ce..b255c505 100644 --- a/src/expressions/type_resolution/type_kinds.rs +++ b/src/expressions/type_resolution/type_kinds.rs @@ -41,6 +41,7 @@ impl AnyValueLeafKind { // A AnyValueLeafKind represents a kind of leaf value. // But a TypeKind represents a type in the hierarchy, which points at a type data. +#[derive(PartialEq, Eq, Clone, Copy)] pub(crate) enum TypeKind { Leaf(AnyValueLeafKind), Parent(ParentTypeKind), @@ -91,6 +92,37 @@ impl TypeKind { } } +/// This is the subtyping ordering. +/// - I define that X <= Y if Y is a subtype of X. +/// - Think "X is shallower than Y in the enum representation" +/// +/// This must be accurate so that the Dynamic References works correctly. +/// e.g. if I have a `a: &mut AnyValue` and a `b: &mut IntegerValue` pointing +/// to the same memory, then I can't use a to change to a String. +impl PartialOrd for TypeKind { + fn partial_cmp(&self, other: &Self) -> Option { + if self == other { + return Some(std::cmp::Ordering::Equal); + } + // TODO: implement a more complete partial ord that reflects the actual subtyping relationships. + match (self, other) { + // None-equal dyns are not subtypes + (TypeKind::Dyn(_), _) => None, + (_, TypeKind::Dyn(_)) => None, + // All non-any-values are strict subtypes of AnyValue + (TypeKind::Parent(ParentTypeKind::Value(_)), _) => Some(std::cmp::Ordering::Less), + (_, TypeKind::Parent(ParentTypeKind::Value(_))) => Some(std::cmp::Ordering::Greater), + // TODO: Non-equal leave types should kinda be an error, assuming they're pointing + // at the same value + (TypeKind::Leaf(_), _) => None, + (_, TypeKind::Leaf(_)) => None, + // TODO: Handle intermediate parents + _ => None, + } + } +} + +#[derive(PartialEq, Eq, Clone, Copy)] pub(crate) enum ParentTypeKind { Value(AnyValueTypeKind), Integer(IntegerTypeKind), @@ -123,6 +155,7 @@ impl ParentTypeKind { } } +#[derive(PartialEq, Eq, Clone, Copy)] pub(crate) enum DynTypeKind { Iterable, } diff --git a/src/misc/dynamic_references/inactive_reference.rs b/src/misc/dynamic_references/inactive_reference.rs new file mode 100644 index 00000000..15d711c0 --- /dev/null +++ b/src/misc/dynamic_references/inactive_reference.rs @@ -0,0 +1,36 @@ +use super::*; + +#[derive(Clone)] +pub(crate) struct InactiveReference(ReferenceCore); + +impl InactiveReference { + pub(super) fn new_root(core: Rc>) -> Self { + let new_id = core.data_mut() + .new_reference(TrackedReference { + path: ReferencePath::leaf(AnyType::type_kind()), + reference_kind: ReferenceKind::Inactive, + creation_span: core.root_span, + }); + Self(ReferenceCore { + pointer: core.root.get(), + id: new_id, + core, + }) + } + + pub(super) fn activate_mutable(self) -> FunctionResult> { + self.0.activate_mutable_reference()?; + Ok(MutableReference(self.0)) + } + + pub(super) fn activate_shared(self) -> FunctionResult> { + self.0.activate_shared_reference()?; + Ok(SharedReference(self.0)) + } +} + +#[derive(Clone)] +pub(crate) struct InactiveSharedReference(pub(super) ReferenceCore); + +#[derive(Clone)] +pub(crate) struct InactiveMutableReference(pub(super) ReferenceCore); \ No newline at end of file diff --git a/src/misc/dynamic_references/mod.rs b/src/misc/dynamic_references/mod.rs new file mode 100644 index 00000000..b98fea21 --- /dev/null +++ b/src/misc/dynamic_references/mod.rs @@ -0,0 +1,85 @@ +//! ## Overview +//! +//! We wish to define a dynamic reference model which upholds the rules of rust +//! references when those references are active, but which is flexible to the +//! needs of the dynamic preinterpret language. +//! +//! In other words, it should catch real bugs, with clear error messages, but +//! not get in the way of things you might want to do in a dynamic language. +//! +//! ## Model +//! +//! Our model is: +//! - When calling a method, all references in arguments must +//! be in an active mode which upholds the rust reference invariants. +//! - This ensures that Rust methods can be called without UB +//! - We do this for user-defined methods to, for consistency, and to allow +//! sensible interface design which matches expectations. +//! - Inside a user-defined method, we don't really want users caring about +//! what a variable points to, as it adds a strictness which could be +//! frustrating without a helpful compiler pointing the way. So, +//! when variables/references aren't in active use, we relax the invariants +//! to a weaker "InactiveReference" which upholds validity invariants +//! (i.e. a reference can't point to a value which isn't of the correct type) +//! but doesn't maintain mutability-related safety invariants (i.e. a +//! shared reference does not prevent mutation if it is inactive). +//! +//! As some examples, it should: +//! - Allow x.a: &mut AnyValue and x.b: &mut AnyValue to exist as active mutable +//! references at the same time +//! - Prevent x: &mut AnyValue and x.a: &AnyValue from existing as active references +//! at the same time +//! - Allow x: &mut Integer to mutate x whilst a currently unused variable +//! y: &Integer exists in preinterpret, but not as an active rust reference. +//! +//! ## Implementation +//! +//! We define a concept of a [`ReferencePath`] with a partial order, roughly +//! signalling a depth/subtyping relation to do with mutable ownership. +//! It is more strictly defined on the ReferencePath docs. +//! +//! There are three reference kinds, each wraps a `*mut T` for ease, and each +//! has the *validity* invariant that the pointer will always point to memory +//! representing the type `T`: +//! - `InactiveReference` +//! - Represents an inactive shared/mutable, or disabled mutable during a two-phase borrow +//! - "Active" `Shared` +//! - Implements `Deref` +//! - The existence of this grants the following safety-invariants: +//! - T is immutable +//! - "Active" `Mutable` +//! - Implemented `DerefMut` +//! - The existence of this implies the following safety-invariants: +//! - No other reference can mutate T +//! +//! This gives rise to the following rules: +//! (a) Can't create a Mutable from an InactiveReference if there is: +//! (a.1) A strictly deeper reference of any kind; which could end up pointing to invalid memory +//! (a.2) Any equal or shallower active reference; as that could break the other reference's safety invariant +//! (b) Can't create an Shared from an InactiveReference if there is: +//! (b.1) Any equal or deeper ActiveMutable, as they could break our safety invariant +//! (b.2) (NOTE: There can't be a strictly shallower ActiveMutable, as that would contravene a.1) +//! (c) Can't map an InactiveReference from A to deeper B if: +//! (c.1) There is a shallower ActiveMutable than B, which could leave us pointing at invalid memory, as per a.1) +//! (c.2) (NOTE: There can't be a strictly shallower ActiveMutable than A, as that would contravene a.1 already) +//! +//! Basically, a.1 and a.2 are our two key invariants, and all our actions must preserve them. +//! +//! Okay, but then -- how do we create `&mut x.a` and `&mut x.b`? As follows: +//! - Start with ActiveMutable(x), deactivate to InactiveReference(x) +//! - Duplicate InactiveReference(x), map InactiveReference(x) => InactiveReference(x.a), Activate +//! - Duplicate InactiveReference(x), map InactiveReference(x) => InactiveReference(x.b), Activate + +mod referenceable; +mod reference_core; +mod inactive_reference; +mod mutable_reference; +mod shared_reference; + +use crate::internal_prelude::*; +use reference_core::*; + +pub(crate) use referenceable::*; +pub(crate) use inactive_reference::*; +pub(crate) use mutable_reference::*; +pub(crate) use shared_reference::*; \ No newline at end of file diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs new file mode 100644 index 00000000..b947ff4a --- /dev/null +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -0,0 +1,34 @@ +use super::*; + +pub(crate) struct MutableReference(pub(super) ReferenceCore); + +impl MutableReference { + pub(crate) fn deactivate(self) -> InactiveMutableReference { + self.0.deactivate_reference(); + InactiveMutableReference(self.0) + } +} + +impl Deref for MutableReference { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: See the rustdoc on dynamic_references/mod.rs for full details. + // To summarize: + // - Provided by the safety invariants of `ReferenceCore` and `ReferenceableCore`. + // - The pointer is guaranteed to be valid and properly aligned for the duration of the reference's lifetime. + // - The reference kind checks ensure that it is not mutably aliased while active. + unsafe { &*self.0.pointer } + } +} + +impl DerefMut for MutableReference { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: See the rustdoc on dynamic_references/mod.rs for full details. + // To summarize: + // - Provided by the safety invariants of `ReferenceCore` and `ReferenceableCore`. + // - The pointer is guaranteed to be valid and properly aligned for the duration of the reference's lifetime. + // - The reference kind checks ensure that it is not aliased while active. + unsafe { &mut *self.0.pointer } + } +} \ No newline at end of file diff --git a/src/misc/dynamic_references/reference_core.rs b/src/misc/dynamic_references/reference_core.rs new file mode 100644 index 00000000..19aaeec1 --- /dev/null +++ b/src/misc/dynamic_references/reference_core.rs @@ -0,0 +1,42 @@ +use super::*; + +pub(super) struct ReferenceCore { + pub(super) pointer: *mut T, + pub(super) id: LocalReferenceId, + pub(super) core: Rc>, +} + +impl ReferenceCore { + pub(super) fn activate_mutable_reference(&self) -> FunctionResult<()> { + self.core.data_mut().activate_mutable_reference(self.id) + } + + pub(super) fn activate_shared_reference(&self) -> FunctionResult<()> { + self.core.data_mut().activate_shared_reference(self.id) + } + + pub(super) fn deactivate_reference(&self) { + self.core.data_mut().deactivate_reference(self.id); + } +} + +impl Clone for ReferenceCore { + fn clone(&self) -> Self { + let mut data = self.core.data_mut(); + let copied_data = data.for_reference(self.id).clone(); + if copied_data.reference_kind == ReferenceKind::ActiveMutable { + panic!("Cannot clone a mutable reference"); + } + Self { + pointer: self.pointer, + id: data.new_reference(copied_data), + core: Rc::clone(&self.core), + } + } +} + +impl Drop for ReferenceCore { + fn drop(&mut self) { + self.core.data_mut().drop_reference(self.id); + } +} \ No newline at end of file diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs new file mode 100644 index 00000000..117ca769 --- /dev/null +++ b/src/misc/dynamic_references/referenceable.rs @@ -0,0 +1,246 @@ +use crate::internal_prelude::*; +use std::cell::UnsafeCell; +use std::cmp::Ordering; +use slotmap::{new_key_type, SlotMap}; + +pub(crate) struct Referenceable { + core: Rc>, +} + +impl Referenceable { + pub(crate) fn new_reference(&self) -> InactiveReference { + InactiveReference::new_root(Rc::clone(&self.core)) + } +} + +pub(super) struct ReferenceableCore { + pub(super) root: UnsafeCell, + pub(super) root_name: String, + pub(super) root_span: SpanRange, + pub(super) data: RefCell, +} + +impl ReferenceableCore { + pub(super) fn data(&self) -> Ref<'_, ReferenceableData> { + self.data.borrow() + } + + pub(super) fn data_mut(&self) -> RefMut<'_, ReferenceableData> { + self.data.borrow_mut() + } + + pub(super) fn display_path(&self, mut f: impl std::fmt::Write, id: LocalReferenceId) -> std::fmt::Result { + f.write_str(&self.root_name)?; + let data = self.data(); + let data = data.for_reference(id); + for part in data.path.parts.iter() { + match part { + PathPart::Value { bound_as } => { + f.write_str(&format!(" (of type {})", bound_as.source_name()))?; + } + PathPart::ArrayChild(i) => { + f.write_char('[')?; + write!(f, "{}", i)?; + f.write_char(']')?; + } + PathPart::ObjectChild(key) => { + if syn::parse_str::(key).is_ok() { + f.write_char('.')?; + f.write_str(key)?; + } else { + f.write_char('[')?; + write!(f, "{:?}", key)?; + f.write_char(']')?; + } + } + } + } + Ok(()) + } +} + +new_key_type! { + pub(crate) struct LocalReferenceId; +} + +pub(super) struct ReferenceableData { + arena: SlotMap, +} + +impl ReferenceableData { + pub(super) fn new_reference(&mut self, data: TrackedReference) -> LocalReferenceId { + self.arena.insert(data) + } + + pub(super) fn for_reference(&self, id: LocalReferenceId) -> &TrackedReference { + self.arena + .get(id) + .expect("reference id not found in map") + } + + fn for_reference_mut(&mut self, id: LocalReferenceId) -> &mut TrackedReference { + self.arena + .get_mut(id) + .expect("reference id not found in map") + } + + pub(super) fn drop_reference(&mut self, id: LocalReferenceId) { + self.arena.remove(id); + } + + pub(super) fn deactivate_reference(&mut self, id: LocalReferenceId) { + self.for_reference_mut(id).reference_kind = ReferenceKind::Inactive; + } + + pub(super) fn activate_mutable_reference(&mut self, id: LocalReferenceId) -> FunctionResult<()> { + let data = self.for_reference(id); + // Perform checks as per the module doc on `dynamic_references` + for (other_id, other_data) in self.arena.iter() { + if other_id != id { + match other_data.path.partial_cmp(&data.path) { + None => continue, + Some(Ordering::Equal | Ordering::Less) => { + if other_data.reference_kind.is_active() { + todo!("// BETTER ERROR: Safety invariant break") + } + }, + Some(Ordering::Greater) => { + todo!("// BETTER ERROR: Validity invariant break") + }, + } + } + } + let data = self.for_reference_mut(id); + data.reference_kind = match data.reference_kind { + ReferenceKind::Inactive => ReferenceKind::ActiveMutable, + _ => panic!("cannot mut-activate an active reference"), + }; + Ok(()) + } + + pub(super) fn activate_shared_reference(&mut self, id: LocalReferenceId) -> FunctionResult<()> { + let data = self.for_reference(id); + // Perform checks as per the module doc on `dynamic_references` + for (other_id, other_data) in self.arena.iter() { + if other_id != id && other_data.reference_kind == ReferenceKind::ActiveMutable { + match other_data.path.partial_cmp(&data.path) { + Some(Ordering::Equal | Ordering::Greater) => { + todo!("// BETTER ERROR: Safety invariant break") + }, + Some(Ordering::Less) => { + panic!("Unexpected mutability invariant break: shared-activating a reference with an active mutable parent. This should already be prevented by the mutable reference's activation checks, so this likely indicates a bug in the implementation.") + }, + _ => continue, + } + } + } + let data = self.for_reference_mut(id); + data.reference_kind = match data.reference_kind { + ReferenceKind::Inactive => ReferenceKind::ActiveShared, + _ => panic!("cannot shared-activate an active reference"), + }; + Ok(()) + } +} + +#[derive(Clone)] +pub(super) struct TrackedReference { + pub(super) path: ReferencePath, + pub(super) reference_kind: ReferenceKind, + pub(super) creation_span: SpanRange, +} + +#[derive(PartialEq, Eq, Copy, Clone)] +pub(super) enum ReferenceKind { + Inactive, + ActiveShared, + ActiveMutable, +} + +impl ReferenceKind { + fn is_active(&self) -> bool { + match self { + ReferenceKind::Inactive => false, + ReferenceKind::ActiveShared | ReferenceKind::ActiveMutable => true, + } + } +} + +/// ## Partial Order +/// +/// Has a partial order defined which says all of the following: +/// - P1 and P2 are incomparable if they can exist as distinct mutable reference +/// - P1 < P2 if P2 is "deeper" than P1, i.e. that: +/// - A mutation of P1 could invalidate a reference to P2 so must be banned +/// - P1 could observe a mutation of P2, but would not invalidate it +/// - P1 == P2 if they are equivalent to the same reference, i.e. that: +/// - A mutation of P1 is possible without invalidating a reference to P2 +/// - P1 could observe a mutation of P2 (and vice versa) +/// +/// If any of these assumptions are wrong, we'll need to revisit this. +#[derive(PartialEq, Eq, Clone)] +pub(super) struct ReferencePath { + parts: Vec, +} + +impl ReferencePath { + pub(super) fn leaf(bound_as: TypeKind) -> Self { + Self { + parts: vec![PathPart::Value { bound_as }], + } + } +} + +impl PartialOrd for ReferencePath { + fn partial_cmp(&self, other: &Self) -> Option { + for (own, other) in self.parts.iter().zip(&other.parts) { + match own.partial_cmp(other) { + // If incomparable, the paths have diverged so are incomparable + None => return None, + Some(Ordering::Equal) => continue, + Some(ordered) => return Some(ordered), + } + } + return Some(self.parts.len().cmp(&other.parts.len())); + } +} + +#[derive(PartialEq, Eq, Clone)] +enum PathPart { + Value { + bound_as: TypeKind, + }, + ArrayChild(usize), + ObjectChild(String), +} + +impl PathPart { + fn bound_type_kind(&self) -> TypeKind { + match self { + PathPart::Value { bound_as } => *bound_as, + PathPart::ArrayChild(_) => ArrayType::type_kind(), + PathPart::ObjectChild(_) => ObjectType::type_kind(), + } + } +} + +impl PartialOrd for PathPart { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (PathPart::ArrayChild(i), PathPart::ArrayChild(j)) if i == j => Some(Ordering::Equal), + (PathPart::ArrayChild(_), PathPart::ArrayChild(_)) => None, + (PathPart::ObjectChild(a), PathPart::ObjectChild(b)) if a == b => Some(Ordering::Equal), + (PathPart::ObjectChild(_), PathPart::ObjectChild(_)) => None, + // TODO: I'm not sure this is right + // - A path being incomparable is a divergence + // - A type being incomparable is: + // - A broken invariant if they are incompatible (e.g. String and Int) + // - ?? if it's compatible dyn and a concrete type (e.g. dyn Iterator and Array) + // - So we probably don't want to make PathPart and TypeKind implement PartialOrd, + // we probably want a more senamtically meaningful output enum which we can handle + // correctly in upstream logic + (this, other) => this.bound_type_kind().partial_cmp(&other.bound_type_kind()), + } + } +} + diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs new file mode 100644 index 00000000..6b98034a --- /dev/null +++ b/src/misc/dynamic_references/shared_reference.rs @@ -0,0 +1,24 @@ +use super::*; + +#[derive(Clone)] +pub(crate) struct SharedReference(pub(super) ReferenceCore); + +impl SharedReference { + pub(crate) fn deactivate(self) -> InactiveSharedReference { + self.0.deactivate_reference(); + InactiveSharedReference(self.0) + } +} + +impl Deref for SharedReference { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: See the rustdoc on dynamic_references/mod.rs for full details. + // To summarize: + // - Provided by the safety invariants of `ReferenceCore` and `ReferenceableCore`. + // - The pointer is guaranteed to be valid and properly aligned for the duration of the reference's lifetime. + // - The reference kind checks ensure that it is not mutably aliased while active. + unsafe { &*self.0.pointer } + } +} diff --git a/src/misc/mod.rs b/src/misc/mod.rs index 36aa4457..5ebfb9a4 100644 --- a/src/misc/mod.rs +++ b/src/misc/mod.rs @@ -1,5 +1,6 @@ mod arena; mod errors; +mod dynamic_references; mod field_inputs; mod iterators; mod keywords; @@ -8,6 +9,7 @@ mod parse_traits; pub(crate) mod string_conversion; pub(crate) use arena::*; +pub(crate) use dynamic_references::*; pub(crate) use errors::*; pub(crate) use field_inputs::*; pub(crate) use iterators::*; From f8473b7294150b017710dbd3922fd8eb69977e3d Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 22 Feb 2026 19:45:07 +0000 Subject: [PATCH 27/49] feat: Add emplacing to the new references --- plans/TODO.md | 4 +- .../dynamic_references/inactive_reference.rs | 36 ------ src/misc/dynamic_references/mod.rs | 53 +++++--- .../dynamic_references/mutable_reference.rs | 117 ++++++++++++++++-- src/misc/dynamic_references/reference_core.rs | 104 ++++++++++++++-- src/misc/dynamic_references/referenceable.rs | 96 ++++++++++---- .../dynamic_references/shared_reference.rs | 100 ++++++++++++++- src/misc/mod.rs | 3 +- 8 files changed, 414 insertions(+), 99 deletions(-) delete mode 100644 src/misc/dynamic_references/inactive_reference.rs diff --git a/plans/TODO.md b/plans/TODO.md index 18aa09e2..4620e002 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -302,12 +302,12 @@ The issue is that we get a variable stored as `arr := DisabledMutable` and then - [x] Create Referenceable, and Reference types - [ ] Create error messages for rule breaks in `referenceable.rs` - [ ] Add the following to `ReferenceCore` and maybe others: + - [x] Emplacing - [ ] Ability to map deeper. Should take a `PathExtension` and a new span. See rule (c) from `dynamic_references/mod.rs` - [ ] Various mapping functions (e.g. array offset; object key) - [ ] Try map - - [ ] Emplacing -- [ ] Check for missing features from `Shared` / `SharedSubRcRefCell` and `Mutable` / `MutableSubRcRefCell` +- [ ] Try replacing `Shared` / `Mutable` ### Other ideas diff --git a/src/misc/dynamic_references/inactive_reference.rs b/src/misc/dynamic_references/inactive_reference.rs deleted file mode 100644 index 15d711c0..00000000 --- a/src/misc/dynamic_references/inactive_reference.rs +++ /dev/null @@ -1,36 +0,0 @@ -use super::*; - -#[derive(Clone)] -pub(crate) struct InactiveReference(ReferenceCore); - -impl InactiveReference { - pub(super) fn new_root(core: Rc>) -> Self { - let new_id = core.data_mut() - .new_reference(TrackedReference { - path: ReferencePath::leaf(AnyType::type_kind()), - reference_kind: ReferenceKind::Inactive, - creation_span: core.root_span, - }); - Self(ReferenceCore { - pointer: core.root.get(), - id: new_id, - core, - }) - } - - pub(super) fn activate_mutable(self) -> FunctionResult> { - self.0.activate_mutable_reference()?; - Ok(MutableReference(self.0)) - } - - pub(super) fn activate_shared(self) -> FunctionResult> { - self.0.activate_shared_reference()?; - Ok(SharedReference(self.0)) - } -} - -#[derive(Clone)] -pub(crate) struct InactiveSharedReference(pub(super) ReferenceCore); - -#[derive(Clone)] -pub(crate) struct InactiveMutableReference(pub(super) ReferenceCore); \ No newline at end of file diff --git a/src/misc/dynamic_references/mod.rs b/src/misc/dynamic_references/mod.rs index b98fea21..5efc3ae3 100644 --- a/src/misc/dynamic_references/mod.rs +++ b/src/misc/dynamic_references/mod.rs @@ -1,12 +1,12 @@ //! ## Overview -//! +//! //! We wish to define a dynamic reference model which upholds the rules of rust //! references when those references are active, but which is flexible to the //! needs of the dynamic preinterpret language. -//! +//! //! In other words, it should catch real bugs, with clear error messages, but //! not get in the way of things you might want to do in a dynamic language. -//! +//! //! ## Model //! //! Our model is: @@ -23,7 +23,7 @@ //! (i.e. a reference can't point to a value which isn't of the correct type) //! but doesn't maintain mutability-related safety invariants (i.e. a //! shared reference does not prevent mutation if it is inactive). -//! +//! //! As some examples, it should: //! - Allow x.a: &mut AnyValue and x.b: &mut AnyValue to exist as active mutable //! references at the same time @@ -37,7 +37,7 @@ //! We define a concept of a [`ReferencePath`] with a partial order, roughly //! signalling a depth/subtyping relation to do with mutable ownership. //! It is more strictly defined on the ReferencePath docs. -//! +//! //! There are three reference kinds, each wraps a `*mut T` for ease, and each //! has the *validity* invariant that the pointer will always point to memory //! representing the type `T`: @@ -59,27 +59,44 @@ //! (b) Can't create an Shared from an InactiveReference if there is: //! (b.1) Any equal or deeper ActiveMutable, as they could break our safety invariant //! (b.2) (NOTE: There can't be a strictly shallower ActiveMutable, as that would contravene a.1) -//! (c) Can't map an InactiveReference from A to deeper B if: -//! (c.1) There is a shallower ActiveMutable than B, which could leave us pointing at invalid memory, as per a.1) -//! (c.2) (NOTE: There can't be a strictly shallower ActiveMutable than A, as that would contravene a.1 already) //! //! Basically, a.1 and a.2 are our two key invariants, and all our actions must preserve them. //! -//! Okay, but then -- how do we create `&mut x.a` and `&mut x.b`? As follows: -//! - Start with ActiveMutable(x), deactivate to InactiveReference(x) -//! - Duplicate InactiveReference(x), map InactiveReference(x) => InactiveReference(x.a), Activate -//! - Duplicate InactiveReference(x), map InactiveReference(x) => InactiveReference(x.b), Activate +//! We also need to be able to map/deepen references, e.g. map x to x.a or &any to &integer. +//! We _could_ do this from/via/to InactiveReference with pointer mappings, and a rule to check (a.1) +//! against the resultant state. This would allow us to map references with a present active +//! mutable on a different path. But it's painful to write and reason about, and we don't really +//! need it. +//! +//! Instead, we map via Active references, and then deactivate when we stop actively using a reference. +//! +//! Okay, but then -- how do we create `&mut x.a` and `&mut x.b` at the same time? As follows: +//! - Start with InactiveReference(x) +//! - Duplicate InactiveReference(x), activate to MutableReference(x), map to MutableReference(x.a), deactivate to InactiveReference(x.a) +//! - Duplicate InactiveReference(x), activate to MutableReference(x), map to MutableReference(x.b), deactivate to InactiveReference(x.b) +//! - Activate both into Mutable references +//! +//! ## Previous Model +//! +//! Was based off an `Rc>` which we could use to create Active/Mutable references, and deactivate them. +//! However, it didn't respect (a.1) and it didn't permit mutiple mutable references on different paths. -mod referenceable; -mod reference_core; -mod inactive_reference; +// TODO[references]: Remove once integrated +#![allow(unused)] mod mutable_reference; +mod reference_core; +mod referenceable; mod shared_reference; +// Prelude for everything under dynamic_references use crate::internal_prelude::*; use reference_core::*; +use slotmap::{new_key_type, SlotMap}; +use std::cell::UnsafeCell; +use std::cmp::Ordering; +use std::mem::ManuallyDrop; +use std::ptr::NonNull; -pub(crate) use referenceable::*; -pub(crate) use inactive_reference::*; pub(crate) use mutable_reference::*; -pub(crate) use shared_reference::*; \ No newline at end of file +pub(crate) use referenceable::*; +pub(crate) use shared_reference::*; diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index b947ff4a..7133c999 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -1,15 +1,60 @@ use super::*; -pub(crate) struct MutableReference(pub(super) ReferenceCore); +pub(crate) struct MutableReference(pub(super) ReferenceCore); -impl MutableReference { +impl MutableReference { pub(crate) fn deactivate(self) -> InactiveMutableReference { self.0.deactivate_reference(); InactiveMutableReference(self.0) } + + /// A powerful map method which lets you place the resultant mapped reference inside + /// a structure arbitrarily. + pub(crate) fn emplace_map( + mut self, + f: impl for<'e> FnOnce(&'e mut T, &mut MutableEmplacerV2<'e, T>) -> O, + ) -> O { + // SAFETY: The validity + safety invariants are upheld by `ReferenceableCore` + // ... assuming this id is marked as a mutable reference for the duration. + // The emplacer ensures that this is upheld whilst it is alive; via either: + // - Delegating to the created MutableReference if it is emplaced + // - Surviving until Drop at the end of this method if it is not emplaced + let copied_mut = unsafe { self.0.pointer.as_mut() }; + let mut emplacer = MutableEmplacerV2(self.0.into_emplacer()); + f(copied_mut, &mut emplacer) + } + + /// SAFETY: + /// - The caller must ensure that the ReferencePathExtension is correct + /// (an overly-specific ReferencePathExtension may cause safety issues) + pub(crate) unsafe fn map( + self, + f: impl FnOnce(&mut T) -> &mut V, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> MutableReference { + self.emplace_map(move |input, emplacer| { + emplacer.emplace(f(input), path_extension, new_span) + }) + } + + /// SAFETY: + /// - The caller must ensure that the ReferencePathExtension is correct + /// (an overly-specific ReferencePathExtension may cause safety issues) + pub(crate) unsafe fn try_map( + self, + f: impl FnOnce(&mut T) -> Result<&mut V, E>, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> Result, (E, MutableReference)> { + self.emplace_map(|input, emplacer| match f(input) { + Ok(output) => Ok(emplacer.emplace(output, path_extension, new_span)), + Err(e) => Err((e, emplacer.revert())), + }) + } } -impl Deref for MutableReference { +impl Deref for MutableReference { type Target = T; fn deref(&self) -> &Self::Target { @@ -18,17 +63,75 @@ impl Deref for MutableReference { // - Provided by the safety invariants of `ReferenceCore` and `ReferenceableCore`. // - The pointer is guaranteed to be valid and properly aligned for the duration of the reference's lifetime. // - The reference kind checks ensure that it is not mutably aliased while active. - unsafe { &*self.0.pointer } + unsafe { self.0.pointer.as_ref() } } } -impl DerefMut for MutableReference { +impl DerefMut for MutableReference { fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: See the rustdoc on dynamic_references/mod.rs for full details. // To summarize: // - Provided by the safety invariants of `ReferenceCore` and `ReferenceableCore`. // - The pointer is guaranteed to be valid and properly aligned for the duration of the reference's lifetime. // - The reference kind checks ensure that it is not aliased while active. - unsafe { &mut *self.0.pointer } + unsafe { self.0.pointer.as_mut() } } -} \ No newline at end of file +} + +#[derive(Clone)] +pub(crate) struct InactiveMutableReference(pub(super) ReferenceCore); + +impl InactiveMutableReference { + pub(crate) fn activate(self) -> FunctionResult> { + self.0.activate_mutable_reference()?; + Ok(MutableReference(self.0)) + } + + pub(crate) fn into_shared(self) -> InactiveSharedReference { + // As an inactive reference, we are free to map between them + // ... we could even enable the other way around, but that'd likely allow breaking + // application invariants which we want to respect. + InactiveSharedReference(self.0) + } +} + +pub(crate) struct MutableEmplacerV2<'e, T: ?Sized>(EmplacerCore<'e, T>); + +impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { + pub(crate) fn revert(&mut self) -> MutableReference { + MutableReference(self.0.revert()) + } + + /// SAFETY: + /// - The caller must ensure that the ReferencePathExtension is correct + /// (an overly-specific ReferencePathExtension may cause safety issues) + pub(crate) unsafe fn emplace( + &mut self, + value: &'e mut V, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> MutableReference { + unsafe { + // SAFETY: The lifetime 'e is equal to the &'e content argument in replace + // So this guarantees that the returned reference is valid as long as the SharedSubRcRefCell exists + self.emplace_unchecked(value, path_extension, new_span) + } + } + + /// SAFETY: + /// - The caller must ensure that the value's lifetime is derived from the original content + /// - The caller must ensure that the ReferencePathExtension is correct + pub(crate) unsafe fn emplace_unchecked( + &mut self, + value: &mut V, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> MutableReference { + // SAFETY: The pointer is from a reference so non-null + let pointer = unsafe { NonNull::new_unchecked(value as *mut V) }; + // SAFETY: + // - The caller ensures that the reference is derived from the original content + // - The caller ensures that the ReferencePathExtension is correct + unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } + } +} diff --git a/src/misc/dynamic_references/reference_core.rs b/src/misc/dynamic_references/reference_core.rs index 19aaeec1..0e0e1fcb 100644 --- a/src/misc/dynamic_references/reference_core.rs +++ b/src/misc/dynamic_references/reference_core.rs @@ -1,12 +1,30 @@ use super::*; -pub(super) struct ReferenceCore { - pub(super) pointer: *mut T, +pub(super) struct ReferenceCore { + pub(super) pointer: NonNull, pub(super) id: LocalReferenceId, pub(super) core: Rc>, } -impl ReferenceCore { +impl ReferenceCore { + pub(super) fn new_root( + core: Rc>, + reference_kind: ReferenceKind, + ) -> Self { + let new_id = core.data_mut().new_reference(TrackedReference { + path: ReferencePath::leaf(AnyType::type_kind()), + reference_kind, + creation_span: core.root_span, + }); + Self { + pointer: core.root(), + id: new_id, + core, + } + } +} + +impl ReferenceCore { pub(super) fn activate_mutable_reference(&self) -> FunctionResult<()> { self.core.data_mut().activate_mutable_reference(self.id) } @@ -18,14 +36,30 @@ impl ReferenceCore { pub(super) fn deactivate_reference(&self) { self.core.data_mut().deactivate_reference(self.id); } + + pub(super) fn into_emplacer<'e>(self) -> EmplacerCore<'e, T> { + // NOTE: The clean-up duty is delegated to the emplacer + let reference = ManuallyDrop::new(self); + let manual_drop = ManualDropReferenceCore { + pointer: reference.pointer, + id: reference.id, + // SAFETY: read's invariants are upheld, and we are manually dropping the reference, + // so we won't run the drop implementation that would mess with the reference count. + core: unsafe { std::ptr::read(&reference.core) }, + }; + EmplacerCore { + inner: Some(manual_drop), + encapsulation_lifetime: std::marker::PhantomData, + } + } } -impl Clone for ReferenceCore { +impl Clone for ReferenceCore { fn clone(&self) -> Self { let mut data = self.core.data_mut(); let copied_data = data.for_reference(self.id).clone(); if copied_data.reference_kind == ReferenceKind::ActiveMutable { - panic!("Cannot clone a mutable reference"); + panic!("Cannot clone an active mutable reference"); } Self { pointer: self.pointer, @@ -35,8 +69,64 @@ impl Clone for ReferenceCore { } } -impl Drop for ReferenceCore { +impl Drop for ReferenceCore { fn drop(&mut self) { self.core.data_mut().drop_reference(self.id); } -} \ No newline at end of file +} + +/// Essentially the same as a [ReferenceCore], but doesn't have the drop implementation. +/// Must be a temporary, whose lifetime exists only as long as the pointer is valid in the given context. +pub(super) struct ManualDropReferenceCore { + pub(super) pointer: NonNull, + pub(super) id: LocalReferenceId, + pub(super) core: Rc>, +} + +pub(super) struct EmplacerCore<'e, T: ?Sized> { + inner: Option>, + encapsulation_lifetime: std::marker::PhantomData<&'e ()>, +} + +impl<'e, T: ?Sized> EmplacerCore<'e, T> { + pub(crate) fn revert(&mut self) -> ReferenceCore { + let core = self.take(); + ReferenceCore { + pointer: core.pointer, + id: core.id, + core: core.core, + } + } + + /// SAFETY: + /// - The caller must ensure that the pointer is derived from the original content + /// - The caller must ensure that the ReferencePathExtension is correct + pub(crate) unsafe fn emplace_unchecked( + &mut self, + pointer: NonNull, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> ReferenceCore { + let emplacer_core = self.take(); + let id = emplacer_core.id; + let core = emplacer_core.core; + core.data_mut() + .derive_reference(id, path_extension, new_span); + ReferenceCore { pointer, id, core } + } + + fn take(&mut self) -> ManualDropReferenceCore { + self.inner + .take() + .expect("You may only emplace or revert once from an emplacer") + } +} + +impl<'e, T: ?Sized> Drop for EmplacerCore<'e, T> { + fn drop(&mut self) { + if let Some(core) = self.inner.take() { + // If the emplacer is still around, we need to manually drop it + core.core.data_mut().drop_reference(core.id); + }; + } +} diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 117ca769..d1f7f244 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -1,26 +1,52 @@ -use crate::internal_prelude::*; -use std::cell::UnsafeCell; -use std::cmp::Ordering; -use slotmap::{new_key_type, SlotMap}; +use super::*; pub(crate) struct Referenceable { core: Rc>, } impl Referenceable { - pub(crate) fn new_reference(&self) -> InactiveReference { - InactiveReference::new_root(Rc::clone(&self.core)) + pub(crate) fn new_inactive_shared(&self) -> InactiveSharedReference { + InactiveSharedReference(ReferenceCore::new_root( + self.core.clone(), + ReferenceKind::Inactive, + )) + } + + pub(crate) fn new_inactive_mutable(&self) -> InactiveMutableReference { + InactiveMutableReference(ReferenceCore::new_root( + self.core.clone(), + ReferenceKind::Inactive, + )) } } pub(super) struct ReferenceableCore { - pub(super) root: UnsafeCell, - pub(super) root_name: String, + // Guaranteed not-null + root: UnsafeCell, + root_name: String, pub(super) root_span: SpanRange, - pub(super) data: RefCell, + data: RefCell, } impl ReferenceableCore { + pub(super) fn new(root: T, root_name: String, root_span: SpanRange) -> Self { + Self { + root: UnsafeCell::new(root), + root_name, + root_span, + data: RefCell::new(ReferenceableData { + arena: SlotMap::with_key(), + }), + } + } + + pub(super) fn root(&self) -> NonNull { + unsafe { + // SAFETY: This is guaranteed non-null + NonNull::new_unchecked(self.root.get()) + } + } + pub(super) fn data(&self) -> Ref<'_, ReferenceableData> { self.data.borrow() } @@ -29,7 +55,11 @@ impl ReferenceableCore { self.data.borrow_mut() } - pub(super) fn display_path(&self, mut f: impl std::fmt::Write, id: LocalReferenceId) -> std::fmt::Result { + pub(super) fn display_path( + &self, + mut f: impl std::fmt::Write, + id: LocalReferenceId, + ) -> std::fmt::Result { f.write_str(&self.root_name)?; let data = self.data(); let data = data.for_reference(id); @@ -73,9 +103,7 @@ impl ReferenceableData { } pub(super) fn for_reference(&self, id: LocalReferenceId) -> &TrackedReference { - self.arena - .get(id) - .expect("reference id not found in map") + self.arena.get(id).expect("reference id not found in map") } fn for_reference_mut(&mut self, id: LocalReferenceId) -> &mut TrackedReference { @@ -92,7 +120,10 @@ impl ReferenceableData { self.for_reference_mut(id).reference_kind = ReferenceKind::Inactive; } - pub(super) fn activate_mutable_reference(&mut self, id: LocalReferenceId) -> FunctionResult<()> { + pub(super) fn activate_mutable_reference( + &mut self, + id: LocalReferenceId, + ) -> FunctionResult<()> { let data = self.for_reference(id); // Perform checks as per the module doc on `dynamic_references` for (other_id, other_data) in self.arena.iter() { @@ -101,12 +132,14 @@ impl ReferenceableData { None => continue, Some(Ordering::Equal | Ordering::Less) => { if other_data.reference_kind.is_active() { + // TODO[references]: Fix this todo!("// BETTER ERROR: Safety invariant break") } - }, + } Some(Ordering::Greater) => { + // TODO[references]: Fix this todo!("// BETTER ERROR: Validity invariant break") - }, + } } } } @@ -125,11 +158,12 @@ impl ReferenceableData { if other_id != id && other_data.reference_kind == ReferenceKind::ActiveMutable { match other_data.path.partial_cmp(&data.path) { Some(Ordering::Equal | Ordering::Greater) => { + // TODO[references] todo!("// BETTER ERROR: Safety invariant break") - }, + } Some(Ordering::Less) => { panic!("Unexpected mutability invariant break: shared-activating a reference with an active mutable parent. This should already be prevented by the mutable reference's activation checks, so this likely indicates a bug in the implementation.") - }, + } _ => continue, } } @@ -141,6 +175,17 @@ impl ReferenceableData { }; Ok(()) } + + pub(super) fn derive_reference( + &mut self, + id: LocalReferenceId, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) { + let data = self.for_reference_mut(id); + data.creation_span = new_span; + // TODO[references]: Extend the path + } } #[derive(Clone)] @@ -167,7 +212,7 @@ impl ReferenceKind { } /// ## Partial Order -/// +/// /// Has a partial order defined which says all of the following: /// - P1 and P2 are incomparable if they can exist as distinct mutable reference /// - P1 < P2 if P2 is "deeper" than P1, i.e. that: @@ -176,7 +221,7 @@ impl ReferenceKind { /// - P1 == P2 if they are equivalent to the same reference, i.e. that: /// - A mutation of P1 is possible without invalidating a reference to P2 /// - P1 could observe a mutation of P2 (and vice versa) -/// +/// /// If any of these assumptions are wrong, we'll need to revisit this. #[derive(PartialEq, Eq, Clone)] pub(super) struct ReferencePath { @@ -201,15 +246,15 @@ impl PartialOrd for ReferencePath { Some(ordered) => return Some(ordered), } } - return Some(self.parts.len().cmp(&other.parts.len())); + Some(self.parts.len().cmp(&other.parts.len())) } } +pub(crate) struct ReferencePathExtension; + #[derive(PartialEq, Eq, Clone)] enum PathPart { - Value { - bound_as: TypeKind, - }, + Value { bound_as: TypeKind }, ArrayChild(usize), ObjectChild(String), } @@ -231,7 +276,7 @@ impl PartialOrd for PathPart { (PathPart::ArrayChild(_), PathPart::ArrayChild(_)) => None, (PathPart::ObjectChild(a), PathPart::ObjectChild(b)) if a == b => Some(Ordering::Equal), (PathPart::ObjectChild(_), PathPart::ObjectChild(_)) => None, - // TODO: I'm not sure this is right + // TODO[references]: I'm not sure this is right // - A path being incomparable is a divergence // - A type being incomparable is: // - A broken invariant if they are incompatible (e.g. String and Int) @@ -243,4 +288,3 @@ impl PartialOrd for PathPart { } } } - diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 6b98034a..7428d5f9 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -1,13 +1,58 @@ use super::*; #[derive(Clone)] -pub(crate) struct SharedReference(pub(super) ReferenceCore); +pub(crate) struct SharedReference(pub(super) ReferenceCore); impl SharedReference { pub(crate) fn deactivate(self) -> InactiveSharedReference { self.0.deactivate_reference(); InactiveSharedReference(self.0) } + + /// A powerful map method which lets you place the resultant mapped reference inside + /// a structure arbitrarily. + pub(crate) fn emplace_map( + self, + f: impl for<'e> FnOnce(&'e T, &mut SharedEmplacerV2<'e, T>) -> O, + ) -> O { + // SAFETY: The validity + safety invariants are upheld by `ReferenceableCore` + // ... assuming this id is marked as a shared reference for the duration. + // The emplacer ensures that this is upheld whilst it is alive; via either: + // - Delegating to the created SharedReference if it is emplaced + // - Surviving until Drop at the end of this method if it is not emplaced + let copied_ref = unsafe { self.0.pointer.as_ref() }; + let mut emplacer = SharedEmplacerV2(self.0.into_emplacer()); + f(copied_ref, &mut emplacer) + } + + /// SAFETY: + /// - The caller must ensure that the ReferencePathExtension is correct + /// (an overly-specific ReferencePathExtension may cause safety issues) + pub(crate) unsafe fn map( + self, + f: impl FnOnce(&T) -> &V, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> SharedReference { + self.emplace_map(move |input, emplacer| { + emplacer.emplace(f(input), path_extension, new_span) + }) + } + + /// SAFETY: + /// - The caller must ensure that the ReferencePathExtension is correct + /// (an overly-specific ReferencePathExtension may cause safety issues) + pub(crate) unsafe fn try_map( + self, + f: impl FnOnce(&T) -> Result<&V, E>, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> Result, (E, SharedReference)> { + self.emplace_map(|input, emplacer| match f(input) { + Ok(output) => Ok(emplacer.emplace(output, path_extension, new_span)), + Err(e) => Err((e, emplacer.revert())), + }) + } } impl Deref for SharedReference { @@ -19,6 +64,57 @@ impl Deref for SharedReference { // - Provided by the safety invariants of `ReferenceCore` and `ReferenceableCore`. // - The pointer is guaranteed to be valid and properly aligned for the duration of the reference's lifetime. // - The reference kind checks ensure that it is not mutably aliased while active. - unsafe { &*self.0.pointer } + unsafe { self.0.pointer.as_ref() } + } +} + +#[derive(Clone)] +pub(crate) struct InactiveSharedReference(pub(super) ReferenceCore); + +impl InactiveSharedReference { + pub(crate) fn activate(self) -> FunctionResult> { + self.0.activate_shared_reference()?; + Ok(SharedReference(self.0)) + } +} + +pub(crate) struct SharedEmplacerV2<'e, T: ?Sized>(EmplacerCore<'e, T>); + +impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { + pub(crate) fn revert(&mut self) -> SharedReference { + SharedReference(self.0.revert()) + } + + /// SAFETY: + /// - The caller must ensure that the ReferencePathExtension is correct + /// (an overly-specific ReferencePathExtension may cause safety issues) + pub(crate) unsafe fn emplace( + &mut self, + value: &'e V, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> SharedReference { + unsafe { + // SAFETY: The lifetime 'e is equal to the &'e content argument in replace + // So this guarantees that the returned reference is valid as long as the SharedSubRcRefCell exists + self.emplace_unchecked(value, path_extension, new_span) + } + } + + /// SAFETY: + /// - The caller must ensure that the value's lifetime is derived from the original content + /// - The caller must ensure that the ReferencePathExtension is correct + pub(crate) unsafe fn emplace_unchecked( + &mut self, + value: &V, + path_extension: ReferencePathExtension, + new_span: SpanRange, + ) -> SharedReference { + // SAFETY: The pointer is from a reference so non-null + let pointer = unsafe { NonNull::new_unchecked(value as *const V as *mut V) }; + // SAFETY: + // - The caller ensures that the reference is derived from the original content + // - The caller ensures that the ReferencePathExtension is correct + unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } } diff --git a/src/misc/mod.rs b/src/misc/mod.rs index 5ebfb9a4..d1d17792 100644 --- a/src/misc/mod.rs +++ b/src/misc/mod.rs @@ -1,6 +1,6 @@ mod arena; -mod errors; mod dynamic_references; +mod errors; mod field_inputs; mod iterators; mod keywords; @@ -9,6 +9,7 @@ mod parse_traits; pub(crate) mod string_conversion; pub(crate) use arena::*; +#[allow(unused)] // TODO: Remove unused once integrated pub(crate) use dynamic_references::*; pub(crate) use errors::*; pub(crate) use field_inputs::*; From e8b3e7446ddc77c7232b34838a37372929180ce9 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 22 Feb 2026 21:51:03 +0000 Subject: [PATCH 28/49] feat: Error messages when reference activation fails --- plans/2026-01-types-and-forms.md | 2 + plans/TODO.md | 5 +- src/expressions/type_resolution/type_kinds.rs | 82 ++-- .../dynamic_references/mutable_reference.rs | 8 +- src/misc/dynamic_references/reference_core.rs | 26 +- src/misc/dynamic_references/referenceable.rs | 357 +++++++++++++----- .../dynamic_references/shared_reference.rs | 7 +- 7 files changed, 345 insertions(+), 142 deletions(-) diff --git a/plans/2026-01-types-and-forms.md b/plans/2026-01-types-and-forms.md index f98009de..c1dc59d5 100644 --- a/plans/2026-01-types-and-forms.md +++ b/plans/2026-01-types-and-forms.md @@ -83,6 +83,8 @@ Honestly, it's really hard. Would need to think about what operations can be per Might come back to this at some point. +Relevant todos are marked as `TODO[non-leaf-form]` + ## What's next? Let's put the "concepts" to-leaf on indefinite pause. It's definitely resulted in a lot of good cleaning up, BUT it's also created a lot of mess. diff --git a/plans/TODO.md b/plans/TODO.md index 4620e002..4d1e071b 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -300,13 +300,12 @@ The issue is that we get a variable stored as `arr := DisabledMutable` and then - [x] Write up model in `dynamic_references/mod.rs` - [x] Add structure for `dynamic_references` - [x] Create Referenceable, and Reference types -- [ ] Create error messages for rule breaks in `referenceable.rs` +- [x] Create error messages for rule breaks in `referenceable.rs` - [ ] Add the following to `ReferenceCore` and maybe others: - [x] Emplacing + - [x] Map, Try map - [ ] Ability to map deeper. Should take a `PathExtension` and a new span. See rule (c) from `dynamic_references/mod.rs` - - [ ] Various mapping functions (e.g. array offset; object key) - - [ ] Try map - [ ] Try replacing `Shared` / `Mutable` diff --git a/src/expressions/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index b255c505..fe9f5de9 100644 --- a/src/expressions/type_resolution/type_kinds.rs +++ b/src/expressions/type_resolution/type_kinds.rs @@ -90,38 +90,74 @@ impl TypeKind { TypeKind::Dyn(dyn_kind) => dyn_kind.source_name(), } } -} -/// This is the subtyping ordering. -/// - I define that X <= Y if Y is a subtype of X. -/// - Think "X is shallower than Y in the enum representation" -/// -/// This must be accurate so that the Dynamic References works correctly. -/// e.g. if I have a `a: &mut AnyValue` and a `b: &mut IntegerValue` pointing -/// to the same memory, then I can't use a to change to a String. -impl PartialOrd for TypeKind { - fn partial_cmp(&self, other: &Self) -> Option { + /// This is the subtyping ordering. + /// - I define that X <= Y if Y is a subtype of X. + /// - Think "X is shallower than Y in the enum representation" + /// + /// This must be accurate so that the Dynamic References works correctly. + /// e.g. if I have a `a: &mut AnyValue` and a `b: &mut IntegerValue` pointing + /// to the same memory, then I can't use a to change to a String. + pub(crate) fn compare_bindings(&self, other: &Self) -> TypeBindingComparison { if self == other { - return Some(std::cmp::Ordering::Equal); + return TypeBindingComparison::Equal; } - // TODO: implement a more complete partial ord that reflects the actual subtyping relationships. match (self, other) { - // None-equal dyns are not subtypes - (TypeKind::Dyn(_), _) => None, - (_, TypeKind::Dyn(_)) => None, + // Dyns are not binding-comparable to other bindings/dyns except themself + (TypeKind::Dyn(_), _) => TypeBindingComparison::Incomparable, + (_, TypeKind::Dyn(_)) => TypeBindingComparison::Incomparable, // All non-any-values are strict subtypes of AnyValue - (TypeKind::Parent(ParentTypeKind::Value(_)), _) => Some(std::cmp::Ordering::Less), - (_, TypeKind::Parent(ParentTypeKind::Value(_))) => Some(std::cmp::Ordering::Greater), - // TODO: Non-equal leave types should kinda be an error, assuming they're pointing - // at the same value - (TypeKind::Leaf(_), _) => None, - (_, TypeKind::Leaf(_)) => None, - // TODO: Handle intermediate parents - _ => None, + (TypeKind::Parent(ParentTypeKind::Value(_)), _) => { + TypeBindingComparison::RightIsMoreSpecific + } + (_, TypeKind::Parent(ParentTypeKind::Value(_))) => { + TypeBindingComparison::LeftIsMoreSpecific + } + // Integer types + ( + TypeKind::Parent(ParentTypeKind::Integer(_)), + TypeKind::Leaf(AnyValueLeafKind::Integer(_)), + ) => TypeBindingComparison::RightIsMoreSpecific, + ( + TypeKind::Leaf(AnyValueLeafKind::Integer(_)), + TypeKind::Parent(ParentTypeKind::Integer(_)), + ) => TypeBindingComparison::LeftIsMoreSpecific, + (TypeKind::Parent(ParentTypeKind::Integer(_)), _) => { + TypeBindingComparison::Incompatible + } + (_, TypeKind::Parent(ParentTypeKind::Integer(_))) => { + TypeBindingComparison::Incompatible + } + // Float types + ( + TypeKind::Parent(ParentTypeKind::Float(_)), + TypeKind::Leaf(AnyValueLeafKind::Float(_)), + ) => TypeBindingComparison::RightIsMoreSpecific, + ( + TypeKind::Leaf(AnyValueLeafKind::Float(_)), + TypeKind::Parent(ParentTypeKind::Float(_)), + ) => TypeBindingComparison::LeftIsMoreSpecific, + (TypeKind::Parent(ParentTypeKind::Float(_)), _) => TypeBindingComparison::Incompatible, + (_, TypeKind::Parent(ParentTypeKind::Float(_))) => TypeBindingComparison::Incompatible, + // Non-equal leaf types are incomparable + (TypeKind::Leaf(_), TypeKind::Leaf(_)) => TypeBindingComparison::Incompatible, } } } +// TODO[non-leaf-form]: This abstraction isn't quite right. +// We probably need to reference whether the form we're looking at +// is compatible with some other form. +pub(crate) enum TypeBindingComparison { + Equal, + RightIsMoreSpecific, + LeftIsMoreSpecific, + Incomparable, + // Indicates that the types are incompatible. + // Such a comparison shouldn't arise between valid references. + Incompatible, +} + #[derive(PartialEq, Eq, Clone, Copy)] pub(crate) enum ParentTypeKind { Value(AnyValueTypeKind), diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index 7133c999..2a811a06 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -4,7 +4,7 @@ pub(crate) struct MutableReference(pub(super) ReferenceCore); impl MutableReference { pub(crate) fn deactivate(self) -> InactiveMutableReference { - self.0.deactivate_reference(); + self.0.core.data_mut().deactivate_reference(self.0.id); InactiveMutableReference(self.0) } @@ -83,7 +83,10 @@ pub(crate) struct InactiveMutableReference(pub(super) ReferenceCore InactiveMutableReference { pub(crate) fn activate(self) -> FunctionResult> { - self.0.activate_mutable_reference()?; + self.0 + .core + .data_mut() + .activate_mutable_reference(self.0.id)?; Ok(MutableReference(self.0)) } @@ -91,6 +94,7 @@ impl InactiveMutableReference { // As an inactive reference, we are free to map between them // ... we could even enable the other way around, but that'd likely allow breaking // application invariants which we want to respect. + self.0.core.data_mut().make_shared(self.0.id); InactiveSharedReference(self.0) } } diff --git a/src/misc/dynamic_references/reference_core.rs b/src/misc/dynamic_references/reference_core.rs index 0e0e1fcb..b587aceb 100644 --- a/src/misc/dynamic_references/reference_core.rs +++ b/src/misc/dynamic_references/reference_core.rs @@ -11,11 +11,15 @@ impl ReferenceCore { core: Rc>, reference_kind: ReferenceKind, ) -> Self { - let new_id = core.data_mut().new_reference(TrackedReference { - path: ReferencePath::leaf(AnyType::type_kind()), - reference_kind, - creation_span: core.root_span, - }); + let new_id = { + let mut data_mut = core.data_mut(); + let creation_span = data_mut.root_span(); + data_mut.new_reference(TrackedReference { + path: ReferencePath::leaf(AnyType::type_kind()), + reference_kind, + creation_span, + }) + }; Self { pointer: core.root(), id: new_id, @@ -25,18 +29,6 @@ impl ReferenceCore { } impl ReferenceCore { - pub(super) fn activate_mutable_reference(&self) -> FunctionResult<()> { - self.core.data_mut().activate_mutable_reference(self.id) - } - - pub(super) fn activate_shared_reference(&self) -> FunctionResult<()> { - self.core.data_mut().activate_shared_reference(self.id) - } - - pub(super) fn deactivate_reference(&self) { - self.core.data_mut().deactivate_reference(self.id); - } - pub(super) fn into_emplacer<'e>(self) -> EmplacerCore<'e, T> { // NOTE: The clean-up duty is delegated to the emplacer let reference = ManuallyDrop::new(self); diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index d1f7f244..55dec454 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -1,3 +1,5 @@ +use std::process::Child; + use super::*; pub(crate) struct Referenceable { @@ -8,14 +10,14 @@ impl Referenceable { pub(crate) fn new_inactive_shared(&self) -> InactiveSharedReference { InactiveSharedReference(ReferenceCore::new_root( self.core.clone(), - ReferenceKind::Inactive, + ReferenceKind::InactiveShared, )) } pub(crate) fn new_inactive_mutable(&self) -> InactiveMutableReference { InactiveMutableReference(ReferenceCore::new_root( self.core.clone(), - ReferenceKind::Inactive, + ReferenceKind::InactiveMutable, )) } } @@ -23,8 +25,6 @@ impl Referenceable { pub(super) struct ReferenceableCore { // Guaranteed not-null root: UnsafeCell, - root_name: String, - pub(super) root_span: SpanRange, data: RefCell, } @@ -32,9 +32,9 @@ impl ReferenceableCore { pub(super) fn new(root: T, root_name: String, root_span: SpanRange) -> Self { Self { root: UnsafeCell::new(root), - root_name, - root_span, data: RefCell::new(ReferenceableData { + root_name, + root_span, arena: SlotMap::with_key(), }), } @@ -54,39 +54,6 @@ impl ReferenceableCore { pub(super) fn data_mut(&self) -> RefMut<'_, ReferenceableData> { self.data.borrow_mut() } - - pub(super) fn display_path( - &self, - mut f: impl std::fmt::Write, - id: LocalReferenceId, - ) -> std::fmt::Result { - f.write_str(&self.root_name)?; - let data = self.data(); - let data = data.for_reference(id); - for part in data.path.parts.iter() { - match part { - PathPart::Value { bound_as } => { - f.write_str(&format!(" (of type {})", bound_as.source_name()))?; - } - PathPart::ArrayChild(i) => { - f.write_char('[')?; - write!(f, "{}", i)?; - f.write_char(']')?; - } - PathPart::ObjectChild(key) => { - if syn::parse_str::(key).is_ok() { - f.write_char('.')?; - f.write_str(key)?; - } else { - f.write_char('[')?; - write!(f, "{:?}", key)?; - f.write_char(']')?; - } - } - } - } - Ok(()) - } } new_key_type! { @@ -94,10 +61,18 @@ new_key_type! { } pub(super) struct ReferenceableData { + // Could be in Referencable Core, but having it here makes the error message API easier + root_name: String, + // Could be in Referencable Core, but having it here makes the error message API easier + root_span: SpanRange, arena: SlotMap, } impl ReferenceableData { + pub(super) fn root_span(&self) -> SpanRange { + self.root_span + } + pub(super) fn new_reference(&mut self, data: TrackedReference) -> LocalReferenceId { self.arena.insert(data) } @@ -117,7 +92,24 @@ impl ReferenceableData { } pub(super) fn deactivate_reference(&mut self, id: LocalReferenceId) { - self.for_reference_mut(id).reference_kind = ReferenceKind::Inactive; + let data = self.for_reference_mut(id); + data.reference_kind = match data.reference_kind { + ReferenceKind::ActiveMutable => ReferenceKind::InactiveMutable, + ReferenceKind::ActiveShared => ReferenceKind::InactiveShared, + _ => panic!("cannot deactivate an inactive reference"), + } + } + + pub(super) fn make_shared(&mut self, id: LocalReferenceId) { + let data = self.for_reference_mut(id); + data.reference_kind = match data.reference_kind { + ReferenceKind::InactiveMutable | ReferenceKind::InactiveShared => { + ReferenceKind::InactiveShared + } + ReferenceKind::ActiveMutable | ReferenceKind::ActiveShared => { + ReferenceKind::ActiveShared + } + } } pub(super) fn activate_mutable_reference( @@ -128,26 +120,35 @@ impl ReferenceableData { // Perform checks as per the module doc on `dynamic_references` for (other_id, other_data) in self.arena.iter() { if other_id != id { - match other_data.path.partial_cmp(&data.path) { + let error_reason = data + .path + .compare(&other_data.path) + .error_comparing_mutable_with_other(other_data.reference_kind.is_active()); + let error_reason = match error_reason { + Some(reason) => reason, None => continue, - Some(Ordering::Equal | Ordering::Less) => { - if other_data.reference_kind.is_active() { - // TODO[references]: Fix this - todo!("// BETTER ERROR: Safety invariant break") - } - } - Some(Ordering::Greater) => { - // TODO[references]: Fix this - todo!("// BETTER ERROR: Validity invariant break") - } - } + }; + let mut error_message = + "Cannot create mutable reference because it clashes with another reference: " + .to_string(); + error_message.push_str(error_reason); + let _ = write!(error_message, "This reference-: "); + self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveMutable)); + let _ = write!(error_message, "Other reference: "); + self.display_path(&mut error_message, other_id, None); + return data.creation_span.ownership_err(error_message); } } + let data = self.for_reference_mut(id); data.reference_kind = match data.reference_kind { - ReferenceKind::Inactive => ReferenceKind::ActiveMutable, + ReferenceKind::InactiveMutable => ReferenceKind::ActiveMutable, + ReferenceKind::InactiveShared => { + panic!("cannot mut-activate an inactive shared reference") + } _ => panic!("cannot mut-activate an active reference"), }; + Ok(()) } @@ -156,21 +157,30 @@ impl ReferenceableData { // Perform checks as per the module doc on `dynamic_references` for (other_id, other_data) in self.arena.iter() { if other_id != id && other_data.reference_kind == ReferenceKind::ActiveMutable { - match other_data.path.partial_cmp(&data.path) { - Some(Ordering::Equal | Ordering::Greater) => { - // TODO[references] - todo!("// BETTER ERROR: Safety invariant break") - } - Some(Ordering::Less) => { - panic!("Unexpected mutability invariant break: shared-activating a reference with an active mutable parent. This should already be prevented by the mutable reference's activation checks, so this likely indicates a bug in the implementation.") - } - _ => continue, - } + let error_reason = other_data + .path + .compare(&data.path) + .error_comparing_mutable_with_other(true); + let error_reason = match error_reason { + Some(reason) => reason, + None => continue, + }; + let mut error_message = + "Cannot create shared reference because it clashes with a mutable reference: " + .to_string(); + error_message.push_str(error_reason); + let _ = write!(error_message, "This reference-: "); + self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveShared)); + let _ = write!(error_message, "Other reference: "); + self.display_path(&mut error_message, other_id, None); + return data.creation_span.ownership_err(error_message); } } let data = self.for_reference_mut(id); data.reference_kind = match data.reference_kind { - ReferenceKind::Inactive => ReferenceKind::ActiveShared, + ReferenceKind::InactiveShared | ReferenceKind::InactiveMutable => { + ReferenceKind::ActiveShared + } _ => panic!("cannot shared-activate an active reference"), }; Ok(()) @@ -186,6 +196,49 @@ impl ReferenceableData { data.creation_span = new_span; // TODO[references]: Extend the path } + + fn display_path( + &self, + mut f: impl std::fmt::Write, + id: LocalReferenceId, + override_kind: Option, + ) -> std::fmt::Result { + let data = self.for_reference(id); + let kind = override_kind.unwrap_or(data.reference_kind); + match kind { + // These are the same width to allow alignment in the error message + ReferenceKind::InactiveShared => f.write_str("[inactive] &")?, + ReferenceKind::InactiveMutable => f.write_str("[inactive] &mut ")?, + ReferenceKind::ActiveShared => f.write_str("[*active*] &")?, + ReferenceKind::ActiveMutable => f.write_str("[*active*] &mut ")?, + } + f.write_str(&self.root_name)?; + for part in data.path.parts.iter() { + match part { + PathPart::Value { bound_as } => { + f.write_str(&format!(" (of type {})", bound_as.source_name()))?; + } + PathPart::Child(child) => match child { + ChildSpecifier::ArrayChild(i) => { + f.write_char('[')?; + write!(f, "{}", i)?; + f.write_char(']')?; + } + ChildSpecifier::ObjectChild(key) => { + if syn::parse_str::(key).is_ok() { + f.write_char('.')?; + f.write_str(key)?; + } else { + f.write_char('[')?; + write!(f, "{:?}", key)?; + f.write_char(']')?; + } + } + }, + } + } + Ok(()) + } } #[derive(Clone)] @@ -197,7 +250,8 @@ pub(super) struct TrackedReference { #[derive(PartialEq, Eq, Copy, Clone)] pub(super) enum ReferenceKind { - Inactive, + InactiveShared, + InactiveMutable, ActiveShared, ActiveMutable, } @@ -205,7 +259,7 @@ pub(super) enum ReferenceKind { impl ReferenceKind { fn is_active(&self) -> bool { match self { - ReferenceKind::Inactive => false, + ReferenceKind::InactiveShared | ReferenceKind::InactiveMutable => false, ReferenceKind::ActiveShared | ReferenceKind::ActiveMutable => true, } } @@ -228,25 +282,86 @@ pub(super) struct ReferencePath { parts: Vec, } +enum PathComparison { + /// The paths have diverged in a manner which permits mutual mutability + /// e.g. left = x.a and right = x.b + Divergent, + /// The paths parts overlap in a manner which forbids mutual mutability, + /// but they are not identical or in an ancestor/descendant relationship. + /// e.g. left = x[0..10], right = x[5..15] + Overlapping, + /// The right path is a descendent of the left path. + /// e.g. right = left.x or right = left[0]["key"] + RightIsDescendent, + /// The left path is a descendent of the right path. + /// e.g. left = right.x or left = right[0]["key"] + LeftIsDescendent, + /// The left and right path refer to the same leaf value. + /// But they may be in different forms. e.g. left = &any and right = &integer + ReferencesEqual(TypeBindingComparison), + /// Represents an impossible comparison which indicates a broken invariant + /// (e.g. left: &integer and right: &string) + Incompatible, +} + +impl PathComparison { + fn error_comparing_mutable_with_other(self, other_is_active: bool) -> Option<&'static str> { + Some(match self { + PathComparison::Divergent => return None, + PathComparison::Overlapping => { + "they overlap, so mutation may invalidate the other reference" + } + PathComparison::RightIsDescendent => { + "mutation may invalidate the other descendent reference" + } + PathComparison::ReferencesEqual(TypeBindingComparison::RightIsMoreSpecific) => { + "mutation may invalidate the other reference with more specific type" + } + PathComparison::ReferencesEqual(TypeBindingComparison::Incomparable) => { + "mutation may invalidate the other reference with an incompatible type" + } + // Activated reference is descendent of existing reference + PathComparison::ReferencesEqual(TypeBindingComparison::Equal) + | PathComparison::ReferencesEqual(TypeBindingComparison::LeftIsMoreSpecific) + | PathComparison::LeftIsDescendent => { + if other_is_active { + "the mutable reference is observable from the other reference, which breaks aliasing rules" + } else { + return None; + } + } + PathComparison::ReferencesEqual(TypeBindingComparison::Incompatible) => { + panic!("Unexpected incompatible type comparison. This indicates a bug in preinterpret.") + } + PathComparison::Incompatible => { + panic!("Unexpected incompatible reference comparison. This indicates a bug in preinterpret.") + } + }) + } +} + impl ReferencePath { pub(super) fn leaf(bound_as: TypeKind) -> Self { Self { parts: vec![PathPart::Value { bound_as }], } } -} -impl PartialOrd for ReferencePath { - fn partial_cmp(&self, other: &Self) -> Option { + fn compare(&self, other: &Self) -> PathComparison { for (own, other) in self.parts.iter().zip(&other.parts) { - match own.partial_cmp(other) { - // If incomparable, the paths have diverged so are incomparable - None => return None, - Some(Ordering::Equal) => continue, - Some(ordered) => return Some(ordered), - } + return match own.compare(other) { + PathPartComparison::Divergent => PathComparison::Divergent, + PathPartComparison::IdenticalChildReference => continue, + PathPartComparison::OverlappingChildReference => PathComparison::Overlapping, + PathPartComparison::RightIsDescendent => PathComparison::RightIsDescendent, + PathPartComparison::LeftIsDescendent => PathComparison::LeftIsDescendent, + PathPartComparison::ReferencesEqual(inner) => { + PathComparison::ReferencesEqual(inner) + } + PathPartComparison::Incompatible => PathComparison::Incompatible, + }; } - Some(self.parts.len().cmp(&other.parts.len())) + unreachable!("BUG: PathParts should be [Child* Value] and so can't end with a comparison of PathPartComparison::IdenticalDeeperReference") } } @@ -255,36 +370,88 @@ pub(crate) struct ReferencePathExtension; #[derive(PartialEq, Eq, Clone)] enum PathPart { Value { bound_as: TypeKind }, + Child(ChildSpecifier), +} + +#[derive(PartialEq, Eq, Clone)] +enum ChildSpecifier { ArrayChild(usize), ObjectChild(String), } -impl PathPart { +impl ChildSpecifier { fn bound_type_kind(&self) -> TypeKind { match self { - PathPart::Value { bound_as } => *bound_as, - PathPart::ArrayChild(_) => ArrayType::type_kind(), - PathPart::ObjectChild(_) => ObjectType::type_kind(), + ChildSpecifier::ArrayChild(_) => ArrayType::type_kind(), + ChildSpecifier::ObjectChild(_) => ObjectType::type_kind(), } } } -impl PartialOrd for PathPart { - fn partial_cmp(&self, other: &Self) -> Option { +enum PathPartComparison { + /// The paths have diverged in a manner which permits mutual mutability + /// e.g. left = x.a and right = x.b + Divergent, + /// The path parts match. + /// e.g. left = root.x.?? and right = root.x.?? + IdenticalChildReference, + /// The paths parts overlap in a manner which forbids mutual mutability, + /// but they are not identical or in an ancestor/descendant relationship. + /// e.g. left = x[0..10], right = x[5..15] + #[allow(unused)] // Kept for future, and to ensure we have the correct abstraction + OverlappingChildReference, + /// The right path is a descendent of the left path. + /// e.g. right = left.x or right = left[0]["key"] + RightIsDescendent, + /// The left path is a descendent of the right path. + /// e.g. left = right.x or left = right[0]["key"] + LeftIsDescendent, + /// The left and right path refer to the same leaf value. + /// But they may be in different forms. e.g. left = &any and right = &integer + ReferencesEqual(TypeBindingComparison), + /// Represents an impossible comparison which indicates a broken invariant + /// (e.g. left: &integer and right: &string) + Incompatible, +} + +impl PathPart { + fn compare(&self, other: &Self) -> PathPartComparison { match (self, other) { - (PathPart::ArrayChild(i), PathPart::ArrayChild(j)) if i == j => Some(Ordering::Equal), - (PathPart::ArrayChild(_), PathPart::ArrayChild(_)) => None, - (PathPart::ObjectChild(a), PathPart::ObjectChild(b)) if a == b => Some(Ordering::Equal), - (PathPart::ObjectChild(_), PathPart::ObjectChild(_)) => None, - // TODO[references]: I'm not sure this is right - // - A path being incomparable is a divergence - // - A type being incomparable is: - // - A broken invariant if they are incompatible (e.g. String and Int) - // - ?? if it's compatible dyn and a concrete type (e.g. dyn Iterator and Array) - // - So we probably don't want to make PathPart and TypeKind implement PartialOrd, - // we probably want a more senamtically meaningful output enum which we can handle - // correctly in upstream logic - (this, other) => this.bound_type_kind().partial_cmp(&other.bound_type_kind()), + (PathPart::Child(a), PathPart::Child(b)) => match (a, b) { + (ChildSpecifier::ArrayChild(i), ChildSpecifier::ArrayChild(j)) if i == j => { + PathPartComparison::IdenticalChildReference + } + (ChildSpecifier::ArrayChild(_), ChildSpecifier::ArrayChild(_)) => { + PathPartComparison::Divergent + } + (ChildSpecifier::ObjectChild(a), ChildSpecifier::ObjectChild(b)) if a == b => { + PathPartComparison::IdenticalChildReference + } + (ChildSpecifier::ObjectChild(_), ChildSpecifier::ObjectChild(_)) => { + PathPartComparison::Divergent + } + _ => PathPartComparison::Incompatible, + }, + (PathPart::Child(a), PathPart::Value { bound_as }) => { + if a.bound_type_kind() == *bound_as { + PathPartComparison::LeftIsDescendent + } else { + PathPartComparison::Incompatible + } + } + (PathPart::Value { bound_as }, PathPart::Child(b)) => { + if b.bound_type_kind() == *bound_as { + PathPartComparison::RightIsDescendent + } else { + PathPartComparison::Incompatible + } + } + ( + PathPart::Value { bound_as }, + PathPart::Value { + bound_as: other_bound_as, + }, + ) => PathPartComparison::ReferencesEqual(bound_as.compare_bindings(other_bound_as)), } } } diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 7428d5f9..44663b16 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -5,7 +5,7 @@ pub(crate) struct SharedReference(pub(super) ReferenceCore); impl SharedReference { pub(crate) fn deactivate(self) -> InactiveSharedReference { - self.0.deactivate_reference(); + self.0.core.data_mut().deactivate_reference(self.0.id); InactiveSharedReference(self.0) } @@ -73,7 +73,10 @@ pub(crate) struct InactiveSharedReference(pub(super) ReferenceCore impl InactiveSharedReference { pub(crate) fn activate(self) -> FunctionResult> { - self.0.activate_shared_reference()?; + self.0 + .core + .data_mut() + .activate_shared_reference(self.0.id)?; Ok(SharedReference(self.0)) } } From 24c6459b1d7e8a3e9390897e6621fea9153f1721 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 22 Feb 2026 23:10:31 +0000 Subject: [PATCH 29/49] feat: Path extensions work --- plans/TODO.md | 4 +- src/expressions/type_resolution/type_kinds.rs | 35 ++++++++---- .../dynamic_references/mutable_reference.rs | 24 ++++----- src/misc/dynamic_references/reference_core.rs | 4 +- src/misc/dynamic_references/referenceable.rs | 54 ++++++++++++++++--- .../dynamic_references/shared_reference.rs | 24 ++++----- 6 files changed, 99 insertions(+), 46 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index 4d1e071b..832a0f90 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -304,11 +304,9 @@ The issue is that we get a variable stored as `arr := DisabledMutable` and then - [ ] Add the following to `ReferenceCore` and maybe others: - [x] Emplacing - [x] Map, Try map - - [ ] Ability to map deeper. Should take a `PathExtension` and a new span. - See rule (c) from `dynamic_references/mod.rs` + - [x] Ability to map deeper. Should take a `PathExtension` and a new span. - [ ] Try replacing `Shared` / `Mutable` - ### Other ideas We could imagine a world where we are more clever over our mutation: diff --git a/src/expressions/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index fe9f5de9..c089e77e 100644 --- a/src/expressions/type_resolution/type_kinds.rs +++ b/src/expressions/type_resolution/type_kinds.rs @@ -91,6 +91,16 @@ impl TypeKind { } } + pub(crate) fn is_tightening_to(&self, other: &Self) -> bool { + match self.compare_bindings(other) { + TypeBindingComparison::Equal => true, + TypeBindingComparison::RightDerivesFromLeft => true, + TypeBindingComparison::LeftDerivesFromRight => false, + TypeBindingComparison::Incomparable => false, + TypeBindingComparison::Incompatible => false, + } + } + /// This is the subtyping ordering. /// - I define that X <= Y if Y is a subtype of X. /// - Think "X is shallower than Y in the enum representation" @@ -103,25 +113,28 @@ impl TypeKind { return TypeBindingComparison::Equal; } match (self, other) { - // Dyns are not binding-comparable to other bindings/dyns except themself - (TypeKind::Dyn(_), _) => TypeBindingComparison::Incomparable, - (_, TypeKind::Dyn(_)) => TypeBindingComparison::Incomparable, + // Dyns are not binding-comparable to other dyns + (TypeKind::Dyn(_), TypeKind::Dyn(_)) => TypeBindingComparison::Incomparable, + // Assuming the values are compatible, a dyn can be derived from any leaf/parent, + // but not the other way around (at present at least) + (TypeKind::Dyn(_), _) => TypeBindingComparison::LeftDerivesFromRight, + (_, TypeKind::Dyn(_)) => TypeBindingComparison::RightDerivesFromLeft, // All non-any-values are strict subtypes of AnyValue (TypeKind::Parent(ParentTypeKind::Value(_)), _) => { - TypeBindingComparison::RightIsMoreSpecific + TypeBindingComparison::RightDerivesFromLeft } (_, TypeKind::Parent(ParentTypeKind::Value(_))) => { - TypeBindingComparison::LeftIsMoreSpecific + TypeBindingComparison::LeftDerivesFromRight } // Integer types ( TypeKind::Parent(ParentTypeKind::Integer(_)), TypeKind::Leaf(AnyValueLeafKind::Integer(_)), - ) => TypeBindingComparison::RightIsMoreSpecific, + ) => TypeBindingComparison::RightDerivesFromLeft, ( TypeKind::Leaf(AnyValueLeafKind::Integer(_)), TypeKind::Parent(ParentTypeKind::Integer(_)), - ) => TypeBindingComparison::LeftIsMoreSpecific, + ) => TypeBindingComparison::LeftDerivesFromRight, (TypeKind::Parent(ParentTypeKind::Integer(_)), _) => { TypeBindingComparison::Incompatible } @@ -132,11 +145,11 @@ impl TypeKind { ( TypeKind::Parent(ParentTypeKind::Float(_)), TypeKind::Leaf(AnyValueLeafKind::Float(_)), - ) => TypeBindingComparison::RightIsMoreSpecific, + ) => TypeBindingComparison::RightDerivesFromLeft, ( TypeKind::Leaf(AnyValueLeafKind::Float(_)), TypeKind::Parent(ParentTypeKind::Float(_)), - ) => TypeBindingComparison::LeftIsMoreSpecific, + ) => TypeBindingComparison::LeftDerivesFromRight, (TypeKind::Parent(ParentTypeKind::Float(_)), _) => TypeBindingComparison::Incompatible, (_, TypeKind::Parent(ParentTypeKind::Float(_))) => TypeBindingComparison::Incompatible, // Non-equal leaf types are incomparable @@ -150,8 +163,8 @@ impl TypeKind { // is compatible with some other form. pub(crate) enum TypeBindingComparison { Equal, - RightIsMoreSpecific, - LeftIsMoreSpecific, + RightDerivesFromLeft, + LeftDerivesFromRight, Incomparable, // Indicates that the types are incompatible. // Such a comparison shouldn't arise between valid references. diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index 2a811a06..329deb1d 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -25,12 +25,12 @@ impl MutableReference { } /// SAFETY: - /// - The caller must ensure that the ReferencePathExtension is correct - /// (an overly-specific ReferencePathExtension may cause safety issues) + /// - The caller must ensure that the PathExtension is correct + /// (an overly-specific PathExtension may cause safety issues) pub(crate) unsafe fn map( self, f: impl FnOnce(&mut T) -> &mut V, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> MutableReference { self.emplace_map(move |input, emplacer| { @@ -39,12 +39,12 @@ impl MutableReference { } /// SAFETY: - /// - The caller must ensure that the ReferencePathExtension is correct - /// (an overly-specific ReferencePathExtension may cause safety issues) + /// - The caller must ensure that the PathExtension is correct + /// (an overly-specific PathExtension may cause safety issues) pub(crate) unsafe fn try_map( self, f: impl FnOnce(&mut T) -> Result<&mut V, E>, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> Result, (E, MutableReference)> { self.emplace_map(|input, emplacer| match f(input) { @@ -107,12 +107,12 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { } /// SAFETY: - /// - The caller must ensure that the ReferencePathExtension is correct - /// (an overly-specific ReferencePathExtension may cause safety issues) + /// - The caller must ensure that the PathExtension is correct + /// (an overly-specific PathExtension may cause safety issues) pub(crate) unsafe fn emplace( &mut self, value: &'e mut V, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> MutableReference { unsafe { @@ -124,18 +124,18 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { /// SAFETY: /// - The caller must ensure that the value's lifetime is derived from the original content - /// - The caller must ensure that the ReferencePathExtension is correct + /// - The caller must ensure that the PathExtension is correct pub(crate) unsafe fn emplace_unchecked( &mut self, value: &mut V, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> MutableReference { // SAFETY: The pointer is from a reference so non-null let pointer = unsafe { NonNull::new_unchecked(value as *mut V) }; // SAFETY: // - The caller ensures that the reference is derived from the original content - // - The caller ensures that the ReferencePathExtension is correct + // - The caller ensures that the PathExtension is correct unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } } diff --git a/src/misc/dynamic_references/reference_core.rs b/src/misc/dynamic_references/reference_core.rs index b587aceb..52eed74e 100644 --- a/src/misc/dynamic_references/reference_core.rs +++ b/src/misc/dynamic_references/reference_core.rs @@ -92,11 +92,11 @@ impl<'e, T: ?Sized> EmplacerCore<'e, T> { /// SAFETY: /// - The caller must ensure that the pointer is derived from the original content - /// - The caller must ensure that the ReferencePathExtension is correct + /// - The caller must ensure that the PathExtension is correct pub(crate) unsafe fn emplace_unchecked( &mut self, pointer: NonNull, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> ReferenceCore { let emplacer_core = self.take(); diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 55dec454..cf45a735 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -189,12 +189,49 @@ impl ReferenceableData { pub(super) fn derive_reference( &mut self, id: LocalReferenceId, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) { let data = self.for_reference_mut(id); data.creation_span = new_span; - // TODO[references]: Extend the path + let mut last_path_part = data.path.parts.last_mut().expect("path is non-empty"); + match (last_path_part, path_extension) { + (last_path_part, PathExtension::Child(specifier, child_bound_as)) => { + let parent_type = specifier.bound_type_kind(); + match last_path_part { + PathPart::Value { bound_as } => { + if !bound_as.is_tightening_to(&parent_type) { + panic!( + "Invalid path extension: cannot derive {} from {}", + parent_type.source_name(), + bound_as.source_name() + ); + } + } + PathPart::Child(_) => { + panic!("Invalid path extension: paths are expected to end in a value") + } + } + *last_path_part = PathPart::Child(specifier); + data.path.parts.push(PathPart::Value { + bound_as: child_bound_as, + }); + } + (PathPart::Value { bound_as }, PathExtension::Tightened(new_bound_as)) => { + if bound_as.is_tightening_to(&new_bound_as) { + *bound_as = new_bound_as; + } else { + panic!( + "Invalid path extension: cannot derive {} from {}", + new_bound_as.source_name(), + bound_as.source_name() + ); + } + } + (PathPart::Child(_), _) => { + panic!("Invalid path extension: paths are expected to end in a value"); + } + } } fn display_path( @@ -314,7 +351,7 @@ impl PathComparison { PathComparison::RightIsDescendent => { "mutation may invalidate the other descendent reference" } - PathComparison::ReferencesEqual(TypeBindingComparison::RightIsMoreSpecific) => { + PathComparison::ReferencesEqual(TypeBindingComparison::RightDerivesFromLeft) => { "mutation may invalidate the other reference with more specific type" } PathComparison::ReferencesEqual(TypeBindingComparison::Incomparable) => { @@ -322,7 +359,7 @@ impl PathComparison { } // Activated reference is descendent of existing reference PathComparison::ReferencesEqual(TypeBindingComparison::Equal) - | PathComparison::ReferencesEqual(TypeBindingComparison::LeftIsMoreSpecific) + | PathComparison::ReferencesEqual(TypeBindingComparison::LeftDerivesFromRight) | PathComparison::LeftIsDescendent => { if other_is_active { "the mutable reference is observable from the other reference, which breaks aliasing rules" @@ -365,7 +402,12 @@ impl ReferencePath { } } -pub(crate) struct ReferencePathExtension; +pub(crate) enum PathExtension { + /// Extends the path with a child reference (e.g. .x or [0]) + Child(ChildSpecifier, TypeKind), + /// Extends the path with a value of a certain type (e.g. dereferencing a pointer) + Tightened(TypeKind), +} #[derive(PartialEq, Eq, Clone)] enum PathPart { @@ -374,7 +416,7 @@ enum PathPart { } #[derive(PartialEq, Eq, Clone)] -enum ChildSpecifier { +pub(crate) enum ChildSpecifier { ArrayChild(usize), ObjectChild(String), } diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 44663b16..5a783fdf 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -26,12 +26,12 @@ impl SharedReference { } /// SAFETY: - /// - The caller must ensure that the ReferencePathExtension is correct - /// (an overly-specific ReferencePathExtension may cause safety issues) + /// - The caller must ensure that the PathExtension is correct + /// (an overly-specific PathExtension may cause safety issues) pub(crate) unsafe fn map( self, f: impl FnOnce(&T) -> &V, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> SharedReference { self.emplace_map(move |input, emplacer| { @@ -40,12 +40,12 @@ impl SharedReference { } /// SAFETY: - /// - The caller must ensure that the ReferencePathExtension is correct - /// (an overly-specific ReferencePathExtension may cause safety issues) + /// - The caller must ensure that the PathExtension is correct + /// (an overly-specific PathExtension may cause safety issues) pub(crate) unsafe fn try_map( self, f: impl FnOnce(&T) -> Result<&V, E>, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> Result, (E, SharedReference)> { self.emplace_map(|input, emplacer| match f(input) { @@ -89,12 +89,12 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { } /// SAFETY: - /// - The caller must ensure that the ReferencePathExtension is correct - /// (an overly-specific ReferencePathExtension may cause safety issues) + /// - The caller must ensure that the PathExtension is correct + /// (an overly-specific PathExtension may cause safety issues) pub(crate) unsafe fn emplace( &mut self, value: &'e V, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> SharedReference { unsafe { @@ -106,18 +106,18 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { /// SAFETY: /// - The caller must ensure that the value's lifetime is derived from the original content - /// - The caller must ensure that the ReferencePathExtension is correct + /// - The caller must ensure that the PathExtension is correct pub(crate) unsafe fn emplace_unchecked( &mut self, value: &V, - path_extension: ReferencePathExtension, + path_extension: PathExtension, new_span: SpanRange, ) -> SharedReference { // SAFETY: The pointer is from a reference so non-null let pointer = unsafe { NonNull::new_unchecked(value as *const V as *mut V) }; // SAFETY: // - The caller ensures that the reference is derived from the original content - // - The caller ensures that the ReferencePathExtension is correct + // - The caller ensures that the PathExtension is correct unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } } From b583151c0c614f04646299e9ea0e0a757e233107 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 22 Feb 2026 23:16:45 +0000 Subject: [PATCH 30/49] docs: Update docs --- plans/TODO.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plans/TODO.md b/plans/TODO.md index 832a0f90..7776dff5 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -305,7 +305,13 @@ The issue is that we get a variable stored as `arr := DisabledMutable` and then - [x] Emplacing - [x] Map, Try map - [x] Ability to map deeper. Should take a `PathExtension` and a new span. -- [ ] Try replacing `Shared` / `Mutable` +- [ ] Try replacing existing RefCell based abstractions with new custom dynamic references, + and report on what breaks: + - `pub(crate) type Referenceable = Rc>;` becomes `pub(crate) type Referenceable = dynamic_references::Referenceable` + - `Shared` / `SharedSubRcRefCell` - both become type aliases for `SharedReference` + - `DisabledShared` becomes `InactiveSharedReference` + - `Mutable` / `MutableSubRcRefCell` - becomes type alias for `MutableReference` + - `DisabledMutable` becomes `InactiveMutableReference` ### Other ideas From eb8fb817973a7f23585ef81852d00918cf73038d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Feb 2026 23:56:00 +0000 Subject: [PATCH 31/49] refactor: Replace RefCell-based abstractions with new dynamic references Replace the old Rc>-based reference system with the new custom dynamic reference types: - Shared becomes a type alias for SharedReference - Mutable becomes a type alias for MutableReference - DisabledShared becomes InactiveSharedReference - DisabledMutable becomes InactiveMutableReference - VariableContent::Referenceable uses new Referenceable struct Key changes: - Added bridge methods (_legacy suffix) to SharedReference and MutableReference for backward compatibility with old map/try_map patterns that didn't require PathExtension - QqqShared and QqqMutable now alias directly to the reference types, collapsing the old form/wrapper type distinction - Added explicit IsArgument impls for SharedReference and MutableReference since the blanket impl only covers leaf types - Manual Clone impls for InactiveSharedReference/InactiveMutableReference to avoid unnecessary T: Clone bound from derive - CopyOnWrite conversions use replace_legacy pattern instead of into_content() for non-leaf types - Updated trybuild test for improved aliasing error message from new reference tracking system https://claude.ai/code/session_01XaCsWwXFYntkKqZNf8kizU --- src/expressions/concepts/forms/any_mut.rs | 3 +- src/expressions/concepts/forms/any_ref.rs | 3 +- src/expressions/concepts/forms/assignee.rs | 12 +- .../concepts/forms/copy_on_write.rs | 8 +- src/expressions/concepts/forms/mutable.rs | 12 +- .../concepts/forms/referenceable.rs | 12 +- src/expressions/concepts/forms/shared.rs | 12 +- src/expressions/concepts/forms/simple_mut.rs | 8 +- src/expressions/concepts/forms/simple_ref.rs | 6 +- src/expressions/evaluation/value_frames.rs | 10 +- src/expressions/type_resolution/arguments.rs | 42 ++- src/interpretation/bindings.rs | 305 ++++-------------- src/interpretation/refs.rs | 30 +- src/interpretation/variable.rs | 6 +- src/misc/dynamic_references/mod.rs | 3 - .../dynamic_references/mutable_reference.rs | 149 ++++++++- src/misc/dynamic_references/referenceable.rs | 23 +- .../dynamic_references/shared_reference.rs | 158 ++++++++- src/misc/mod.rs | 4 +- src/misc/mut_rc_ref_cell.rs | 8 + .../expressions/swap_itself.stderr | 15 +- 21 files changed, 508 insertions(+), 321 deletions(-) diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index 43f623e9..7bd47f25 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -60,7 +60,6 @@ impl MapFromArgument for BeAnyMut { ) -> FunctionResult> { Ok(value .expect_mutable() - .0 - .replace(|inner, emplacer| inner.as_mut_value().into_mutable_any_mut(emplacer))) + .replace_legacy(|inner, emplacer| inner.as_mut_value().into_mutable_any_mut(emplacer))) } } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index b813264b..c316fe8d 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -55,7 +55,6 @@ impl MapFromArgument for BeAnyRef { ) -> FunctionResult> { Ok(value .expect_shared() - .0 - .replace(|inner, emplacer| inner.as_ref_value().into_shared_any_ref(emplacer))) + .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared_any_ref(emplacer))) } } diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index 489bb712..e673d517 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -1,6 +1,6 @@ use super::*; -pub(crate) struct QqqAssignee(pub(crate) MutableSubRcRefCell); +pub(crate) struct QqqAssignee(pub(crate) MutableReference); impl IsValueContent for QqqAssignee { type Type = L::Type; @@ -37,9 +37,13 @@ impl IsDynCompatibleForm for BeAssignee { T::Leaf: CastDyn, { leaf.0 - .replace(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(QqqAssignee(emplacer.emplace(mapped))), - Err(this) => Err(QqqAssignee(emplacer.emplace(this))), + .replace_legacy(|content, emplacer| match ::map_mut(content) { + Ok(mapped) => Ok(QqqAssignee(unsafe { + emplacer.emplace_unchecked_legacy(mapped) + })), + Err(this) => Err(QqqAssignee(unsafe { + emplacer.emplace_unchecked_legacy(this) + })), }) } } diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index 8a644ddb..e76deb5e 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -80,10 +80,14 @@ impl MapFromArgument for BeCopyOnWrite { match value.expect_copy_on_write().inner { CopyOnWriteInner::Owned(owned) => Ok(BeCopyOnWrite::new_owned(owned)), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - Ok(BeCopyOnWrite::new_shared_in_place_of_owned(shared)) + let content = shared + .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + Ok(BeCopyOnWrite::new_shared_in_place_of_owned(content)) } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - Ok(BeCopyOnWrite::new_shared_in_place_of_shared(shared)) + let content = shared + .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + Ok(BeCopyOnWrite::new_shared_in_place_of_shared(content)) } } } diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index d8c79110..944354d9 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -1,6 +1,6 @@ use super::*; -pub(crate) type QqqMutable = MutableSubRcRefCell; +pub(crate) type QqqMutable = MutableReference; impl IsValueContent for QqqMutable { type Type = L::Type; @@ -36,9 +36,9 @@ impl IsDynCompatibleForm for BeMutable { where T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.replace_legacy(|content, emplacer| match ::map_mut(content) { + Ok(mapped) => Ok(unsafe { emplacer.emplace_unchecked_legacy(mapped) }), + Err(this) => Err(unsafe { emplacer.emplace_unchecked_legacy(this) }), }) } } @@ -61,7 +61,9 @@ impl MapFromArgument for BeMutable { fn from_argument_value( value: ArgumentValue, ) -> FunctionResult> { - Ok(value.expect_mutable().into_content()) + Ok(value + .expect_mutable() + .replace_legacy(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer))) } } diff --git a/src/expressions/concepts/forms/referenceable.rs b/src/expressions/concepts/forms/referenceable.rs index 47b7b622..470eb455 100644 --- a/src/expressions/concepts/forms/referenceable.rs +++ b/src/expressions/concepts/forms/referenceable.rs @@ -1,19 +1,21 @@ use super::*; -pub(crate) type Referenceable = Rc>; +// The old `pub(crate) type Referenceable = Rc>;` has been replaced by +// `dynamic_references::Referenceable` which is NOT generic (always wraps AnyValue). +// For the form system, we use Rc> directly as the leaf type. -impl IsValueContent for Referenceable { +impl IsValueContent for Rc> { type Type = L::Type; type Form = BeReferenceable; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for Referenceable { +impl<'a, L: IsValueLeaf> IntoValueContent<'a> for Rc> { fn into_content(self) -> Content<'a, Self::Type, Self::Form> { self } } -impl<'a, L: IsValueLeaf> FromValueContent<'a> for Referenceable { +impl<'a, L: IsValueLeaf> FromValueContent<'a> for Rc> { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } @@ -29,5 +31,5 @@ pub(crate) struct BeReferenceable; impl IsForm for BeReferenceable {} impl IsHierarchicalForm for BeReferenceable { - type Leaf<'a, T: IsLeafType> = Referenceable; + type Leaf<'a, T: IsLeafType> = Rc>; } diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index e9a21302..538ec497 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -1,6 +1,6 @@ use super::*; -pub(crate) type QqqShared = SharedSubRcRefCell; +pub(crate) type QqqShared = SharedReference; impl IsValueContent for QqqShared { type Type = L::Type; @@ -36,9 +36,9 @@ impl IsDynCompatibleForm for BeShared { where T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_ref(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.replace_legacy(|content, emplacer| match ::map_ref(content) { + Ok(mapped) => Ok(unsafe { emplacer.emplace_unchecked_legacy(mapped) }), + Err(this) => Err(unsafe { emplacer.emplace_unchecked_legacy(this) }), }) } } @@ -55,7 +55,9 @@ impl MapFromArgument for BeShared { fn from_argument_value( value: ArgumentValue, ) -> FunctionResult> { - Ok(value.expect_shared().into_content()) + Ok(value + .expect_shared() + .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer))) } } diff --git a/src/expressions/concepts/forms/simple_mut.rs b/src/expressions/concepts/forms/simple_mut.rs index fcd4bbe4..2cced381 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -76,7 +76,7 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { self.emplacer.emplace_unchecked(leaf) } + unsafe { self.emplacer.emplace_unchecked_legacy(leaf) } } }; let __mapper = __InlineMapper { emplacer }; @@ -107,7 +107,7 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { QqqAssignee(self.emplacer.emplace_unchecked(leaf)) } + unsafe { QqqAssignee(self.emplacer.emplace_unchecked_legacy(leaf)) } } }; let __mapper = __InlineMapper { emplacer }; @@ -138,7 +138,9 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { Mutable(self.emplacer.emplace_unchecked(leaf)).into() } + let mutable_ref: MutableReference<_> = + unsafe { self.emplacer.emplace_unchecked_legacy(leaf) }; + mutable_ref.into() } }; let __mapper = __InlineMapper { emplacer }; diff --git a/src/expressions/concepts/forms/simple_ref.rs b/src/expressions/concepts/forms/simple_ref.rs index d466b492..82b99eb5 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -76,7 +76,7 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { self.emplacer.emplace_unchecked(leaf) } + unsafe { self.emplacer.emplace_unchecked_legacy(leaf) } } }; let __mapper = __InlineMapper { emplacer }; @@ -107,7 +107,9 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { Shared(self.emplacer.emplace_unchecked(leaf)).into() } + let shared_ref: SharedReference<_> = + unsafe { self.emplacer.emplace_unchecked_legacy(leaf) }; + shared_ref.into() } }; let __mapper = __InlineMapper { emplacer }; diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index f32972a8..1f1a76a6 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -1056,10 +1056,12 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { let mapped = value.expect_any_value_and_map( |shared| { shared - .try_map(|value| (interface.shared_access)(ctx, value)) + .try_map_legacy(|value| (interface.shared_access)(ctx, value)) .map_err(|(e, _)| e) }, - |mutable| mutable.try_map(|value| (interface.mutable_access)(ctx, value, auto_create)), + |mutable| { + mutable.try_map_legacy(|value| (interface.mutable_access)(ctx, value, auto_create)) + }, |owned| (interface.owned_access)(ctx, owned), )?; @@ -1153,11 +1155,11 @@ impl EvaluationFrame for ValueIndexAccessBuilder { let result = source.expect_any_value_and_map( |shared| { shared - .try_map(|value| (interface.shared_access)(ctx, value, index)) + .try_map_legacy(|value| (interface.shared_access)(ctx, value, index)) .map_err(|(e, _)| e) }, |mutable| { - mutable.try_map(|value| { + mutable.try_map_legacy(|value| { (interface.mutable_access)(ctx, value, index, auto_create) }) }, diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index 57f97a28..f3b61b1e 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -58,12 +58,16 @@ impl IsArgument for ArgumentValue { } } -impl + ResolvableArgumentTarget + ?Sized> IsArgument for Shared { - type ValueType = T::ValueType; +// NOTE: IsArgument for SharedReference with leaf T is now provided by the blanket impl +// in content.rs via FromValueContent + MapFromArgument (BeShared). +// For the parent type AnyValue (which is not IsValueLeaf), we provide an explicit impl: +impl IsArgument for SharedReference { + type ValueType = AnyType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; fn from_argument(argument: Spanned) -> FunctionResult { - T::resolve_shared(argument.expect_shared(), "This argument") + let Spanned(shared, _span) = argument.expect_shared(); + Ok(shared) } } @@ -90,12 +94,16 @@ impl + ResolvableArgumentTarget + ?Sized> IsArgum } } -impl + ResolvableArgumentTarget + ?Sized> IsArgument for Mutable { - type ValueType = T::ValueType; +// NOTE: IsArgument for MutableReference with leaf T is now provided by the blanket impl +// in content.rs via FromValueContent + MapFromArgument (BeMutable). +// For the parent type AnyValue (which is not IsValueLeaf), we provide an explicit impl: +impl IsArgument for MutableReference { + type ValueType = AnyType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; fn from_argument(argument: Spanned) -> FunctionResult { - T::resolve_mutable(argument.expect_mutable(), "This argument") + let Spanned(mutable, _span) = argument.expect_mutable(); + Ok(mutable) } } @@ -165,7 +173,9 @@ impl> ResolveAs for Spanned { } } -impl + ?Sized> ResolveAs> for Spanned { +impl + ?Sized + 'static> ResolveAs> + for Spanned +{ fn resolve_as(self, resolution_target: &str) -> FunctionResult> { T::resolve_shared(self, resolution_target) } @@ -185,7 +195,9 @@ impl<'a, T: ResolvableShared + ?Sized> ResolveAs> } } -impl + ?Sized> ResolveAs> for Spanned { +impl + ?Sized + 'static> ResolveAs> + for Spanned +{ fn resolve_as(self, resolution_target: &str) -> FunctionResult> { T::resolve_mutable(self, resolution_target) } @@ -242,9 +254,12 @@ pub(crate) trait ResolvableShared { fn resolve_shared( Spanned(value, span): Spanned>, resolution_target: &str, - ) -> FunctionResult> { + ) -> FunctionResult> + where + Self: 'static, + { value - .try_map(|v| { + .try_map_legacy(|v| { Self::resolve_from_ref( v, ResolutionContext { @@ -304,9 +319,12 @@ pub(crate) trait ResolvableMutable { fn resolve_mutable( Spanned(value, span): Spanned>, resolution_target: &str, - ) -> FunctionResult> { + ) -> FunctionResult> + where + Self: 'static, + { value - .try_map(|v| { + .try_map_legacy(|v| { Self::resolve_from_mut( v, ResolutionContext { diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index c83c0355..aa1fa17f 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -1,7 +1,6 @@ use super::*; use std::borrow::{Borrow, ToOwned}; -use std::rc::Rc; pub(super) enum VariableState { Uninitialized, @@ -16,7 +15,7 @@ pub(super) enum VariableState { #[derive(Clone)] pub(crate) enum VariableContent { // Owned, but possibly with pre-existing references - Referenceable(Referenceable), + Referenceable(Referenceable), Shared(DisabledShared), Mutable(DisabledMutable), } @@ -24,8 +23,8 @@ pub(crate) enum VariableContent { impl VariableContent { fn into_owned_as_only_owner(self) -> Result, VariableContent> { match self { - VariableContent::Referenceable(data) => match Rc::try_unwrap(data) { - Ok(unique) => Ok(unique.into_inner()), + VariableContent::Referenceable(data) => match data.try_into_inner() { + Ok(owned) => Ok(owned), Err(original) => Err(VariableContent::Referenceable(original)), }, other => Err(other), @@ -182,13 +181,13 @@ impl VariableBinding { fn into_mut(self) -> FunctionResult { match self.content { - VariableContent::Referenceable(referenceable) => { - let inner = MutableSubRcRefCell::new(referenceable).map_err(|_| { + VariableContent::Referenceable(referenceable) => referenceable + .new_inactive_mutable() + .activate() + .map_err(|_| { self.variable_span .ownership_error::(MUTABLE_ERROR_MESSAGE) - })?; - Ok(Mutable(inner)) - } + }), VariableContent::Mutable(disabled) => disabled.enable(self.variable_span.span_range()), VariableContent::Shared(shared) => self .variable_span @@ -199,11 +198,10 @@ impl VariableBinding { fn into_shared(self) -> FunctionResult { match self.content { VariableContent::Referenceable(referenceable) => { - let inner = SharedSubRcRefCell::new(referenceable).map_err(|_| { + referenceable.new_inactive_shared().activate().map_err(|_| { self.variable_span .ownership_error::(SHARED_ERROR_MESSAGE) - })?; - Ok(Shared(inner)) + }) } VariableContent::Mutable(mutable) => mutable .into_shared() @@ -215,15 +213,18 @@ impl VariableBinding { fn into_late_bound(self) -> FunctionResult { match self.content { VariableContent::Referenceable(referenceable) => { - match MutableSubRcRefCell::new(referenceable) { - Ok(mutable) => Ok(LateBoundValue::Mutable(Mutable(mutable))), - Err(referenceable) => { - let shared = SharedSubRcRefCell::new(referenceable).map_err(|_| { + let inactive_mut = referenceable.new_inactive_mutable(); + match inactive_mut.activate() { + Ok(mutable) => Ok(LateBoundValue::Mutable(mutable)), + Err(_) => { + // Mutable failed, try shared + let inactive_shared = referenceable.new_inactive_shared(); + let shared = inactive_shared.activate().map_err(|_| { self.variable_span .ownership_error::(SHARED_ERROR_MESSAGE) })?; Ok(LateBoundValue::Shared(LateBoundSharedValue::new( - Shared(shared), + shared, self.variable_span.syn_error(SHARED_ERROR_MESSAGE), ))) } @@ -353,17 +354,34 @@ impl Deref for LateBoundValue { } } +// ============================================================================ +// Type aliases: Old names → New dynamic reference types +// ============================================================================ + +/// Type alias: `Shared` is now `SharedReference`. +pub(crate) type Shared = SharedReference; + +/// Type alias: `Mutable` is now `MutableReference`. +pub(crate) type Mutable = MutableReference; + +/// Type alias: `DisabledShared` is now `InactiveSharedReference`. +pub(crate) type DisabledShared = InactiveSharedReference; + +/// Type alias: `DisabledMutable` is now `InactiveMutableReference`. +pub(crate) type DisabledMutable = InactiveMutableReference; + pub(crate) type AssigneeValue = AnyValueAssignee; +pub(crate) type SharedValue = AnyValueShared; /// A binding of a unique (mutable) reference to a value. /// See [`ArgumentOwnership::Assignee`] for more details. /// /// If you need span information, wrap with `Spanned>`. -pub(crate) struct Assignee(pub Mutable); +pub(crate) struct Assignee(pub MutableReference); impl AssigneeValue { pub(crate) fn set(&mut self, content: impl IntoAnyValue) { - *self.0 .0 = content.into_any_value(); + *self.0 = content.into_any_value(); } } @@ -380,8 +398,7 @@ where { fn into_content(self) -> Content<'static, Self::Type, Self::Form> { self.0 - .0 - .replace(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer)) + .replace_legacy(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer)) } } @@ -399,69 +416,6 @@ impl DerefMut for Assignee { } } -/// A simple wrapper for a mutable reference to a value. -/// -/// Can be destructured as: `Mutable(cell): Mutable` -/// -/// If you need span information, wrap with `Spanned>`. -pub(crate) struct Mutable(pub(crate) MutableSubRcRefCell); - -impl Mutable { - pub(crate) fn into_shared(self) -> Shared { - Shared(self.0.into_shared()) - } - - #[allow(unused)] - pub(crate) fn map( - self, - value_map: impl for<'a> FnOnce(&'a mut T) -> &'a mut V, - ) -> Mutable { - Mutable(self.0.map(value_map)) - } - - /// Maps the mutable reference, returning the error and original reference on failure. - pub(crate) fn try_map( - self, - value_map: impl for<'a> FnOnce(&'a mut T) -> Result<&'a mut V, E>, - ) -> Result, (E, Mutable)> { - match self.0.try_map(value_map) { - Ok(mapped) => Ok(Mutable(mapped)), - Err((e, original)) => Err((e, Mutable(original))), - } - } - - /// Disables this mutable reference, releasing the borrow on the RefCell. - /// Returns a `DisabledMutable` which can be cloned and later re-enabled. - pub(crate) fn disable(self) -> DisabledMutable { - DisabledMutable(self.0.disable()) - } -} - -/// A disabled mutable reference that can be safely cloned and dropped. -pub(crate) struct DisabledMutable( - pub(crate) DisabledMutableSubRcRefCell, -); - -impl Clone for DisabledMutable { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl DisabledMutable { - /// Re-enables this disabled mutable reference by re-acquiring the borrow. - pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { - self.0 - .enable() - .map(Mutable) - .map_err(|_| span.ownership_error(MUTABLE_ERROR_MESSAGE)) - } - - pub(crate) fn into_shared(self) -> DisabledShared { - DisabledShared(self.0.into_shared()) - } -} - pub(crate) static MUTABLE_ERROR_MESSAGE: &str = "The variable cannot be modified as it is already being modified"; pub(crate) static SHARED_TO_MUTABLE_ERROR_MESSAGE: &str = @@ -474,116 +428,6 @@ impl Spanned { } } -impl AnyValueMutable { - pub(crate) fn new_from_owned(value: AnyValue) -> Self { - Mutable(MutableSubRcRefCell::new_from_owned(value)) - } -} - -impl IsValueContent for Mutable { - type Type = X::Type; - type Form = BeMutable; -} - -impl> IntoValueContent<'static> for Mutable -where - X::Type: IsHierarchicalType = X>, - X::Form: IsHierarchicalForm, - X::Form: LeafAsMutForm, -{ - fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - self.0 - .replace(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer)) - } -} - -impl AsMut for Mutable { - fn as_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl AsRef for Mutable { - fn as_ref(&self) -> &T { - &self.0 - } -} - -impl Deref for Mutable { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Mutable { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -pub(crate) type SharedValue = AnyValueShared; - -/// A simple wrapper for a shared (immutable) reference to a value. -/// -/// Can be destructured as: `Shared(cell): Shared` -/// -/// If you need span information, wrap with `Spanned>`. -pub(crate) struct Shared(pub(crate) SharedSubRcRefCell); - -#[allow(unused)] -impl Shared { - pub(crate) fn clone(this: &Shared) -> Self { - Shared(SharedSubRcRefCell::clone(&this.0)) - } - - /// Maps the shared reference, returning the error and original reference on failure. - pub(crate) fn try_map( - self, - value_map: impl for<'a> FnOnce(&'a T) -> Result<&'a V, E>, - ) -> Result, (E, Shared)> { - match self.0.try_map(value_map) { - Ok(mapped) => Ok(Shared(mapped)), - Err((e, original)) => Err((e, Shared(original))), - } - } - - pub(crate) fn map(self, value_map: impl FnOnce(&T) -> &V) -> Shared { - Shared(self.0.map(value_map)) - } - - /// Disables this shared reference, releasing the borrow on the RefCell. - /// Returns a `DisabledShared` which can be cloned and later re-enabled. - pub(crate) fn disable(self) -> DisabledShared { - DisabledShared(self.0.disable()) - } -} - -/// A disabled shared reference that can be safely cloned and dropped. -pub(crate) struct DisabledShared( - pub(crate) DisabledSharedSubRcRefCell, -); - -impl Clone for DisabledShared { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl DisabledShared { - /// Re-enables this disabled shared reference by re-acquiring the borrow. - pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { - self.0 - .enable() - .map(Shared) - .map_err(|_| span.ownership_error(SHARED_ERROR_MESSAGE)) - } -} - -pub(crate) static SHARED_ERROR_MESSAGE: &str = - "The variable cannot be read as it is already being modified"; - impl Spanned { pub(crate) fn transparent_clone(&self) -> FunctionResult { let value = self.0.as_ref().try_transparent_clone(self.1)?; @@ -591,46 +435,19 @@ impl Spanned { } } -impl AnyValueShared { - pub(crate) fn new_from_owned(value: AnyValue) -> Self { - Shared(SharedSubRcRefCell::new_from_owned(value)) - } - - pub(crate) fn infallible_clone(&self) -> AnyValue { - self.0.clone() - } -} - -impl IsValueContent for Shared { - type Type = X::Type; - type Form = BeShared; -} - -impl> IntoValueContent<'static> for Shared -where - X::Type: IsHierarchicalType = X>, - X::Form: IsHierarchicalForm, - X::Form: LeafAsRefForm, -{ - fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - self.0 - .replace(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)) - } -} +pub(crate) static SHARED_ERROR_MESSAGE: &str = + "The variable cannot be read as it is already being modified"; -impl AsRef for Shared { - fn as_ref(&self) -> &T { - &self.0 - } -} +// ============================================================================ +// IntoValueContent impls for the new types (formerly on Shared / Mutable) +// ============================================================================ -impl Deref for Shared { - type Target = T; +// Note: IsValueContent and IntoValueContent are implemented via QqqShared/QqqMutable +// in the forms system, since SharedReference IS the leaf type now. - fn deref(&self) -> &Self::Target { - &self.0 - } -} +// ============================================================================ +// CopyOnWrite +// ============================================================================ /// Copy-on-write value that can be either owned or shared pub(crate) struct CopyOnWrite { @@ -642,20 +459,20 @@ pub(crate) enum CopyOnWriteInner { Owned(Owned), /// For use when the CopyOnWrite value effectively represents the owned value (post-clone). /// In this case, returning a Cow is just an optimization and we can always clone infallibly. - SharedWithInfallibleCloning(Shared), + SharedWithInfallibleCloning(SharedReference), /// For use when the CopyOnWrite value represents a pre-cloned read-only value. /// A transparent clone may fail in this case at use time. - SharedWithTransparentCloning(Shared), + SharedWithTransparentCloning(SharedReference), } impl CopyOnWrite { - pub(crate) fn shared_in_place_of_owned(shared: Shared) -> Self { + pub(crate) fn shared_in_place_of_owned(shared: SharedReference) -> Self { Self { inner: CopyOnWriteInner::SharedWithInfallibleCloning(shared), } } - pub(crate) fn shared_in_place_of_shared(shared: Shared) -> Self { + pub(crate) fn shared_in_place_of_shared(shared: SharedReference) -> Self { Self { inner: CopyOnWriteInner::SharedWithTransparentCloning(shared), } @@ -693,7 +510,7 @@ impl CopyOnWrite { pub(crate) fn map( self, - map_shared: impl FnOnce(Shared) -> FunctionResult>, + map_shared: impl FnOnce(SharedReference) -> FunctionResult>, map_owned: impl FnOnce(Owned) -> FunctionResult>, ) -> FunctionResult> { let inner = match self.inner { @@ -710,7 +527,7 @@ impl CopyOnWrite { pub(crate) fn map_into( self, - map_shared: impl FnOnce(Shared) -> U, + map_shared: impl FnOnce(SharedReference) -> U, map_owned: impl FnOnce(Owned) -> U, ) -> U { match self.inner { @@ -720,7 +537,7 @@ impl CopyOnWrite { } } - /// Disables this copy-on-write value, releasing any borrow on the RefCell. + /// Disables this copy-on-write value, releasing any borrow. /// Returns a `DisabledCopyOnWrite` which can be cloned and later re-enabled. pub(crate) fn disable(self) -> DisabledCopyOnWrite { let inner = match self.inner { @@ -743,8 +560,8 @@ pub(crate) struct DisabledCopyOnWrite { enum DisabledCopyOnWriteInner { Owned(Owned), - SharedWithInfallibleCloning(DisabledShared), - SharedWithTransparentCloning(DisabledShared), + SharedWithInfallibleCloning(InactiveSharedReference), + SharedWithTransparentCloning(InactiveSharedReference), } impl Clone for DisabledCopyOnWrite @@ -799,11 +616,15 @@ where AnyLevelCopyOnWrite::::Owned(owned).into_copy_on_write() } CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(shared.into_content()) + let content = shared + .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(content) .into_copy_on_write() } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - AnyLevelCopyOnWrite::::SharedWithTransparentCloning(shared.into_content()) + let content = shared + .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + AnyLevelCopyOnWrite::::SharedWithTransparentCloning(content) .into_copy_on_write() } } diff --git a/src/interpretation/refs.rs b/src/interpretation/refs.rs index 216638ef..dba9b3bf 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -3,7 +3,7 @@ use std::mem::transmute; use super::*; /// A flexible type which can either be a reference to a value of type `T`, -/// or an emplaced reference from a [`Shared`]. +/// or an emplaced reference from a [`SharedReference`]. pub(crate) struct AnyRef<'a, T: ?Sized + 'static> { inner: AnyRefInner<'a, T>, } @@ -16,7 +16,7 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { inner: AnyRefInner::Direct(f(value)), }, AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.map(|x| f(x))), + inner: AnyRefInner::Encapsulated(shared.map_legacy(|x| f(x))), }, } } @@ -31,7 +31,7 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { inner: AnyRefInner::Direct(f(value)?), }, AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.map_optional(f)?), + inner: AnyRefInner::Encapsulated(shared.map_optional_legacy(f)?), }, }) } @@ -107,15 +107,15 @@ impl<'a, T: ?Sized> ToSpannedRef<'a> for &'a T { } } -impl<'a, T: ?Sized> From> for AnyRef<'a, T> { - fn from(value: Shared) -> Self { +impl<'a, T: ?Sized> From> for AnyRef<'a, T> { + fn from(value: SharedReference) -> Self { Self { - inner: AnyRefInner::Encapsulated(value.0), + inner: AnyRefInner::Encapsulated(value), } } } -impl ToSpannedRef<'static> for Shared { +impl ToSpannedRef<'static> for SharedReference { type Target = T; fn into_ref(self) -> AnyRef<'static, Self::Target> { self.into() @@ -127,7 +127,7 @@ impl ToSpannedRef<'static> for Shared { enum AnyRefInner<'a, T: 'static + ?Sized> { Direct(&'a T), - Encapsulated(SharedSubRcRefCell), + Encapsulated(SharedReference), } impl<'a, T: 'static + ?Sized> Deref for AnyRef<'a, T> { @@ -142,7 +142,7 @@ impl<'a, T: 'static + ?Sized> Deref for AnyRef<'a, T> { } /// A flexible type which can either be a mutable reference to a value of type `T`, -/// or an emplaced reference from a [`Mutable`]. +/// or an emplaced reference from a [`MutableReference`]. pub(crate) struct AnyMut<'a, T: 'static + ?Sized> { inner: AnyMutInner<'a, T>, } @@ -166,7 +166,7 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { inner: AnyMutInner::Direct(f(value)), }, AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.map(|x| f(x))), + inner: AnyMutInner::Encapsulated(mutable.map_legacy(|x| f(x))), }, } } @@ -181,7 +181,7 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { inner: AnyMutInner::Direct(f(value)?), }, AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.map_optional(f)?), + inner: AnyMutInner::Encapsulated(mutable.map_optional_legacy(f)?), }, }) } @@ -248,17 +248,17 @@ impl<'a, T: ?Sized + 'static> IntoAnyMut<'a> for &'a mut T { } } -impl<'a, T: ?Sized> From> for AnyMut<'a, T> { - fn from(value: Mutable) -> Self { +impl<'a, T: ?Sized> From> for AnyMut<'a, T> { + fn from(value: MutableReference) -> Self { Self { - inner: AnyMutInner::Encapsulated(value.0), + inner: AnyMutInner::Encapsulated(value), } } } enum AnyMutInner<'a, T: 'static + ?Sized> { Direct(&'a mut T), - Encapsulated(MutableSubRcRefCell), + Encapsulated(MutableReference), } impl<'a, T: 'static + ?Sized> Deref for AnyMut<'a, T> { diff --git a/src/interpretation/variable.rs b/src/interpretation/variable.rs index dd67fc24..b02e06bc 100644 --- a/src/interpretation/variable.rs +++ b/src/interpretation/variable.rs @@ -68,7 +68,11 @@ impl VariableDefinition { pub(crate) fn define(&self, interpreter: &mut Interpreter, value_source: impl IntoAnyValue) { interpreter.define_variable( self.id, - VariableContent::Referenceable(Rc::new(RefCell::new(value_source.into_any_value()))), + VariableContent::Referenceable(Referenceable::new( + value_source.into_any_value(), + "".to_string(), + SpanRange::new_single(Span::call_site()), + )), ); } } diff --git a/src/misc/dynamic_references/mod.rs b/src/misc/dynamic_references/mod.rs index 5efc3ae3..c07ac467 100644 --- a/src/misc/dynamic_references/mod.rs +++ b/src/misc/dynamic_references/mod.rs @@ -81,8 +81,6 @@ //! Was based off an `Rc>` which we could use to create Active/Mutable references, and deactivate them. //! However, it didn't respect (a.1) and it didn't permit mutiple mutable references on different paths. -// TODO[references]: Remove once integrated -#![allow(unused)] mod mutable_reference; mod reference_core; mod referenceable; @@ -93,7 +91,6 @@ use crate::internal_prelude::*; use reference_core::*; use slotmap::{new_key_type, SlotMap}; use std::cell::UnsafeCell; -use std::cmp::Ordering; use std::mem::ManuallyDrop; use std::ptr::NonNull; diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index 329deb1d..e70f7d8a 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -54,6 +54,115 @@ impl MutableReference { } } +impl MutableReference { + /// Creates a new MutableReference from an owned value, wrapping it in a Referenceable. + pub(crate) fn new_from_owned(value: AnyValue) -> Self { + let referenceable = Referenceable::new( + value, + "".to_string(), + SpanRange::new_single(Span::call_site()), + ); + referenceable + .new_inactive_mutable() + .activate() + .expect("Freshly created referenceable must be borrowable as mutable") + } +} + +impl MutableReference { + /// Disables this mutable reference (bridge for old `disable()` API). + /// Equivalent to `deactivate()` in the new naming. + pub(crate) fn disable(self) -> InactiveMutableReference { + self.deactivate() + } + + /// Converts this mutable reference into a shared reference. + /// Bridge for old `into_shared()` API. + pub(crate) fn into_shared(self) -> SharedReference { + let inactive = self.deactivate(); + let inactive_shared = inactive.into_shared(); + inactive_shared + .activate() + .expect("Converting mutable to shared should always succeed since we just released the mutable borrow") + } + + /// Safe map that uses a placeholder path extension. + /// Bridge method for migration. + pub(crate) fn map_legacy( + self, + value_map: impl FnOnce(&mut T) -> &mut V, + ) -> MutableReference { + self.emplace_map(move |input, emplacer| { + // SAFETY: Conservative path extension + unsafe { + emplacer.emplace( + value_map(input), + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + } + }) + } + + /// Safe try_map that uses a placeholder path extension. + /// Bridge method for migration. + pub(crate) fn try_map_legacy( + self, + value_map: impl FnOnce(&mut T) -> Result<&mut V, E>, + ) -> Result, (E, MutableReference)> { + self.emplace_map(|input, emplacer| match value_map(input) { + Ok(output) => { + // SAFETY: Conservative path extension + Ok(unsafe { + emplacer.emplace( + output, + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + }) + } + Err(e) => Err((e, emplacer.revert())), + }) + } + + /// Safe map_optional that uses a placeholder path extension. + /// Bridge method for migration. + #[allow(unused)] + pub(crate) fn map_optional_legacy( + self, + value_map: impl FnOnce(&mut T) -> Option<&mut V>, + ) -> Option> { + self.emplace_map(|input, emplacer| match value_map(input) { + Some(output) => Some(unsafe { + emplacer.emplace( + output, + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + }), + None => { + let _ = emplacer.revert(); + None + } + }) + } + + /// Bridge for the old `replace()` pattern. + pub(crate) fn replace_legacy( + self, + f: impl for<'e> FnOnce(&'e mut T, &mut MutableEmplacerV2<'e, T>) -> O, + ) -> O { + self.emplace_map(f) + } +} + +impl InactiveMutableReference { + /// Re-enables this inactive mutable reference (bridge for old `enable()` API). + pub(crate) fn enable(self, _span: SpanRange) -> FunctionResult> { + self.activate() + } +} + impl Deref for MutableReference { type Target = T; @@ -67,6 +176,18 @@ impl Deref for MutableReference { } } +impl AsRef for MutableReference { + fn as_ref(&self) -> &T { + self + } +} + +impl AsMut for MutableReference { + fn as_mut(&mut self) -> &mut T { + self + } +} + impl DerefMut for MutableReference { fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: See the rustdoc on dynamic_references/mod.rs for full details. @@ -78,9 +199,14 @@ impl DerefMut for MutableReference { } } -#[derive(Clone)] pub(crate) struct InactiveMutableReference(pub(super) ReferenceCore); +impl Clone for InactiveMutableReference { + fn clone(&self) -> Self { + InactiveMutableReference(self.0.clone()) + } +} + impl InactiveMutableReference { pub(crate) fn activate(self) -> FunctionResult> { self.0 @@ -138,4 +264,25 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { // - The caller ensures that the PathExtension is correct unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } + + /// Legacy bridge: emplace_unchecked without PathExtension. + /// Uses a conservative default path extension. + /// + /// SAFETY: + /// - The caller must ensure that the value's lifetime is derived from the original content + pub(crate) unsafe fn emplace_unchecked_legacy( + &mut self, + value: &mut V, + ) -> MutableReference { + unsafe { + self.emplace_unchecked( + value, + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + } + } } + +/// Legacy type alias for backward compatibility +pub(crate) type MutableEmplacer<'e, T> = MutableEmplacerV2<'e, T>; diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index cf45a735..6314a3c2 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -1,12 +1,17 @@ -use std::process::Child; - use super::*; +#[derive(Clone)] pub(crate) struct Referenceable { core: Rc>, } impl Referenceable { + pub(crate) fn new(root: AnyValue, root_name: String, root_span: SpanRange) -> Self { + Self { + core: Rc::new(ReferenceableCore::new(root, root_name, root_span)), + } + } + pub(crate) fn new_inactive_shared(&self) -> InactiveSharedReference { InactiveSharedReference(ReferenceCore::new_root( self.core.clone(), @@ -20,6 +25,14 @@ impl Referenceable { ReferenceKind::InactiveMutable, )) } + + /// Attempts to unwrap the inner value if this is the sole owner (no outstanding references). + pub(crate) fn try_into_inner(self) -> Result { + match Rc::try_unwrap(self.core) { + Ok(core) => Ok(core.into_inner()), + Err(core) => Err(Self { core }), + } + } } pub(super) struct ReferenceableCore { @@ -54,6 +67,10 @@ impl ReferenceableCore { pub(super) fn data_mut(&self) -> RefMut<'_, ReferenceableData> { self.data.borrow_mut() } + + pub(super) fn into_inner(self) -> T { + self.root.into_inner() + } } new_key_type! { @@ -194,7 +211,7 @@ impl ReferenceableData { ) { let data = self.for_reference_mut(id); data.creation_span = new_span; - let mut last_path_part = data.path.parts.last_mut().expect("path is non-empty"); + let last_path_part = data.path.parts.last_mut().expect("path is non-empty"); match (last_path_part, path_extension) { (last_path_part, PathExtension::Child(specifier, child_bound_as)) => { let parent_type = specifier.bound_type_kind(); diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 5a783fdf..d1f437bb 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -1,9 +1,14 @@ use super::*; -#[derive(Clone)] pub(crate) struct SharedReference(pub(super) ReferenceCore); -impl SharedReference { +impl Clone for SharedReference { + fn clone(&self) -> Self { + SharedReference(self.0.clone()) + } +} + +impl SharedReference { pub(crate) fn deactivate(self) -> InactiveSharedReference { self.0.core.data_mut().deactivate_reference(self.0.id); InactiveSharedReference(self.0) @@ -55,7 +60,120 @@ impl SharedReference { } } -impl Deref for SharedReference { +impl SharedReference { + /// Creates a new SharedReference from an owned value, wrapping it in a Referenceable. + /// Uses a placeholder name and span for the Referenceable root. + pub(crate) fn new_from_owned(value: AnyValue) -> Self { + let referenceable = Referenceable::new( + value, + "".to_string(), + SpanRange::new_single(Span::call_site()), + ); + referenceable + .new_inactive_shared() + .activate() + .expect("Freshly created referenceable must be borrowable as shared") + } + + /// Clones the inner value. This is infallible because AnyValue always supports clone. + pub(crate) fn infallible_clone(&self) -> AnyValue { + (**self).clone() + } +} + +impl SharedReference { + /// Disables this shared reference (bridge for old `disable()` API). + /// Equivalent to `deactivate()` in the new naming. + pub(crate) fn disable(self) -> InactiveSharedReference { + self.deactivate() + } + + /// Safe map that uses a placeholder path extension. + /// This is a bridge method for migration - callers should eventually switch to + /// the unsafe `map()` with proper PathExtension. + pub(crate) fn map_legacy( + self, + value_map: impl FnOnce(&T) -> &V, + ) -> SharedReference { + self.emplace_map(move |input, emplacer| { + // SAFETY: We use Tightened(AnyType::type_kind()) as a conservative path extension + // that says "we're still at the same depth, just changing our view". + // This may be overly conservative but won't cause memory safety issues. + unsafe { + emplacer.emplace( + value_map(input), + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + } + }) + } + + /// Safe try_map that uses a placeholder path extension. + /// Bridge method for migration. + pub(crate) fn try_map_legacy( + self, + value_map: impl FnOnce(&T) -> Result<&V, E>, + ) -> Result, (E, SharedReference)> { + self.emplace_map(|input, emplacer| match value_map(input) { + Ok(output) => { + // SAFETY: Same conservative path extension as map_legacy + Ok(unsafe { + emplacer.emplace( + output, + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + }) + } + Err(e) => Err((e, emplacer.revert())), + }) + } + + /// Safe map_optional that uses a placeholder path extension. + /// Bridge method for migration. + pub(crate) fn map_optional_legacy( + self, + value_map: impl FnOnce(&T) -> Option<&V>, + ) -> Option> { + self.emplace_map(|input, emplacer| match value_map(input) { + Some(output) => { + // SAFETY: Same conservative path extension as map_legacy + Some(unsafe { + emplacer.emplace( + output, + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + }) + } + None => { + let _ = emplacer.revert(); // drop the reverted reference + None + } + }) + } + + /// Bridge for the old `replace()` pattern. + /// Uses the emplace_map internally with a legacy path extension. + pub(crate) fn replace_legacy( + self, + f: impl for<'e> FnOnce(&'e T, &mut SharedEmplacerV2<'e, T>) -> O, + ) -> O { + self.emplace_map(f) + } +} + +impl InactiveSharedReference { + /// Re-enables this inactive shared reference (bridge for old `enable()` API). + /// The span parameter is kept for API compatibility but activation errors + /// now include span information from the reference itself. + pub(crate) fn enable(self, _span: SpanRange) -> FunctionResult> { + self.activate() + } +} + +impl Deref for SharedReference { type Target = T; fn deref(&self) -> &Self::Target { @@ -68,9 +186,20 @@ impl Deref for SharedReference { } } -#[derive(Clone)] +impl AsRef for SharedReference { + fn as_ref(&self) -> &T { + self + } +} + pub(crate) struct InactiveSharedReference(pub(super) ReferenceCore); +impl Clone for InactiveSharedReference { + fn clone(&self) -> Self { + InactiveSharedReference(self.0.clone()) + } +} + impl InactiveSharedReference { pub(crate) fn activate(self) -> FunctionResult> { self.0 @@ -120,4 +249,25 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { // - The caller ensures that the PathExtension is correct unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } + + /// Legacy bridge: emplace_unchecked without PathExtension. + /// Uses a conservative default path extension. + /// + /// SAFETY: + /// - The caller must ensure that the value's lifetime is derived from the original content + pub(crate) unsafe fn emplace_unchecked_legacy( + &mut self, + value: &V, + ) -> SharedReference { + unsafe { + self.emplace_unchecked( + value, + PathExtension::Tightened(AnyType::type_kind()), + SpanRange::new_single(Span::call_site()), + ) + } + } } + +/// Legacy type alias for backward compatibility +pub(crate) type SharedEmplacer<'e, T> = SharedEmplacerV2<'e, T>; diff --git a/src/misc/mod.rs b/src/misc/mod.rs index d1d17792..11b389c5 100644 --- a/src/misc/mod.rs +++ b/src/misc/mod.rs @@ -9,13 +9,13 @@ mod parse_traits; pub(crate) mod string_conversion; pub(crate) use arena::*; -#[allow(unused)] // TODO: Remove unused once integrated pub(crate) use dynamic_references::*; pub(crate) use errors::*; pub(crate) use field_inputs::*; pub(crate) use iterators::*; pub(crate) use keywords::*; -pub(crate) use mut_rc_ref_cell::*; +// Old RefCell-based abstractions - no longer glob-exported; replaced by dynamic_references. +// pub(crate) use mut_rc_ref_cell::*; pub(crate) use parse_traits::*; use crate::internal_prelude::*; diff --git a/src/misc/mut_rc_ref_cell.rs b/src/misc/mut_rc_ref_cell.rs index 8bf147a1..2ff7912a 100644 --- a/src/misc/mut_rc_ref_cell.rs +++ b/src/misc/mut_rc_ref_cell.rs @@ -1,5 +1,13 @@ +#![allow(unused, dead_code)] +// This module is no longer exported - kept for reference during migration. +// All types have been replaced by dynamic_references equivalents. + use crate::internal_prelude::*; use std::cell::{BorrowError, BorrowMutError}; +use std::rc::Rc; + +// Local alias to avoid conflict with dynamic_references::Referenceable +type Referenceable = Rc>; /// A mutable reference to a sub-value `U` inside a [`Rc>`]. /// Only one [`MutableSubRcRefCell`] can exist at a time for a given [`Rc>`]. diff --git a/tests/compilation_failures/expressions/swap_itself.stderr b/tests/compilation_failures/expressions/swap_itself.stderr index 29c94dab..c8fab41e 100644 --- a/tests/compilation_failures/expressions/swap_itself.stderr +++ b/tests/compilation_failures/expressions/swap_itself.stderr @@ -1,5 +1,12 @@ -error: The variable cannot be modified as it is already being modified - --> tests/compilation_failures/expressions/swap_itself.rs:6:16 +error: Cannot create mutable reference because it clashes with another reference: the mutable reference is observable from the other reference, which breaks aliasing rulesThis reference-: [*active*] &mut (of type any)Other reference: [*active*] &mut (of type any) + --> tests/compilation_failures/expressions/swap_itself.rs:4:13 | -6 | a.swap(a); - | ^ +4 | let _ = run!{ + | _____________^ +5 | | let a = "a"; +6 | | a.swap(a); +7 | | a +8 | | }; + | |_____^ + | + = note: this error originates in the macro `run` (in Nightly builds, run with -Z macro-backtrace for more info) From 68f7692f4c920ce805e95303deec80ba25a14d6d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Feb 2026 00:34:33 +0000 Subject: [PATCH 32/49] refactor: Replace _legacy bridge methods with proper PathExtension and SpanRange Replace all _legacy bridge methods (map_legacy, try_map_legacy, map_optional_legacy, replace_legacy, emplace_unchecked_legacy) with calls using proper PathExtension values and real SpanRange values. Key changes: - Add current_span() to EmplacerCore, SharedEmplacerV2, MutableEmplacerV2, SharedReference, MutableReference, AnyRefEmplacer, and AnyMutEmplacer - Use PathExtension::Tightened(T::type_kind()) for type-narrowing operations (leaf_to_dyn, into_shared, into_mutable, into_assignee, etc.) - Use PathExtension::Child(ObjectChild(name), AnyType) for property access - Use real spans from emplacer.current_span() instead of Span::call_site() - Make AnyRef/AnyMut map methods and emplacers take PathExtension + SpanRange - Remove all _legacy bridge methods from SharedReference and MutableReference https://claude.ai/code/session_01XaCsWwXFYntkKqZNf8kizU --- src/expressions/concepts/forms/any_mut.rs | 15 +- src/expressions/concepts/forms/any_ref.rs | 15 +- src/expressions/concepts/forms/assignee.rs | 16 +- .../concepts/forms/copy_on_write.rs | 4 +- src/expressions/concepts/forms/mutable.rs | 19 ++- src/expressions/concepts/forms/shared.rs | 19 ++- src/expressions/concepts/forms/simple_mut.rs | 28 ++- src/expressions/concepts/forms/simple_ref.rs | 19 ++- src/expressions/evaluation/value_frames.rs | 56 ++++-- src/expressions/type_resolution/arguments.rs | 52 ++++-- src/interpretation/bindings.rs | 6 +- src/interpretation/refs.rs | 159 +++++++++++++++--- .../dynamic_references/mutable_reference.rs | 98 ++--------- src/misc/dynamic_references/reference_core.rs | 6 + .../dynamic_references/shared_reference.rs | 104 ++---------- 15 files changed, 350 insertions(+), 266 deletions(-) diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index 7bd47f25..e370292f 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -33,9 +33,16 @@ impl IsDynCompatibleForm for BeAnyMut { where T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.replace(|content, emplacer| { + let span = emplacer.current_span(); + match ::map_mut(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace(mapped, PathExtension::Tightened(T::type_kind()), span) + }), + Err(this) => Err(unsafe { + emplacer.emplace(this, PathExtension::Tightened(T::type_kind()), span) + }), + } }) } } @@ -60,6 +67,6 @@ impl MapFromArgument for BeAnyMut { ) -> FunctionResult> { Ok(value .expect_mutable() - .replace_legacy(|inner, emplacer| inner.as_mut_value().into_mutable_any_mut(emplacer))) + .emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable_any_mut(emplacer))) } } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index c316fe8d..61e690e1 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -34,9 +34,16 @@ impl IsDynCompatibleForm for BeAnyRef { where T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_ref(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.replace(|content, emplacer| { + let span = emplacer.current_span(); + match ::map_ref(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace(mapped, PathExtension::Tightened(T::type_kind()), span) + }), + Err(this) => Err(unsafe { + emplacer.emplace(this, PathExtension::Tightened(T::type_kind()), span) + }), + } }) } } @@ -55,6 +62,6 @@ impl MapFromArgument for BeAnyRef { ) -> FunctionResult> { Ok(value .expect_shared() - .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared_any_ref(emplacer))) + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared_any_ref(emplacer))) } } diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index e673d517..5a3065e9 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -36,15 +36,21 @@ impl IsDynCompatibleForm for BeAssignee { where T::Leaf: CastDyn, { - leaf.0 - .replace_legacy(|content, emplacer| match ::map_mut(content) { + leaf.0.emplace_map(|content, emplacer| { + let span = emplacer.current_span(); + match ::map_mut(content) { Ok(mapped) => Ok(QqqAssignee(unsafe { - emplacer.emplace_unchecked_legacy(mapped) + emplacer.emplace_unchecked( + mapped, + PathExtension::Tightened(T::type_kind()), + span, + ) })), Err(this) => Err(QqqAssignee(unsafe { - emplacer.emplace_unchecked_legacy(this) + emplacer.emplace_unchecked(this, PathExtension::Tightened(T::type_kind()), span) })), - }) + } + }) } } diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index e76deb5e..0cf03e50 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -81,12 +81,12 @@ impl MapFromArgument for BeCopyOnWrite { CopyOnWriteInner::Owned(owned) => Ok(BeCopyOnWrite::new_owned(owned)), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { let content = shared - .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); Ok(BeCopyOnWrite::new_shared_in_place_of_owned(content)) } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { let content = shared - .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); Ok(BeCopyOnWrite::new_shared_in_place_of_shared(content)) } } diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index 944354d9..4f8ce835 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -36,9 +36,20 @@ impl IsDynCompatibleForm for BeMutable { where T::Leaf: CastDyn, { - leaf.replace_legacy(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(unsafe { emplacer.emplace_unchecked_legacy(mapped) }), - Err(this) => Err(unsafe { emplacer.emplace_unchecked_legacy(this) }), + leaf.emplace_map(|content, emplacer| { + let span = emplacer.current_span(); + match ::map_mut(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace_unchecked( + mapped, + PathExtension::Tightened(T::type_kind()), + span, + ) + }), + Err(this) => Err(unsafe { + emplacer.emplace_unchecked(this, PathExtension::Tightened(T::type_kind()), span) + }), + } }) } } @@ -63,7 +74,7 @@ impl MapFromArgument for BeMutable { ) -> FunctionResult> { Ok(value .expect_mutable() - .replace_legacy(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer))) + .emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer))) } } diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index 538ec497..b402a824 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -36,9 +36,20 @@ impl IsDynCompatibleForm for BeShared { where T::Leaf: CastDyn, { - leaf.replace_legacy(|content, emplacer| match ::map_ref(content) { - Ok(mapped) => Ok(unsafe { emplacer.emplace_unchecked_legacy(mapped) }), - Err(this) => Err(unsafe { emplacer.emplace_unchecked_legacy(this) }), + leaf.emplace_map(|content, emplacer| { + let span = emplacer.current_span(); + match ::map_ref(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace_unchecked( + mapped, + PathExtension::Tightened(T::type_kind()), + span, + ) + }), + Err(this) => Err(unsafe { + emplacer.emplace_unchecked(this, PathExtension::Tightened(T::type_kind()), span) + }), + } }) } } @@ -57,7 +68,7 @@ impl MapFromArgument for BeShared { ) -> FunctionResult> { Ok(value .expect_shared() - .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer))) + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer))) } } diff --git a/src/expressions/concepts/forms/simple_mut.rs b/src/expressions/concepts/forms/simple_mut.rs index 2cced381..3100a8c9 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -76,7 +76,14 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { self.emplacer.emplace_unchecked_legacy(leaf) } + let span = self.emplacer.current_span(); + unsafe { + self.emplacer.emplace_unchecked( + leaf, + PathExtension::Tightened(T::type_kind()), + span, + ) + } } }; let __mapper = __InlineMapper { emplacer }; @@ -107,7 +114,14 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { QqqAssignee(self.emplacer.emplace_unchecked_legacy(leaf)) } + let span = self.emplacer.current_span(); + unsafe { + QqqAssignee(self.emplacer.emplace_unchecked( + leaf, + PathExtension::Tightened(T::type_kind()), + span, + )) + } } }; let __mapper = __InlineMapper { emplacer }; @@ -138,8 +152,14 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - let mutable_ref: MutableReference<_> = - unsafe { self.emplacer.emplace_unchecked_legacy(leaf) }; + let span = self.emplacer.current_span(); + let mutable_ref: MutableReference<_> = unsafe { + self.emplacer.emplace_unchecked( + leaf, + PathExtension::Tightened(T::type_kind()), + span, + ) + }; mutable_ref.into() } }; diff --git a/src/expressions/concepts/forms/simple_ref.rs b/src/expressions/concepts/forms/simple_ref.rs index 82b99eb5..072c79ab 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -76,7 +76,14 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - unsafe { self.emplacer.emplace_unchecked_legacy(leaf) } + let span = self.emplacer.current_span(); + unsafe { + self.emplacer.emplace_unchecked( + leaf, + PathExtension::Tightened(T::type_kind()), + span, + ) + } } }; let __mapper = __InlineMapper { emplacer }; @@ -107,8 +114,14 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - let shared_ref: SharedReference<_> = - unsafe { self.emplacer.emplace_unchecked_legacy(leaf) }; + let span = self.emplacer.current_span(); + let shared_ref: SharedReference<_> = unsafe { + self.emplacer.emplace_unchecked( + leaf, + PathExtension::Tightened(T::type_kind()), + span, + ) + }; shared_ref.into() } }; diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index 1f1a76a6..321ad343 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -1053,14 +1053,35 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { }; let auto_create = context.requested_ownership().requests_auto_create(); + let property_name_for_shared = property_name.clone(); let mapped = value.expect_any_value_and_map( |shared| { - shared - .try_map_legacy(|value| (interface.shared_access)(ctx, value)) - .map_err(|(e, _)| e) + // SAFETY: Property access navigates to a named child of the source value. + unsafe { + shared + .try_map( + |value| (interface.shared_access)(ctx, value), + PathExtension::Child( + ChildSpecifier::ObjectChild(property_name_for_shared), + AnyType::type_kind(), + ), + result_span, + ) + .map_err(|(e, _)| e) + } }, |mutable| { - mutable.try_map_legacy(|value| (interface.mutable_access)(ctx, value, auto_create)) + // SAFETY: Property access navigates to a named child of the source value. + unsafe { + mutable.try_map( + |value| (interface.mutable_access)(ctx, value, auto_create), + PathExtension::Child( + ChildSpecifier::ObjectChild(property_name), + AnyType::type_kind(), + ), + result_span, + ) + } }, |owned| (interface.owned_access)(ctx, owned), )?; @@ -1152,20 +1173,33 @@ impl EvaluationFrame for ValueIndexAccessBuilder { }; let auto_create = context.requested_ownership().requests_auto_create(); + let result_span = SpanRange::new_between(source_span, self.access.span_range()); let result = source.expect_any_value_and_map( |shared| { - shared - .try_map_legacy(|value| (interface.shared_access)(ctx, value, index)) - .map_err(|(e, _)| e) + // SAFETY: Tightened(AnyType) is conservative for index access. + // A future improvement could derive ChildSpecifier from the index value. + unsafe { + shared + .try_map( + |value| (interface.shared_access)(ctx, value, index), + PathExtension::Tightened(AnyType::type_kind()), + result_span, + ) + .map_err(|(e, _)| e) + } }, |mutable| { - mutable.try_map_legacy(|value| { - (interface.mutable_access)(ctx, value, index, auto_create) - }) + // SAFETY: Tightened(AnyType) is conservative for index access. + unsafe { + mutable.try_map( + |value| (interface.mutable_access)(ctx, value, index, auto_create), + PathExtension::Tightened(AnyType::type_kind()), + result_span, + ) + } }, |owned| (interface.owned_access)(ctx, owned, index), )?; - let result_span = SpanRange::new_between(source_span, self.access.span_range()); context.return_not_necessarily_matching_requested(Spanned(result, result_span))? } }) diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index f3b61b1e..ca523672 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -258,17 +258,25 @@ pub(crate) trait ResolvableShared { where Self: 'static, { - value - .try_map_legacy(|v| { - Self::resolve_from_ref( - v, - ResolutionContext { - span_range: &span, - resolution_target, + // SAFETY: Tightened(AnyType) is conservative - it preserves the current path depth. + // The span is the actual source expression span for accurate error messages. + unsafe { + value + .try_map( + |v| { + Self::resolve_from_ref( + v, + ResolutionContext { + span_range: &span, + resolution_target, + }, + ) }, + PathExtension::Tightened(AnyType::type_kind()), + span, ) - }) - .map_err(|(err, _)| err) + .map_err(|(err, _)| err) + } } fn resolve_ref<'a>( @@ -323,17 +331,25 @@ pub(crate) trait ResolvableMutable { where Self: 'static, { - value - .try_map_legacy(|v| { - Self::resolve_from_mut( - v, - ResolutionContext { - span_range: &span, - resolution_target, + // SAFETY: Tightened(AnyType) is conservative - it preserves the current path depth. + // The span is the actual source expression span for accurate error messages. + unsafe { + value + .try_map( + |v| { + Self::resolve_from_mut( + v, + ResolutionContext { + span_range: &span, + resolution_target, + }, + ) }, + PathExtension::Tightened(AnyType::type_kind()), + span, ) - }) - .map_err(|(err, _)| err) + .map_err(|(err, _)| err) + } } fn resolve_ref_mut<'a>( diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index aa1fa17f..0afe92a1 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -398,7 +398,7 @@ where { fn into_content(self) -> Content<'static, Self::Type, Self::Form> { self.0 - .replace_legacy(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer)) + .emplace_map(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer)) } } @@ -617,13 +617,13 @@ where } CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { let content = shared - .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(content) .into_copy_on_write() } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { let content = shared - .replace_legacy(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); AnyLevelCopyOnWrite::::SharedWithTransparentCloning(content) .into_copy_on_write() } diff --git a/src/interpretation/refs.rs b/src/interpretation/refs.rs index dba9b3bf..07b9ce7d 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -9,29 +9,50 @@ pub(crate) struct AnyRef<'a, T: ?Sized + 'static> { } impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { + /// SAFETY: The caller must ensure the PathExtension is correct. #[allow(unused)] - pub(crate) fn map(self, f: impl for<'r> FnOnce(&'r T) -> &'r S) -> AnyRef<'a, S> { + pub(crate) unsafe fn map( + self, + f: impl for<'r> FnOnce(&'r T) -> &'r S, + path_extension: PathExtension, + new_span: SpanRange, + ) -> AnyRef<'a, S> { match self.inner { AnyRefInner::Direct(value) => AnyRef { inner: AnyRefInner::Direct(f(value)), }, AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.map_legacy(|x| f(x))), + inner: AnyRefInner::Encapsulated(unsafe { + shared.map(|x| f(x), path_extension, new_span) + }), }, } } + /// SAFETY: The caller must ensure the PathExtension is correct. #[allow(unused)] - pub(crate) fn map_optional( + pub(crate) unsafe fn map_optional( self, f: impl for<'r> FnOnce(&'r T) -> Option<&'r S>, + path_extension: PathExtension, + new_span: SpanRange, ) -> Option> { Some(match self.inner { AnyRefInner::Direct(value) => AnyRef { inner: AnyRefInner::Direct(f(value)?), }, AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.map_optional_legacy(f)?), + inner: AnyRefInner::Encapsulated(shared.emplace_map(|input, emplacer| match f( + input, + ) { + Some(output) => { + Some(unsafe { emplacer.emplace(output, path_extension, new_span) }) + } + None => { + let _ = emplacer.revert(); + None + } + })?), }, }) } @@ -60,26 +81,62 @@ pub(crate) struct AnyRefEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { } impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { - pub(crate) fn emplace(&mut self, value: &'e V) -> AnyRef<'a, V> { + /// Returns the current span of the underlying reference (if encapsulated), + /// or a placeholder span (if direct). + pub(crate) fn current_span(&self) -> SpanRange { + match &self + .inner + .as_ref() + .expect("Emplacer already consumed") + .inner + { + AnyRefInner::Direct(_) => SpanRange::new_single(Span::call_site()), + AnyRefInner::Encapsulated(shared) => shared.current_span(), + } + } + + /// SAFETY: The caller must ensure the PathExtension is correct. + pub(crate) unsafe fn emplace( + &mut self, + value: &'e V, + path_extension: PathExtension, + new_span: SpanRange, + ) -> AnyRef<'a, V> { unsafe { // SAFETY: The lifetime 'e is equal to the &'e content argument in replace // So this guarantees that the returned reference is valid as long as the AnyRef exists - self.emplace_unchecked(value) + self.emplace_unchecked(value, path_extension, new_span) } } - // SAFETY: - // * The caller must ensure that the value's lifetime is derived from the original content + /// SAFETY: + /// - The caller must ensure that the value's lifetime is derived from the original content + /// - The caller must ensure the PathExtension is correct pub(crate) unsafe fn emplace_unchecked( &mut self, value: &V, + path_extension: PathExtension, + new_span: SpanRange, ) -> AnyRef<'a, V> { - self.inner + let any_ref = self + .inner .take() - .expect("You can only emplace to create a new AnyRef value once") - .map(|_| + .expect("You can only emplace to create a new AnyRef value once"); + match any_ref.inner { + AnyRefInner::Direct(_) => AnyRef { // SAFETY: As defined in the rustdoc above - unsafe { transmute::<&V, &'static V>(value) }) + inner: AnyRefInner::Direct(unsafe { transmute::<&V, &'static V>(value) }), + }, + AnyRefInner::Encapsulated(shared) => AnyRef { + inner: AnyRefInner::Encapsulated(unsafe { + shared.map( + |_| transmute::<&V, &'static V>(value), + path_extension, + new_span, + ) + }), + }, + } } } @@ -156,32 +213,50 @@ impl<'a, T: ?Sized> From<&'a mut T> for AnyMut<'a, T> { } impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { + /// SAFETY: The caller must ensure the PathExtension is correct. #[allow(unused)] - pub(crate) fn map( + pub(crate) unsafe fn map( self, f: impl for<'r> FnOnce(&'r mut T) -> &'r mut S, + path_extension: PathExtension, + new_span: SpanRange, ) -> AnyMut<'a, S> { match self.inner { AnyMutInner::Direct(value) => AnyMut { inner: AnyMutInner::Direct(f(value)), }, AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.map_legacy(|x| f(x))), + inner: AnyMutInner::Encapsulated(unsafe { + mutable.map(|x| f(x), path_extension, new_span) + }), }, } } + /// SAFETY: The caller must ensure the PathExtension is correct. #[allow(unused)] - pub(crate) fn map_optional( + pub(crate) unsafe fn map_optional( self, f: impl for<'r> FnOnce(&'r mut T) -> Option<&'r mut S>, + path_extension: PathExtension, + new_span: SpanRange, ) -> Option> { Some(match self.inner { AnyMutInner::Direct(value) => AnyMut { inner: AnyMutInner::Direct(f(value)?), }, AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.map_optional_legacy(f)?), + inner: AnyMutInner::Encapsulated(mutable.emplace_map( + |input, emplacer| match f(input) { + Some(output) => { + Some(unsafe { emplacer.emplace(output, path_extension, new_span) }) + } + None => { + let _ = emplacer.revert(); + None + } + }, + )?), }, }) } @@ -211,26 +286,62 @@ pub(crate) struct AnyMutEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { } impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { - pub(crate) fn emplace(&mut self, value: &'e mut V) -> AnyMut<'a, V> { + /// Returns the current span of the underlying reference (if encapsulated), + /// or a placeholder span (if direct). + pub(crate) fn current_span(&self) -> SpanRange { + match &self + .inner + .as_ref() + .expect("Emplacer already consumed") + .inner + { + AnyMutInner::Direct(_) => SpanRange::new_single(Span::call_site()), + AnyMutInner::Encapsulated(mutable) => mutable.current_span(), + } + } + + /// SAFETY: The caller must ensure the PathExtension is correct. + pub(crate) unsafe fn emplace( + &mut self, + value: &'e mut V, + path_extension: PathExtension, + new_span: SpanRange, + ) -> AnyMut<'a, V> { unsafe { // SAFETY: The lifetime 'e is equal to the &'e content argument in replace // So this guarantees that the returned reference is valid as long as the AnyMut exists - self.emplace_unchecked(value) + self.emplace_unchecked(value, path_extension, new_span) } } - // SAFETY: - // * The caller must ensure that the value's lifetime is derived from the original content + /// SAFETY: + /// - The caller must ensure that the value's lifetime is derived from the original content + /// - The caller must ensure the PathExtension is correct pub(crate) unsafe fn emplace_unchecked( &mut self, value: &mut V, + path_extension: PathExtension, + new_span: SpanRange, ) -> AnyMut<'a, V> { - self.inner + let any_mut = self + .inner .take() - .expect("You can only emplace to create a new AnyMut value once") - .map(|_| + .expect("You can only emplace to create a new AnyMut value once"); + match any_mut.inner { + AnyMutInner::Direct(_) => AnyMut { // SAFETY: As defined in the rustdoc above - unsafe { transmute::<&mut V, &'static mut V>(value) }) + inner: AnyMutInner::Direct(unsafe { transmute::<&mut V, &'static mut V>(value) }), + }, + AnyMutInner::Encapsulated(mutable) => AnyMut { + inner: AnyMutInner::Encapsulated(unsafe { + mutable.map( + |_| transmute::<&mut V, &'static mut V>(value), + path_extension, + new_span, + ) + }), + }, + } } } diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index e70f7d8a..f1c35220 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -70,6 +70,11 @@ impl MutableReference { } impl MutableReference { + /// Returns the current creation span of the tracked reference. + pub(crate) fn current_span(&self) -> SpanRange { + self.0.core.data().for_reference(self.0.id).creation_span + } + /// Disables this mutable reference (bridge for old `disable()` API). /// Equivalent to `deactivate()` in the new naming. pub(crate) fn disable(self) -> InactiveMutableReference { @@ -85,75 +90,6 @@ impl MutableReference { .activate() .expect("Converting mutable to shared should always succeed since we just released the mutable borrow") } - - /// Safe map that uses a placeholder path extension. - /// Bridge method for migration. - pub(crate) fn map_legacy( - self, - value_map: impl FnOnce(&mut T) -> &mut V, - ) -> MutableReference { - self.emplace_map(move |input, emplacer| { - // SAFETY: Conservative path extension - unsafe { - emplacer.emplace( - value_map(input), - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - } - }) - } - - /// Safe try_map that uses a placeholder path extension. - /// Bridge method for migration. - pub(crate) fn try_map_legacy( - self, - value_map: impl FnOnce(&mut T) -> Result<&mut V, E>, - ) -> Result, (E, MutableReference)> { - self.emplace_map(|input, emplacer| match value_map(input) { - Ok(output) => { - // SAFETY: Conservative path extension - Ok(unsafe { - emplacer.emplace( - output, - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - }) - } - Err(e) => Err((e, emplacer.revert())), - }) - } - - /// Safe map_optional that uses a placeholder path extension. - /// Bridge method for migration. - #[allow(unused)] - pub(crate) fn map_optional_legacy( - self, - value_map: impl FnOnce(&mut T) -> Option<&mut V>, - ) -> Option> { - self.emplace_map(|input, emplacer| match value_map(input) { - Some(output) => Some(unsafe { - emplacer.emplace( - output, - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - }), - None => { - let _ = emplacer.revert(); - None - } - }) - } - - /// Bridge for the old `replace()` pattern. - pub(crate) fn replace_legacy( - self, - f: impl for<'e> FnOnce(&'e mut T, &mut MutableEmplacerV2<'e, T>) -> O, - ) -> O { - self.emplace_map(f) - } } impl InactiveMutableReference { @@ -232,6 +168,12 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { MutableReference(self.0.revert()) } + /// Returns the current creation span of the tracked reference. + /// Useful for preserving the span during internal type-narrowing operations. + pub(crate) fn current_span(&self) -> SpanRange { + self.0.current_span() + } + /// SAFETY: /// - The caller must ensure that the PathExtension is correct /// (an overly-specific PathExtension may cause safety issues) @@ -264,24 +206,6 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { // - The caller ensures that the PathExtension is correct unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } - - /// Legacy bridge: emplace_unchecked without PathExtension. - /// Uses a conservative default path extension. - /// - /// SAFETY: - /// - The caller must ensure that the value's lifetime is derived from the original content - pub(crate) unsafe fn emplace_unchecked_legacy( - &mut self, - value: &mut V, - ) -> MutableReference { - unsafe { - self.emplace_unchecked( - value, - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - } - } } /// Legacy type alias for backward compatibility diff --git a/src/misc/dynamic_references/reference_core.rs b/src/misc/dynamic_references/reference_core.rs index 52eed74e..bc3e5fef 100644 --- a/src/misc/dynamic_references/reference_core.rs +++ b/src/misc/dynamic_references/reference_core.rs @@ -90,6 +90,12 @@ impl<'e, T: ?Sized> EmplacerCore<'e, T> { } } + /// Returns the current creation span of the tracked reference. + pub(crate) fn current_span(&self) -> SpanRange { + let inner = self.inner.as_ref().expect("Emplacer already consumed"); + inner.core.data().for_reference(inner.id).creation_span + } + /// SAFETY: /// - The caller must ensure that the pointer is derived from the original content /// - The caller must ensure that the PathExtension is correct diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index d1f437bb..349d6b74 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -82,86 +82,16 @@ impl SharedReference { } impl SharedReference { + /// Returns the current creation span of the tracked reference. + pub(crate) fn current_span(&self) -> SpanRange { + self.0.core.data().for_reference(self.0.id).creation_span + } + /// Disables this shared reference (bridge for old `disable()` API). /// Equivalent to `deactivate()` in the new naming. pub(crate) fn disable(self) -> InactiveSharedReference { self.deactivate() } - - /// Safe map that uses a placeholder path extension. - /// This is a bridge method for migration - callers should eventually switch to - /// the unsafe `map()` with proper PathExtension. - pub(crate) fn map_legacy( - self, - value_map: impl FnOnce(&T) -> &V, - ) -> SharedReference { - self.emplace_map(move |input, emplacer| { - // SAFETY: We use Tightened(AnyType::type_kind()) as a conservative path extension - // that says "we're still at the same depth, just changing our view". - // This may be overly conservative but won't cause memory safety issues. - unsafe { - emplacer.emplace( - value_map(input), - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - } - }) - } - - /// Safe try_map that uses a placeholder path extension. - /// Bridge method for migration. - pub(crate) fn try_map_legacy( - self, - value_map: impl FnOnce(&T) -> Result<&V, E>, - ) -> Result, (E, SharedReference)> { - self.emplace_map(|input, emplacer| match value_map(input) { - Ok(output) => { - // SAFETY: Same conservative path extension as map_legacy - Ok(unsafe { - emplacer.emplace( - output, - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - }) - } - Err(e) => Err((e, emplacer.revert())), - }) - } - - /// Safe map_optional that uses a placeholder path extension. - /// Bridge method for migration. - pub(crate) fn map_optional_legacy( - self, - value_map: impl FnOnce(&T) -> Option<&V>, - ) -> Option> { - self.emplace_map(|input, emplacer| match value_map(input) { - Some(output) => { - // SAFETY: Same conservative path extension as map_legacy - Some(unsafe { - emplacer.emplace( - output, - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - }) - } - None => { - let _ = emplacer.revert(); // drop the reverted reference - None - } - }) - } - - /// Bridge for the old `replace()` pattern. - /// Uses the emplace_map internally with a legacy path extension. - pub(crate) fn replace_legacy( - self, - f: impl for<'e> FnOnce(&'e T, &mut SharedEmplacerV2<'e, T>) -> O, - ) -> O { - self.emplace_map(f) - } } impl InactiveSharedReference { @@ -217,6 +147,12 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { SharedReference(self.0.revert()) } + /// Returns the current creation span of the tracked reference. + /// Useful for preserving the span during internal type-narrowing operations. + pub(crate) fn current_span(&self) -> SpanRange { + self.0.current_span() + } + /// SAFETY: /// - The caller must ensure that the PathExtension is correct /// (an overly-specific PathExtension may cause safety issues) @@ -249,24 +185,6 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { // - The caller ensures that the PathExtension is correct unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } - - /// Legacy bridge: emplace_unchecked without PathExtension. - /// Uses a conservative default path extension. - /// - /// SAFETY: - /// - The caller must ensure that the value's lifetime is derived from the original content - pub(crate) unsafe fn emplace_unchecked_legacy( - &mut self, - value: &V, - ) -> SharedReference { - unsafe { - self.emplace_unchecked( - value, - PathExtension::Tightened(AnyType::type_kind()), - SpanRange::new_single(Span::call_site()), - ) - } - } } /// Legacy type alias for backward compatibility From 0b255693d7ddccb60b3a122f828ee60bcdb30fc9 Mon Sep 17 00:00:00 2001 From: David Edey Date: Tue, 24 Feb 2026 23:47:50 +0000 Subject: [PATCH 33/49] fix: Partial improvements / fixes --- src/expressions/concepts/content.rs | 15 +- src/expressions/concepts/form.rs | 7 +- src/expressions/concepts/forms/any_mut.rs | 16 +- src/expressions/concepts/forms/any_ref.rs | 16 +- src/expressions/concepts/forms/assignee.rs | 16 +- src/expressions/concepts/forms/mod.rs | 2 - src/expressions/concepts/forms/mutable.rs | 25 +- src/expressions/concepts/forms/owned.rs | 6 +- .../concepts/forms/referenceable.rs | 35 -- src/expressions/concepts/forms/shared.rs | 25 +- src/expressions/concepts/forms/simple_mut.rs | 18 +- src/expressions/concepts/forms/simple_ref.rs | 14 +- src/expressions/concepts/type_traits.rs | 25 +- src/expressions/evaluation/value_frames.rs | 22 +- src/expressions/expression_parsing.rs | 6 +- src/expressions/type_resolution/arguments.rs | 74 +-- src/expressions/type_resolution/type_kinds.rs | 58 ++- src/expressions/values/any_value.rs | 2 +- src/internal_prelude.rs | 2 +- src/interpretation/bindings.rs | 4 +- src/interpretation/refs.rs | 49 +- src/interpretation/variable.rs | 4 +- .../dynamic_references/mutable_reference.rs | 42 +- src/misc/dynamic_references/reference_core.rs | 8 +- src/misc/dynamic_references/referenceable.rs | 76 +-- .../dynamic_references/shared_reference.rs | 42 +- src/misc/mod.rs | 3 - src/misc/mut_rc_ref_cell.rs | 469 ------------------ .../expressions/swap_itself.stderr | 18 +- 29 files changed, 239 insertions(+), 860 deletions(-) delete mode 100644 src/expressions/concepts/forms/referenceable.rs delete mode 100644 src/misc/mut_rc_ref_cell.rs diff --git a/src/expressions/concepts/content.rs b/src/expressions/concepts/content.rs index 641cbcd0..c3502101 100644 --- a/src/expressions/concepts/content.rs +++ b/src/expressions/concepts/content.rs @@ -2,8 +2,7 @@ use super::*; /// Shorthand for representing the form F of a type T with a particular lifetime 'a. pub(crate) type Content<'a, T, F> = ::Content<'a, F>; -pub(crate) type DynContent<'a, D, F> = - ::DynLeaf<'a, ::DynContent>; +pub(crate) type DynContent<'a, D, F> = ::DynLeaf<'a, D>; /// For types which have an associated value (type and form) pub(crate) trait IsValueContent { @@ -69,18 +68,6 @@ where { self.upcast() } - - fn into_referenceable(self) -> Content<'a, Self::Type, BeReferenceable> - where - Self: IsValueContent, - { - map_via_leaf! { - input: (Content<'a, Self::Type, Self::Form>) = self.into_content(), - fn map_leaf(leaf) -> (Content<'a, T, BeReferenceable>) { - Rc::new(RefCell::new(leaf)) - } - } - } } impl<'a, C> Spanned diff --git a/src/expressions/concepts/form.rs b/src/expressions/concepts/form.rs index 6337ce19..1ebce65f 100644 --- a/src/expressions/concepts/form.rs +++ b/src/expressions/concepts/form.rs @@ -53,18 +53,19 @@ pub(crate) trait IsDynCompatibleForm: IsHierarchicalForm { /// The container for a dyn Trait based type. /// The DynLeaf can be similar to the standard leaf, but must be /// able to support an unsized D. - type DynLeaf<'a, D: 'static + ?Sized>; + type DynLeaf<'a, D: IsDynType>; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn; + T::Leaf: CastDyn; } pub(crate) trait MapFromArgument: IsHierarchicalForm { const ARGUMENT_OWNERSHIP: ArgumentOwnership; + // TODO[references]: Have this take a span so that we can store it into the emplacer fn from_argument_value(value: ArgumentValue) -> FunctionResult>; } diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index e370292f..7fcc4389 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -25,23 +25,21 @@ impl IsHierarchicalForm for BeAnyMut { } impl IsDynCompatibleForm for BeAnyMut { - type DynLeaf<'a, D: 'static + ?Sized> = AnyMut<'a, D>; + type DynLeaf<'a, D: IsDynType> = AnyMut<'a, D::DynContent>; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| { - let span = emplacer.current_span(); + leaf.emplace_map(|content, emplacer| { match ::map_mut(content) { Ok(mapped) => Ok(unsafe { - emplacer.emplace(mapped, PathExtension::Tightened(T::type_kind()), span) - }), - Err(this) => Err(unsafe { - emplacer.emplace(this, PathExtension::Tightened(T::type_kind()), span) + // TODO[references]: Pass a span here by propagating from DynResolveFrom + emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), None) }), + Err(this) => Err(emplacer.revert()), } }) } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index 61e690e1..f7ed377e 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -26,23 +26,21 @@ impl IsHierarchicalForm for BeAnyRef { } impl IsDynCompatibleForm for BeAnyRef { - type DynLeaf<'a, D: 'static + ?Sized> = crate::internal_prelude::AnyRef<'a, D>; + type DynLeaf<'a, D: IsDynType> = crate::internal_prelude::AnyRef<'a, D::DynContent>; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| { - let span = emplacer.current_span(); + leaf.emplace_map(|content, emplacer| { match ::map_ref(content) { Ok(mapped) => Ok(unsafe { - emplacer.emplace(mapped, PathExtension::Tightened(T::type_kind()), span) - }), - Err(this) => Err(unsafe { - emplacer.emplace(this, PathExtension::Tightened(T::type_kind()), span) + // TODO[references]: Pass a span here by propagating from DynResolveFrom + emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), None) }), + Err(this) => Err(emplacer.revert()), } }) } diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index 5a3065e9..ea08247a 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -28,27 +28,25 @@ impl IsHierarchicalForm for BeAssignee { } impl IsDynCompatibleForm for BeAssignee { - type DynLeaf<'a, D: 'static + ?Sized> = QqqAssignee; + type DynLeaf<'a, D: IsDynType> = QqqAssignee; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { leaf.0.emplace_map(|content, emplacer| { - let span = emplacer.current_span(); match ::map_mut(content) { Ok(mapped) => Ok(QqqAssignee(unsafe { + // TODO[references]: Pass a span here by propagating from DynResolveFrom emplacer.emplace_unchecked( mapped, - PathExtension::Tightened(T::type_kind()), - span, + PathExtension::Tightened(D::type_kind()), + None, ) })), - Err(this) => Err(QqqAssignee(unsafe { - emplacer.emplace_unchecked(this, PathExtension::Tightened(T::type_kind()), span) - })), + Err(this) => Err(QqqAssignee(emplacer.revert())), } }) } diff --git a/src/expressions/concepts/forms/mod.rs b/src/expressions/concepts/forms/mod.rs index 3f42e697..17102ad6 100644 --- a/src/expressions/concepts/forms/mod.rs +++ b/src/expressions/concepts/forms/mod.rs @@ -9,7 +9,6 @@ mod copy_on_write; mod late_bound; mod mutable; mod owned; -mod referenceable; mod shared; mod simple_mut; mod simple_ref; @@ -22,7 +21,6 @@ pub(crate) use copy_on_write::*; pub(crate) use late_bound::*; pub(crate) use mutable::*; pub(crate) use owned::*; -pub(crate) use referenceable::*; pub(crate) use shared::*; pub(crate) use simple_mut::*; pub(crate) use simple_ref::*; diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index 4f8ce835..8c3a1feb 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -28,28 +28,19 @@ impl IsHierarchicalForm for BeMutable { } impl IsDynCompatibleForm for BeMutable { - type DynLeaf<'a, D: 'static + ?Sized> = QqqMutable; + type DynLeaf<'a, D: IsDynType> = QqqMutable; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.emplace_map(|content, emplacer| { - let span = emplacer.current_span(); - match ::map_mut(content) { - Ok(mapped) => Ok(unsafe { - emplacer.emplace_unchecked( - mapped, - PathExtension::Tightened(T::type_kind()), - span, - ) - }), - Err(this) => Err(unsafe { - emplacer.emplace_unchecked(this, PathExtension::Tightened(T::type_kind()), span) - }), - } + leaf.emplace_map(|content, emplacer| match ::map_mut(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace_unchecked(mapped, PathExtension::Tightened(D::type_kind()), None) + }), + Err(_) => Err(emplacer.revert()), }) } } diff --git a/src/expressions/concepts/forms/owned.rs b/src/expressions/concepts/forms/owned.rs index 09873161..4fe3e8a9 100644 --- a/src/expressions/concepts/forms/owned.rs +++ b/src/expressions/concepts/forms/owned.rs @@ -21,13 +21,13 @@ impl IsHierarchicalForm for BeOwned { } impl IsDynCompatibleForm for BeOwned { - type DynLeaf<'a, D: 'static + ?Sized> = Box; + type DynLeaf<'a, D: IsDynType> = Box; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { ::map_boxed(Box::new(leaf)).map_err(|boxed| *boxed) } diff --git a/src/expressions/concepts/forms/referenceable.rs b/src/expressions/concepts/forms/referenceable.rs deleted file mode 100644 index 470eb455..00000000 --- a/src/expressions/concepts/forms/referenceable.rs +++ /dev/null @@ -1,35 +0,0 @@ -use super::*; - -// The old `pub(crate) type Referenceable = Rc>;` has been replaced by -// `dynamic_references::Referenceable` which is NOT generic (always wraps AnyValue). -// For the form system, we use Rc> directly as the leaf type. - -impl IsValueContent for Rc> { - type Type = L::Type; - type Form = BeReferenceable; -} - -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for Rc> { - fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self - } -} - -impl<'a, L: IsValueLeaf> FromValueContent<'a> for Rc> { - fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { - content - } -} - -/// Roughly equivalent to an owned, but wrapped so that it can be turned into a Shared/Mutable easily. -/// This is useful for the content of variables. -/// -/// Note that Referenceable form does not support dyn casting, because Rc> cannot be -/// directly cast to Rc>. -#[derive(Copy, Clone)] -pub(crate) struct BeReferenceable; -impl IsForm for BeReferenceable {} - -impl IsHierarchicalForm for BeReferenceable { - type Leaf<'a, T: IsLeafType> = Rc>; -} diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index b402a824..1f6cb40f 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -28,28 +28,19 @@ impl IsHierarchicalForm for BeShared { } impl IsDynCompatibleForm for BeShared { - type DynLeaf<'a, D: 'static + ?Sized> = QqqShared; + type DynLeaf<'a, D: IsDynType> = QqqShared; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.emplace_map(|content, emplacer| { - let span = emplacer.current_span(); - match ::map_ref(content) { - Ok(mapped) => Ok(unsafe { - emplacer.emplace_unchecked( - mapped, - PathExtension::Tightened(T::type_kind()), - span, - ) - }), - Err(this) => Err(unsafe { - emplacer.emplace_unchecked(this, PathExtension::Tightened(T::type_kind()), span) - }), - } + leaf.emplace_map(|content, emplacer| match ::map_ref(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace_unchecked(mapped, PathExtension::Tightened(D::type_kind()), None) + }), + Err(_) => Err(emplacer.revert()), }) } } diff --git a/src/expressions/concepts/forms/simple_mut.rs b/src/expressions/concepts/forms/simple_mut.rs index 3100a8c9..8b82e88b 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -29,13 +29,13 @@ impl IsHierarchicalForm for BeMut { } impl IsDynCompatibleForm for BeMut { - type DynLeaf<'a, D: 'static + ?Sized> = &'a mut D; + type DynLeaf<'a, D: IsDynType> = &'a mut D::DynContent; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { ::map_mut(leaf) } @@ -76,12 +76,12 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - let span = self.emplacer.current_span(); unsafe { self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - span, + // TODO[references]: Pass a span here by propagating from DynResolveFrom + None, ) } } @@ -114,12 +114,12 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - let span = self.emplacer.current_span(); unsafe { QqqAssignee(self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - span, + // TODO[references]: Pass a span here by propagating from DynResolveFrom + None, )) } } @@ -152,12 +152,12 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - let span = self.emplacer.current_span(); let mutable_ref: MutableReference<_> = unsafe { self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - span, + // TODO[references]: Pass a span here by propagating from DynResolveFrom + None, ) }; mutable_ref.into() diff --git a/src/expressions/concepts/forms/simple_ref.rs b/src/expressions/concepts/forms/simple_ref.rs index 072c79ab..5c740cf6 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -29,13 +29,13 @@ impl IsHierarchicalForm for BeRef { } impl IsDynCompatibleForm for BeRef { - type DynLeaf<'a, D: 'static + ?Sized> = &'a D; + type DynLeaf<'a, D: IsDynType> = &'a D::DynContent; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( leaf: Self::Leaf<'a, T>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { ::map_ref(leaf) } @@ -76,12 +76,12 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - let span = self.emplacer.current_span(); unsafe { self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - span, + // TODO[references]: Pass a span here by propagating from DynResolveFrom + None, ) } } @@ -114,12 +114,12 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e so this is valid - let span = self.emplacer.current_span(); let shared_ref: SharedReference<_> = unsafe { self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - span, + // TODO[references]: Pass a span here by propagating from DynResolveFrom + None, ) }; shared_ref.into() diff --git a/src/expressions/concepts/type_traits.rs b/src/expressions/concepts/type_traits.rs index d267a660..55b921f5 100644 --- a/src/expressions/concepts/type_traits.rs +++ b/src/expressions/concepts/type_traits.rs @@ -57,7 +57,6 @@ pub(crate) trait IsLeafType: + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> - + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> @@ -104,6 +103,7 @@ pub(crate) trait DowncastFrom: IsHierarchicalType { } pub(crate) trait DynResolveFrom: IsDynType { + // TODO[references]: Have this take a span so it can propagate to the emplacer fn downcast_from<'a, F: IsHierarchicalForm + IsDynCompatibleForm>( content: Content<'a, T, F>, ) -> Result, Content<'a, T, F>>; @@ -472,7 +472,6 @@ macro_rules! define_parent_type { } } - impl<'a, F: IsHierarchicalForm> Copy for $content<'a, F> where $( Content<'a, $variant_type, F>: Copy ),* @@ -712,9 +711,9 @@ macro_rules! define_leaf_type { pub(crate) use define_leaf_type; -pub(crate) struct DynMapper(std::marker::PhantomData); +pub(crate) struct DynMapper(std::marker::PhantomData); -impl DynMapper { +impl DynMapper { pub(crate) const fn new() -> Self { Self(std::marker::PhantomData) } @@ -770,7 +769,7 @@ pub(crate) use impl_value_content_traits; macro_rules! define_dyn_type { ( $type_def_vis:vis $type_def:ident, - content: $dyn_type:ty, + content: $dyn_content:ty, dyn_kind: DynTypeKind::$dyn_kind:ident, type_name: $source_type_name:literal, articled_value_name: $articled_value_name:literal, @@ -802,18 +801,18 @@ macro_rules! define_dyn_type { } impl IsDynType for $type_def { - type DynContent = $dyn_type; + type DynContent = $dyn_content; } impl_type_feature_resolver! { impl TypeFeatureResolver for $type_def: [$type_def] } - impl IsDynLeaf for $dyn_type { + impl IsDynLeaf for $dyn_content { type Type = $type_def; } - impl IsArgument for Box<$dyn_type> { + impl IsArgument for Box<$dyn_content> { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Owned; fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { @@ -822,7 +821,7 @@ macro_rules! define_dyn_type { } } - impl<'a> IsArgument for AnyRef<'a, $dyn_type> { + impl<'a> IsArgument for AnyRef<'a, $dyn_content> { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { @@ -831,7 +830,7 @@ macro_rules! define_dyn_type { } } - impl<'a> IsArgument for AnyMut<'a, $dyn_type> { + impl<'a> IsArgument for AnyMut<'a, $dyn_content> { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { @@ -843,12 +842,12 @@ macro_rules! define_dyn_type { impl DynResolveFrom for $type_def { fn downcast_from<'a, F: IsHierarchicalForm + IsDynCompatibleForm>(content: Content<'a, T, F>) -> Result, Content<'a, T, F>> { - T::map_with::<'a, F, _>(DynMapper::<$dyn_type>::new(), content) + T::map_with::<'a, F, _>(DynMapper::<$type_def>::new(), content) } } - impl LeafMapper for DynMapper<$dyn_type> { - type Output<'a, T: IsHierarchicalType> = Result, Content<'a, T, F>>; + impl LeafMapper for DynMapper<$type_def> { + type Output<'a, T: IsHierarchicalType> = Result, Content<'a, T, F>>; fn to_parent_output<'a, T: IsChildType>( output: Self::Output<'a, T>, diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index 321ad343..5b20f42c 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -452,13 +452,15 @@ impl ArgumentOwnership { ArgumentOwnership::Owned => Ok(ArgumentValue::Owned( copy_on_write.clone_to_owned_transparently(span)?, )), - ArgumentOwnership::Shared => Ok(ArgumentValue::Shared(copy_on_write.into_shared())), + ArgumentOwnership::Shared => Ok(ArgumentValue::Shared(copy_on_write.into_shared(span))), ArgumentOwnership::Mutable => { if copy_on_write.acts_as_shared_reference() { span.ownership_err("A mutable reference is required, but a shared reference was received, this indicates a possible bug as the updated value won't be accessible. To proceed regardless, use `.clone()` to get a mutable reference to a cloned value.") } else { Ok(ArgumentValue::Mutable(Mutable::new_from_owned( copy_on_write.clone_to_owned_transparently(span)?, + None, + span, ))) } } @@ -559,9 +561,9 @@ impl ArgumentOwnership { ArgumentOwnership::CopyOnWrite => { Ok(ArgumentValue::CopyOnWrite(CopyOnWrite::owned(owned))) } - ArgumentOwnership::Mutable => { - Ok(ArgumentValue::Mutable(Mutable::new_from_owned(owned))) - } + ArgumentOwnership::Mutable => Ok(ArgumentValue::Mutable(Mutable::new_from_owned( + owned, None, span, + ))), ArgumentOwnership::Assignee { .. } => { if is_from_last_use { span.ownership_err("The final usage of a variable cannot be assigned to. You can use `let _ = ..` to discard a value.") @@ -569,7 +571,9 @@ impl ArgumentOwnership { span.ownership_err("An owned value cannot be assigned to.") } } - ArgumentOwnership::Shared => Ok(ArgumentValue::Shared(Shared::new_from_owned(owned))), + ArgumentOwnership::Shared => Ok(ArgumentValue::Shared(Shared::new_from_owned( + owned, None, span, + ))), } } } @@ -1065,7 +1069,7 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { ChildSpecifier::ObjectChild(property_name_for_shared), AnyType::type_kind(), ), - result_span, + Some(result_span), ) .map_err(|(e, _)| e) } @@ -1079,7 +1083,7 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { ChildSpecifier::ObjectChild(property_name), AnyType::type_kind(), ), - result_span, + Some(result_span), ) } }, @@ -1183,7 +1187,7 @@ impl EvaluationFrame for ValueIndexAccessBuilder { .try_map( |value| (interface.shared_access)(ctx, value, index), PathExtension::Tightened(AnyType::type_kind()), - result_span, + Some(result_span), ) .map_err(|(e, _)| e) } @@ -1194,7 +1198,7 @@ impl EvaluationFrame for ValueIndexAccessBuilder { mutable.try_map( |value| (interface.mutable_access)(ctx, value, index, auto_create), PathExtension::Tightened(AnyType::type_kind()), - result_span, + Some(result_span), ) } }, diff --git a/src/expressions/expression_parsing.rs b/src/expressions/expression_parsing.rs index 7784557c..4bc53b20 100644 --- a/src/expressions/expression_parsing.rs +++ b/src/expressions/expression_parsing.rs @@ -123,7 +123,7 @@ impl<'a> ExpressionParser<'a> { "true" | "false" => { let bool = input.parse::()?; UnaryAtom::Leaf(Leaf::Value(Spanned( - SharedValue::new_from_owned(bool.value.into_any_value()), + SharedValue::new_from_owned(bool.value.into_any_value(), None, bool.span.span_range()), bool.span.span_range(), ))) } @@ -136,7 +136,7 @@ impl<'a> ExpressionParser<'a> { "None" => { let none_ident = input.parse_any_ident()?; // consume the "None" token UnaryAtom::Leaf(Leaf::Value(Spanned( - SharedValue::new_from_owned(().into_any_value()), + SharedValue::new_from_owned(().into_any_value(), None, none_ident.span().span_range()), none_ident.span().span_range(), ))) } @@ -155,7 +155,7 @@ impl<'a> ExpressionParser<'a> { let lit: syn::Lit = input.parse()?; let span_range = lit.span().span_range(); UnaryAtom::Leaf(Leaf::Value(Spanned( - SharedValue::new_from_owned(AnyValue::for_syn_lit(lit)), + SharedValue::new_from_owned(AnyValue::for_syn_lit(lit), None, span_range), span_range, ))) }, diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index ca523672..54d084ac 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -58,10 +58,7 @@ impl IsArgument for ArgumentValue { } } -// NOTE: IsArgument for SharedReference with leaf T is now provided by the blanket impl -// in content.rs via FromValueContent + MapFromArgument (BeShared). -// For the parent type AnyValue (which is not IsValueLeaf), we provide an explicit impl: -impl IsArgument for SharedReference { +impl IsArgument for Shared { type ValueType = AnyType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; @@ -94,10 +91,7 @@ impl + ResolvableArgumentTarget + ?Sized> IsArgum } } -// NOTE: IsArgument for MutableReference with leaf T is now provided by the blanket impl -// in content.rs via FromValueContent + MapFromArgument (BeMutable). -// For the parent type AnyValue (which is not IsValueLeaf), we provide an explicit impl: -impl IsArgument for MutableReference { +impl IsArgument for Mutable { type ValueType = AnyType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; @@ -223,7 +217,7 @@ pub(crate) trait ResolvableArgumentTarget { type ValueType: TypeData; } -pub(crate) trait ResolvableOwned: Sized { +pub(crate) trait ResolvableOwned: Sized { fn resolve_from_value(value: T, context: ResolutionContext) -> FunctionResult; fn resolve_spanned_from_value( @@ -247,7 +241,7 @@ pub(crate) trait ResolvableOwned: Sized { } } -pub(crate) trait ResolvableShared { +pub(crate) trait ResolvableShared { fn resolve_from_ref<'a>(value: &'a T, context: ResolutionContext) -> FunctionResult<&'a Self>; /// The `resolution_target` should be capitalized, e.g. "This argument" or "The value destructed with an object pattern" @@ -258,8 +252,7 @@ pub(crate) trait ResolvableShared { where Self: 'static, { - // SAFETY: Tightened(AnyType) is conservative - it preserves the current path depth. - // The span is the actual source expression span for accurate error messages. + // SAFETY: Tightened(T::Type) is correct unsafe { value .try_map( @@ -272,8 +265,9 @@ pub(crate) trait ResolvableShared { }, ) }, - PathExtension::Tightened(AnyType::type_kind()), - span, + // TODO[references]: Move unsafe to this constructor so the blocks can be smaller + PathExtension::Tightened(T::Type::type_kind()), + Some(span), ) .map_err(|(err, _)| err) } @@ -308,7 +302,7 @@ pub(crate) trait ResolvableShared { } } -pub(crate) trait ResolvableMutable { +pub(crate) trait ResolvableMutable { fn resolve_from_mut<'a>( value: &'a mut T, context: ResolutionContext, @@ -331,8 +325,7 @@ pub(crate) trait ResolvableMutable { where Self: 'static, { - // SAFETY: Tightened(AnyType) is conservative - it preserves the current path depth. - // The span is the actual source expression span for accurate error messages. + // SAFETY: Tightened(T::Type) is correct unsafe { value .try_map( @@ -345,8 +338,8 @@ pub(crate) trait ResolvableMutable { }, ) }, - PathExtension::Tightened(AnyType::type_kind()), - span, + PathExtension::Tightened(T::Type::type_kind()), + Some(span), ) .map_err(|(err, _)| err) } @@ -443,46 +436,3 @@ macro_rules! impl_resolvable_argument_for { } pub(crate) use impl_resolvable_argument_for; - -macro_rules! impl_delegated_resolvable_argument_for { - (($value:ident: $delegate:ty) -> $type:ty { $expr:expr }) => { - impl ResolvableArgumentTarget for $type { - type ValueType = <$delegate as ResolvableArgumentTarget>::ValueType; - } - - impl ResolvableOwned for $type { - fn resolve_from_value( - input_value: Value, - context: ResolutionContext, - ) -> FunctionResult { - let $value: $delegate = - ResolvableOwned::::resolve_from_value(input_value, context)?; - Ok($expr) - } - } - - impl ResolvableShared for $type { - fn resolve_from_ref<'a>( - input_value: &'a Value, - context: ResolutionContext, - ) -> FunctionResult<&'a Self> { - let $value: &$delegate = - ResolvableShared::::resolve_from_ref(input_value, context)?; - Ok(&$expr) - } - } - - impl ResolvableMutable for $type { - fn resolve_from_mut<'a>( - input_value: &'a mut Value, - context: ResolutionContext, - ) -> FunctionResult<&'a mut Self> { - let $value: &mut $delegate = - ResolvableMutable::::resolve_from_mut(input_value, context)?; - Ok(&mut $expr) - } - } - }; -} - -pub(crate) use impl_delegated_resolvable_argument_for; diff --git a/src/expressions/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index c089e77e..7fe69984 100644 --- a/src/expressions/type_resolution/type_kinds.rs +++ b/src/expressions/type_resolution/type_kinds.rs @@ -94,8 +94,10 @@ impl TypeKind { pub(crate) fn is_tightening_to(&self, other: &Self) -> bool { match self.compare_bindings(other) { TypeBindingComparison::Equal => true, - TypeBindingComparison::RightDerivesFromLeft => true, - TypeBindingComparison::LeftDerivesFromRight => false, + TypeBindingComparison::RightDerivesFromLeftButIsNotSubtype => true, + TypeBindingComparison::RightIsSubtypeOfLeft => true, + TypeBindingComparison::LeftDerivesFromRightButIsNotSubtype => false, + TypeBindingComparison::LeftIsSubtypeOfRight => false, TypeBindingComparison::Incomparable => false, TypeBindingComparison::Incompatible => false, } @@ -117,24 +119,24 @@ impl TypeKind { (TypeKind::Dyn(_), TypeKind::Dyn(_)) => TypeBindingComparison::Incomparable, // Assuming the values are compatible, a dyn can be derived from any leaf/parent, // but not the other way around (at present at least) - (TypeKind::Dyn(_), _) => TypeBindingComparison::LeftDerivesFromRight, - (_, TypeKind::Dyn(_)) => TypeBindingComparison::RightDerivesFromLeft, + (TypeKind::Dyn(_), _) => TypeBindingComparison::LeftDerivesFromRightButIsNotSubtype, + (_, TypeKind::Dyn(_)) => TypeBindingComparison::RightDerivesFromLeftButIsNotSubtype, // All non-any-values are strict subtypes of AnyValue (TypeKind::Parent(ParentTypeKind::Value(_)), _) => { - TypeBindingComparison::RightDerivesFromLeft + TypeBindingComparison::RightIsSubtypeOfLeft } (_, TypeKind::Parent(ParentTypeKind::Value(_))) => { - TypeBindingComparison::LeftDerivesFromRight + TypeBindingComparison::LeftIsSubtypeOfRight } // Integer types ( TypeKind::Parent(ParentTypeKind::Integer(_)), TypeKind::Leaf(AnyValueLeafKind::Integer(_)), - ) => TypeBindingComparison::RightDerivesFromLeft, + ) => TypeBindingComparison::RightIsSubtypeOfLeft, ( TypeKind::Leaf(AnyValueLeafKind::Integer(_)), TypeKind::Parent(ParentTypeKind::Integer(_)), - ) => TypeBindingComparison::LeftDerivesFromRight, + ) => TypeBindingComparison::LeftIsSubtypeOfRight, (TypeKind::Parent(ParentTypeKind::Integer(_)), _) => { TypeBindingComparison::Incompatible } @@ -145,11 +147,11 @@ impl TypeKind { ( TypeKind::Parent(ParentTypeKind::Float(_)), TypeKind::Leaf(AnyValueLeafKind::Float(_)), - ) => TypeBindingComparison::RightDerivesFromLeft, + ) => TypeBindingComparison::RightIsSubtypeOfLeft, ( TypeKind::Leaf(AnyValueLeafKind::Float(_)), TypeKind::Parent(ParentTypeKind::Float(_)), - ) => TypeBindingComparison::LeftDerivesFromRight, + ) => TypeBindingComparison::LeftIsSubtypeOfRight, (TypeKind::Parent(ParentTypeKind::Float(_)), _) => TypeBindingComparison::Incompatible, (_, TypeKind::Parent(ParentTypeKind::Float(_))) => TypeBindingComparison::Incompatible, // Non-equal leaf types are incomparable @@ -163,8 +165,14 @@ impl TypeKind { // is compatible with some other form. pub(crate) enum TypeBindingComparison { Equal, - RightDerivesFromLeft, - LeftDerivesFromRight, + // e.g. U8Value is a subtype of IntegerValue + RightIsSubtypeOfLeft, + // e.g. dyn IterableValue derives from ArrayValue, but isn't a subtype of it. + RightDerivesFromLeftButIsNotSubtype, + // e.g. U8Value is a subtype of IntegerValue + LeftIsSubtypeOfRight, + // e.g. dyn IterableValue derives from ArrayValue, but isn't a subtype of it. + LeftDerivesFromRightButIsNotSubtype, Incomparable, // Indicates that the types are incompatible. // Such a comparison shouldn't arise between valid references. @@ -322,22 +330,34 @@ impl TypeProperty { ) -> FunctionResult> { let resolver = self.source_type.kind.feature_resolver(); // TODO[performance] - lazily initialize properties as Shared - let property_name = &self.property.to_string(); - if let Some(value) = resolver.resolve_type_property(property_name) { + let property_name = self.property.to_string(); + if let Some(value) = resolver.resolve_type_property(&property_name) { return ownership.map_from_shared(Spanned( - SharedValue::new_from_owned(value.into_any_value()), + SharedValue::new_from_owned( + value.into_any_value(), + Some(property_name), + self.span_range(), + ), self.span_range(), )); } - if let Some(method) = resolver.resolve_method(property_name) { + if let Some(method) = resolver.resolve_method(&property_name) { return ownership.map_from_shared(Spanned( - SharedValue::new_from_owned(method.into_any_value()), + SharedValue::new_from_owned( + method.into_any_value(), + Some(property_name.clone()), + self.span_range(), + ), self.span_range(), )); } - if let Some(function) = resolver.resolve_type_function(property_name) { + if let Some(function) = resolver.resolve_type_function(&property_name) { return ownership.map_from_shared(Spanned( - SharedValue::new_from_owned(function.into_any_value()), + SharedValue::new_from_owned( + function.into_any_value(), + Some(property_name.clone()), + self.span_range(), + ), self.span_range(), )); } diff --git a/src/expressions/values/any_value.rs b/src/expressions/values/any_value.rs index d3799c35..ceea4b08 100644 --- a/src/expressions/values/any_value.rs +++ b/src/expressions/values/any_value.rs @@ -50,7 +50,7 @@ define_type_features! { fn as_mut(Spanned(this, span): Spanned) -> FunctionResult { Ok(match this { - ArgumentValue::Owned(owned) => Mutable::new_from_owned(owned), + ArgumentValue::Owned(owned) => Mutable::new_from_owned(owned, None, span), ArgumentValue::CopyOnWrite(copy_on_write) => ArgumentOwnership::Mutable .map_from_copy_on_write(Spanned(copy_on_write, span))? .expect_mutable(), diff --git a/src/internal_prelude.rs b/src/internal_prelude.rs index 7fb94530..93d60f59 100644 --- a/src/internal_prelude.rs +++ b/src/internal_prelude.rs @@ -2,7 +2,7 @@ pub(crate) use proc_macro2::{extra::*, *}; pub(crate) use quote::ToTokens; pub(crate) use std::{ borrow::{Borrow, Cow}, - cell::{Ref, RefCell, RefMut}, + cell::{RefCell, RefMut}, collections::{BTreeMap, HashMap, HashSet}, fmt::Debug, fmt::Write, diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 0afe92a1..e38d45a8 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -675,9 +675,9 @@ impl CopyOnWrite { } /// Converts to shared reference - pub(crate) fn into_shared(self) -> SharedValue { + pub(crate) fn into_shared(self, span: SpanRange) -> SharedValue { match self.inner { - CopyOnWriteInner::Owned(owned) => SharedValue::new_from_owned(owned), + CopyOnWriteInner::Owned(owned) => SharedValue::new_from_owned(owned, None, span), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => shared, CopyOnWriteInner::SharedWithTransparentCloning(shared) => shared, } diff --git a/src/interpretation/refs.rs b/src/interpretation/refs.rs index 07b9ce7d..54c4e9bf 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -15,7 +15,7 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { self, f: impl for<'r> FnOnce(&'r T) -> &'r S, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> AnyRef<'a, S> { match self.inner { AnyRefInner::Direct(value) => AnyRef { @@ -35,7 +35,7 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { self, f: impl for<'r> FnOnce(&'r T) -> Option<&'r S>, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> Option> { Some(match self.inner { AnyRefInner::Direct(value) => AnyRef { @@ -57,7 +57,7 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { }) } - pub(crate) fn replace( + pub(crate) fn emplace_map( self, f: impl for<'e> FnOnce(&'e T, &mut AnyRefEmplacer<'a, 'e, T>) -> O, ) -> O { @@ -81,18 +81,8 @@ pub(crate) struct AnyRefEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { } impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { - /// Returns the current span of the underlying reference (if encapsulated), - /// or a placeholder span (if direct). - pub(crate) fn current_span(&self) -> SpanRange { - match &self - .inner - .as_ref() - .expect("Emplacer already consumed") - .inner - { - AnyRefInner::Direct(_) => SpanRange::new_single(Span::call_site()), - AnyRefInner::Encapsulated(shared) => shared.current_span(), - } + pub(crate) fn revert(&mut self) -> AnyRef<'a, T> { + self.inner.take().expect("Emplacer already consumed") } /// SAFETY: The caller must ensure the PathExtension is correct. @@ -100,7 +90,7 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { &mut self, value: &'e V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> AnyRef<'a, V> { unsafe { // SAFETY: The lifetime 'e is equal to the &'e content argument in replace @@ -116,7 +106,7 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { &mut self, value: &V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> AnyRef<'a, V> { let any_ref = self .inner @@ -219,7 +209,7 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { self, f: impl for<'r> FnOnce(&'r mut T) -> &'r mut S, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> AnyMut<'a, S> { match self.inner { AnyMutInner::Direct(value) => AnyMut { @@ -239,7 +229,7 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { self, f: impl for<'r> FnOnce(&'r mut T) -> Option<&'r mut S>, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> Option> { Some(match self.inner { AnyMutInner::Direct(value) => AnyMut { @@ -261,11 +251,12 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { }) } - pub(crate) fn replace( + pub(crate) fn emplace_map( mut self, f: impl for<'e> FnOnce(&'e mut T, &mut AnyMutEmplacer<'a, 'e, T>) -> O, ) -> O { let copied_mut = self.deref_mut() as *mut T; + // TODO[references]: Change the emplacer to take a *mut T directly, to avoid the duplicate &mut T let mut emplacer = AnyMutEmplacer { inner: Some(self), encapsulation_lifetime: std::marker::PhantomData, @@ -286,18 +277,8 @@ pub(crate) struct AnyMutEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { } impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { - /// Returns the current span of the underlying reference (if encapsulated), - /// or a placeholder span (if direct). - pub(crate) fn current_span(&self) -> SpanRange { - match &self - .inner - .as_ref() - .expect("Emplacer already consumed") - .inner - { - AnyMutInner::Direct(_) => SpanRange::new_single(Span::call_site()), - AnyMutInner::Encapsulated(mutable) => mutable.current_span(), - } + pub(crate) fn revert(&mut self) -> AnyMut<'a, T> { + self.inner.take().expect("Emplacer already consumed") } /// SAFETY: The caller must ensure the PathExtension is correct. @@ -305,7 +286,7 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { &mut self, value: &'e mut V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> AnyMut<'a, V> { unsafe { // SAFETY: The lifetime 'e is equal to the &'e content argument in replace @@ -321,7 +302,7 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { &mut self, value: &mut V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> AnyMut<'a, V> { let any_mut = self .inner diff --git a/src/interpretation/variable.rs b/src/interpretation/variable.rs index b02e06bc..2c05e064 100644 --- a/src/interpretation/variable.rs +++ b/src/interpretation/variable.rs @@ -70,8 +70,8 @@ impl VariableDefinition { self.id, VariableContent::Referenceable(Referenceable::new( value_source.into_any_value(), - "".to_string(), - SpanRange::new_single(Span::call_site()), + Some(self.ident.to_string()), + self.ident.span_range(), )), ); } diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index f1c35220..07427a9e 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -12,7 +12,7 @@ impl MutableReference { /// a structure arbitrarily. pub(crate) fn emplace_map( mut self, - f: impl for<'e> FnOnce(&'e mut T, &mut MutableEmplacerV2<'e, T>) -> O, + f: impl for<'e> FnOnce(&'e mut T, &mut MutableEmplacer<'e, T>) -> O, ) -> O { // SAFETY: The validity + safety invariants are upheld by `ReferenceableCore` // ... assuming this id is marked as a mutable reference for the duration. @@ -20,7 +20,7 @@ impl MutableReference { // - Delegating to the created MutableReference if it is emplaced // - Surviving until Drop at the end of this method if it is not emplaced let copied_mut = unsafe { self.0.pointer.as_mut() }; - let mut emplacer = MutableEmplacerV2(self.0.into_emplacer()); + let mut emplacer = MutableEmplacer(self.0.into_emplacer()); f(copied_mut, &mut emplacer) } @@ -31,7 +31,7 @@ impl MutableReference { self, f: impl FnOnce(&mut T) -> &mut V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> MutableReference { self.emplace_map(move |input, emplacer| { emplacer.emplace(f(input), path_extension, new_span) @@ -45,7 +45,7 @@ impl MutableReference { self, f: impl FnOnce(&mut T) -> Result<&mut V, E>, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> Result, (E, MutableReference)> { self.emplace_map(|input, emplacer| match f(input) { Ok(output) => Ok(emplacer.emplace(output, path_extension, new_span)), @@ -56,12 +56,12 @@ impl MutableReference { impl MutableReference { /// Creates a new MutableReference from an owned value, wrapping it in a Referenceable. - pub(crate) fn new_from_owned(value: AnyValue) -> Self { - let referenceable = Referenceable::new( - value, - "".to_string(), - SpanRange::new_single(Span::call_site()), - ); + pub(crate) fn new_from_owned( + value: AnyValue, + root_name: Option, + span_range: SpanRange, + ) -> Self { + let referenceable = Referenceable::new(value, root_name, span_range); referenceable .new_inactive_mutable() .activate() @@ -70,11 +70,6 @@ impl MutableReference { } impl MutableReference { - /// Returns the current creation span of the tracked reference. - pub(crate) fn current_span(&self) -> SpanRange { - self.0.core.data().for_reference(self.0.id).creation_span - } - /// Disables this mutable reference (bridge for old `disable()` API). /// Equivalent to `deactivate()` in the new naming. pub(crate) fn disable(self) -> InactiveMutableReference { @@ -161,19 +156,13 @@ impl InactiveMutableReference { } } -pub(crate) struct MutableEmplacerV2<'e, T: ?Sized>(EmplacerCore<'e, T>); +pub(crate) struct MutableEmplacer<'e, T: ?Sized>(EmplacerCore<'e, T>); -impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { +impl<'e, T: ?Sized> MutableEmplacer<'e, T> { pub(crate) fn revert(&mut self) -> MutableReference { MutableReference(self.0.revert()) } - /// Returns the current creation span of the tracked reference. - /// Useful for preserving the span during internal type-narrowing operations. - pub(crate) fn current_span(&self) -> SpanRange { - self.0.current_span() - } - /// SAFETY: /// - The caller must ensure that the PathExtension is correct /// (an overly-specific PathExtension may cause safety issues) @@ -181,7 +170,7 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { &mut self, value: &'e mut V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> MutableReference { unsafe { // SAFETY: The lifetime 'e is equal to the &'e content argument in replace @@ -197,7 +186,7 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { &mut self, value: &mut V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> MutableReference { // SAFETY: The pointer is from a reference so non-null let pointer = unsafe { NonNull::new_unchecked(value as *mut V) }; @@ -207,6 +196,3 @@ impl<'e, T: ?Sized> MutableEmplacerV2<'e, T> { unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } } - -/// Legacy type alias for backward compatibility -pub(crate) type MutableEmplacer<'e, T> = MutableEmplacerV2<'e, T>; diff --git a/src/misc/dynamic_references/reference_core.rs b/src/misc/dynamic_references/reference_core.rs index bc3e5fef..7285f914 100644 --- a/src/misc/dynamic_references/reference_core.rs +++ b/src/misc/dynamic_references/reference_core.rs @@ -90,12 +90,6 @@ impl<'e, T: ?Sized> EmplacerCore<'e, T> { } } - /// Returns the current creation span of the tracked reference. - pub(crate) fn current_span(&self) -> SpanRange { - let inner = self.inner.as_ref().expect("Emplacer already consumed"); - inner.core.data().for_reference(inner.id).creation_span - } - /// SAFETY: /// - The caller must ensure that the pointer is derived from the original content /// - The caller must ensure that the PathExtension is correct @@ -103,7 +97,7 @@ impl<'e, T: ?Sized> EmplacerCore<'e, T> { &mut self, pointer: NonNull, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> ReferenceCore { let emplacer_core = self.take(); let id = emplacer_core.id; diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 6314a3c2..e24c44e4 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -6,7 +6,7 @@ pub(crate) struct Referenceable { } impl Referenceable { - pub(crate) fn new(root: AnyValue, root_name: String, root_span: SpanRange) -> Self { + pub(crate) fn new(root: AnyValue, root_name: Option, root_span: SpanRange) -> Self { Self { core: Rc::new(ReferenceableCore::new(root, root_name, root_span)), } @@ -42,7 +42,7 @@ pub(super) struct ReferenceableCore { } impl ReferenceableCore { - pub(super) fn new(root: T, root_name: String, root_span: SpanRange) -> Self { + pub(super) fn new(root: T, root_name: Option, root_span: SpanRange) -> Self { Self { root: UnsafeCell::new(root), data: RefCell::new(ReferenceableData { @@ -60,10 +60,6 @@ impl ReferenceableCore { } } - pub(super) fn data(&self) -> Ref<'_, ReferenceableData> { - self.data.borrow() - } - pub(super) fn data_mut(&self) -> RefMut<'_, ReferenceableData> { self.data.borrow_mut() } @@ -79,7 +75,7 @@ new_key_type! { pub(super) struct ReferenceableData { // Could be in Referencable Core, but having it here makes the error message API easier - root_name: String, + root_name: Option, // Could be in Referencable Core, but having it here makes the error message API easier root_span: SpanRange, arena: SlotMap, @@ -145,14 +141,14 @@ impl ReferenceableData { Some(reason) => reason, None => continue, }; - let mut error_message = - "Cannot create mutable reference because it clashes with another reference: " - .to_string(); - error_message.push_str(error_reason); - let _ = write!(error_message, "This reference-: "); - self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveMutable)); - let _ = write!(error_message, "Other reference: "); - self.display_path(&mut error_message, other_id, None); + let mut error_message = String::new(); + let _ = write!(error_message, "Cannot create a mutable reference because it clashes with an existing reference:"); + let _ = write!(error_message, "\nThis reference : "); + let _ = + self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveMutable)); + let _ = write!(error_message, "\nOther reference: "); + let _ = self.display_path(&mut error_message, other_id, None); + let _ = write!(error_message, "\nReason : {}\n", error_reason); return data.creation_span.ownership_err(error_message); } } @@ -182,14 +178,14 @@ impl ReferenceableData { Some(reason) => reason, None => continue, }; - let mut error_message = - "Cannot create shared reference because it clashes with a mutable reference: " - .to_string(); - error_message.push_str(error_reason); - let _ = write!(error_message, "This reference-: "); - self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveShared)); - let _ = write!(error_message, "Other reference: "); - self.display_path(&mut error_message, other_id, None); + let mut error_message = String::new(); + let _ = write!(error_message, "Cannot create a shared reference because it clashes with an existing mutable reference:"); + let _ = write!(error_message, "\nThis reference : "); + let _ = + self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveShared)); + let _ = write!(error_message, "\nOther reference: "); + let _ = self.display_path(&mut error_message, other_id, None); + let _ = write!(error_message, "\nReason : {}\n", error_reason); return data.creation_span.ownership_err(error_message); } } @@ -207,10 +203,12 @@ impl ReferenceableData { &mut self, id: LocalReferenceId, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) { let data = self.for_reference_mut(id); - data.creation_span = new_span; + if let Some(span) = new_span { + data.creation_span = span; + } let last_path_part = data.path.parts.last_mut().expect("path is non-empty"); match (last_path_part, path_extension) { (last_path_part, PathExtension::Child(specifier, child_bound_as)) => { @@ -266,7 +264,10 @@ impl ReferenceableData { ReferenceKind::ActiveShared => f.write_str("[*active*] &")?, ReferenceKind::ActiveMutable => f.write_str("[*active*] &mut ")?, } - f.write_str(&self.root_name)?; + match self.root_name.as_ref() { + Some(name) => f.write_str(name)?, + None => f.write_str("[root]")?, + } for part in data.path.parts.iter() { match part { PathPart::Value { bound_as } => { @@ -299,6 +300,8 @@ impl ReferenceableData { pub(super) struct TrackedReference { pub(super) path: ReferencePath, pub(super) reference_kind: ReferenceKind, + /// Storing this span isn't strictly necessary for now... + /// But it's added for a future world where the diagnostic API is stabilized and we can add a diagnostic onto the clashing reference. pub(super) creation_span: SpanRange, } @@ -363,23 +366,27 @@ impl PathComparison { Some(match self { PathComparison::Divergent => return None, PathComparison::Overlapping => { - "they overlap, so mutation may invalidate the other reference" + "mutation may invalidate the other overlapping reference" } PathComparison::RightIsDescendent => { "mutation may invalidate the other descendent reference" } - PathComparison::ReferencesEqual(TypeBindingComparison::RightDerivesFromLeft) => { - "mutation may invalidate the other reference with more specific type" - } - PathComparison::ReferencesEqual(TypeBindingComparison::Incomparable) => { + PathComparison::ReferencesEqual(TypeBindingComparison::RightIsSubtypeOfLeft) + | PathComparison::ReferencesEqual( + TypeBindingComparison::RightDerivesFromLeftButIsNotSubtype, + ) + | PathComparison::ReferencesEqual( + TypeBindingComparison::LeftDerivesFromRightButIsNotSubtype, + ) + | PathComparison::ReferencesEqual(TypeBindingComparison::Incomparable) => { "mutation may invalidate the other reference with an incompatible type" } - // Activated reference is descendent of existing reference + // Mutable reference is a descendent of the other reference PathComparison::ReferencesEqual(TypeBindingComparison::Equal) - | PathComparison::ReferencesEqual(TypeBindingComparison::LeftDerivesFromRight) + | PathComparison::ReferencesEqual(TypeBindingComparison::LeftIsSubtypeOfRight) | PathComparison::LeftIsDescendent => { if other_is_active { - "the mutable reference is observable from the other reference, which breaks aliasing rules" + "mutation may be observed from the other active reference, which breaks aliasing rules" } else { return None; } @@ -434,6 +441,7 @@ enum PathPart { #[derive(PartialEq, Eq, Clone)] pub(crate) enum ChildSpecifier { + #[allow(unused)] // TODO[references]: Use this correctly ArrayChild(usize), ObjectChild(String), } diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 349d6b74..4807fb8f 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -18,7 +18,7 @@ impl SharedReference { /// a structure arbitrarily. pub(crate) fn emplace_map( self, - f: impl for<'e> FnOnce(&'e T, &mut SharedEmplacerV2<'e, T>) -> O, + f: impl for<'e> FnOnce(&'e T, &mut SharedEmplacer<'e, T>) -> O, ) -> O { // SAFETY: The validity + safety invariants are upheld by `ReferenceableCore` // ... assuming this id is marked as a shared reference for the duration. @@ -26,7 +26,7 @@ impl SharedReference { // - Delegating to the created SharedReference if it is emplaced // - Surviving until Drop at the end of this method if it is not emplaced let copied_ref = unsafe { self.0.pointer.as_ref() }; - let mut emplacer = SharedEmplacerV2(self.0.into_emplacer()); + let mut emplacer = SharedEmplacer(self.0.into_emplacer()); f(copied_ref, &mut emplacer) } @@ -37,7 +37,7 @@ impl SharedReference { self, f: impl FnOnce(&T) -> &V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> SharedReference { self.emplace_map(move |input, emplacer| { emplacer.emplace(f(input), path_extension, new_span) @@ -51,7 +51,7 @@ impl SharedReference { self, f: impl FnOnce(&T) -> Result<&V, E>, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> Result, (E, SharedReference)> { self.emplace_map(|input, emplacer| match f(input) { Ok(output) => Ok(emplacer.emplace(output, path_extension, new_span)), @@ -63,12 +63,12 @@ impl SharedReference { impl SharedReference { /// Creates a new SharedReference from an owned value, wrapping it in a Referenceable. /// Uses a placeholder name and span for the Referenceable root. - pub(crate) fn new_from_owned(value: AnyValue) -> Self { - let referenceable = Referenceable::new( - value, - "".to_string(), - SpanRange::new_single(Span::call_site()), - ); + pub(crate) fn new_from_owned( + value: AnyValue, + root_name: Option, + span_range: SpanRange, + ) -> Self { + let referenceable = Referenceable::new(value, root_name, span_range); referenceable .new_inactive_shared() .activate() @@ -82,11 +82,6 @@ impl SharedReference { } impl SharedReference { - /// Returns the current creation span of the tracked reference. - pub(crate) fn current_span(&self) -> SpanRange { - self.0.core.data().for_reference(self.0.id).creation_span - } - /// Disables this shared reference (bridge for old `disable()` API). /// Equivalent to `deactivate()` in the new naming. pub(crate) fn disable(self) -> InactiveSharedReference { @@ -140,19 +135,13 @@ impl InactiveSharedReference { } } -pub(crate) struct SharedEmplacerV2<'e, T: ?Sized>(EmplacerCore<'e, T>); +pub(crate) struct SharedEmplacer<'e, T: ?Sized>(EmplacerCore<'e, T>); -impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { +impl<'e, T: ?Sized> SharedEmplacer<'e, T> { pub(crate) fn revert(&mut self) -> SharedReference { SharedReference(self.0.revert()) } - /// Returns the current creation span of the tracked reference. - /// Useful for preserving the span during internal type-narrowing operations. - pub(crate) fn current_span(&self) -> SpanRange { - self.0.current_span() - } - /// SAFETY: /// - The caller must ensure that the PathExtension is correct /// (an overly-specific PathExtension may cause safety issues) @@ -160,7 +149,7 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { &mut self, value: &'e V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> SharedReference { unsafe { // SAFETY: The lifetime 'e is equal to the &'e content argument in replace @@ -176,7 +165,7 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { &mut self, value: &V, path_extension: PathExtension, - new_span: SpanRange, + new_span: Option, ) -> SharedReference { // SAFETY: The pointer is from a reference so non-null let pointer = unsafe { NonNull::new_unchecked(value as *const V as *mut V) }; @@ -186,6 +175,3 @@ impl<'e, T: ?Sized> SharedEmplacerV2<'e, T> { unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } } } - -/// Legacy type alias for backward compatibility -pub(crate) type SharedEmplacer<'e, T> = SharedEmplacerV2<'e, T>; diff --git a/src/misc/mod.rs b/src/misc/mod.rs index 11b389c5..2ce22658 100644 --- a/src/misc/mod.rs +++ b/src/misc/mod.rs @@ -4,7 +4,6 @@ mod errors; mod field_inputs; mod iterators; mod keywords; -mod mut_rc_ref_cell; mod parse_traits; pub(crate) mod string_conversion; @@ -14,8 +13,6 @@ pub(crate) use errors::*; pub(crate) use field_inputs::*; pub(crate) use iterators::*; pub(crate) use keywords::*; -// Old RefCell-based abstractions - no longer glob-exported; replaced by dynamic_references. -// pub(crate) use mut_rc_ref_cell::*; pub(crate) use parse_traits::*; use crate::internal_prelude::*; diff --git a/src/misc/mut_rc_ref_cell.rs b/src/misc/mut_rc_ref_cell.rs deleted file mode 100644 index 2ff7912a..00000000 --- a/src/misc/mut_rc_ref_cell.rs +++ /dev/null @@ -1,469 +0,0 @@ -#![allow(unused, dead_code)] -// This module is no longer exported - kept for reference during migration. -// All types have been replaced by dynamic_references equivalents. - -use crate::internal_prelude::*; -use std::cell::{BorrowError, BorrowMutError}; -use std::rc::Rc; - -// Local alias to avoid conflict with dynamic_references::Referenceable -type Referenceable = Rc>; - -/// A mutable reference to a sub-value `U` inside a [`Rc>`]. -/// Only one [`MutableSubRcRefCell`] can exist at a time for a given [`Rc>`]. -pub(crate) struct MutableSubRcRefCell { - /// This is actually a reference to the contents of the RefCell - /// but we store it using `unsafe` as `'static`, and use unsafe blocks - /// to ensure it's dropped first. - /// - /// SAFETY: This *must* appear before `pointed_at` so that it's dropped first. - ref_mut: RefMut<'static, U>, - pointed_at: Rc>, -} - -impl MutableSubRcRefCell { - pub(crate) fn new(pointed_at: Rc>) -> Result>> { - let ref_mut = match pointed_at.try_borrow_mut() { - Ok(ref_mut) => { - // SAFETY: We must ensure that this lifetime lives as long as the - // reference to pointed_at (i.e. the RefCell). - // This is guaranteed by the fact that the only time we drop the RefCell - // is when we drop the MutRcRefCell, and we ensure that the RefMut is dropped first. - unsafe { - Some(less_buggy_transmute::< - std::cell::RefMut<'_, T>, - std::cell::RefMut<'static, T>, - >(ref_mut)) - } - } - Err(_) => None, - }; - match ref_mut { - Some(ref_mut) => Ok(Self { - ref_mut, - pointed_at, - }), - None => Err(pointed_at), - } - } - - pub(crate) fn new_from_owned(owned: T) -> Self - where - T: Sized, - { - let rc = Rc::new(RefCell::new(owned)); - Self::new(rc).unwrap_or_else(|_| unreachable!("New refcell must be mut borrowable")) - } -} - -impl MutableSubRcRefCell { - pub(crate) fn into_shared(self) -> SharedSubRcRefCell { - let ptr = self.ref_mut.deref() as *const U; - drop(self.ref_mut); - // SAFETY: - // - The pointer was previously a reference, so it is safe to deference it here - // (the pointer is pointing into the Rc> which hasn't moved) - // - All our invariants for SharedSubRcRefCell / MutableSubRcRefCell are maintained - unsafe { - // The unwrap cannot panic because we just held a mutable borrow, we're not in Sync land, so no-one else can have a borrow. - SharedSubRcRefCell::new(self.pointed_at) - .unwrap_or_else(|_| { - unreachable!("Must be able to create shared after holding mutable") - }) - .map(|_| &*ptr) - } - } - - /// Disables this mutable reference, releasing the borrow on the RefCell. - /// Returns a `DisabledMutableSubRcRefCell` which can be cloned and later re-enabled. - pub(crate) fn disable(self) -> DisabledMutableSubRcRefCell { - let sub_ptr = self.ref_mut.deref() as *const U as *mut U; - // Drop the RefMut to release the borrow - drop(self.ref_mut); - DisabledMutableSubRcRefCell { - pointed_at: self.pointed_at, - sub_ptr, - } - } - - pub(crate) fn map( - self, - f: impl FnOnce(&mut U) -> &mut V, - ) -> MutableSubRcRefCell { - MutableSubRcRefCell { - ref_mut: RefMut::map(self.ref_mut, f), - pointed_at: self.pointed_at, - } - } - - pub(crate) fn map_optional( - self, - f: impl FnOnce(&mut U) -> Option<&mut V>, - ) -> Option> { - Some(MutableSubRcRefCell { - ref_mut: RefMut::filter_map(self.ref_mut, f).ok()?, - pointed_at: self.pointed_at, - }) - } - - pub(crate) fn try_map( - self, - f: impl FnOnce(&mut U) -> Result<&mut V, E>, - ) -> Result, (E, MutableSubRcRefCell)> { - let mut error = None; - let outcome = RefMut::filter_map(self.ref_mut, |inner| match f(inner) { - Ok(value) => Some(value), - Err(e) => { - error = Some(e); - None - } - }); - match outcome { - Ok(ref_mut) => Ok(MutableSubRcRefCell { - ref_mut, - pointed_at: self.pointed_at, - }), - Err(original_ref_mut) => Err(( - error.unwrap(), - MutableSubRcRefCell { - ref_mut: original_ref_mut, - pointed_at: self.pointed_at, - }, - )), - } - } - - pub(crate) fn replace( - mut self, - f: impl for<'a> FnOnce(&'a mut U, &mut MutableSubEmplacer<'a, T, U>) -> O, - ) -> O { - let ref_mut = self.ref_mut.deref_mut() as *mut U; - let mut emplacer = MutableSubEmplacer { - inner: Some(self), - encapsulation_lifetime: std::marker::PhantomData, - }; - f( - // SAFETY: We are cloning a mutable reference here, but it is safe because: - // - What it's pointing at still lives, as RefMut still lives inside emplacer.inner - // - No other "mutable reference" is created from the RefMut except at encapsulation time - unsafe { &mut *ref_mut }, - &mut emplacer, - ) - } -} - -#[allow(unused)] -pub(crate) type MutableEmplacer<'e, U> = MutableSubEmplacer<'e, AnyValue, U>; - -pub(crate) struct MutableSubEmplacer<'e, T: 'static + ?Sized, U: 'static + ?Sized> { - inner: Option>, - encapsulation_lifetime: std::marker::PhantomData<&'e ()>, -} - -impl<'e, T: 'static + ?Sized, U: 'static + ?Sized> MutableSubEmplacer<'e, T, U> { - pub(crate) fn emplace( - &mut self, - value: &'e mut V, - ) -> MutableSubRcRefCell { - unsafe { - // SAFETY: The lifetime 'e is equal to the &'e content argument in replace - // So this guarantees that the returned reference is valid as long as the MutableSubRcRefCell exists - self.emplace_unchecked(value) - } - } - - // SAFETY: - // * The caller must ensure that the value's lifetime is derived from the original content - pub(crate) unsafe fn emplace_unchecked( - &mut self, - value: &mut V, - ) -> MutableSubRcRefCell { - self.inner - .take() - .expect("You can only emplace to create a new Mutable value once") - .map(|_| - // SAFETY: As defined in the rustdoc above - unsafe { less_buggy_transmute::<&mut V, &'static mut V>(value) }) - } -} - -impl DerefMut for MutableSubRcRefCell { - fn deref_mut(&mut self) -> &mut U { - &mut self.ref_mut - } -} - -impl Deref for MutableSubRcRefCell { - type Target = U; - fn deref(&self) -> &U { - &self.ref_mut - } -} - -/// A disabled mutable reference that can be safely cloned and dropped. -/// -/// This type holds just the `Rc` and a raw pointer to the sub-value, -/// without an active borrow on the RefCell. This means: -/// - Dropping is safe (no borrow count to decrement) -/// - Cloning is safe (just clones the Rc and copies the pointer) -/// - The value cannot be accessed until re-enabled -pub(crate) struct DisabledMutableSubRcRefCell { - pointed_at: Rc>, - sub_ptr: *mut U, -} - -impl Clone for DisabledMutableSubRcRefCell { - fn clone(&self) -> Self { - Self { - pointed_at: Rc::clone(&self.pointed_at), - sub_ptr: self.sub_ptr, - } - } -} - -impl DisabledMutableSubRcRefCell { - /// Re-enables this disabled mutable reference by re-acquiring the borrow. - /// - /// Returns an error if the RefCell is currently borrowed. - pub(crate) fn enable(self) -> Result, BorrowMutError> { - let ref_mut = self.pointed_at.try_borrow_mut()?; - // SAFETY: - // - sub_ptr was derived from a valid &mut U inside the RefCell - // - The Rc is still alive, so the RefCell contents haven't moved - // - We just acquired a mutable borrow, so we have exclusive access - let ref_mut = RefMut::map(ref_mut, |_| unsafe { &mut *self.sub_ptr }); - Ok(MutableSubRcRefCell { - ref_mut: unsafe { less_buggy_transmute::, RefMut<'static, U>>(ref_mut) }, - pointed_at: self.pointed_at, - }) - } - - pub(crate) fn into_shared(self) -> DisabledSharedSubRcRefCell { - DisabledSharedSubRcRefCell { - pointed_at: self.pointed_at, - sub_ptr: self.sub_ptr as *const U, - } - } -} - -/// A shared (immutable) reference to a sub-value `U` inside a [`Rc>`]. -/// Many [`SharedSubRcRefCell`] can exist at the same time for a given [`Rc>`], -/// but if any exist, then no [`MutableSubRcRefCell`] can exist. -pub(crate) struct SharedSubRcRefCell { - /// This is actually a reference to the contents of the RefCell - /// but we store it using `unsafe` as `'static`, and use unsafe blocks - /// to ensure it's dropped first. - /// - /// SAFETY: This *must* appear before `pointed_at` so that it's dropped first. - shared_ref: Ref<'static, U>, - pointed_at: Rc>, -} - -impl SharedSubRcRefCell { - pub(crate) fn new(pointed_at: Referenceable) -> Result> { - let shared_ref = match pointed_at.try_borrow() { - Ok(shared_ref) => { - // SAFETY: We must ensure that this lifetime lives as long as the - // reference to pointed_at (i.e. the RefCell). - // This is guaranteed by the fact that the only time we drop the RefCell - // is when we drop the SharedSubRcRefCell, and we ensure that the Ref is dropped first. - unsafe { - Some(less_buggy_transmute::, Ref<'static, T>>( - shared_ref, - )) - } - } - Err(_) => None, - }; - match shared_ref { - Some(shared_ref) => Ok(Self { - shared_ref, - pointed_at, - }), - None => Err(pointed_at), - } - } - - pub(crate) fn new_from_owned(owned: T) -> Self - where - T: Sized, - { - let rc = Rc::new(RefCell::new(owned)); - Self::new(rc).unwrap_or_else(|_| unreachable!("New refcell must be borrowable")) - } -} - -impl SharedSubRcRefCell { - pub(crate) fn clone(this: &SharedSubRcRefCell) -> Self { - Self { - shared_ref: Ref::clone(&this.shared_ref), - pointed_at: Rc::clone(&this.pointed_at), - } - } - - pub(crate) fn map( - self, - f: impl for<'a> FnOnce(&'a U) -> &'a V, - ) -> SharedSubRcRefCell { - SharedSubRcRefCell { - shared_ref: Ref::map(self.shared_ref, f), - pointed_at: self.pointed_at, - } - } - - pub(crate) fn map_optional( - self, - f: impl FnOnce(&U) -> Option<&V>, - ) -> Option> { - Some(SharedSubRcRefCell { - shared_ref: Ref::filter_map(self.shared_ref, f).ok()?, - pointed_at: self.pointed_at, - }) - } - - pub(crate) fn try_map( - self, - f: impl FnOnce(&U) -> Result<&V, E>, - ) -> Result, (E, SharedSubRcRefCell)> { - let mut error = None; - let outcome = Ref::filter_map(self.shared_ref, |inner| match f(inner) { - Ok(value) => Some(value), - Err(e) => { - error = Some(e); - None - } - }); - match outcome { - Ok(shared_ref) => Ok(SharedSubRcRefCell { - shared_ref, - pointed_at: self.pointed_at, - }), - Err(original_shared_ref) => Err(( - error.unwrap(), - SharedSubRcRefCell { - shared_ref: original_shared_ref, - pointed_at: self.pointed_at, - }, - )), - } - } - - pub(crate) fn replace( - self, - f: impl for<'e> FnOnce(&'e U, &mut SharedSubEmplacer<'e, T, U>) -> O, - ) -> O { - let copied_ref = Ref::clone(&self.shared_ref); - let mut emplacer = SharedSubEmplacer { - inner: Some(self), - encapsulation_lifetime: std::marker::PhantomData, - }; - f(&*copied_ref, &mut emplacer) - } - - /// Disables this shared reference, releasing the borrow on the RefCell. - /// Returns a `DisabledSharedSubRcRefCell` which can be cloned and later re-enabled. - pub(crate) fn disable(self) -> DisabledSharedSubRcRefCell { - let sub_ptr = self.shared_ref.deref() as *const U; - // Drop the Ref to release the borrow - drop(self.shared_ref); - DisabledSharedSubRcRefCell { - pointed_at: self.pointed_at, - sub_ptr, - } - } -} - -pub(crate) type SharedEmplacer<'e, U> = SharedSubEmplacer<'e, AnyValue, U>; - -pub(crate) struct SharedSubEmplacer<'e, T: ?Sized, U: 'static + ?Sized> { - inner: Option>, - encapsulation_lifetime: std::marker::PhantomData<&'e ()>, -} - -impl<'e, T: 'static + ?Sized, U: 'static + ?Sized> SharedSubEmplacer<'e, T, U> { - pub(crate) fn emplace( - &mut self, - value: &'e V, - ) -> SharedSubRcRefCell { - unsafe { - // SAFETY: The lifetime 'e is equal to the &'e content argument in replace - // So this guarantees that the returned reference is valid as long as the SharedSubRcRefCell exists - self.emplace_unchecked(value) - } - } - - // SAFETY: - // * The caller must ensure that the value's lifetime is derived from the original content - pub(crate) unsafe fn emplace_unchecked( - &mut self, - value: &V, - ) -> SharedSubRcRefCell { - self.inner - .take() - .expect("You can only emplace to create a new shared value once") - .map(|_| - // SAFETY: As defined in the rustdoc above - unsafe { less_buggy_transmute::<&V, &'static V>(value) }) - } -} - -impl Deref for SharedSubRcRefCell { - type Target = U; - - fn deref(&self) -> &U { - &self.shared_ref - } -} - -/// A disabled shared reference that can be safely cloned and dropped. -/// -/// This type holds just the `Rc` and a raw pointer to the sub-value, -/// without an active borrow on the RefCell. This means: -/// - Dropping is safe (no borrow count to decrement) -/// - Cloning is safe (just clones the Rc and copies the pointer) -/// - The value cannot be accessed until re-enabled -pub(crate) struct DisabledSharedSubRcRefCell { - pointed_at: Rc>, - sub_ptr: *const U, -} - -impl Clone for DisabledSharedSubRcRefCell { - fn clone(&self) -> Self { - Self { - pointed_at: Rc::clone(&self.pointed_at), - sub_ptr: self.sub_ptr, - } - } -} - -impl DisabledSharedSubRcRefCell { - /// Re-enables this disabled shared reference by re-acquiring the borrow. - /// - /// Returns an error if the RefCell is currently mutably borrowed. - pub(crate) fn enable(self) -> Result, BorrowError> { - let shared_ref = self.pointed_at.try_borrow()?; - // SAFETY: - // - sub_ptr was derived from a valid &U inside the RefCell - // - The Rc is still alive, so the RefCell contents haven't moved - // - We just acquired a shared borrow, so the data is valid - let shared_ref = Ref::map(shared_ref, |_| unsafe { &*self.sub_ptr }); - Ok(SharedSubRcRefCell { - shared_ref: unsafe { less_buggy_transmute::, Ref<'static, U>>(shared_ref) }, - pointed_at: self.pointed_at, - }) - } -} - -/// SAFETY: The user must ensure that the two types are transmutable, -/// and in particular are the same size -unsafe fn less_buggy_transmute(t: T) -> U { - // std::mem::transmute::, Ref<'static, T>> for T: ?Sized - // Is fine on latest Rust, but on MSRV only incorrectly flags: - // > error[E0512]: cannot transmute between types of different sizes, or dependently-sized types - // Likely on old versions of Rust, it assumes that Ref is therefore ?Sized (it's not). - // To workaround this, we use a recommendation from https://users.rust-lang.org/t/transmute-doesnt-work-on-generic-types/87272 - // using transmute_copy and manual forgetting - use std::mem::ManuallyDrop; - debug_assert!(std::mem::size_of::() == std::mem::size_of::()); - std::mem::transmute_copy::, U>(&ManuallyDrop::new(t)) -} diff --git a/tests/compilation_failures/expressions/swap_itself.stderr b/tests/compilation_failures/expressions/swap_itself.stderr index c8fab41e..97a8b675 100644 --- a/tests/compilation_failures/expressions/swap_itself.stderr +++ b/tests/compilation_failures/expressions/swap_itself.stderr @@ -1,12 +1,8 @@ -error: Cannot create mutable reference because it clashes with another reference: the mutable reference is observable from the other reference, which breaks aliasing rulesThis reference-: [*active*] &mut (of type any)Other reference: [*active*] &mut (of type any) - --> tests/compilation_failures/expressions/swap_itself.rs:4:13 +error: Cannot create a mutable reference because it clashes with an existing reference: + This reference : [*active*] &mut a (of type any) + Other reference: [*active*] &mut a (of type any) + Reason : mutation may be observed from the other active reference, which breaks aliasing rules + --> tests/compilation_failures/expressions/swap_itself.rs:5:13 | -4 | let _ = run!{ - | _____________^ -5 | | let a = "a"; -6 | | a.swap(a); -7 | | a -8 | | }; - | |_____^ - | - = note: this error originates in the macro `run` (in Nightly builds, run with -Z macro-backtrace for more info) +5 | let a = "a"; + | ^ From 8d5c52b34746434ad52bea52df209a9f84bd0260 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Feb 2026 00:35:54 +0000 Subject: [PATCH 34/49] refactor: Improve shared/mutable reference safety and span threading - Introduce MappedRef and MappedMut types with unsafe constructors to confine unsafety to construction rather than use sites - Update SharedReference/MutableReference map/try_map to accept closures returning MappedRef/MappedMut, making callers safe - Fix &mut aliasing UB in AnyMut::emplace_map by storing *mut T in emplacer state instead of duplicate &mut T - Have PropertyAccessInterface/IndexAccessInterface return MappedRef/MappedMut with proper ChildSpecifier (ObjectChild/ArrayChild) - Add output_span_range to PropertyAccessCallContext/IndexAccessCallContext - Thread SpanRange through DynResolveFrom, leaf_to_dyn, and from_argument_value to eliminate None span arguments - Remove all TODO[references] comments https://claude.ai/code/session_01XaCsWwXFYntkKqZNf8kizU --- src/expressions/concepts/content.rs | 2 +- src/expressions/concepts/form.rs | 8 +- src/expressions/concepts/forms/any_mut.rs | 23 +-- src/expressions/concepts/forms/any_ref.rs | 23 +-- src/expressions/concepts/forms/argument.rs | 2 +- src/expressions/concepts/forms/assignee.rs | 16 +- .../concepts/forms/copy_on_write.rs | 12 +- src/expressions/concepts/forms/mutable.rs | 12 +- src/expressions/concepts/forms/owned.rs | 4 +- src/expressions/concepts/forms/shared.rs | 12 +- src/expressions/concepts/forms/simple_mut.rs | 23 ++- src/expressions/concepts/forms/simple_ref.rs | 16 +- src/expressions/concepts/type_traits.rs | 28 ++- src/expressions/evaluation/value_frames.rs | 60 ++---- src/expressions/type_resolution/arguments.rs | 71 +++---- .../type_resolution/interface_macros.rs | 29 +-- src/expressions/type_resolution/type_data.rs | 28 ++- src/expressions/values/array.rs | 84 ++++---- src/expressions/values/object.rs | 80 +++++--- src/interpretation/bindings.rs | 21 +- src/interpretation/refs.rs | 190 +++++++++++------- .../dynamic_references/mutable_reference.rs | 58 +++--- src/misc/dynamic_references/referenceable.rs | 53 ++++- .../dynamic_references/shared_reference.rs | 58 +++--- .../expressions/swap_itself.stderr | 1 + 25 files changed, 520 insertions(+), 394 deletions(-) diff --git a/src/expressions/concepts/content.rs b/src/expressions/concepts/content.rs index c3502101..16ae7a21 100644 --- a/src/expressions/concepts/content.rs +++ b/src/expressions/concepts/content.rs @@ -213,7 +213,7 @@ impl< type ValueType = T; const OWNERSHIP: ArgumentOwnership = F::ARGUMENT_OWNERSHIP; fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { - let ownership_mapped = F::from_argument_value(value)?; + let ownership_mapped = F::from_argument_value(Spanned(value, span_range))?; let type_mapped = T::resolve(ownership_mapped, span_range, "This argument")?; Ok(X::from_content(type_mapped)) } diff --git a/src/expressions/concepts/form.rs b/src/expressions/concepts/form.rs index 1ebce65f..8a06301d 100644 --- a/src/expressions/concepts/form.rs +++ b/src/expressions/concepts/form.rs @@ -56,7 +56,7 @@ pub(crate) trait IsDynCompatibleForm: IsHierarchicalForm { type DynLeaf<'a, D: IsDynType>; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + leaf: Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn; @@ -65,9 +65,9 @@ pub(crate) trait IsDynCompatibleForm: IsHierarchicalForm { pub(crate) trait MapFromArgument: IsHierarchicalForm { const ARGUMENT_OWNERSHIP: ArgumentOwnership; - // TODO[references]: Have this take a span so that we can store it into the emplacer - fn from_argument_value(value: ArgumentValue) - -> FunctionResult>; + fn from_argument_value( + value: Spanned, + ) -> FunctionResult>; } pub(crate) trait MapIntoReturned: IsHierarchicalForm { diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index 7fcc4389..d6af2ad3 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -28,19 +28,16 @@ impl IsDynCompatibleForm for BeAnyMut { type DynLeaf<'a, D: IsDynType> = AnyMut<'a, D::DynContent>; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, { - leaf.emplace_map(|content, emplacer| { - match ::map_mut(content) { - Ok(mapped) => Ok(unsafe { - // TODO[references]: Pass a span here by propagating from DynResolveFrom - emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), None) - }), - Err(this) => Err(emplacer.revert()), - } + leaf.emplace_map(|content, emplacer| match ::map_mut(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) + }), + Err(_this) => Err(emplacer.revert()), }) } } @@ -61,10 +58,10 @@ impl MapFromArgument for BeAnyMut { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; fn from_argument_value( - value: ArgumentValue, + Spanned(value, span): Spanned, ) -> FunctionResult> { - Ok(value - .expect_mutable() - .emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable_any_mut(emplacer))) + Ok(value.expect_mutable().emplace_map(|inner, emplacer| { + inner.as_mut_value().into_mutable_any_mut(emplacer, span) + })) } } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index f7ed377e..2b82e77d 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -29,19 +29,16 @@ impl IsDynCompatibleForm for BeAnyRef { type DynLeaf<'a, D: IsDynType> = crate::internal_prelude::AnyRef<'a, D::DynContent>; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, { - leaf.emplace_map(|content, emplacer| { - match ::map_ref(content) { - Ok(mapped) => Ok(unsafe { - // TODO[references]: Pass a span here by propagating from DynResolveFrom - emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), None) - }), - Err(this) => Err(emplacer.revert()), - } + leaf.emplace_map(|content, emplacer| match ::map_ref(content) { + Ok(mapped) => Ok(unsafe { + emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) + }), + Err(_this) => Err(emplacer.revert()), }) } } @@ -56,10 +53,10 @@ impl MapFromArgument for BeAnyRef { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; fn from_argument_value( - value: ArgumentValue, + Spanned(value, span): Spanned, ) -> FunctionResult> { - Ok(value - .expect_shared() - .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared_any_ref(emplacer))) + Ok(value.expect_shared().emplace_map(|inner, emplacer| { + inner.as_ref_value().into_shared_any_ref(emplacer, span) + })) } } diff --git a/src/expressions/concepts/forms/argument.rs b/src/expressions/concepts/forms/argument.rs index f7cafdfc..bbe3fda5 100644 --- a/src/expressions/concepts/forms/argument.rs +++ b/src/expressions/concepts/forms/argument.rs @@ -37,7 +37,7 @@ impl MapFromArgument for BeArgument { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::AsIs; fn from_argument_value( - value: ArgumentValue, + Spanned(value, _span): Spanned, ) -> FunctionResult> { todo!("Argument") } diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index ea08247a..08ea7f52 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -31,24 +31,22 @@ impl IsDynCompatibleForm for BeAssignee { type DynLeaf<'a, D: IsDynType> = QqqAssignee; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, { - leaf.0.emplace_map(|content, emplacer| { - match ::map_mut(content) { + leaf.0 + .emplace_map(|content, emplacer| match ::map_mut(content) { Ok(mapped) => Ok(QqqAssignee(unsafe { - // TODO[references]: Pass a span here by propagating from DynResolveFrom emplacer.emplace_unchecked( mapped, PathExtension::Tightened(D::type_kind()), - None, + Some(span), ) })), - Err(this) => Err(QqqAssignee(emplacer.revert())), - } - }) + Err(_this) => Err(QqqAssignee(emplacer.revert())), + }) } } @@ -69,7 +67,7 @@ impl MapFromArgument for BeAssignee { ArgumentOwnership::Assignee { auto_create: false }; fn from_argument_value( - value: ArgumentValue, + Spanned(value, _span): Spanned, ) -> FunctionResult> { Ok(value.expect_assignee().into_content()) } diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index 0cf03e50..34d51f09 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -75,18 +75,20 @@ impl MapFromArgument for BeCopyOnWrite { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::CopyOnWrite; fn from_argument_value( - value: ArgumentValue, + Spanned(value, span): Spanned, ) -> FunctionResult> { match value.expect_copy_on_write().inner { CopyOnWriteInner::Owned(owned) => Ok(BeCopyOnWrite::new_owned(owned)), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - let content = shared - .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + let content = shared.emplace_map(|inner, emplacer| { + inner.as_ref_value().into_shared(emplacer, span) + }); Ok(BeCopyOnWrite::new_shared_in_place_of_owned(content)) } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - let content = shared - .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + let content = shared.emplace_map(|inner, emplacer| { + inner.as_ref_value().into_shared(emplacer, span) + }); Ok(BeCopyOnWrite::new_shared_in_place_of_shared(content)) } } diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index 8c3a1feb..89437c9c 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -31,14 +31,18 @@ impl IsDynCompatibleForm for BeMutable { type DynLeaf<'a, D: IsDynType> = QqqMutable; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, { leaf.emplace_map(|content, emplacer| match ::map_mut(content) { Ok(mapped) => Ok(unsafe { - emplacer.emplace_unchecked(mapped, PathExtension::Tightened(D::type_kind()), None) + emplacer.emplace_unchecked( + mapped, + PathExtension::Tightened(D::type_kind()), + Some(span), + ) }), Err(_) => Err(emplacer.revert()), }) @@ -61,11 +65,11 @@ impl MapFromArgument for BeMutable { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; fn from_argument_value( - value: ArgumentValue, + Spanned(value, span): Spanned, ) -> FunctionResult> { Ok(value .expect_mutable() - .emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer))) + .emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer, span))) } } diff --git a/src/expressions/concepts/forms/owned.rs b/src/expressions/concepts/forms/owned.rs index 4fe3e8a9..b572c53a 100644 --- a/src/expressions/concepts/forms/owned.rs +++ b/src/expressions/concepts/forms/owned.rs @@ -24,7 +24,7 @@ impl IsDynCompatibleForm for BeOwned { type DynLeaf<'a, D: IsDynType> = Box; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, _span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, @@ -49,7 +49,7 @@ impl MapFromArgument for BeOwned { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Owned; fn from_argument_value( - value: ArgumentValue, + Spanned(value, _span): Spanned, ) -> FunctionResult> { Ok(value.expect_owned()) } diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index 1f6cb40f..e3d6f474 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -31,14 +31,18 @@ impl IsDynCompatibleForm for BeShared { type DynLeaf<'a, D: IsDynType> = QqqShared; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, { leaf.emplace_map(|content, emplacer| match ::map_ref(content) { Ok(mapped) => Ok(unsafe { - emplacer.emplace_unchecked(mapped, PathExtension::Tightened(D::type_kind()), None) + emplacer.emplace_unchecked( + mapped, + PathExtension::Tightened(D::type_kind()), + Some(span), + ) }), Err(_) => Err(emplacer.revert()), }) @@ -55,11 +59,11 @@ impl MapFromArgument for BeShared { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; fn from_argument_value( - value: ArgumentValue, + Spanned(value, span): Spanned, ) -> FunctionResult> { Ok(value .expect_shared() - .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer))) + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer, span))) } } diff --git a/src/expressions/concepts/forms/simple_mut.rs b/src/expressions/concepts/forms/simple_mut.rs index 8b82e88b..6eeb78a2 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -32,7 +32,7 @@ impl IsDynCompatibleForm for BeMut { type DynLeaf<'a, D: IsDynType> = &'a mut D::DynContent; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, _span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, @@ -55,12 +55,14 @@ where fn into_mutable<'b, T: 'static>( self, emplacer: &'b mut MutableEmplacer<'a, T>, + span: SpanRange, ) -> Content<'static, Self::Type, BeMutable> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut MutableEmplacer<'e2, X>, + span: SpanRange, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeMutable>; @@ -80,25 +82,26 @@ where self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - // TODO[references]: Pass a span here by propagating from DynResolveFrom - None, + Some(self.span), ) } } }; - let __mapper = __InlineMapper { emplacer }; + let __mapper = __InlineMapper { emplacer, span }; ::map_with::(__mapper, self) } fn into_assignee<'b, T: 'static>( self, emplacer: &'b mut MutableEmplacer<'a, T>, + span: SpanRange, ) -> Content<'static, Self::Type, BeAssignee> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut MutableEmplacer<'e2, X>, + span: SpanRange, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAssignee>; @@ -118,25 +121,26 @@ where QqqAssignee(self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - // TODO[references]: Pass a span here by propagating from DynResolveFrom - None, + Some(self.span), )) } } }; - let __mapper = __InlineMapper { emplacer }; + let __mapper = __InlineMapper { emplacer, span }; ::map_with::(__mapper, self) } fn into_mutable_any_mut<'b, T: 'static>( self, emplacer: &'b mut MutableEmplacer<'a, T>, + span: SpanRange, ) -> Content<'static, Self::Type, BeAnyMut> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut MutableEmplacer<'e2, X>, + span: SpanRange, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAnyMut>; @@ -156,14 +160,13 @@ where self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - // TODO[references]: Pass a span here by propagating from DynResolveFrom - None, + Some(self.span), ) }; mutable_ref.into() } }; - let __mapper = __InlineMapper { emplacer }; + let __mapper = __InlineMapper { emplacer, span }; ::map_with::(__mapper, self) } } diff --git a/src/expressions/concepts/forms/simple_ref.rs b/src/expressions/concepts/forms/simple_ref.rs index 5c740cf6..4047f55b 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -32,7 +32,7 @@ impl IsDynCompatibleForm for BeRef { type DynLeaf<'a, D: IsDynType> = &'a D::DynContent; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( - leaf: Self::Leaf<'a, T>, + Spanned(leaf, _span): Spanned>, ) -> Result, Content<'a, T, Self>> where T::Leaf: CastDyn, @@ -55,12 +55,14 @@ where fn into_shared<'b, T: 'static>( self, emplacer: &'b mut SharedEmplacer<'a, T>, + span: SpanRange, ) -> Content<'static, Self::Type, BeShared> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut SharedEmplacer<'e2, X>, + span: SpanRange, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeShared>; @@ -80,25 +82,26 @@ where self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - // TODO[references]: Pass a span here by propagating from DynResolveFrom - None, + Some(self.span), ) } } }; - let __mapper = __InlineMapper { emplacer }; + let __mapper = __InlineMapper { emplacer, span }; ::map_with::(__mapper, self) } fn into_shared_any_ref<'b, T: 'static>( self, emplacer: &'b mut SharedEmplacer<'a, T>, + span: SpanRange, ) -> Content<'static, Self::Type, BeAnyRef> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut SharedEmplacer<'e2, X>, + span: SpanRange, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAnyRef>; @@ -118,14 +121,13 @@ where self.emplacer.emplace_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - // TODO[references]: Pass a span here by propagating from DynResolveFrom - None, + Some(self.span), ) }; shared_ref.into() } }; - let __mapper = __InlineMapper { emplacer }; + let __mapper = __InlineMapper { emplacer, span }; ::map_with::(__mapper, self) } } diff --git a/src/expressions/concepts/type_traits.rs b/src/expressions/concepts/type_traits.rs index 55b921f5..52c6f4b9 100644 --- a/src/expressions/concepts/type_traits.rs +++ b/src/expressions/concepts/type_traits.rs @@ -103,9 +103,9 @@ pub(crate) trait DowncastFrom: IsHierarchicalType { } pub(crate) trait DynResolveFrom: IsDynType { - // TODO[references]: Have this take a span so it can propagate to the emplacer fn downcast_from<'a, F: IsHierarchicalForm + IsDynCompatibleForm>( content: Content<'a, T, F>, + span: SpanRange, ) -> Result, Content<'a, T, F>>; fn resolve<'a, F: IsHierarchicalForm + IsDynCompatibleForm>( @@ -113,7 +113,7 @@ pub(crate) trait DynResolveFrom: IsDynType { span_range: SpanRange, resolution_target: &str, ) -> FunctionResult> { - let content = match Self::downcast_from(content) { + let content = match Self::downcast_from(content, span_range) { Ok(c) => c, Err(existing) => { let leaf_kind = T::content_to_leaf_kind::(&existing); @@ -711,11 +711,17 @@ macro_rules! define_leaf_type { pub(crate) use define_leaf_type; -pub(crate) struct DynMapper(std::marker::PhantomData); +pub(crate) struct DynMapper { + _phantom: std::marker::PhantomData, + pub(crate) span: SpanRange, +} impl DynMapper { - pub(crate) const fn new() -> Self { - Self(std::marker::PhantomData) + pub(crate) fn new(span: SpanRange) -> Self { + Self { + _phantom: std::marker::PhantomData, + span, + } } } @@ -816,7 +822,7 @@ macro_rules! define_dyn_type { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Owned; fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { - let form_mapped = BeOwned::from_argument_value(value)?; + let form_mapped = BeOwned::from_argument_value(Spanned(value, span_range))?; <$type_def as DynResolveFrom>::resolve(form_mapped, span_range, "This argument") } } @@ -825,7 +831,7 @@ macro_rules! define_dyn_type { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { - let form_mapped = BeAnyRef::from_argument_value(value)?; + let form_mapped = BeAnyRef::from_argument_value(Spanned(value, span_range))?; <$type_def as DynResolveFrom>::resolve(form_mapped, span_range, "This argument") } } @@ -834,15 +840,15 @@ macro_rules! define_dyn_type { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { - let form_mapped = BeAnyMut::from_argument_value(value)?; + let form_mapped = BeAnyMut::from_argument_value(Spanned(value, span_range))?; <$type_def as DynResolveFrom>::resolve(form_mapped, span_range, "This argument") } } impl DynResolveFrom for $type_def { - fn downcast_from<'a, F: IsHierarchicalForm + IsDynCompatibleForm>(content: Content<'a, T, F>) -> Result, Content<'a, T, F>> { - T::map_with::<'a, F, _>(DynMapper::<$type_def>::new(), content) + fn downcast_from<'a, F: IsHierarchicalForm + IsDynCompatibleForm>(content: Content<'a, T, F>, span: SpanRange) -> Result, Content<'a, T, F>> { + T::map_with::<'a, F, _>(DynMapper::<$type_def>::new(span), content) } } @@ -862,7 +868,7 @@ macro_rules! define_dyn_type { self, leaf: F::Leaf<'a, T>, ) -> Self::Output<'a, T> { - F::leaf_to_dyn(leaf) + F::leaf_to_dyn(Spanned(leaf, self.span)) } } }; diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index 5b20f42c..9ed95f01 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -1054,39 +1054,17 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { let ctx = PropertyAccessCallContext { property: &self.access, + output_span_range: result_span, }; let auto_create = context.requested_ownership().requests_auto_create(); - let property_name_for_shared = property_name.clone(); let mapped = value.expect_any_value_and_map( |shared| { - // SAFETY: Property access navigates to a named child of the source value. - unsafe { - shared - .try_map( - |value| (interface.shared_access)(ctx, value), - PathExtension::Child( - ChildSpecifier::ObjectChild(property_name_for_shared), - AnyType::type_kind(), - ), - Some(result_span), - ) - .map_err(|(e, _)| e) - } - }, - |mutable| { - // SAFETY: Property access navigates to a named child of the source value. - unsafe { - mutable.try_map( - |value| (interface.mutable_access)(ctx, value, auto_create), - PathExtension::Child( - ChildSpecifier::ObjectChild(property_name), - AnyType::type_kind(), - ), - Some(result_span), - ) - } + shared + .try_map(|value| (interface.shared_access)(ctx, value)) + .map_err(|(e, _)| e) }, + |mutable| mutable.try_map(|value| (interface.mutable_access)(ctx, value, auto_create)), |owned| (interface.owned_access)(ctx, owned), )?; @@ -1172,35 +1150,23 @@ impl EvaluationFrame for ValueIndexAccessBuilder { let index = value.expect_shared(); let index = index.as_ref_value().spanned(span); + let result_span = SpanRange::new_between(source_span, self.access.span_range()); let ctx = IndexAccessCallContext { access: &self.access, + output_span_range: result_span, }; let auto_create = context.requested_ownership().requests_auto_create(); - let result_span = SpanRange::new_between(source_span, self.access.span_range()); let result = source.expect_any_value_and_map( |shared| { - // SAFETY: Tightened(AnyType) is conservative for index access. - // A future improvement could derive ChildSpecifier from the index value. - unsafe { - shared - .try_map( - |value| (interface.shared_access)(ctx, value, index), - PathExtension::Tightened(AnyType::type_kind()), - Some(result_span), - ) - .map_err(|(e, _)| e) - } + shared + .try_map(|value| (interface.shared_access)(ctx, value, index)) + .map_err(|(e, _)| e) }, |mutable| { - // SAFETY: Tightened(AnyType) is conservative for index access. - unsafe { - mutable.try_map( - |value| (interface.mutable_access)(ctx, value, index, auto_create), - PathExtension::Tightened(AnyType::type_kind()), - Some(result_span), - ) - } + mutable.try_map(|value| { + (interface.mutable_access)(ctx, value, index, auto_create) + }) }, |owned| (interface.owned_access)(ctx, owned, index), )?; diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index 54d084ac..be59dc96 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -252,25 +252,25 @@ pub(crate) trait ResolvableShared { where Self: 'static, { - // SAFETY: Tightened(T::Type) is correct - unsafe { - value - .try_map( - |v| { - Self::resolve_from_ref( - v, - ResolutionContext { - span_range: &span, - resolution_target, - }, - ) + value + .try_map(|v| { + let resolved = Self::resolve_from_ref( + v, + ResolutionContext { + span_range: &span, + resolution_target, }, - // TODO[references]: Move unsafe to this constructor so the blocks can be smaller - PathExtension::Tightened(T::Type::type_kind()), - Some(span), - ) - .map_err(|(err, _)| err) - } + )?; + // SAFETY: Tightened(T::Type) correctly describes type narrowing + Ok(unsafe { + MappedRef::new( + resolved, + PathExtension::Tightened(T::Type::type_kind()), + span, + ) + }) + }) + .map_err(|(err, _)| err) } fn resolve_ref<'a>( @@ -325,24 +325,25 @@ pub(crate) trait ResolvableMutable { where Self: 'static, { - // SAFETY: Tightened(T::Type) is correct - unsafe { - value - .try_map( - |v| { - Self::resolve_from_mut( - v, - ResolutionContext { - span_range: &span, - resolution_target, - }, - ) + value + .try_map(|v| { + let resolved = Self::resolve_from_mut( + v, + ResolutionContext { + span_range: &span, + resolution_target, }, - PathExtension::Tightened(T::Type::type_kind()), - Some(span), - ) - .map_err(|(err, _)| err) - } + )?; + // SAFETY: Tightened(T::Type) correctly describes type narrowing + Ok(unsafe { + MappedMut::new( + resolved, + PathExtension::Tightened(T::Type::type_kind()), + span, + ) + }) + }) + .map_err(|(err, _)| err) } fn resolve_ref_mut<'a>( diff --git a/src/expressions/type_resolution/interface_macros.rs b/src/expressions/type_resolution/interface_macros.rs index 245b0bb0..f8b1c46f 100644 --- a/src/expressions/type_resolution/interface_macros.rs +++ b/src/expressions/type_resolution/interface_macros.rs @@ -313,10 +313,10 @@ where // ============================================================================ pub(crate) fn apply_property_shared<'a, S: ResolvableShared + ?Sized + 'a>( - f: for<'b> fn(PropertyAccessCallContext, &'b S) -> FunctionResult<&'b AnyValue>, + f: for<'b> fn(PropertyAccessCallContext, &'b S) -> FunctionResult>, ctx: PropertyAccessCallContext, source: &'a AnyValue, -) -> FunctionResult<&'a AnyValue> { +) -> FunctionResult> { let source = S::resolve_from_ref( source, ResolutionContext::new(&ctx.property.span_range(), "The property access source"), @@ -325,11 +325,15 @@ pub(crate) fn apply_property_shared<'a, S: ResolvableShared + ?Sized + } pub(crate) fn apply_property_mutable<'a, S: ResolvableMutable + ?Sized + 'a>( - f: for<'b> fn(PropertyAccessCallContext, &'b mut S, bool) -> FunctionResult<&'b mut AnyValue>, + f: for<'b> fn( + PropertyAccessCallContext, + &'b mut S, + bool, + ) -> FunctionResult>, ctx: PropertyAccessCallContext, source: &'a mut AnyValue, auto_create: bool, -) -> FunctionResult<&'a mut AnyValue> { +) -> FunctionResult> { let source = S::resolve_from_mut( source, ResolutionContext::new(&ctx.property.span_range(), "The property access source"), @@ -358,11 +362,11 @@ pub(crate) fn apply_index_shared<'a, S: ResolvableShared + ?Sized + 'a IndexAccessCallContext, &'b S, Spanned, - ) -> FunctionResult<&'b AnyValue>, + ) -> FunctionResult>, ctx: IndexAccessCallContext, source: &'a AnyValue, index: Spanned, -) -> FunctionResult<&'a AnyValue> { +) -> FunctionResult> { let source = S::resolve_from_ref( source, ResolutionContext::new(&ctx.access.span_range(), "The index access source"), @@ -370,18 +374,19 @@ pub(crate) fn apply_index_shared<'a, S: ResolvableShared + ?Sized + 'a f(ctx, source, index) } +#[allow(clippy::type_complexity)] pub(crate) fn apply_index_mutable<'a, S: ResolvableMutable + ?Sized + 'a>( f: for<'b> fn( IndexAccessCallContext, &'b mut S, Spanned, bool, - ) -> FunctionResult<&'b mut AnyValue>, + ) -> FunctionResult>, ctx: IndexAccessCallContext, source: &'a mut AnyValue, index: Spanned, auto_create: bool, -) -> FunctionResult<&'a mut AnyValue> { +) -> FunctionResult> { let source = S::resolve_from_mut( source, ResolutionContext::new(&ctx.access.span_range(), "The index access source"), @@ -601,9 +606,9 @@ macro_rules! define_type_features { #[allow(unused)] use super::*; - pub(crate) fn shared<'a>(if_empty!([$($property_shared_context)?][_ctx]): PropertyAccessCallContext, $($property_shared_args)*) -> FunctionResult<&'a AnyValue> $property_shared_body + pub(crate) fn shared<'a>(if_empty!([$($property_shared_context)?][_ctx]): PropertyAccessCallContext, $($property_shared_args)*) -> FunctionResult> $property_shared_body - pub(crate) fn mutable<'a>(if_empty!([$($property_mutable_context)?][_ctx]): PropertyAccessCallContext, $($property_mutable_args)*) -> FunctionResult<&'a mut AnyValue> $property_mutable_body + pub(crate) fn mutable<'a>(if_empty!([$($property_mutable_context)?][_ctx]): PropertyAccessCallContext, $($property_mutable_args)*) -> FunctionResult> $property_mutable_body pub(crate) fn owned(if_empty!([$($property_owned_context)?][_ctx]): PropertyAccessCallContext, $($property_owned_args)*) -> FunctionResult $property_owned_body } @@ -622,9 +627,9 @@ macro_rules! define_type_features { #[allow(unused)] use super::*; - pub(crate) fn shared<'a>(if_empty!([$($index_shared_context)?][_ctx]): IndexAccessCallContext, $($index_shared_args)*) -> FunctionResult<&'a AnyValue> $index_shared_body + pub(crate) fn shared<'a>(if_empty!([$($index_shared_context)?][_ctx]): IndexAccessCallContext, $($index_shared_args)*) -> FunctionResult> $index_shared_body - pub(crate) fn mutable<'a>(if_empty!([$($index_mutable_context)?][_ctx]): IndexAccessCallContext, $($index_mutable_args)*) -> FunctionResult<&'a mut AnyValue> $index_mutable_body + pub(crate) fn mutable<'a>(if_empty!([$($index_mutable_context)?][_ctx]): IndexAccessCallContext, $($index_mutable_args)*) -> FunctionResult> $index_mutable_body pub(crate) fn owned(if_empty!([$($index_owned_context)?][_ctx]): IndexAccessCallContext, $($index_owned_args)*) -> FunctionResult $index_owned_body } diff --git a/src/expressions/type_resolution/type_data.rs b/src/expressions/type_resolution/type_data.rs index c31cd275..c678c584 100644 --- a/src/expressions/type_resolution/type_data.rs +++ b/src/expressions/type_resolution/type_data.rs @@ -338,23 +338,28 @@ impl BinaryOperationInterface { #[derive(Clone, Copy)] pub(crate) struct PropertyAccessCallContext<'a> { pub property: &'a PropertyAccess, + pub output_span_range: SpanRange, } /// Interface for property access on a type (e.g., `obj.field`). /// /// Unlike unary/binary operations which return owned values, property access -/// returns references into the source value. This requires three separate -/// access methods for shared, mutable, and owned access patterns. +/// returns references into the source value. The shared and mutable access methods +/// return [`MappedRef`]/[`MappedMut`] which bundle the result with a [`PathExtension`] +/// describing the navigation. pub(crate) struct PropertyAccessInterface { - /// Access a property by shared reference. - pub shared_access: - for<'a> fn(PropertyAccessCallContext, &'a AnyValue) -> FunctionResult<&'a AnyValue>, + /// Access a property by shared reference, returning a [`MappedRef`] with path info. + pub shared_access: for<'a> fn( + PropertyAccessCallContext, + &'a AnyValue, + ) -> FunctionResult>, /// Access a property by mutable reference, optionally auto-creating if missing. + /// Returns a [`MappedMut`] with path info. pub mutable_access: for<'a> fn( PropertyAccessCallContext, &'a mut AnyValue, bool, - ) -> FunctionResult<&'a mut AnyValue>, + ) -> FunctionResult>, /// Extract a property from an owned value. pub owned_access: fn(PropertyAccessCallContext, AnyValue) -> FunctionResult, } @@ -367,28 +372,31 @@ pub(crate) struct PropertyAccessInterface { #[derive(Clone, Copy)] pub(crate) struct IndexAccessCallContext<'a> { pub access: &'a IndexAccess, + pub output_span_range: SpanRange, } /// Interface for index access on a type (e.g., `arr[0]` or `obj["key"]`). /// /// Similar to property access, but the index is an evaluated expression -/// rather than a static identifier. +/// rather than a static identifier. The shared and mutable access methods +/// return [`MappedRef`]/[`MappedMut`] which bundle the result with a [`PathExtension`]. pub(crate) struct IndexAccessInterface { /// The ownership requirement for the index value. pub index_ownership: ArgumentOwnership, - /// Access an element by shared reference. + /// Access an element by shared reference, returning a [`MappedRef`] with path info. pub shared_access: for<'a> fn( IndexAccessCallContext, &'a AnyValue, Spanned, - ) -> FunctionResult<&'a AnyValue>, + ) -> FunctionResult>, /// Access an element by mutable reference, optionally auto-creating if missing. + /// Returns a [`MappedMut`] with path info. pub mutable_access: for<'a> fn( IndexAccessCallContext, &'a mut AnyValue, Spanned, bool, - ) -> FunctionResult<&'a mut AnyValue>, + ) -> FunctionResult>, /// Extract an element from an owned value. pub owned_access: fn(IndexAccessCallContext, AnyValue, Spanned) -> FunctionResult, diff --git a/src/expressions/values/array.rs b/src/expressions/values/array.rs index daf4b5ea..d40ade4c 100644 --- a/src/expressions/values/array.rs +++ b/src/expressions/values/array.rs @@ -59,42 +59,6 @@ impl ArrayValue { }) } - pub(super) fn index_mut( - &mut self, - Spanned(index, span_range): Spanned, - ) -> FunctionResult<&mut AnyValue> { - Ok(match index { - AnyValueContent::Integer(integer) => { - let index = - self.resolve_valid_index_from_integer(Spanned(integer, span_range), false)?; - &mut self.items[index] - } - AnyValueContent::Range(..) => { - // TODO[slice-support] Temporary until we add slice types - we error here - return span_range.ownership_err("Currently, a range-indexed array must be owned. Use `.take()` or `.clone()` before indexing [..]"); - } - _ => return span_range.type_err("The index must be an integer or a range"), - }) - } - - pub(super) fn index_ref( - &self, - Spanned(index, span_range): Spanned, - ) -> FunctionResult<&AnyValue> { - Ok(match index { - AnyValueContent::Integer(integer) => { - let index = - self.resolve_valid_index_from_integer(Spanned(integer, span_range), false)?; - &self.items[index] - } - AnyValueContent::Range(..) => { - // TODO[slice-support] Temporary until we add slice types - we error here - return span_range.ownership_err("Currently, a range-indexed array must be owned. Use `.take()` or `.clone()` before indexing [..]"); - } - _ => return span_range.type_err("The index must be an integer or a range"), - }) - } - pub(super) fn resolve_valid_index( &self, Spanned(index, span_range): Spanned, @@ -233,11 +197,51 @@ define_type_features! { } } index_access(ArrayValue) { - fn shared(source: &'a ArrayValue, index: Spanned) { - source.index_ref(index) + [ctx] fn shared(source: &'a ArrayValue, Spanned(index, span_range): Spanned) { + match index { + AnyValueContent::Integer(integer) => { + let idx = source.resolve_valid_index_from_integer(Spanned(integer, span_range), false)?; + // SAFETY: ArrayChild correctly describes navigating to an array element + Ok(unsafe { + MappedRef::new( + &source.items[idx], + PathExtension::Child( + ChildSpecifier::ArrayChild(idx), + AnyType::type_kind(), + ), + ctx.output_span_range, + ) + }) + } + AnyValueContent::Range(..) => { + // TODO[slice-support] Temporary until we add slice types - we error here + span_range.ownership_err("Currently, a range-indexed array must be owned. Use `.take()` or `.clone()` before indexing [..]") + } + _ => span_range.type_err("The index must be an integer or a range"), + } } - fn mutable(source: &'a mut ArrayValue, index: Spanned, _auto_create: bool) { - source.index_mut(index) + [ctx] fn mutable(source: &'a mut ArrayValue, Spanned(index, span_range): Spanned, _auto_create: bool) { + match index { + AnyValueContent::Integer(integer) => { + let idx = source.resolve_valid_index_from_integer(Spanned(integer, span_range), false)?; + // SAFETY: ArrayChild correctly describes navigating to an array element + Ok(unsafe { + MappedMut::new( + &mut source.items[idx], + PathExtension::Child( + ChildSpecifier::ArrayChild(idx), + AnyType::type_kind(), + ), + ctx.output_span_range, + ) + }) + } + AnyValueContent::Range(..) => { + // TODO[slice-support] Temporary until we add slice types - we error here + span_range.ownership_err("Currently, a range-indexed array must be owned. Use `.take()` or `.clone()` before indexing [..]") + } + _ => span_range.type_err("The index must be an integer or a range"), + } } fn owned(source: ArrayValue, index: Spanned) { source.into_indexed(index) diff --git a/src/expressions/values/object.rs b/src/expressions/values/object.rs index aa8dd59b..e927826f 100644 --- a/src/expressions/values/object.rs +++ b/src/expressions/values/object.rs @@ -72,23 +72,6 @@ impl ObjectValue { } } - pub(super) fn index_mut( - &mut self, - index: Spanned, - auto_create: bool, - ) -> FunctionResult<&mut AnyValue> { - let index: Spanned<&str> = index.downcast_resolve("An object key")?; - self.mut_entry(index.map(|s| s.to_string()), auto_create) - } - - pub(super) fn index_ref(&self, index: Spanned) -> FunctionResult<&AnyValue> { - let key: Spanned<&str> = index.downcast_resolve("An object key")?; - match self.entries.get(*key) { - Some(entry) => Ok(&entry.value), - None => Ok(static_none_ref()), - } - } - pub(super) fn property_mut( &mut self, access: &PropertyAccess, @@ -286,21 +269,72 @@ define_type_features! { } property_access(ObjectValue) { [ctx] fn shared(source: &'a ObjectValue) { - source.property_ref(ctx.property) + let value = source.property_ref(ctx.property)?; + // SAFETY: ObjectChild correctly describes navigating to a named property + Ok(unsafe { + MappedRef::new( + value, + PathExtension::Child( + ChildSpecifier::ObjectChild(ctx.property.property.to_string()), + AnyType::type_kind(), + ), + ctx.output_span_range, + ) + }) } [ctx] fn mutable(source: &'a mut ObjectValue, auto_create: bool) { - source.property_mut(ctx.property, auto_create) + let value = source.property_mut(ctx.property, auto_create)?; + // SAFETY: ObjectChild correctly describes navigating to a named property + Ok(unsafe { + MappedMut::new( + value, + PathExtension::Child( + ChildSpecifier::ObjectChild(ctx.property.property.to_string()), + AnyType::type_kind(), + ), + ctx.output_span_range, + ) + }) } [ctx] fn owned(source: ObjectValue) { source.into_property(ctx.property) } } index_access(ObjectValue) { - fn shared(source: &'a ObjectValue, index: Spanned) { - source.index_ref(index) + [ctx] fn shared(source: &'a ObjectValue, index: Spanned) { + let key: Spanned<&str> = index.downcast_resolve("An object key")?; + let key_string = key.to_string(); + let value = match source.entries.get(*key) { + Some(entry) => &entry.value, + None => static_none_ref(), + }; + // SAFETY: ObjectChild correctly describes navigating to a named key + Ok(unsafe { + MappedRef::new( + value, + PathExtension::Child( + ChildSpecifier::ObjectChild(key_string), + AnyType::type_kind(), + ), + ctx.output_span_range, + ) + }) } - fn mutable(source: &'a mut ObjectValue, index: Spanned, auto_create: bool) { - source.index_mut(index, auto_create) + [ctx] fn mutable(source: &'a mut ObjectValue, index: Spanned, auto_create: bool) { + let key: Spanned<&str> = index.downcast_resolve("An object key")?; + let key_string = key.to_string(); + let value = source.mut_entry(key.map(|s| s.to_string()), auto_create)?; + // SAFETY: ObjectChild correctly describes navigating to a named key + Ok(unsafe { + MappedMut::new( + value, + PathExtension::Child( + ChildSpecifier::ObjectChild(key_string), + AnyType::type_kind(), + ), + ctx.output_span_range, + ) + }) } fn owned(source: ObjectValue, index: Spanned) { source.into_indexed(index) diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index e38d45a8..6678efb9 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -397,8 +397,11 @@ where X::Form: LeafAsMutForm, { fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - self.0 - .emplace_map(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer)) + self.0.emplace_map(|inner, emplacer| { + inner + .as_mut_value() + .into_assignee(emplacer, Span::call_site().span_range()) + }) } } @@ -616,14 +619,20 @@ where AnyLevelCopyOnWrite::::Owned(owned).into_copy_on_write() } CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - let content = shared - .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + let content = shared.emplace_map(|inner, emplacer| { + inner + .as_ref_value() + .into_shared(emplacer, Span::call_site().span_range()) + }); AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(content) .into_copy_on_write() } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - let content = shared - .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)); + let content = shared.emplace_map(|inner, emplacer| { + inner + .as_ref_value() + .into_shared(emplacer, Span::call_site().span_range()) + }); AnyLevelCopyOnWrite::::SharedWithTransparentCloning(content) .into_copy_on_write() } diff --git a/src/interpretation/refs.rs b/src/interpretation/refs.rs index 54c4e9bf..66817a0f 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -9,45 +9,53 @@ pub(crate) struct AnyRef<'a, T: ?Sized + 'static> { } impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { - /// SAFETY: The caller must ensure the PathExtension is correct. + /// Maps this `AnyRef` using a closure that returns a [`MappedRef`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedRef::new`] constructor. #[allow(unused)] - pub(crate) unsafe fn map( + pub(crate) fn map( self, - f: impl for<'r> FnOnce(&'r T) -> &'r S, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r T) -> MappedRef<'r, S>, ) -> AnyRef<'a, S> { match self.inner { - AnyRefInner::Direct(value) => AnyRef { - inner: AnyRefInner::Direct(f(value)), - }, + AnyRefInner::Direct(value) => { + let mapped = f(value); + AnyRef { + inner: AnyRefInner::Direct(mapped.value), + } + } AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(unsafe { - shared.map(|x| f(x), path_extension, new_span) - }), + inner: AnyRefInner::Encapsulated(shared.map(f)), }, } } - /// SAFETY: The caller must ensure the PathExtension is correct. + /// Maps this `AnyRef` using a closure that returns an optional [`MappedRef`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedRef::new`] constructor. #[allow(unused)] - pub(crate) unsafe fn map_optional( + pub(crate) fn map_optional( self, - f: impl for<'r> FnOnce(&'r T) -> Option<&'r S>, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r T) -> Option>, ) -> Option> { Some(match self.inner { - AnyRefInner::Direct(value) => AnyRef { - inner: AnyRefInner::Direct(f(value)?), - }, + AnyRefInner::Direct(value) => { + let mapped = f(value)?; + AnyRef { + inner: AnyRefInner::Direct(mapped.value), + } + } AnyRefInner::Encapsulated(shared) => AnyRef { inner: AnyRefInner::Encapsulated(shared.emplace_map(|input, emplacer| match f( input, ) { - Some(output) => { - Some(unsafe { emplacer.emplace(output, path_extension, new_span) }) - } + Some(mapped) => Some(unsafe { + emplacer.emplace_unchecked( + mapped.value, + mapped.path_extension, + Some(mapped.span), + ) + }), None => { let _ = emplacer.revert(); None @@ -118,13 +126,13 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { inner: AnyRefInner::Direct(unsafe { transmute::<&V, &'static V>(value) }), }, AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(unsafe { - shared.map( - |_| transmute::<&V, &'static V>(value), + inner: AnyRefInner::Encapsulated(shared.emplace_map(|_, emplacer| unsafe { + emplacer.emplace_unchecked( + transmute::<&V, &'static V>(value), path_extension, new_span, ) - }), + })), }, } } @@ -203,44 +211,52 @@ impl<'a, T: ?Sized> From<&'a mut T> for AnyMut<'a, T> { } impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { - /// SAFETY: The caller must ensure the PathExtension is correct. + /// Maps this `AnyMut` using a closure that returns a [`MappedMut`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedMut::new`] constructor. #[allow(unused)] - pub(crate) unsafe fn map( + pub(crate) fn map( self, - f: impl for<'r> FnOnce(&'r mut T) -> &'r mut S, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r mut T) -> MappedMut<'r, S>, ) -> AnyMut<'a, S> { match self.inner { - AnyMutInner::Direct(value) => AnyMut { - inner: AnyMutInner::Direct(f(value)), - }, + AnyMutInner::Direct(value) => { + let mapped = f(value); + AnyMut { + inner: AnyMutInner::Direct(mapped.value), + } + } AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(unsafe { - mutable.map(|x| f(x), path_extension, new_span) - }), + inner: AnyMutInner::Encapsulated(mutable.map(f)), }, } } - /// SAFETY: The caller must ensure the PathExtension is correct. + /// Maps this `AnyMut` using a closure that returns an optional [`MappedMut`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedMut::new`] constructor. #[allow(unused)] - pub(crate) unsafe fn map_optional( + pub(crate) fn map_optional( self, - f: impl for<'r> FnOnce(&'r mut T) -> Option<&'r mut S>, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r mut T) -> Option>, ) -> Option> { Some(match self.inner { - AnyMutInner::Direct(value) => AnyMut { - inner: AnyMutInner::Direct(f(value)?), - }, + AnyMutInner::Direct(value) => { + let mapped = f(value)?; + AnyMut { + inner: AnyMutInner::Direct(mapped.value), + } + } AnyMutInner::Encapsulated(mutable) => AnyMut { inner: AnyMutInner::Encapsulated(mutable.emplace_map( |input, emplacer| match f(input) { - Some(output) => { - Some(unsafe { emplacer.emplace(output, path_extension, new_span) }) - } + Some(mapped) => Some(unsafe { + emplacer.emplace_unchecked( + mapped.value, + mapped.path_extension, + Some(mapped.span), + ) + }), None => { let _ = emplacer.revert(); None @@ -252,33 +268,63 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { } pub(crate) fn emplace_map( - mut self, + self, f: impl for<'e> FnOnce(&'e mut T, &mut AnyMutEmplacer<'a, 'e, T>) -> O, ) -> O { - let copied_mut = self.deref_mut() as *mut T; - // TODO[references]: Change the emplacer to take a *mut T directly, to avoid the duplicate &mut T - let mut emplacer = AnyMutEmplacer { - inner: Some(self), - encapsulation_lifetime: std::marker::PhantomData, - }; - f( - // SAFETY: We are cloning a mutable reference here, but it is safe because: - // - What it's pointing at still lives, inside emplacer.inner - // - No other "mutable reference" is created except at encapsulation time - unsafe { &mut *copied_mut }, - &mut emplacer, - ) + match self.inner { + AnyMutInner::Direct(direct_mut) => { + // Convert to raw pointer to avoid &mut aliasing: the emplacer + // stores the raw pointer, so no second &mut T exists. + let raw_ptr = direct_mut as *mut T; + let mut emplacer = AnyMutEmplacer { + inner: Some(AnyMutEmplacerState::Direct(raw_ptr, PhantomData)), + encapsulation_lifetime: std::marker::PhantomData, + }; + // SAFETY: raw_ptr was derived from a valid &'a mut T, and the original + // reference was consumed by converting to *mut T. + f(unsafe { &mut *raw_ptr }, &mut emplacer) + } + AnyMutInner::Encapsulated(mut mutable) => { + // MutableReference internally stores NonNull (a raw pointer), + // not &mut T, so there is no aliasing issue. + let raw_ptr = (&mut *mutable) as *mut T; + let mut emplacer = AnyMutEmplacer { + inner: Some(AnyMutEmplacerState::Encapsulated(mutable)), + encapsulation_lifetime: std::marker::PhantomData, + }; + // SAFETY: raw_ptr was derived from the MutableReference which + // uses NonNull internally (not &mut), so no aliasing occurs. + f(unsafe { &mut *raw_ptr }, &mut emplacer) + } + } } } pub(crate) struct AnyMutEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { - inner: Option>, + inner: Option>, encapsulation_lifetime: std::marker::PhantomData<&'e ()>, } +/// Stores either a raw pointer (for Direct) or a MutableReference (for Encapsulated), +/// avoiding &mut aliasing that would occur if we stored the full AnyMut. +enum AnyMutEmplacerState<'a, T: 'static + ?Sized> { + Direct(*mut T, PhantomData<&'a mut T>), + Encapsulated(MutableReference), +} + impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { pub(crate) fn revert(&mut self) -> AnyMut<'a, T> { - self.inner.take().expect("Emplacer already consumed") + let state = self.inner.take().expect("Emplacer already consumed"); + match state { + AnyMutEmplacerState::Direct(ptr, _) => AnyMut { + // SAFETY: ptr was derived from a valid &'a mut T and no other + // &mut T currently exists (the one passed to the closure has ended). + inner: AnyMutInner::Direct(unsafe { &mut *ptr }), + }, + AnyMutEmplacerState::Encapsulated(mutable) => AnyMut { + inner: AnyMutInner::Encapsulated(mutable), + }, + } } /// SAFETY: The caller must ensure the PathExtension is correct. @@ -304,23 +350,23 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { path_extension: PathExtension, new_span: Option, ) -> AnyMut<'a, V> { - let any_mut = self + let state = self .inner .take() .expect("You can only emplace to create a new AnyMut value once"); - match any_mut.inner { - AnyMutInner::Direct(_) => AnyMut { + match state { + AnyMutEmplacerState::Direct(_, _) => AnyMut { // SAFETY: As defined in the rustdoc above inner: AnyMutInner::Direct(unsafe { transmute::<&mut V, &'static mut V>(value) }), }, - AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(unsafe { - mutable.map( - |_| transmute::<&mut V, &'static mut V>(value), + AnyMutEmplacerState::Encapsulated(mutable) => AnyMut { + inner: AnyMutInner::Encapsulated(mutable.emplace_map(|_, emplacer| unsafe { + emplacer.emplace_unchecked( + transmute::<&mut V, &'static mut V>(value), path_extension, new_span, ) - }), + })), }, } } diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index 07427a9e..84158a61 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -24,31 +24,41 @@ impl MutableReference { f(copied_mut, &mut emplacer) } - /// SAFETY: - /// - The caller must ensure that the PathExtension is correct - /// (an overly-specific PathExtension may cause safety issues) - pub(crate) unsafe fn map( + /// Maps this mutable reference using a closure that returns a [`MappedMut`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedMut::new`] constructor, + /// making this method itself safe. + pub(crate) fn map( self, - f: impl FnOnce(&mut T) -> &mut V, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r mut T) -> MappedMut<'r, V>, ) -> MutableReference { self.emplace_map(move |input, emplacer| { - emplacer.emplace(f(input), path_extension, new_span) + let mapped = f(input); + // SAFETY: MappedMut constructor already validated the PathExtension + unsafe { + emplacer.emplace_unchecked(mapped.value, mapped.path_extension, Some(mapped.span)) + } }) } - /// SAFETY: - /// - The caller must ensure that the PathExtension is correct - /// (an overly-specific PathExtension may cause safety issues) - pub(crate) unsafe fn try_map( + /// Fallible version of [`map`](Self::map) that returns the original reference on error. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedMut::new`] constructor. + pub(crate) fn try_map( self, - f: impl FnOnce(&mut T) -> Result<&mut V, E>, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r mut T) -> Result, E>, ) -> Result, (E, MutableReference)> { self.emplace_map(|input, emplacer| match f(input) { - Ok(output) => Ok(emplacer.emplace(output, path_extension, new_span)), + Ok(mapped) => { + // SAFETY: MappedMut constructor already validated the PathExtension + Ok(unsafe { + emplacer.emplace_unchecked( + mapped.value, + mapped.path_extension, + Some(mapped.span), + ) + }) + } Err(e) => Err((e, emplacer.revert())), }) } @@ -163,22 +173,6 @@ impl<'e, T: ?Sized> MutableEmplacer<'e, T> { MutableReference(self.0.revert()) } - /// SAFETY: - /// - The caller must ensure that the PathExtension is correct - /// (an overly-specific PathExtension may cause safety issues) - pub(crate) unsafe fn emplace( - &mut self, - value: &'e mut V, - path_extension: PathExtension, - new_span: Option, - ) -> MutableReference { - unsafe { - // SAFETY: The lifetime 'e is equal to the &'e content argument in replace - // So this guarantees that the returned reference is valid as long as the SharedSubRcRefCell exists - self.emplace_unchecked(value, path_extension, new_span) - } - } - /// SAFETY: /// - The caller must ensure that the value's lifetime is derived from the original content /// - The caller must ensure that the PathExtension is correct diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index e24c44e4..86f1256e 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -433,6 +433,58 @@ pub(crate) enum PathExtension { Tightened(TypeKind), } +/// A mapped shared reference bundled with its [`PathExtension`] and span. +/// +/// Constructing this is unsafe because the caller must ensure the +/// [`PathExtension`] correctly describes the relationship between the +/// source and mapped reference. +pub(crate) struct MappedRef<'a, V: ?Sized> { + pub(crate) value: &'a V, + pub(crate) path_extension: PathExtension, + pub(crate) span: SpanRange, +} + +impl<'a, V: ?Sized> MappedRef<'a, V> { + /// SAFETY: The caller must ensure that the PathExtension correctly describes + /// the navigation from the source reference to this mapped reference. + /// An overly-specific PathExtension may cause safety issues. + pub(crate) unsafe fn new(value: &'a V, path_extension: PathExtension, span: SpanRange) -> Self { + Self { + value, + path_extension, + span, + } + } +} + +/// A mapped mutable reference bundled with its [`PathExtension`] and span. +/// +/// Constructing this is unsafe because the caller must ensure the +/// [`PathExtension`] correctly describes the relationship between the +/// source and mapped reference. +pub(crate) struct MappedMut<'a, V: ?Sized> { + pub(crate) value: &'a mut V, + pub(crate) path_extension: PathExtension, + pub(crate) span: SpanRange, +} + +impl<'a, V: ?Sized> MappedMut<'a, V> { + /// SAFETY: The caller must ensure that the PathExtension correctly describes + /// the navigation from the source reference to this mapped reference. + /// An overly-specific PathExtension may cause safety issues. + pub(crate) unsafe fn new( + value: &'a mut V, + path_extension: PathExtension, + span: SpanRange, + ) -> Self { + Self { + value, + path_extension, + span, + } + } +} + #[derive(PartialEq, Eq, Clone)] enum PathPart { Value { bound_as: TypeKind }, @@ -441,7 +493,6 @@ enum PathPart { #[derive(PartialEq, Eq, Clone)] pub(crate) enum ChildSpecifier { - #[allow(unused)] // TODO[references]: Use this correctly ArrayChild(usize), ObjectChild(String), } diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 4807fb8f..30f813c6 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -30,31 +30,41 @@ impl SharedReference { f(copied_ref, &mut emplacer) } - /// SAFETY: - /// - The caller must ensure that the PathExtension is correct - /// (an overly-specific PathExtension may cause safety issues) - pub(crate) unsafe fn map( + /// Maps this shared reference using a closure that returns a [`MappedRef`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedRef::new`] constructor, + /// making this method itself safe. + pub(crate) fn map( self, - f: impl FnOnce(&T) -> &V, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r T) -> MappedRef<'r, V>, ) -> SharedReference { self.emplace_map(move |input, emplacer| { - emplacer.emplace(f(input), path_extension, new_span) + let mapped = f(input); + // SAFETY: MappedRef constructor already validated the PathExtension + unsafe { + emplacer.emplace_unchecked(mapped.value, mapped.path_extension, Some(mapped.span)) + } }) } - /// SAFETY: - /// - The caller must ensure that the PathExtension is correct - /// (an overly-specific PathExtension may cause safety issues) - pub(crate) unsafe fn try_map( + /// Fallible version of [`map`](Self::map) that returns the original reference on error. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedRef::new`] constructor. + pub(crate) fn try_map( self, - f: impl FnOnce(&T) -> Result<&V, E>, - path_extension: PathExtension, - new_span: Option, + f: impl for<'r> FnOnce(&'r T) -> Result, E>, ) -> Result, (E, SharedReference)> { self.emplace_map(|input, emplacer| match f(input) { - Ok(output) => Ok(emplacer.emplace(output, path_extension, new_span)), + Ok(mapped) => { + // SAFETY: MappedRef constructor already validated the PathExtension + Ok(unsafe { + emplacer.emplace_unchecked( + mapped.value, + mapped.path_extension, + Some(mapped.span), + ) + }) + } Err(e) => Err((e, emplacer.revert())), }) } @@ -142,22 +152,6 @@ impl<'e, T: ?Sized> SharedEmplacer<'e, T> { SharedReference(self.0.revert()) } - /// SAFETY: - /// - The caller must ensure that the PathExtension is correct - /// (an overly-specific PathExtension may cause safety issues) - pub(crate) unsafe fn emplace( - &mut self, - value: &'e V, - path_extension: PathExtension, - new_span: Option, - ) -> SharedReference { - unsafe { - // SAFETY: The lifetime 'e is equal to the &'e content argument in replace - // So this guarantees that the returned reference is valid as long as the SharedSubRcRefCell exists - self.emplace_unchecked(value, path_extension, new_span) - } - } - /// SAFETY: /// - The caller must ensure that the value's lifetime is derived from the original content /// - The caller must ensure that the PathExtension is correct diff --git a/tests/compilation_failures/expressions/swap_itself.stderr b/tests/compilation_failures/expressions/swap_itself.stderr index 97a8b675..b9335319 100644 --- a/tests/compilation_failures/expressions/swap_itself.stderr +++ b/tests/compilation_failures/expressions/swap_itself.stderr @@ -2,6 +2,7 @@ error: Cannot create a mutable reference because it clashes with an existing ref This reference : [*active*] &mut a (of type any) Other reference: [*active*] &mut a (of type any) Reason : mutation may be observed from the other active reference, which breaks aliasing rules + --> tests/compilation_failures/expressions/swap_itself.rs:5:13 | 5 | let a = "a"; From d4ab592a6bca909436068e2ba9fd88e6651dd8a3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Feb 2026 01:32:27 +0000 Subject: [PATCH 35/49] refactor: Restore emplace on emplacers and make MappedRef/MappedMut fields private - Make MappedRef/MappedMut fields private, add into_parts() for decomposition. This preserves the invariant that construction must go through the unsafe new() constructor. - Add emplace() method back to SharedEmplacer/MutableEmplacer with lifetime-checked &'e V parameter, keeping emplace_unchecked for cases where the compiler can't prove the lifetime (e.g. __InlineMapper). - Switch callers from emplace_unchecked to emplace where the lifetime is available: SharedReference::map/try_map, MutableReference::map/ try_map, AnyRef::map_optional, AnyMut::map_optional, and leaf_to_dyn implementations in shared.rs, mutable.rs, and assignee.rs. https://claude.ai/code/session_01XaCsWwXFYntkKqZNf8kizU --- src/expressions/concepts/forms/assignee.rs | 6 +-- src/expressions/concepts/forms/mutable.rs | 6 +-- src/expressions/concepts/forms/shared.rs | 6 +-- src/interpretation/refs.rs | 42 +++++++++---------- .../dynamic_references/mutable_reference.rs | 34 +++++++++------ src/misc/dynamic_references/referenceable.rs | 20 ++++++--- .../dynamic_references/shared_reference.rs | 34 +++++++++------ 7 files changed, 79 insertions(+), 69 deletions(-) diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index 08ea7f52..f85a84f6 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -39,11 +39,7 @@ impl IsDynCompatibleForm for BeAssignee { leaf.0 .emplace_map(|content, emplacer| match ::map_mut(content) { Ok(mapped) => Ok(QqqAssignee(unsafe { - emplacer.emplace_unchecked( - mapped, - PathExtension::Tightened(D::type_kind()), - Some(span), - ) + emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) })), Err(_this) => Err(QqqAssignee(emplacer.revert())), }) diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index 89437c9c..5c81337f 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -38,11 +38,7 @@ impl IsDynCompatibleForm for BeMutable { { leaf.emplace_map(|content, emplacer| match ::map_mut(content) { Ok(mapped) => Ok(unsafe { - emplacer.emplace_unchecked( - mapped, - PathExtension::Tightened(D::type_kind()), - Some(span), - ) + emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) }), Err(_) => Err(emplacer.revert()), }) diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index e3d6f474..bbc93072 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -38,11 +38,7 @@ impl IsDynCompatibleForm for BeShared { { leaf.emplace_map(|content, emplacer| match ::map_ref(content) { Ok(mapped) => Ok(unsafe { - emplacer.emplace_unchecked( - mapped, - PathExtension::Tightened(D::type_kind()), - Some(span), - ) + emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) }), Err(_) => Err(emplacer.revert()), }) diff --git a/src/interpretation/refs.rs b/src/interpretation/refs.rs index 66817a0f..2f7c0f98 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -19,9 +19,9 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { ) -> AnyRef<'a, S> { match self.inner { AnyRefInner::Direct(value) => { - let mapped = f(value); + let (value, _path_extension, _span) = f(value).into_parts(); AnyRef { - inner: AnyRefInner::Direct(mapped.value), + inner: AnyRefInner::Direct(value), } } AnyRefInner::Encapsulated(shared) => AnyRef { @@ -40,22 +40,21 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { ) -> Option> { Some(match self.inner { AnyRefInner::Direct(value) => { - let mapped = f(value)?; + let (value, _path_extension, _span) = f(value)?.into_parts(); AnyRef { - inner: AnyRefInner::Direct(mapped.value), + inner: AnyRefInner::Direct(value), } } AnyRefInner::Encapsulated(shared) => AnyRef { inner: AnyRefInner::Encapsulated(shared.emplace_map(|input, emplacer| match f( input, ) { - Some(mapped) => Some(unsafe { - emplacer.emplace_unchecked( - mapped.value, - mapped.path_extension, - Some(mapped.span), - ) - }), + Some(mapped) => { + let (value, path_extension, span) = mapped.into_parts(); + // SAFETY: PathExtension validated by MappedRef constructor. + // Lifetime checked by emplace (value: &'e S). + Some(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) + } None => { let _ = emplacer.revert(); None @@ -221,9 +220,9 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { ) -> AnyMut<'a, S> { match self.inner { AnyMutInner::Direct(value) => { - let mapped = f(value); + let (value, _path_extension, _span) = f(value).into_parts(); AnyMut { - inner: AnyMutInner::Direct(mapped.value), + inner: AnyMutInner::Direct(value), } } AnyMutInner::Encapsulated(mutable) => AnyMut { @@ -242,21 +241,20 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { ) -> Option> { Some(match self.inner { AnyMutInner::Direct(value) => { - let mapped = f(value)?; + let (value, _path_extension, _span) = f(value)?.into_parts(); AnyMut { - inner: AnyMutInner::Direct(mapped.value), + inner: AnyMutInner::Direct(value), } } AnyMutInner::Encapsulated(mutable) => AnyMut { inner: AnyMutInner::Encapsulated(mutable.emplace_map( |input, emplacer| match f(input) { - Some(mapped) => Some(unsafe { - emplacer.emplace_unchecked( - mapped.value, - mapped.path_extension, - Some(mapped.span), - ) - }), + Some(mapped) => { + let (value, path_extension, span) = mapped.into_parts(); + // SAFETY: PathExtension validated by MappedMut constructor. + // Lifetime checked by emplace (value: &'e mut S). + Some(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) + } None => { let _ = emplacer.revert(); None diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index 84158a61..8350cd26 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -33,11 +33,10 @@ impl MutableReference { f: impl for<'r> FnOnce(&'r mut T) -> MappedMut<'r, V>, ) -> MutableReference { self.emplace_map(move |input, emplacer| { - let mapped = f(input); - // SAFETY: MappedMut constructor already validated the PathExtension - unsafe { - emplacer.emplace_unchecked(mapped.value, mapped.path_extension, Some(mapped.span)) - } + let (value, path_extension, span) = f(input).into_parts(); + // SAFETY: MappedMut constructor already validated the PathExtension. + // The lifetime is checked by emplace (value: &'e mut V). + unsafe { emplacer.emplace(value, path_extension, Some(span)) } }) } @@ -50,14 +49,10 @@ impl MutableReference { ) -> Result, (E, MutableReference)> { self.emplace_map(|input, emplacer| match f(input) { Ok(mapped) => { - // SAFETY: MappedMut constructor already validated the PathExtension - Ok(unsafe { - emplacer.emplace_unchecked( - mapped.value, - mapped.path_extension, - Some(mapped.span), - ) - }) + let (value, path_extension, span) = mapped.into_parts(); + // SAFETY: MappedMut constructor already validated the PathExtension. + // The lifetime is checked by emplace (value: &'e mut V). + Ok(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) } Err(e) => Err((e, emplacer.revert())), }) @@ -173,6 +168,19 @@ impl<'e, T: ?Sized> MutableEmplacer<'e, T> { MutableReference(self.0.revert()) } + /// SAFETY: The caller must ensure that the PathExtension is correct. + /// + /// The lifetime `'e` is checked by the compiler, ensuring the value is derived + /// from the original content. + pub(crate) unsafe fn emplace( + &mut self, + value: &'e mut V, + path_extension: PathExtension, + new_span: Option, + ) -> MutableReference { + unsafe { self.emplace_unchecked(value, path_extension, new_span) } + } + /// SAFETY: /// - The caller must ensure that the value's lifetime is derived from the original content /// - The caller must ensure that the PathExtension is correct diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 86f1256e..b680a2a1 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -439,9 +439,9 @@ pub(crate) enum PathExtension { /// [`PathExtension`] correctly describes the relationship between the /// source and mapped reference. pub(crate) struct MappedRef<'a, V: ?Sized> { - pub(crate) value: &'a V, - pub(crate) path_extension: PathExtension, - pub(crate) span: SpanRange, + value: &'a V, + path_extension: PathExtension, + span: SpanRange, } impl<'a, V: ?Sized> MappedRef<'a, V> { @@ -455,6 +455,10 @@ impl<'a, V: ?Sized> MappedRef<'a, V> { span, } } + + pub(crate) fn into_parts(self) -> (&'a V, PathExtension, SpanRange) { + (self.value, self.path_extension, self.span) + } } /// A mapped mutable reference bundled with its [`PathExtension`] and span. @@ -463,9 +467,9 @@ impl<'a, V: ?Sized> MappedRef<'a, V> { /// [`PathExtension`] correctly describes the relationship between the /// source and mapped reference. pub(crate) struct MappedMut<'a, V: ?Sized> { - pub(crate) value: &'a mut V, - pub(crate) path_extension: PathExtension, - pub(crate) span: SpanRange, + value: &'a mut V, + path_extension: PathExtension, + span: SpanRange, } impl<'a, V: ?Sized> MappedMut<'a, V> { @@ -483,6 +487,10 @@ impl<'a, V: ?Sized> MappedMut<'a, V> { span, } } + + pub(crate) fn into_parts(self) -> (&'a mut V, PathExtension, SpanRange) { + (self.value, self.path_extension, self.span) + } } #[derive(PartialEq, Eq, Clone)] diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 30f813c6..918ffe33 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -39,11 +39,10 @@ impl SharedReference { f: impl for<'r> FnOnce(&'r T) -> MappedRef<'r, V>, ) -> SharedReference { self.emplace_map(move |input, emplacer| { - let mapped = f(input); - // SAFETY: MappedRef constructor already validated the PathExtension - unsafe { - emplacer.emplace_unchecked(mapped.value, mapped.path_extension, Some(mapped.span)) - } + let (value, path_extension, span) = f(input).into_parts(); + // SAFETY: MappedRef constructor already validated the PathExtension. + // The lifetime is checked by emplace (value: &'e V). + unsafe { emplacer.emplace(value, path_extension, Some(span)) } }) } @@ -56,14 +55,10 @@ impl SharedReference { ) -> Result, (E, SharedReference)> { self.emplace_map(|input, emplacer| match f(input) { Ok(mapped) => { - // SAFETY: MappedRef constructor already validated the PathExtension - Ok(unsafe { - emplacer.emplace_unchecked( - mapped.value, - mapped.path_extension, - Some(mapped.span), - ) - }) + let (value, path_extension, span) = mapped.into_parts(); + // SAFETY: MappedRef constructor already validated the PathExtension. + // The lifetime is checked by emplace (value: &'e V). + Ok(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) } Err(e) => Err((e, emplacer.revert())), }) @@ -152,6 +147,19 @@ impl<'e, T: ?Sized> SharedEmplacer<'e, T> { SharedReference(self.0.revert()) } + /// SAFETY: The caller must ensure that the PathExtension is correct. + /// + /// The lifetime `'e` is checked by the compiler, ensuring the value is derived + /// from the original content. + pub(crate) unsafe fn emplace( + &mut self, + value: &'e V, + path_extension: PathExtension, + new_span: Option, + ) -> SharedReference { + unsafe { self.emplace_unchecked(value, path_extension, new_span) } + } + /// SAFETY: /// - The caller must ensure that the value's lifetime is derived from the original content /// - The caller must ensure that the PathExtension is correct From 7f03b272c9da45724e9f6508c4105493b9867fca Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Feb 2026 01:59:22 +0000 Subject: [PATCH 36/49] refactor: Make emplace methods safe by taking MappedRef/MappedMut directly - Move MappedRef to shared_reference.rs and MappedMut to mutable_reference.rs - Add new_unchecked constructors for lifetime transmute cases (__InlineMapper) - Change all emplacer emplace methods to take MappedRef/MappedMut, making them safe - Drop emplace_unchecked from all emplacers (SharedEmplacer, MutableEmplacer, AnyRefEmplacer, AnyMutEmplacer) - All unsafe is now confined to MappedRef/MappedMut constructors https://claude.ai/code/session_01XaCsWwXFYntkKqZNf8kizU --- src/expressions/concepts/forms/any_mut.rs | 10 +- src/expressions/concepts/forms/any_ref.rs | 10 +- src/expressions/concepts/forms/assignee.rs | 10 +- src/expressions/concepts/forms/mutable.rs | 10 +- src/expressions/concepts/forms/shared.rs | 10 +- src/expressions/concepts/forms/simple_mut.rs | 36 ++++--- src/expressions/concepts/forms/simple_ref.rs | 22 ++-- src/interpretation/refs.rs | 102 ++++++------------ .../dynamic_references/mutable_reference.rs | 102 +++++++++++------- src/misc/dynamic_references/referenceable.rs | 60 ----------- .../dynamic_references/shared_reference.rs | 98 ++++++++++------- 11 files changed, 227 insertions(+), 243 deletions(-) diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index d6af2ad3..5a867a9c 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -34,9 +34,13 @@ impl IsDynCompatibleForm for BeAnyMut { T::Leaf: CastDyn, { leaf.emplace_map(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(unsafe { - emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) - }), + Ok(mapped) => { + // SAFETY: PathExtension::Tightened correctly describes type narrowing + let mapped_mut = unsafe { + MappedMut::new(mapped, PathExtension::Tightened(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_mut)) + } Err(_this) => Err(emplacer.revert()), }) } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index 2b82e77d..e67f3704 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -35,9 +35,13 @@ impl IsDynCompatibleForm for BeAnyRef { T::Leaf: CastDyn, { leaf.emplace_map(|content, emplacer| match ::map_ref(content) { - Ok(mapped) => Ok(unsafe { - emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) - }), + Ok(mapped) => { + // SAFETY: PathExtension::Tightened correctly describes type narrowing + let mapped_ref = unsafe { + MappedRef::new(mapped, PathExtension::Tightened(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_ref)) + } Err(_this) => Err(emplacer.revert()), }) } diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index f85a84f6..1c02fc80 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -38,9 +38,13 @@ impl IsDynCompatibleForm for BeAssignee { { leaf.0 .emplace_map(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(QqqAssignee(unsafe { - emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) - })), + Ok(mapped) => { + // SAFETY: PathExtension::Tightened correctly describes type narrowing + let mapped_mut = unsafe { + MappedMut::new(mapped, PathExtension::Tightened(D::type_kind()), span) + }; + Ok(QqqAssignee(emplacer.emplace(mapped_mut))) + } Err(_this) => Err(QqqAssignee(emplacer.revert())), }) } diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index 5c81337f..ac40ba80 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -37,9 +37,13 @@ impl IsDynCompatibleForm for BeMutable { T::Leaf: CastDyn, { leaf.emplace_map(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(unsafe { - emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) - }), + Ok(mapped) => { + // SAFETY: PathExtension::Tightened correctly describes type narrowing + let mapped_mut = unsafe { + MappedMut::new(mapped, PathExtension::Tightened(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_mut)) + } Err(_) => Err(emplacer.revert()), }) } diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index bbc93072..baf3cc1e 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -37,9 +37,13 @@ impl IsDynCompatibleForm for BeShared { T::Leaf: CastDyn, { leaf.emplace_map(|content, emplacer| match ::map_ref(content) { - Ok(mapped) => Ok(unsafe { - emplacer.emplace(mapped, PathExtension::Tightened(D::type_kind()), Some(span)) - }), + Ok(mapped) => { + // SAFETY: PathExtension::Tightened correctly describes type narrowing + let mapped_ref = unsafe { + MappedRef::new(mapped, PathExtension::Tightened(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_ref)) + } Err(_) => Err(emplacer.revert()), }) } diff --git a/src/expressions/concepts/forms/simple_mut.rs b/src/expressions/concepts/forms/simple_mut.rs index 6eeb78a2..f87fe41d 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -77,14 +77,16 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { - self.emplacer.emplace_unchecked( + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension::Tightened correctly describes type narrowing + let mapped = unsafe { + MappedMut::new_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - Some(self.span), + self.span, ) - } + }; + self.emplacer.emplace(mapped) } }; let __mapper = __InlineMapper { emplacer, span }; @@ -116,14 +118,16 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { - QqqAssignee(self.emplacer.emplace_unchecked( + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension::Tightened correctly describes type narrowing + let mapped = unsafe { + MappedMut::new_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - Some(self.span), - )) - } + self.span, + ) + }; + QqqAssignee(self.emplacer.emplace(mapped)) } }; let __mapper = __InlineMapper { emplacer, span }; @@ -155,14 +159,16 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - let mutable_ref: MutableReference<_> = unsafe { - self.emplacer.emplace_unchecked( + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension::Tightened correctly describes type narrowing + let mapped = unsafe { + MappedMut::new_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - Some(self.span), + self.span, ) }; + let mutable_ref: MutableReference<_> = self.emplacer.emplace(mapped); mutable_ref.into() } }; diff --git a/src/expressions/concepts/forms/simple_ref.rs b/src/expressions/concepts/forms/simple_ref.rs index 4047f55b..00e7ff7e 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -77,14 +77,16 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { - self.emplacer.emplace_unchecked( + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension::Tightened correctly describes type narrowing + let mapped = unsafe { + MappedRef::new_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - Some(self.span), + self.span, ) - } + }; + self.emplacer.emplace(mapped) } }; let __mapper = __InlineMapper { emplacer, span }; @@ -116,14 +118,16 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - let shared_ref: SharedReference<_> = unsafe { - self.emplacer.emplace_unchecked( + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension::Tightened correctly describes type narrowing + let mapped = unsafe { + MappedRef::new_unchecked( leaf, PathExtension::Tightened(T::type_kind()), - Some(self.span), + self.span, ) }; + let shared_ref: SharedReference<_> = self.emplacer.emplace(mapped); shared_ref.into() } }; diff --git a/src/interpretation/refs.rs b/src/interpretation/refs.rs index 2f7c0f98..bf8faa57 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -1,5 +1,3 @@ -use std::mem::transmute; - use super::*; /// A flexible type which can either be a reference to a value of type `T`, @@ -49,12 +47,7 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { inner: AnyRefInner::Encapsulated(shared.emplace_map(|input, emplacer| match f( input, ) { - Some(mapped) => { - let (value, path_extension, span) = mapped.into_parts(); - // SAFETY: PathExtension validated by MappedRef constructor. - // Lifetime checked by emplace (value: &'e S). - Some(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) - } + Some(mapped) => Some(emplacer.emplace(mapped)), None => { let _ = emplacer.revert(); None @@ -92,45 +85,30 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { self.inner.take().expect("Emplacer already consumed") } - /// SAFETY: The caller must ensure the PathExtension is correct. - pub(crate) unsafe fn emplace( - &mut self, - value: &'e V, - path_extension: PathExtension, - new_span: Option, - ) -> AnyRef<'a, V> { - unsafe { - // SAFETY: The lifetime 'e is equal to the &'e content argument in replace - // So this guarantees that the returned reference is valid as long as the AnyRef exists - self.emplace_unchecked(value, path_extension, new_span) - } - } - - /// SAFETY: - /// - The caller must ensure that the value's lifetime is derived from the original content - /// - The caller must ensure the PathExtension is correct - pub(crate) unsafe fn emplace_unchecked( + /// Emplaces a mapped shared reference, consuming the emplacer's reference tracking. + /// + /// This is safe because all preconditions (correct PathExtension and valid reference + /// derivation) are validated by the [`MappedRef`] constructor. + pub(crate) fn emplace( &mut self, - value: &V, - path_extension: PathExtension, - new_span: Option, + mapped: MappedRef<'e, V>, ) -> AnyRef<'a, V> { let any_ref = self .inner .take() .expect("You can only emplace to create a new AnyRef value once"); + let (value, path_extension, span) = mapped.into_parts(); match any_ref.inner { AnyRefInner::Direct(_) => AnyRef { - // SAFETY: As defined in the rustdoc above - inner: AnyRefInner::Direct(unsafe { transmute::<&V, &'static V>(value) }), + // 'e: 'a is guaranteed by the struct constraint, so coercion is valid + inner: AnyRefInner::Direct(value), }, AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.emplace_map(|_, emplacer| unsafe { - emplacer.emplace_unchecked( - transmute::<&V, &'static V>(value), - path_extension, - new_span, - ) + inner: AnyRefInner::Encapsulated(shared.emplace_map(|_, emplacer| { + // SAFETY: The value's lifetime ('e) matches the SharedEmplacer's lifetime + // since both are derived from the same underlying data + let remapped = unsafe { MappedRef::new_unchecked(value, path_extension, span) }; + emplacer.emplace(remapped) })), }, } @@ -249,12 +227,7 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { AnyMutInner::Encapsulated(mutable) => AnyMut { inner: AnyMutInner::Encapsulated(mutable.emplace_map( |input, emplacer| match f(input) { - Some(mapped) => { - let (value, path_extension, span) = mapped.into_parts(); - // SAFETY: PathExtension validated by MappedMut constructor. - // Lifetime checked by emplace (value: &'e mut S). - Some(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) - } + Some(mapped) => Some(emplacer.emplace(mapped)), None => { let _ = emplacer.revert(); None @@ -325,45 +298,30 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { } } - /// SAFETY: The caller must ensure the PathExtension is correct. - pub(crate) unsafe fn emplace( - &mut self, - value: &'e mut V, - path_extension: PathExtension, - new_span: Option, - ) -> AnyMut<'a, V> { - unsafe { - // SAFETY: The lifetime 'e is equal to the &'e content argument in replace - // So this guarantees that the returned reference is valid as long as the AnyMut exists - self.emplace_unchecked(value, path_extension, new_span) - } - } - - /// SAFETY: - /// - The caller must ensure that the value's lifetime is derived from the original content - /// - The caller must ensure the PathExtension is correct - pub(crate) unsafe fn emplace_unchecked( + /// Emplaces a mapped mutable reference, consuming the emplacer's reference tracking. + /// + /// This is safe because all preconditions (correct PathExtension and valid reference + /// derivation) are validated by the [`MappedMut`] constructor. + pub(crate) fn emplace( &mut self, - value: &mut V, - path_extension: PathExtension, - new_span: Option, + mapped: MappedMut<'e, V>, ) -> AnyMut<'a, V> { let state = self .inner .take() .expect("You can only emplace to create a new AnyMut value once"); + let (value, path_extension, span) = mapped.into_parts(); match state { AnyMutEmplacerState::Direct(_, _) => AnyMut { - // SAFETY: As defined in the rustdoc above - inner: AnyMutInner::Direct(unsafe { transmute::<&mut V, &'static mut V>(value) }), + // 'e: 'a is guaranteed by the struct constraint, so coercion is valid + inner: AnyMutInner::Direct(value), }, AnyMutEmplacerState::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.emplace_map(|_, emplacer| unsafe { - emplacer.emplace_unchecked( - transmute::<&mut V, &'static mut V>(value), - path_extension, - new_span, - ) + inner: AnyMutInner::Encapsulated(mutable.emplace_map(|_, emplacer| { + // SAFETY: The value's lifetime ('e) matches the MutableEmplacer's lifetime + // since both are derived from the same underlying data + let remapped = unsafe { MappedMut::new_unchecked(value, path_extension, span) }; + emplacer.emplace(remapped) })), }, } diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index 8350cd26..0ce3bcef 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -1,3 +1,5 @@ +use std::mem::transmute; + use super::*; pub(crate) struct MutableReference(pub(super) ReferenceCore); @@ -32,12 +34,7 @@ impl MutableReference { self, f: impl for<'r> FnOnce(&'r mut T) -> MappedMut<'r, V>, ) -> MutableReference { - self.emplace_map(move |input, emplacer| { - let (value, path_extension, span) = f(input).into_parts(); - // SAFETY: MappedMut constructor already validated the PathExtension. - // The lifetime is checked by emplace (value: &'e mut V). - unsafe { emplacer.emplace(value, path_extension, Some(span)) } - }) + self.emplace_map(move |input, emplacer| emplacer.emplace(f(input))) } /// Fallible version of [`map`](Self::map) that returns the original reference on error. @@ -48,12 +45,7 @@ impl MutableReference { f: impl for<'r> FnOnce(&'r mut T) -> Result, E>, ) -> Result, (E, MutableReference)> { self.emplace_map(|input, emplacer| match f(input) { - Ok(mapped) => { - let (value, path_extension, span) = mapped.into_parts(); - // SAFETY: MappedMut constructor already validated the PathExtension. - // The lifetime is checked by emplace (value: &'e mut V). - Ok(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) - } + Ok(mapped) => Ok(emplacer.emplace(mapped)), Err(e) => Err((e, emplacer.revert())), }) } @@ -161,6 +153,54 @@ impl InactiveMutableReference { } } +/// A mapped mutable reference bundled with its [`PathExtension`] and span. +/// +/// Constructing this is unsafe because the caller must ensure the +/// [`PathExtension`] correctly describes the relationship between the +/// source and mapped reference. +pub(crate) struct MappedMut<'a, V: ?Sized> { + value: &'a mut V, + path_extension: PathExtension, + span: SpanRange, +} + +impl<'a, V: ?Sized> MappedMut<'a, V> { + /// SAFETY: The caller must ensure that the PathExtension correctly describes + /// the navigation from the source reference to this mapped reference. + /// An overly-specific PathExtension may cause safety issues. + pub(crate) unsafe fn new( + value: &'a mut V, + path_extension: PathExtension, + span: SpanRange, + ) -> Self { + Self { + value, + path_extension, + span, + } + } + + /// SAFETY: In addition to the safety requirements of [`new`](Self::new), the caller + /// must ensure that the reference lifetime is valid for the target lifetime `'a`. + /// This is needed when the compiler cannot prove the lifetime relationship + /// (e.g. in LeafMapper implementations where `'l` and `'e` cannot be unified). + pub(crate) unsafe fn new_unchecked<'any>( + value: &'any mut V, + path_extension: PathExtension, + span: SpanRange, + ) -> Self { + Self { + value: unsafe { transmute::<&'any mut V, &'a mut V>(value) }, + path_extension, + span, + } + } + + pub(crate) fn into_parts(self) -> (&'a mut V, PathExtension, SpanRange) { + (self.value, self.path_extension, self.span) + } +} + pub(crate) struct MutableEmplacer<'e, T: ?Sized>(EmplacerCore<'e, T>); impl<'e, T: ?Sized> MutableEmplacer<'e, T> { @@ -168,33 +208,23 @@ impl<'e, T: ?Sized> MutableEmplacer<'e, T> { MutableReference(self.0.revert()) } - /// SAFETY: The caller must ensure that the PathExtension is correct. + /// Emplaces a mapped mutable reference, consuming the emplacer's reference tracking. /// - /// The lifetime `'e` is checked by the compiler, ensuring the value is derived - /// from the original content. - pub(crate) unsafe fn emplace( + /// This is safe because all preconditions (correct PathExtension and valid reference + /// derivation) are validated by the [`MappedMut`] constructor. + pub(crate) fn emplace( &mut self, - value: &'e mut V, - path_extension: PathExtension, - new_span: Option, - ) -> MutableReference { - unsafe { self.emplace_unchecked(value, path_extension, new_span) } - } - - /// SAFETY: - /// - The caller must ensure that the value's lifetime is derived from the original content - /// - The caller must ensure that the PathExtension is correct - pub(crate) unsafe fn emplace_unchecked( - &mut self, - value: &mut V, - path_extension: PathExtension, - new_span: Option, + mapped: MappedMut<'e, V>, ) -> MutableReference { - // SAFETY: The pointer is from a reference so non-null + let (value, path_extension, span) = mapped.into_parts(); + // SAFETY: The pointer is from a valid reference (guaranteed by MappedMut constructor), + // and the PathExtension was validated by the MappedMut constructor. let pointer = unsafe { NonNull::new_unchecked(value as *mut V) }; - // SAFETY: - // - The caller ensures that the reference is derived from the original content - // - The caller ensures that the PathExtension is correct - unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } + unsafe { + MutableReference( + self.0 + .emplace_unchecked(pointer, path_extension, Some(span)), + ) + } } } diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index b680a2a1..2cb40216 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -433,66 +433,6 @@ pub(crate) enum PathExtension { Tightened(TypeKind), } -/// A mapped shared reference bundled with its [`PathExtension`] and span. -/// -/// Constructing this is unsafe because the caller must ensure the -/// [`PathExtension`] correctly describes the relationship between the -/// source and mapped reference. -pub(crate) struct MappedRef<'a, V: ?Sized> { - value: &'a V, - path_extension: PathExtension, - span: SpanRange, -} - -impl<'a, V: ?Sized> MappedRef<'a, V> { - /// SAFETY: The caller must ensure that the PathExtension correctly describes - /// the navigation from the source reference to this mapped reference. - /// An overly-specific PathExtension may cause safety issues. - pub(crate) unsafe fn new(value: &'a V, path_extension: PathExtension, span: SpanRange) -> Self { - Self { - value, - path_extension, - span, - } - } - - pub(crate) fn into_parts(self) -> (&'a V, PathExtension, SpanRange) { - (self.value, self.path_extension, self.span) - } -} - -/// A mapped mutable reference bundled with its [`PathExtension`] and span. -/// -/// Constructing this is unsafe because the caller must ensure the -/// [`PathExtension`] correctly describes the relationship between the -/// source and mapped reference. -pub(crate) struct MappedMut<'a, V: ?Sized> { - value: &'a mut V, - path_extension: PathExtension, - span: SpanRange, -} - -impl<'a, V: ?Sized> MappedMut<'a, V> { - /// SAFETY: The caller must ensure that the PathExtension correctly describes - /// the navigation from the source reference to this mapped reference. - /// An overly-specific PathExtension may cause safety issues. - pub(crate) unsafe fn new( - value: &'a mut V, - path_extension: PathExtension, - span: SpanRange, - ) -> Self { - Self { - value, - path_extension, - span, - } - } - - pub(crate) fn into_parts(self) -> (&'a mut V, PathExtension, SpanRange) { - (self.value, self.path_extension, self.span) - } -} - #[derive(PartialEq, Eq, Clone)] enum PathPart { Value { bound_as: TypeKind }, diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 918ffe33..742ec1af 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -1,3 +1,5 @@ +use std::mem::transmute; + use super::*; pub(crate) struct SharedReference(pub(super) ReferenceCore); @@ -38,12 +40,7 @@ impl SharedReference { self, f: impl for<'r> FnOnce(&'r T) -> MappedRef<'r, V>, ) -> SharedReference { - self.emplace_map(move |input, emplacer| { - let (value, path_extension, span) = f(input).into_parts(); - // SAFETY: MappedRef constructor already validated the PathExtension. - // The lifetime is checked by emplace (value: &'e V). - unsafe { emplacer.emplace(value, path_extension, Some(span)) } - }) + self.emplace_map(move |input, emplacer| emplacer.emplace(f(input))) } /// Fallible version of [`map`](Self::map) that returns the original reference on error. @@ -54,12 +51,7 @@ impl SharedReference { f: impl for<'r> FnOnce(&'r T) -> Result, E>, ) -> Result, (E, SharedReference)> { self.emplace_map(|input, emplacer| match f(input) { - Ok(mapped) => { - let (value, path_extension, span) = mapped.into_parts(); - // SAFETY: MappedRef constructor already validated the PathExtension. - // The lifetime is checked by emplace (value: &'e V). - Ok(unsafe { emplacer.emplace(value, path_extension, Some(span)) }) - } + Ok(mapped) => Ok(emplacer.emplace(mapped)), Err(e) => Err((e, emplacer.revert())), }) } @@ -140,6 +132,50 @@ impl InactiveSharedReference { } } +/// A mapped shared reference bundled with its [`PathExtension`] and span. +/// +/// Constructing this is unsafe because the caller must ensure the +/// [`PathExtension`] correctly describes the relationship between the +/// source and mapped reference. +pub(crate) struct MappedRef<'a, V: ?Sized> { + value: &'a V, + path_extension: PathExtension, + span: SpanRange, +} + +impl<'a, V: ?Sized> MappedRef<'a, V> { + /// SAFETY: The caller must ensure that the PathExtension correctly describes + /// the navigation from the source reference to this mapped reference. + /// An overly-specific PathExtension may cause safety issues. + pub(crate) unsafe fn new(value: &'a V, path_extension: PathExtension, span: SpanRange) -> Self { + Self { + value, + path_extension, + span, + } + } + + /// SAFETY: In addition to the safety requirements of [`new`](Self::new), the caller + /// must ensure that the reference lifetime is valid for the target lifetime `'a`. + /// This is needed when the compiler cannot prove the lifetime relationship + /// (e.g. in LeafMapper implementations where `'l` and `'e` cannot be unified). + pub(crate) unsafe fn new_unchecked<'any>( + value: &'any V, + path_extension: PathExtension, + span: SpanRange, + ) -> Self { + Self { + value: unsafe { transmute::<&'any V, &'a V>(value) }, + path_extension, + span, + } + } + + pub(crate) fn into_parts(self) -> (&'a V, PathExtension, SpanRange) { + (self.value, self.path_extension, self.span) + } +} + pub(crate) struct SharedEmplacer<'e, T: ?Sized>(EmplacerCore<'e, T>); impl<'e, T: ?Sized> SharedEmplacer<'e, T> { @@ -147,33 +183,23 @@ impl<'e, T: ?Sized> SharedEmplacer<'e, T> { SharedReference(self.0.revert()) } - /// SAFETY: The caller must ensure that the PathExtension is correct. + /// Emplaces a mapped shared reference, consuming the emplacer's reference tracking. /// - /// The lifetime `'e` is checked by the compiler, ensuring the value is derived - /// from the original content. - pub(crate) unsafe fn emplace( - &mut self, - value: &'e V, - path_extension: PathExtension, - new_span: Option, - ) -> SharedReference { - unsafe { self.emplace_unchecked(value, path_extension, new_span) } - } - - /// SAFETY: - /// - The caller must ensure that the value's lifetime is derived from the original content - /// - The caller must ensure that the PathExtension is correct - pub(crate) unsafe fn emplace_unchecked( + /// This is safe because all preconditions (correct PathExtension and valid reference + /// derivation) are validated by the [`MappedRef`] constructor. + pub(crate) fn emplace( &mut self, - value: &V, - path_extension: PathExtension, - new_span: Option, + mapped: MappedRef<'e, V>, ) -> SharedReference { - // SAFETY: The pointer is from a reference so non-null + let (value, path_extension, span) = mapped.into_parts(); + // SAFETY: The pointer is from a valid reference (guaranteed by MappedRef constructor), + // and the PathExtension was validated by the MappedRef constructor. let pointer = unsafe { NonNull::new_unchecked(value as *const V as *mut V) }; - // SAFETY: - // - The caller ensures that the reference is derived from the original content - // - The caller ensures that the PathExtension is correct - unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, new_span)) } + unsafe { + SharedReference( + self.0 + .emplace_unchecked(pointer, path_extension, Some(span)), + ) + } } } From aa0c9d5767b2e70aeed2ee921af549d34976be50 Mon Sep 17 00:00:00 2001 From: David Edey Date: Wed, 25 Feb 2026 01:49:01 +0000 Subject: [PATCH 37/49] feat: Fix IntoValueContent for Shared/Mutable --- src/expressions/concepts/form.rs | 7 ++++ src/expressions/concepts/forms/any_mut.rs | 12 +++++- src/expressions/concepts/forms/any_ref.rs | 12 +++++- src/expressions/concepts/forms/argument.rs | 8 ++++ src/expressions/concepts/forms/assignee.rs | 8 ++++ .../concepts/forms/copy_on_write.rs | 18 ++++---- src/expressions/concepts/forms/late_bound.rs | 8 ++++ src/expressions/concepts/forms/mutable.rs | 27 +++++++++--- src/expressions/concepts/forms/owned.rs | 8 ++++ src/expressions/concepts/forms/shared.rs | 27 +++++++++--- src/expressions/concepts/forms/simple_mut.rs | 41 ++++++++++++------- src/expressions/concepts/forms/simple_ref.rs | 36 ++++++++++------ src/expressions/concepts/type_traits.rs | 33 +++++++++++++++ src/interpretation/bindings.rs | 15 ++----- .../dynamic_references/mutable_reference.rs | 15 +++---- .../dynamic_references/shared_reference.rs | 15 +++---- 16 files changed, 211 insertions(+), 79 deletions(-) diff --git a/src/expressions/concepts/form.rs b/src/expressions/concepts/form.rs index 8a06301d..a7113215 100644 --- a/src/expressions/concepts/form.rs +++ b/src/expressions/concepts/form.rs @@ -18,6 +18,13 @@ pub(crate) trait IsHierarchicalForm: IsForm { type Leaf<'a, T: IsLeafType>: IsValueContent + IntoValueContent<'a> + FromValueContent<'a>; + + /// Proof of covariance: shrinks the lifetime of a leaf from `'a` to `'b`. + /// Implementors should use the identity function `{ leaf }` as the body. + /// This will only compile if the leaf type is genuinely covariant in `'a`. + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b; } pub(crate) trait LeafAsRefForm: IsHierarchicalForm { diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index 5a867a9c..a4761ec8 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -22,6 +22,14 @@ pub(crate) struct BeAnyMut; impl IsForm for BeAnyMut {} impl IsHierarchicalForm for BeAnyMut { type Leaf<'a, T: IsLeafType> = AnyMut<'a, T::Leaf>; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeAnyMut { @@ -65,7 +73,9 @@ impl MapFromArgument for BeAnyMut { Spanned(value, span): Spanned, ) -> FunctionResult> { Ok(value.expect_mutable().emplace_map(|inner, emplacer| { - inner.as_mut_value().into_mutable_any_mut(emplacer, span) + inner + .as_mut_value() + .into_mutable_any_mut(emplacer, Some(span)) })) } } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index e67f3704..51407b86 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -23,6 +23,14 @@ impl IsForm for BeAnyRef {} impl IsHierarchicalForm for BeAnyRef { type Leaf<'a, T: IsLeafType> = crate::internal_prelude::AnyRef<'a, T::Leaf>; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeAnyRef { @@ -60,7 +68,9 @@ impl MapFromArgument for BeAnyRef { Spanned(value, span): Spanned, ) -> FunctionResult> { Ok(value.expect_shared().emplace_map(|inner, emplacer| { - inner.as_ref_value().into_shared_any_ref(emplacer, span) + inner + .as_ref_value() + .into_shared_any_ref(emplacer, Some(span)) })) } } diff --git a/src/expressions/concepts/forms/argument.rs b/src/expressions/concepts/forms/argument.rs index bbe3fda5..ce455b06 100644 --- a/src/expressions/concepts/forms/argument.rs +++ b/src/expressions/concepts/forms/argument.rs @@ -31,6 +31,14 @@ impl IsForm for BeArgument {} impl IsHierarchicalForm for BeArgument { type Leaf<'a, T: IsLeafType> = Argument; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl MapFromArgument for BeArgument { diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index 1c02fc80..37a8744c 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -25,6 +25,14 @@ impl IsForm for BeAssignee {} impl IsHierarchicalForm for BeAssignee { type Leaf<'a, T: IsLeafType> = QqqAssignee; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeAssignee { diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index 34d51f09..742d01bc 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -34,6 +34,14 @@ impl IsForm for BeCopyOnWrite {} impl IsHierarchicalForm for BeCopyOnWrite { type Leaf<'a, T: IsLeafType> = QqqCopyOnWrite; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl BeCopyOnWrite { @@ -80,16 +88,10 @@ impl MapFromArgument for BeCopyOnWrite { match value.expect_copy_on_write().inner { CopyOnWriteInner::Owned(owned) => Ok(BeCopyOnWrite::new_owned(owned)), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - let content = shared.emplace_map(|inner, emplacer| { - inner.as_ref_value().into_shared(emplacer, span) - }); - Ok(BeCopyOnWrite::new_shared_in_place_of_owned(content)) + Ok(BeCopyOnWrite::new_shared_in_place_of_owned(shared)) } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - let content = shared.emplace_map(|inner, emplacer| { - inner.as_ref_value().into_shared(emplacer, span) - }); - Ok(BeCopyOnWrite::new_shared_in_place_of_shared(content)) + Ok(BeCopyOnWrite::new_shared_in_place_of_shared(shared)) } } } diff --git a/src/expressions/concepts/forms/late_bound.rs b/src/expressions/concepts/forms/late_bound.rs index 0395e1ca..67ba3bc1 100644 --- a/src/expressions/concepts/forms/late_bound.rs +++ b/src/expressions/concepts/forms/late_bound.rs @@ -44,6 +44,14 @@ impl IsForm for BeLateBound {} impl IsHierarchicalForm for BeLateBound { type Leaf<'a, T: IsLeafType> = QqqLateBound; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } pub(crate) struct QqqLateBoundOwned { diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index ac40ba80..40e57ca5 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -2,17 +2,26 @@ use super::*; pub(crate) type QqqMutable = MutableReference; -impl IsValueContent for QqqMutable { - type Type = L::Type; +impl IsValueContent for QqqMutable { + type Type = X::Type; type Form = BeMutable; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqMutable { +impl<'a, X: IsValueContent> IntoValueContent<'a> for QqqMutable +where + X: 'static, + X::Type: IsHierarchicalType = X>, + X::Form: IsHierarchicalForm, + X::Form: LeafAsMutForm, +{ fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self + self.emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer, None)) } } +// Note we can't implement this more widely than leaves. +// This is because e.g. Content has Mutable() in its leaves, +// This can't be mapped back to having Mutable(Content). impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqMutable { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content @@ -25,6 +34,14 @@ impl IsForm for BeMutable {} impl IsHierarchicalForm for BeMutable { type Leaf<'a, T: IsLeafType> = QqqMutable; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeMutable { @@ -69,7 +86,7 @@ impl MapFromArgument for BeMutable { ) -> FunctionResult> { Ok(value .expect_mutable() - .emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer, span))) + .emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer, Some(span)))) } } diff --git a/src/expressions/concepts/forms/owned.rs b/src/expressions/concepts/forms/owned.rs index b572c53a..5a575a03 100644 --- a/src/expressions/concepts/forms/owned.rs +++ b/src/expressions/concepts/forms/owned.rs @@ -18,6 +18,14 @@ impl IsForm for BeOwned {} impl IsHierarchicalForm for BeOwned { type Leaf<'a, T: IsLeafType> = T::Leaf; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeOwned { diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index baf3cc1e..5015adf5 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -2,17 +2,26 @@ use super::*; pub(crate) type QqqShared = SharedReference; -impl IsValueContent for QqqShared { - type Type = L::Type; +impl IsValueContent for QqqShared { + type Type = X::Type; type Form = BeShared; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqShared { +impl<'a, X: IsValueContent> IntoValueContent<'a> for QqqShared +where + X: 'static, + X::Type: IsHierarchicalType = X>, + X::Form: IsHierarchicalForm, + X::Form: LeafAsRefForm, +{ fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self + self.emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer, None)) } } +// Note we can't implement this more widely than leaves. +// This is because e.g. Content has Shared() in its leaves, +// This can't be mapped back to having Shared(Content). impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqShared { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content @@ -25,6 +34,14 @@ impl IsForm for BeShared {} impl IsHierarchicalForm for BeShared { type Leaf<'a, T: IsLeafType> = QqqShared; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeShared { @@ -63,7 +80,7 @@ impl MapFromArgument for BeShared { ) -> FunctionResult> { Ok(value .expect_shared() - .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer, span))) + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer, Some(span)))) } } diff --git a/src/expressions/concepts/forms/simple_mut.rs b/src/expressions/concepts/forms/simple_mut.rs index f87fe41d..ff447af5 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -26,6 +26,14 @@ impl IsForm for BeMut {} impl IsHierarchicalForm for BeMut { type Leaf<'a, T: IsLeafType> = &'a mut T::Leaf; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeMut { @@ -52,17 +60,17 @@ where Self: IsValueContent, Self::Type: IsHierarchicalType = Self>, { - fn into_mutable<'b, T: 'static>( + fn into_mutable<'o, 'b, T: 'static>( self, emplacer: &'b mut MutableEmplacer<'a, T>, - span: SpanRange, - ) -> Content<'static, Self::Type, BeMutable> + span: Option, + ) -> Content<'o, Self::Type, BeMutable> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut MutableEmplacer<'e2, X>, - span: SpanRange, + span: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeMutable>; @@ -90,20 +98,21 @@ where } }; let __mapper = __InlineMapper { emplacer, span }; - ::map_with::(__mapper, self) + let content = ::map_with::(__mapper, self); + ::covariant::(content) } - fn into_assignee<'b, T: 'static>( + fn into_assignee<'o, 'b, T: 'static>( self, emplacer: &'b mut MutableEmplacer<'a, T>, - span: SpanRange, - ) -> Content<'static, Self::Type, BeAssignee> + span: Option, + ) -> Content<'o, Self::Type, BeAssignee> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut MutableEmplacer<'e2, X>, - span: SpanRange, + span: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAssignee>; @@ -131,20 +140,21 @@ where } }; let __mapper = __InlineMapper { emplacer, span }; - ::map_with::(__mapper, self) + let content = ::map_with::(__mapper, self); + ::covariant::(content) } - fn into_mutable_any_mut<'b, T: 'static>( + fn into_mutable_any_mut<'o, 'b, T: 'static>( self, emplacer: &'b mut MutableEmplacer<'a, T>, - span: SpanRange, - ) -> Content<'static, Self::Type, BeAnyMut> + span: Option, + ) -> Content<'o, Self::Type, BeAnyMut> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut MutableEmplacer<'e2, X>, - span: SpanRange, + span: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAnyMut>; @@ -173,7 +183,8 @@ where } }; let __mapper = __InlineMapper { emplacer, span }; - ::map_with::(__mapper, self) + let content = ::map_with::(__mapper, self); + ::covariant::(content) } } diff --git a/src/expressions/concepts/forms/simple_ref.rs b/src/expressions/concepts/forms/simple_ref.rs index 00e7ff7e..c9086171 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -26,6 +26,14 @@ impl IsForm for BeRef {} impl IsHierarchicalForm for BeRef { type Leaf<'a, T: IsLeafType> = &'a T::Leaf; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeRef { @@ -52,19 +60,19 @@ where Self: IsValueContent, Self::Type: IsHierarchicalType = Self>, { - fn into_shared<'b, T: 'static>( + fn into_shared<'o, 'b, T: 'static>( self, emplacer: &'b mut SharedEmplacer<'a, T>, - span: SpanRange, - ) -> Content<'static, Self::Type, BeShared> + span: Option, + ) -> Content<'o, Self::Type, BeShared> where Self: Sized, { - struct __InlineMapper<'b, 'e2, X: 'static> { - emplacer: &'b mut SharedEmplacer<'e2, X>, - span: SpanRange, + struct __InlineMapper<'b, 'e, X: 'static> { + emplacer: &'b mut SharedEmplacer<'e, X>, + span: Option, } - impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { + impl<'b, 'e, X> LeafMapper for __InlineMapper<'b, 'e, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeShared>; fn to_parent_output<'a, T: IsChildType>( @@ -90,20 +98,21 @@ where } }; let __mapper = __InlineMapper { emplacer, span }; - ::map_with::(__mapper, self) + let content = ::map_with::(__mapper, self); + ::covariant::(content) } - fn into_shared_any_ref<'b, T: 'static>( + fn into_shared_any_ref<'o, 'b, T: 'static>( self, emplacer: &'b mut SharedEmplacer<'a, T>, - span: SpanRange, - ) -> Content<'static, Self::Type, BeAnyRef> + span: Option, + ) -> Content<'o, Self::Type, BeAnyRef> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut SharedEmplacer<'e2, X>, - span: SpanRange, + span: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAnyRef>; @@ -132,7 +141,8 @@ where } }; let __mapper = __InlineMapper { emplacer, span }; - ::map_with::(__mapper, self) + let content = ::map_with::(__mapper, self); + ::covariant::(content) } } diff --git a/src/expressions/concepts/type_traits.rs b/src/expressions/concepts/type_traits.rs index 52c6f4b9..02b09942 100644 --- a/src/expressions/concepts/type_traits.rs +++ b/src/expressions/concepts/type_traits.rs @@ -42,6 +42,15 @@ pub(crate) trait IsHierarchicalType: IsType { fn content_to_leaf_kind( content: &Self::Content<'_, F>, ) -> Self::LeafKind; + + /// Shrinks the lifetime of a content value from `'a` to `'b`. + /// This is safe because all forms' leaf types are covariant in `'a`, + /// as enforced by [`IsHierarchicalForm::covariant_leaf`]. + fn covariant<'a, 'b, F: IsHierarchicalForm>( + content: Self::Content<'a, F>, + ) -> Self::Content<'b, F> + where + 'a: 'b; } pub(crate) trait IsLeafType: @@ -455,6 +464,20 @@ macro_rules! define_parent_type { ) -> Self::LeafKind { content.kind() } + + #[inline] + fn covariant<'a, 'b, F: IsHierarchicalForm>( + content: Self::Content<'a, F>, + ) -> Self::Content<'b, F> + where + 'a: 'b, + { + match content { + $( $content::$variant(x) => $content::$variant( + <$variant_type>::covariant::(x) + ), )* + } + } } $content_vis enum $content<'a, F: IsHierarchicalForm> { @@ -633,6 +656,16 @@ macro_rules! define_leaf_type { ) -> Self::LeafKind { $kind } + + #[inline] + fn covariant<'a, 'b, F: IsHierarchicalForm>( + content: Self::Content<'a, F>, + ) -> Self::Content<'b, F> + where + 'a: 'b, + { + F::covariant_leaf::(content) + } } impl IsLeafType for $type_def { diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 6678efb9..7d789a91 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -397,11 +397,8 @@ where X::Form: LeafAsMutForm, { fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - self.0.emplace_map(|inner, emplacer| { - inner - .as_mut_value() - .into_assignee(emplacer, Span::call_site().span_range()) - }) + self.0 + .emplace_map(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer, None)) } } @@ -620,18 +617,14 @@ where } CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { let content = shared.emplace_map(|inner, emplacer| { - inner - .as_ref_value() - .into_shared(emplacer, Span::call_site().span_range()) + inner.as_ref_value().into_shared(emplacer, None) }); AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(content) .into_copy_on_write() } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { let content = shared.emplace_map(|inner, emplacer| { - inner - .as_ref_value() - .into_shared(emplacer, Span::call_site().span_range()) + inner.as_ref_value().into_shared(emplacer, None) }); AnyLevelCopyOnWrite::::SharedWithTransparentCloning(content) .into_copy_on_write() diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index 0ce3bcef..b4fafec4 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -161,7 +161,7 @@ impl InactiveMutableReference { pub(crate) struct MappedMut<'a, V: ?Sized> { value: &'a mut V, path_extension: PathExtension, - span: SpanRange, + span: Option, } impl<'a, V: ?Sized> MappedMut<'a, V> { @@ -176,7 +176,7 @@ impl<'a, V: ?Sized> MappedMut<'a, V> { Self { value, path_extension, - span, + span: Some(span), } } @@ -187,7 +187,7 @@ impl<'a, V: ?Sized> MappedMut<'a, V> { pub(crate) unsafe fn new_unchecked<'any>( value: &'any mut V, path_extension: PathExtension, - span: SpanRange, + span: Option, ) -> Self { Self { value: unsafe { transmute::<&'any mut V, &'a mut V>(value) }, @@ -196,7 +196,7 @@ impl<'a, V: ?Sized> MappedMut<'a, V> { } } - pub(crate) fn into_parts(self) -> (&'a mut V, PathExtension, SpanRange) { + pub(crate) fn into_parts(self) -> (&'a mut V, PathExtension, Option) { (self.value, self.path_extension, self.span) } } @@ -220,11 +220,6 @@ impl<'e, T: ?Sized> MutableEmplacer<'e, T> { // SAFETY: The pointer is from a valid reference (guaranteed by MappedMut constructor), // and the PathExtension was validated by the MappedMut constructor. let pointer = unsafe { NonNull::new_unchecked(value as *mut V) }; - unsafe { - MutableReference( - self.0 - .emplace_unchecked(pointer, path_extension, Some(span)), - ) - } + unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, span)) } } } diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 742ec1af..1bc62f91 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -140,7 +140,7 @@ impl InactiveSharedReference { pub(crate) struct MappedRef<'a, V: ?Sized> { value: &'a V, path_extension: PathExtension, - span: SpanRange, + span: Option, } impl<'a, V: ?Sized> MappedRef<'a, V> { @@ -151,7 +151,7 @@ impl<'a, V: ?Sized> MappedRef<'a, V> { Self { value, path_extension, - span, + span: Some(span), } } @@ -162,7 +162,7 @@ impl<'a, V: ?Sized> MappedRef<'a, V> { pub(crate) unsafe fn new_unchecked<'any>( value: &'any V, path_extension: PathExtension, - span: SpanRange, + span: Option, ) -> Self { Self { value: unsafe { transmute::<&'any V, &'a V>(value) }, @@ -171,7 +171,7 @@ impl<'a, V: ?Sized> MappedRef<'a, V> { } } - pub(crate) fn into_parts(self) -> (&'a V, PathExtension, SpanRange) { + pub(crate) fn into_parts(self) -> (&'a V, PathExtension, Option) { (self.value, self.path_extension, self.span) } } @@ -195,11 +195,6 @@ impl<'e, T: ?Sized> SharedEmplacer<'e, T> { // SAFETY: The pointer is from a valid reference (guaranteed by MappedRef constructor), // and the PathExtension was validated by the MappedRef constructor. let pointer = unsafe { NonNull::new_unchecked(value as *const V as *mut V) }; - unsafe { - SharedReference( - self.0 - .emplace_unchecked(pointer, path_extension, Some(span)), - ) - } + unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, span)) } } } From ee0472ed511fd15ef6ba2aae285f880e5cf61113 Mon Sep 17 00:00:00 2001 From: David Edey Date: Wed, 25 Feb 2026 02:10:43 +0000 Subject: [PATCH 38/49] fix: Fix swap_itself whitespace --- tests/compilation_failures/expressions/swap_itself.stderr | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/compilation_failures/expressions/swap_itself.stderr b/tests/compilation_failures/expressions/swap_itself.stderr index b9335319..97a8b675 100644 --- a/tests/compilation_failures/expressions/swap_itself.stderr +++ b/tests/compilation_failures/expressions/swap_itself.stderr @@ -2,7 +2,6 @@ error: Cannot create a mutable reference because it clashes with an existing ref This reference : [*active*] &mut a (of type any) Other reference: [*active*] &mut a (of type any) Reason : mutation may be observed from the other active reference, which breaks aliasing rules - --> tests/compilation_failures/expressions/swap_itself.rs:5:13 | 5 | let a = "a"; From 930c6bbac510e09f573f78095a5aad7861d4177f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Feb 2026 02:29:35 +0000 Subject: [PATCH 39/49] fix: Add set_tracked_span to fix borrow-conflict error spans The swap_itself.stderr error was pointing to `let a = "a"` instead of the second `a` in `a.swap(a)` because enable() ignored its span parameter and creation_span was never updated from the root span. - Add set_tracked_span to SharedReference, MutableReference, and their inactive variants - Update enable() to call set_tracked_span before activate(), so borrow-conflict errors point to the usage site - Update VariableBinding::into_mut/into_shared/into_late_bound to set the tracked span before initial activation too https://claude.ai/code/session_01XaCsWwXFYntkKqZNf8kizU --- src/interpretation/bindings.rs | 34 +++++++++++-------- .../dynamic_references/mutable_reference.rs | 20 +++++++++-- src/misc/dynamic_references/referenceable.rs | 4 +++ .../dynamic_references/shared_reference.rs | 22 +++++++++--- .../expressions/swap_itself.stderr | 7 ++-- 5 files changed, 63 insertions(+), 24 deletions(-) diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 7d789a91..7fbd91c0 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -180,15 +180,17 @@ impl VariableBinding { } fn into_mut(self) -> FunctionResult { + let span = self.variable_span.span_range(); match self.content { - VariableContent::Referenceable(referenceable) => referenceable - .new_inactive_mutable() - .activate() - .map_err(|_| { + VariableContent::Referenceable(referenceable) => { + let inactive = referenceable.new_inactive_mutable(); + inactive.set_tracked_span(span); + inactive.activate().map_err(|_| { self.variable_span .ownership_error::(MUTABLE_ERROR_MESSAGE) - }), - VariableContent::Mutable(disabled) => disabled.enable(self.variable_span.span_range()), + }) + } + VariableContent::Mutable(disabled) => disabled.enable(span), VariableContent::Shared(shared) => self .variable_span .ownership_err(SHARED_TO_MUTABLE_ERROR_MESSAGE), @@ -196,29 +198,33 @@ impl VariableBinding { } fn into_shared(self) -> FunctionResult { + let span = self.variable_span.span_range(); match self.content { VariableContent::Referenceable(referenceable) => { - referenceable.new_inactive_shared().activate().map_err(|_| { + let inactive = referenceable.new_inactive_shared(); + inactive.set_tracked_span(span); + inactive.activate().map_err(|_| { self.variable_span .ownership_error::(SHARED_ERROR_MESSAGE) }) } - VariableContent::Mutable(mutable) => mutable - .into_shared() - .enable(self.variable_span.span_range()), - VariableContent::Shared(shared) => shared.enable(self.variable_span.span_range()), + VariableContent::Mutable(mutable) => mutable.into_shared().enable(span), + VariableContent::Shared(shared) => shared.enable(span), } } fn into_late_bound(self) -> FunctionResult { + let span = self.variable_span.span_range(); match self.content { VariableContent::Referenceable(referenceable) => { let inactive_mut = referenceable.new_inactive_mutable(); + inactive_mut.set_tracked_span(span); match inactive_mut.activate() { Ok(mutable) => Ok(LateBoundValue::Mutable(mutable)), Err(_) => { // Mutable failed, try shared let inactive_shared = referenceable.new_inactive_shared(); + inactive_shared.set_tracked_span(span); let shared = inactive_shared.activate().map_err(|_| { self.variable_span .ownership_error::(SHARED_ERROR_MESSAGE) @@ -230,12 +236,10 @@ impl VariableBinding { } } } - VariableContent::Mutable(mutable) => Ok(LateBoundValue::Mutable( - mutable.enable(self.variable_span.span_range())?, - )), + VariableContent::Mutable(mutable) => Ok(LateBoundValue::Mutable(mutable.enable(span)?)), VariableContent::Shared(shared) => { Ok(LateBoundValue::Shared(LateBoundSharedValue::new( - shared.enable(self.variable_span.span_range())?, + shared.enable(span)?, self.variable_span .syn_error(SHARED_TO_MUTABLE_ERROR_MESSAGE), ))) diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index b4fafec4..b148213d 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -82,13 +82,29 @@ impl MutableReference { .activate() .expect("Converting mutable to shared should always succeed since we just released the mutable borrow") } + + /// Updates the tracked span for this reference, which is used in + /// borrow-conflict error messages to point to the usage site. + #[allow(unused)] + pub(crate) fn set_tracked_span(&self, span: SpanRange) { + self.0.core.data_mut().set_creation_span(self.0.id, span); + } } impl InactiveMutableReference { - /// Re-enables this inactive mutable reference (bridge for old `enable()` API). - pub(crate) fn enable(self, _span: SpanRange) -> FunctionResult> { + /// Re-enables this inactive mutable reference. + /// Updates the tracked span to the given usage-site span before activating, + /// so that borrow-conflict errors point to the correct location. + pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { + self.set_tracked_span(span); self.activate() } + + /// Updates the tracked span for this reference, which is used in + /// borrow-conflict error messages to point to the usage site. + pub(crate) fn set_tracked_span(&self, span: SpanRange) { + self.0.core.data_mut().set_creation_span(self.0.id, span); + } } impl Deref for MutableReference { diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 2cb40216..7b6c0eb0 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -104,6 +104,10 @@ impl ReferenceableData { self.arena.remove(id); } + pub(super) fn set_creation_span(&mut self, id: LocalReferenceId, span: SpanRange) { + self.for_reference_mut(id).creation_span = span; + } + pub(super) fn deactivate_reference(&mut self, id: LocalReferenceId) { let data = self.for_reference_mut(id); data.reference_kind = match data.reference_kind { diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 1bc62f91..5e4c19d2 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -84,15 +84,29 @@ impl SharedReference { pub(crate) fn disable(self) -> InactiveSharedReference { self.deactivate() } + + /// Updates the tracked span for this reference, which is used in + /// borrow-conflict error messages to point to the usage site. + #[allow(unused)] + pub(crate) fn set_tracked_span(&self, span: SpanRange) { + self.0.core.data_mut().set_creation_span(self.0.id, span); + } } impl InactiveSharedReference { - /// Re-enables this inactive shared reference (bridge for old `enable()` API). - /// The span parameter is kept for API compatibility but activation errors - /// now include span information from the reference itself. - pub(crate) fn enable(self, _span: SpanRange) -> FunctionResult> { + /// Re-enables this inactive shared reference. + /// Updates the tracked span to the given usage-site span before activating, + /// so that borrow-conflict errors point to the correct location. + pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { + self.set_tracked_span(span); self.activate() } + + /// Updates the tracked span for this reference, which is used in + /// borrow-conflict error messages to point to the usage site. + pub(crate) fn set_tracked_span(&self, span: SpanRange) { + self.0.core.data_mut().set_creation_span(self.0.id, span); + } } impl Deref for SharedReference { diff --git a/tests/compilation_failures/expressions/swap_itself.stderr b/tests/compilation_failures/expressions/swap_itself.stderr index 97a8b675..de6412af 100644 --- a/tests/compilation_failures/expressions/swap_itself.stderr +++ b/tests/compilation_failures/expressions/swap_itself.stderr @@ -2,7 +2,8 @@ error: Cannot create a mutable reference because it clashes with an existing ref This reference : [*active*] &mut a (of type any) Other reference: [*active*] &mut a (of type any) Reason : mutation may be observed from the other active reference, which breaks aliasing rules - --> tests/compilation_failures/expressions/swap_itself.rs:5:13 + + --> tests/compilation_failures/expressions/swap_itself.rs:6:16 | -5 | let a = "a"; - | ^ +6 | a.swap(a); + | ^ From 09d0c3a749c3bfa2a40b884d5bac18dc014ca3e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Feb 2026 02:50:21 +0000 Subject: [PATCH 40/49] refactor: Make activate() take SpanRange, remove enable/set_tracked_span Move span-setting into activate() so callers don't need a separate set_tracked_span step. Remove enable() and set_tracked_span() from Inactive*Reference types. Add new_active_shared/new_active_mutable convenience methods to Referenceable. https://claude.ai/code/session_01XaCsWwXFYntkKqZNf8kizU --- src/expressions/evaluation/value_frames.rs | 12 +++--- src/interpretation/bindings.rs | 38 ++++++++----------- .../dynamic_references/mutable_reference.rs | 38 +++++-------------- src/misc/dynamic_references/referenceable.rs | 27 ++++++++++--- .../dynamic_references/shared_reference.rs | 30 ++------------- 5 files changed, 57 insertions(+), 88 deletions(-) diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index 9ed95f01..d88224a3 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -101,14 +101,14 @@ impl DisabledArgumentValue { DisabledArgumentValue::CopyOnWrite(copy_on_write) => { Ok(ArgumentValue::CopyOnWrite(copy_on_write.enable(span)?)) } - DisabledArgumentValue::Mutable(mutable) => { - Ok(ArgumentValue::Mutable(mutable.enable(span)?)) + DisabledArgumentValue::Mutable(inactive) => { + Ok(ArgumentValue::Mutable(inactive.activate(span)?)) } - DisabledArgumentValue::Assignee(assignee) => { - Ok(ArgumentValue::Assignee(Assignee(assignee.enable(span)?))) + DisabledArgumentValue::Assignee(inactive) => { + Ok(ArgumentValue::Assignee(Assignee(inactive.activate(span)?))) } - DisabledArgumentValue::Shared(shared) => { - Ok(ArgumentValue::Shared(shared.enable(span)?)) + DisabledArgumentValue::Shared(inactive) => { + Ok(ArgumentValue::Shared(inactive.activate(span)?)) } } } diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 7fbd91c0..9fc8f7b7 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -183,14 +183,12 @@ impl VariableBinding { let span = self.variable_span.span_range(); match self.content { VariableContent::Referenceable(referenceable) => { - let inactive = referenceable.new_inactive_mutable(); - inactive.set_tracked_span(span); - inactive.activate().map_err(|_| { + referenceable.new_active_mutable(span).map_err(|_| { self.variable_span .ownership_error::(MUTABLE_ERROR_MESSAGE) }) } - VariableContent::Mutable(disabled) => disabled.enable(span), + VariableContent::Mutable(inactive) => inactive.activate(span), VariableContent::Shared(shared) => self .variable_span .ownership_err(SHARED_TO_MUTABLE_ERROR_MESSAGE), @@ -201,15 +199,13 @@ impl VariableBinding { let span = self.variable_span.span_range(); match self.content { VariableContent::Referenceable(referenceable) => { - let inactive = referenceable.new_inactive_shared(); - inactive.set_tracked_span(span); - inactive.activate().map_err(|_| { + referenceable.new_active_shared(span).map_err(|_| { self.variable_span .ownership_error::(SHARED_ERROR_MESSAGE) }) } - VariableContent::Mutable(mutable) => mutable.into_shared().enable(span), - VariableContent::Shared(shared) => shared.enable(span), + VariableContent::Mutable(inactive) => inactive.into_shared().activate(span), + VariableContent::Shared(inactive) => inactive.activate(span), } } @@ -217,15 +213,11 @@ impl VariableBinding { let span = self.variable_span.span_range(); match self.content { VariableContent::Referenceable(referenceable) => { - let inactive_mut = referenceable.new_inactive_mutable(); - inactive_mut.set_tracked_span(span); - match inactive_mut.activate() { + match referenceable.new_active_mutable(span) { Ok(mutable) => Ok(LateBoundValue::Mutable(mutable)), Err(_) => { // Mutable failed, try shared - let inactive_shared = referenceable.new_inactive_shared(); - inactive_shared.set_tracked_span(span); - let shared = inactive_shared.activate().map_err(|_| { + let shared = referenceable.new_active_shared(span).map_err(|_| { self.variable_span .ownership_error::(SHARED_ERROR_MESSAGE) })?; @@ -236,10 +228,12 @@ impl VariableBinding { } } } - VariableContent::Mutable(mutable) => Ok(LateBoundValue::Mutable(mutable.enable(span)?)), - VariableContent::Shared(shared) => { + VariableContent::Mutable(inactive) => { + Ok(LateBoundValue::Mutable(inactive.activate(span)?)) + } + VariableContent::Shared(inactive) => { Ok(LateBoundValue::Shared(LateBoundSharedValue::new( - shared.enable(span)?, + inactive.activate(span)?, self.variable_span .syn_error(SHARED_TO_MUTABLE_ERROR_MESSAGE), ))) @@ -593,11 +587,11 @@ impl DisabledCopyOnWrite { pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { let inner = match self.inner { DisabledCopyOnWriteInner::Owned(owned) => CopyOnWriteInner::Owned(owned), - DisabledCopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - CopyOnWriteInner::SharedWithInfallibleCloning(shared.enable(span)?) + DisabledCopyOnWriteInner::SharedWithInfallibleCloning(inactive) => { + CopyOnWriteInner::SharedWithInfallibleCloning(inactive.activate(span)?) } - DisabledCopyOnWriteInner::SharedWithTransparentCloning(shared) => { - CopyOnWriteInner::SharedWithTransparentCloning(shared.enable(span)?) + DisabledCopyOnWriteInner::SharedWithTransparentCloning(inactive) => { + CopyOnWriteInner::SharedWithTransparentCloning(inactive.activate(span)?) } }; Ok(CopyOnWrite { inner }) diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index b148213d..c3d17e28 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -60,8 +60,7 @@ impl MutableReference { ) -> Self { let referenceable = Referenceable::new(value, root_name, span_range); referenceable - .new_inactive_mutable() - .activate() + .new_active_mutable(span_range) .expect("Freshly created referenceable must be borrowable as mutable") } } @@ -76,35 +75,18 @@ impl MutableReference { /// Converts this mutable reference into a shared reference. /// Bridge for old `into_shared()` API. pub(crate) fn into_shared(self) -> SharedReference { + let span = self + .0 + .core + .data_mut() + .for_reference(self.0.id) + .creation_span; let inactive = self.deactivate(); let inactive_shared = inactive.into_shared(); inactive_shared - .activate() + .activate(span) .expect("Converting mutable to shared should always succeed since we just released the mutable borrow") } - - /// Updates the tracked span for this reference, which is used in - /// borrow-conflict error messages to point to the usage site. - #[allow(unused)] - pub(crate) fn set_tracked_span(&self, span: SpanRange) { - self.0.core.data_mut().set_creation_span(self.0.id, span); - } -} - -impl InactiveMutableReference { - /// Re-enables this inactive mutable reference. - /// Updates the tracked span to the given usage-site span before activating, - /// so that borrow-conflict errors point to the correct location. - pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { - self.set_tracked_span(span); - self.activate() - } - - /// Updates the tracked span for this reference, which is used in - /// borrow-conflict error messages to point to the usage site. - pub(crate) fn set_tracked_span(&self, span: SpanRange) { - self.0.core.data_mut().set_creation_span(self.0.id, span); - } } impl Deref for MutableReference { @@ -152,11 +134,11 @@ impl Clone for InactiveMutableReference { } impl InactiveMutableReference { - pub(crate) fn activate(self) -> FunctionResult> { + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { self.0 .core .data_mut() - .activate_mutable_reference(self.0.id)?; + .activate_mutable_reference(self.0.id, span)?; Ok(MutableReference(self.0)) } diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 7b6c0eb0..31de0f01 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -26,6 +26,20 @@ impl Referenceable { )) } + pub(crate) fn new_active_shared( + &self, + span: SpanRange, + ) -> FunctionResult> { + self.new_inactive_shared().activate(span) + } + + pub(crate) fn new_active_mutable( + &self, + span: SpanRange, + ) -> FunctionResult> { + self.new_inactive_mutable().activate(span) + } + /// Attempts to unwrap the inner value if this is the sole owner (no outstanding references). pub(crate) fn try_into_inner(self) -> Result { match Rc::try_unwrap(self.core) { @@ -104,10 +118,6 @@ impl ReferenceableData { self.arena.remove(id); } - pub(super) fn set_creation_span(&mut self, id: LocalReferenceId, span: SpanRange) { - self.for_reference_mut(id).creation_span = span; - } - pub(super) fn deactivate_reference(&mut self, id: LocalReferenceId) { let data = self.for_reference_mut(id); data.reference_kind = match data.reference_kind { @@ -132,7 +142,9 @@ impl ReferenceableData { pub(super) fn activate_mutable_reference( &mut self, id: LocalReferenceId, + span: SpanRange, ) -> FunctionResult<()> { + self.for_reference_mut(id).creation_span = span; let data = self.for_reference(id); // Perform checks as per the module doc on `dynamic_references` for (other_id, other_data) in self.arena.iter() { @@ -169,7 +181,12 @@ impl ReferenceableData { Ok(()) } - pub(super) fn activate_shared_reference(&mut self, id: LocalReferenceId) -> FunctionResult<()> { + pub(super) fn activate_shared_reference( + &mut self, + id: LocalReferenceId, + span: SpanRange, + ) -> FunctionResult<()> { + self.for_reference_mut(id).creation_span = span; let data = self.for_reference(id); // Perform checks as per the module doc on `dynamic_references` for (other_id, other_data) in self.arena.iter() { diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 5e4c19d2..245ba601 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -67,8 +67,7 @@ impl SharedReference { ) -> Self { let referenceable = Referenceable::new(value, root_name, span_range); referenceable - .new_inactive_shared() - .activate() + .new_active_shared(span_range) .expect("Freshly created referenceable must be borrowable as shared") } @@ -84,29 +83,6 @@ impl SharedReference { pub(crate) fn disable(self) -> InactiveSharedReference { self.deactivate() } - - /// Updates the tracked span for this reference, which is used in - /// borrow-conflict error messages to point to the usage site. - #[allow(unused)] - pub(crate) fn set_tracked_span(&self, span: SpanRange) { - self.0.core.data_mut().set_creation_span(self.0.id, span); - } -} - -impl InactiveSharedReference { - /// Re-enables this inactive shared reference. - /// Updates the tracked span to the given usage-site span before activating, - /// so that borrow-conflict errors point to the correct location. - pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { - self.set_tracked_span(span); - self.activate() - } - - /// Updates the tracked span for this reference, which is used in - /// borrow-conflict error messages to point to the usage site. - pub(crate) fn set_tracked_span(&self, span: SpanRange) { - self.0.core.data_mut().set_creation_span(self.0.id, span); - } } impl Deref for SharedReference { @@ -137,11 +113,11 @@ impl Clone for InactiveSharedReference { } impl InactiveSharedReference { - pub(crate) fn activate(self) -> FunctionResult> { + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { self.0 .core .data_mut() - .activate_shared_reference(self.0.id)?; + .activate_shared_reference(self.0.id, span)?; Ok(SharedReference(self.0)) } } From 48ef9efda6f9841b7bac12ee7752cc425ed19013 Mon Sep 17 00:00:00 2001 From: David Edey Date: Wed, 25 Feb 2026 02:52:16 +0000 Subject: [PATCH 41/49] docs: Update TODOs --- plans/TODO.md | 26 +++++++++++++++++++------- src/interpretation/bindings.rs | 10 ++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index 7776dff5..ba9b359a 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -305,13 +305,25 @@ The issue is that we get a variable stored as `arr := DisabledMutable` and then - [x] Emplacing - [x] Map, Try map - [x] Ability to map deeper. Should take a `PathExtension` and a new span. -- [ ] Try replacing existing RefCell based abstractions with new custom dynamic references, - and report on what breaks: - - `pub(crate) type Referenceable = Rc>;` becomes `pub(crate) type Referenceable = dynamic_references::Referenceable` - - `Shared` / `SharedSubRcRefCell` - both become type aliases for `SharedReference` - - `DisabledShared` becomes `InactiveSharedReference` - - `Mutable` / `MutableSubRcRefCell` - becomes type alias for `MutableReference` - - `DisabledMutable` becomes `InactiveMutableReference` +- [x] Try replacing existing RefCell based abstractions with new custom dynamic references, and report on what breaks: + - `pub(crate) type Referenceable = Rc>;` becomes `pub(crate) type Referenceable = dynamic_references::Referenceable` + - `Shared` / `SharedSubRcRefCell` - both become type aliases for `SharedReference` + - `DisabledShared` becomes `InactiveSharedReference` + - `Mutable` / `MutableSubRcRefCell` - becomes type alias for `MutableReference` + - `DisabledMutable` becomes `InactiveMutableReference` +- [ ] Replace all the aliases: + - Remove `QqqShared`, `QqqMutable`, `SharedValue`, `AssigneeValue` + - Move `type Shared` and `type Mutable` alongside `SharedReference` / `MutableReference` + - Remove references to `MutableReference` and `SharedReference` outside these aliases + - Rename `SharedReference -> Shared` and `MutableReference -> Mutable` + - Rename `DisabledShared` -> `InactiveShared` and same for `Mutable` + - Remove `type AssigneeValue` and `type SharedValue` + - Move `Assignee` out of bindings. To e.g. `dynamic_references` + - See what else can be deleted from `bindings.rs` + - Rename `PathExtension::Tightened` to `PathExtension::TypeNarrowed` + - Rename `DisabledArgumentValue` and `DisabledCopyOnWrite` to + `Inactive__` and their method from `enable` to `activate` and ditto + with `disable -> deactivate` ### Other ideas diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 9fc8f7b7..fca3309b 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -614,17 +614,11 @@ where AnyLevelCopyOnWrite::::Owned(owned).into_copy_on_write() } CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - let content = shared.emplace_map(|inner, emplacer| { - inner.as_ref_value().into_shared(emplacer, None) - }); - AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(content) + AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(shared.into_content()) .into_copy_on_write() } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - let content = shared.emplace_map(|inner, emplacer| { - inner.as_ref_value().into_shared(emplacer, None) - }); - AnyLevelCopyOnWrite::::SharedWithTransparentCloning(content) + AnyLevelCopyOnWrite::::SharedWithTransparentCloning(shared.into_content()) .into_copy_on_write() } } From b2459b4b94915ae2e1994aba809d71898942f190 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 28 Feb 2026 08:38:20 +0000 Subject: [PATCH 42/49] refactor: Simplify aliases for forms --- plans/TODO.md | 55 ++-- src/expressions/closures.rs | 4 +- src/expressions/concepts/forms/any_mut.rs | 4 +- src/expressions/concepts/forms/any_ref.rs | 4 +- src/expressions/concepts/forms/argument.rs | 6 +- src/expressions/concepts/forms/assignee.rs | 55 +++- .../concepts/forms/copy_on_write.rs | 6 +- src/expressions/concepts/forms/late_bound.rs | 4 +- src/expressions/concepts/forms/mutable.rs | 16 +- src/expressions/concepts/forms/shared.rs | 16 +- src/expressions/concepts/forms/simple_mut.rs | 17 +- src/expressions/concepts/forms/simple_ref.rs | 11 +- src/expressions/evaluation/evaluator.rs | 6 +- src/expressions/evaluation/node_conversion.rs | 2 +- src/expressions/evaluation/value_frames.rs | 103 ++++---- src/expressions/expression.rs | 4 +- src/expressions/expression_parsing.rs | 6 +- src/expressions/type_resolution/arguments.rs | 57 +++-- src/expressions/type_resolution/outputs.rs | 2 +- src/expressions/type_resolution/type_kinds.rs | 8 +- src/expressions/values/any_value.rs | 23 +- src/expressions/values/function.rs | 2 +- src/interpretation/bindings.rs | 235 +++++------------- src/interpretation/refs.rs | 207 ++++++++------- src/interpretation/variable.rs | 2 +- .../dynamic_references/mutable_reference.rs | 88 +++---- src/misc/dynamic_references/reference_core.rs | 3 +- src/misc/dynamic_references/referenceable.rs | 30 +-- .../dynamic_references/shared_reference.rs | 67 +++-- src/misc/errors.rs | 9 + 30 files changed, 490 insertions(+), 562 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index ba9b359a..eba29e01 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -272,36 +272,13 @@ Suddenly dawned on me - my Disabled arguments might not be safe. * Imagine if I get `my_arr = [[]]` a Shared `my_arr[0]` then do `my_arr.pop()` * Then we enable `my_arr[0]` and get a use after free(!!). e.g. `my_arr[0].push(my_arr.pop())`. -Instead, as per Rust two-phase borrows https://rustc-dev-guide.rust-lang.org/borrow-check/two-phase-borrows.html - -- We shouldn't be able to disable a `Shared` -- A `Disabled` becomes a `Shared`... - -I have a branch `spike/fix-disabled-abstraction` to explore this... Which converts a Shared back to a Mutable (which I think is technically UB and can't be done without it). Regardless, it works for a spike, and we get a few failures: -* `stream_append_can_use_self_in_appender` - these are OK to fail! -* This test is *not* OK to fail: -```rust -#[test] -fn can_pass_owned_to_mutable_argument() { - run! { - let push_twice_and_return_mut = |arr: &mut any| { - arr.push(0); - arr.push(0); - arr - }; - %[_].assert_eq(push_twice_and_return_mut([1, 2, 3]).len(), 5); - } -} -``` - -The issue is that we get a variable stored as `arr := DisabledMutable` and then when we do `arr.push()` we clone the DisabledMutable, and then enable it... - ### Task list - [x] Write up model in `dynamic_references/mod.rs` - [x] Add structure for `dynamic_references` - [x] Create Referenceable, and Reference types - [x] Create error messages for rule breaks in `referenceable.rs` -- [ ] Add the following to `ReferenceCore` and maybe others: +- [x] Add the following to `ReferenceCore` and maybe others: - [x] Emplacing - [x] Map, Try map - [x] Ability to map deeper. Should take a `PathExtension` and a new span. @@ -311,19 +288,23 @@ The issue is that we get a variable stored as `arr := DisabledMutable` and then - `DisabledShared` becomes `InactiveSharedReference` - `Mutable` / `MutableSubRcRefCell` - becomes type alias for `MutableReference` - `DisabledMutable` becomes `InactiveMutableReference` -- [ ] Replace all the aliases: - - Remove `QqqShared`, `QqqMutable`, `SharedValue`, `AssigneeValue` - - Move `type Shared` and `type Mutable` alongside `SharedReference` / `MutableReference` - - Remove references to `MutableReference` and `SharedReference` outside these aliases - - Rename `SharedReference -> Shared` and `MutableReference -> Mutable` - - Rename `DisabledShared` -> `InactiveShared` and same for `Mutable` - - Remove `type AssigneeValue` and `type SharedValue` - - Move `Assignee` out of bindings. To e.g. `dynamic_references` - - See what else can be deleted from `bindings.rs` - - Rename `PathExtension::Tightened` to `PathExtension::TypeNarrowed` - - Rename `DisabledArgumentValue` and `DisabledCopyOnWrite` to - `Inactive__` and their method from `enable` to `activate` and ditto - with `disable -> deactivate` +- [x] Replace all the aliases: + - [x] Remove `Shared`, `Mutable`, `SharedValue`, `AssigneeValue` + - [x] Move `type Shared` and `type Mutable` alongside `SharedReference` / `MutableReference` + - [x] Remove references to `MutableReference` and `SharedReference` outside these aliases + - [x] Rename `SharedReference -> Shared` and `MutableReference -> Mutable` + - [x] Rename `DisabledShared` -> `InactiveShared` and same for `Mutable` + - [x] Remove `type AssigneeValue` and `type SharedValue` + - [x] Move `Assignee` out of bindings. To e.g. `dynamic_references` + - [x] Rename `PathExtension::Tightening` to `PathExtension::TypeNarrowing` + - [x] Rename `DisabledArgumentValue` and `DisabledCopyOnWrite` to `Inactive__` and their method from `enable` to `activate` and ditto with `disable -> deactivate` +- [ ] See what else can be deleted from `bindings.rs` + - [ ] Unify LateBound into `QqqLateBound` + - [ ] Unify CopyOnWrite into `QqqCopyOnWrite` +- [ ] Add various tests: + - [ ] Stretching different error messages + - [ ] Showing I can do e.g. `x.a += x.b` + - [ ] Show that `let my_arr = [[]]; my_arr[0].push(my_arr.pop())` gives a suitable error ### Other ideas diff --git a/src/expressions/closures.rs b/src/expressions/closures.rs index 5c1da71a..1bff2d1c 100644 --- a/src/expressions/closures.rs +++ b/src/expressions/closures.rs @@ -89,7 +89,7 @@ impl ClosureValue { (Pattern::Variable(variable), ArgumentValue::Shared(shared)) => { context.interpreter.define_variable( variable.definition.id, - VariableContent::Shared(shared.disable()), + VariableContent::Shared(shared.deactivate()), ); } (_, ArgumentValue::Shared(_)) => { @@ -100,7 +100,7 @@ impl ClosureValue { (Pattern::Variable(variable), ArgumentValue::Mutable(mutable)) => { context.interpreter.define_variable( variable.definition.id, - VariableContent::Mutable(mutable.disable()), + VariableContent::Mutable(mutable.deactivate()), ); } (_, ArgumentValue::Mutable(_)) => { diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index a4761ec8..bf08c3ce 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -43,9 +43,9 @@ impl IsDynCompatibleForm for BeAnyMut { { leaf.emplace_map(|content, emplacer| match ::map_mut(content) { Ok(mapped) => { - // SAFETY: PathExtension::Tightened correctly describes type narrowing + // SAFETY: PathExtension is correct for mapping to a dyn type let mapped_mut = unsafe { - MappedMut::new(mapped, PathExtension::Tightened(D::type_kind()), span) + MappedMut::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) }; Ok(emplacer.emplace(mapped_mut)) } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index 51407b86..e073e31a 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -44,9 +44,9 @@ impl IsDynCompatibleForm for BeAnyRef { { leaf.emplace_map(|content, emplacer| match ::map_ref(content) { Ok(mapped) => { - // SAFETY: PathExtension::Tightened correctly describes type narrowing + // SAFETY: PathExtension is correct for mapping to a dyn type let mapped_ref = unsafe { - MappedRef::new(mapped, PathExtension::Tightened(D::type_kind()), span) + MappedRef::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) }; Ok(emplacer.emplace(mapped_ref)) } diff --git a/src/expressions/concepts/forms/argument.rs b/src/expressions/concepts/forms/argument.rs index ce455b06..5c260ced 100644 --- a/src/expressions/concepts/forms/argument.rs +++ b/src/expressions/concepts/forms/argument.rs @@ -3,9 +3,9 @@ use super::*; pub(crate) enum Argument { Owned(L), CopyOnWrite(QqqCopyOnWrite), - Mutable(QqqMutable), - Assignee(QqqAssignee), - Shared(QqqShared), + Mutable(Mutable), + Assignee(Assignee), + Shared(Shared), } impl IsValueContent for Argument { diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index 37a8744c..9973b56c 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -1,30 +1,61 @@ use super::*; -pub(crate) struct QqqAssignee(pub(crate) MutableReference); +/// A binding of a unique (mutable) reference to a value. +/// See [`ArgumentOwnership::Assignee`] for more details. +/// +/// If you need span information, wrap with `Spanned>`. +pub(crate) struct Assignee(pub(crate) Mutable); -impl IsValueContent for QqqAssignee { - type Type = L::Type; +impl AnyValueAssignee { + pub(crate) fn set(&mut self, content: impl IntoAnyValue) { + *self.0 = content.into_any_value(); + } +} + +impl IsValueContent for Assignee { + type Type = X::Type; type Form = BeAssignee; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqAssignee { +impl<'a, X: IsValueContent> IntoValueContent<'a> for Assignee +where + X: 'static, + X::Type: IsHierarchicalType = X>, + X::Form: IsHierarchicalForm, + X::Form: LeafAsMutForm, +{ fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self + self.0 + .emplace_map(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer, None)) } } -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqAssignee { +impl<'a, L: IsValueLeaf> FromValueContent<'a> for Assignee { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } } +impl Deref for Assignee { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Assignee { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + #[derive(Copy, Clone)] pub(crate) struct BeAssignee; impl IsForm for BeAssignee {} impl IsHierarchicalForm for BeAssignee { - type Leaf<'a, T: IsLeafType> = QqqAssignee; + type Leaf<'a, T: IsLeafType> = Assignee; #[inline] fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> @@ -36,7 +67,7 @@ impl IsHierarchicalForm for BeAssignee { } impl IsDynCompatibleForm for BeAssignee { - type DynLeaf<'a, D: IsDynType> = QqqAssignee; + type DynLeaf<'a, D: IsDynType> = Assignee; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( Spanned(leaf, span): Spanned>, @@ -47,13 +78,13 @@ impl IsDynCompatibleForm for BeAssignee { leaf.0 .emplace_map(|content, emplacer| match ::map_mut(content) { Ok(mapped) => { - // SAFETY: PathExtension::Tightened correctly describes type narrowing + // SAFETY: PathExtension is correct for mapping to a dyn type let mapped_mut = unsafe { - MappedMut::new(mapped, PathExtension::Tightened(D::type_kind()), span) + MappedMut::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) }; - Ok(QqqAssignee(emplacer.emplace(mapped_mut))) + Ok(Assignee(emplacer.emplace(mapped_mut))) } - Err(_this) => Err(QqqAssignee(emplacer.revert())), + Err(_this) => Err(Assignee(emplacer.revert())), }) } } diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index 742d01bc..7bec58b9 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -5,10 +5,10 @@ pub(crate) enum QqqCopyOnWrite { Owned(Owned), /// For use when the CopyOnWrite value effectively represents the owned value (post-clone). /// In this case, returning a Cow is just an optimization and we can always clone infallibly. - SharedWithInfallibleCloning(QqqShared), + SharedWithInfallibleCloning(Shared), /// For use when the CopyOnWrite value represents a pre-cloned read-only value. /// A transparent clone may fail in this case at use time. - SharedWithTransparentCloning(QqqShared), + SharedWithTransparentCloning(Shared), } impl IsValueContent for QqqCopyOnWrite { @@ -221,7 +221,7 @@ where // // Apply map, get Shared> // let shared = map_via_leaf! { // input: (Content<'a, Self::Type, BeShared>) = shared, - // fn map_leaf(leaf) -> (ExecutionResult>>) { + // fn map_leaf(leaf) -> (ExecutionResult>>) { // leaf.try_map(|value_ref| { // let from_ref = value_ref.into_any(); // inner_map_ref(from_ref) // &Content diff --git a/src/expressions/concepts/forms/late_bound.rs b/src/expressions/concepts/forms/late_bound.rs index 67ba3bc1..22f45a18 100644 --- a/src/expressions/concepts/forms/late_bound.rs +++ b/src/expressions/concepts/forms/late_bound.rs @@ -6,7 +6,7 @@ pub(crate) enum QqqLateBound { /// A copy-on-write value that can be converted to an owned value CopyOnWrite(QqqCopyOnWrite), /// A mutable reference - Mutable(QqqMutable), + Mutable(Mutable), /// A shared reference where mutable access failed for a specific reason Shared(QqqLateBoundShared), } @@ -61,6 +61,6 @@ pub(crate) struct QqqLateBoundOwned { /// A shared value where mutable access failed for a specific reason pub(crate) struct QqqLateBoundShared { - pub(crate) shared: QqqShared, + pub(crate) shared: Shared, pub(crate) reason_not_mutable: syn::Error, } diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index 40e57ca5..e26fcf36 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -1,13 +1,11 @@ use super::*; -pub(crate) type QqqMutable = MutableReference; - -impl IsValueContent for QqqMutable { +impl IsValueContent for Mutable { type Type = X::Type; type Form = BeMutable; } -impl<'a, X: IsValueContent> IntoValueContent<'a> for QqqMutable +impl<'a, X: IsValueContent> IntoValueContent<'a> for Mutable where X: 'static, X::Type: IsHierarchicalType = X>, @@ -22,7 +20,7 @@ where // Note we can't implement this more widely than leaves. // This is because e.g. Content has Mutable() in its leaves, // This can't be mapped back to having Mutable(Content). -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqMutable { +impl<'a, L: IsValueLeaf> FromValueContent<'a> for Mutable { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } @@ -33,7 +31,7 @@ pub(crate) struct BeMutable; impl IsForm for BeMutable {} impl IsHierarchicalForm for BeMutable { - type Leaf<'a, T: IsLeafType> = QqqMutable; + type Leaf<'a, T: IsLeafType> = Mutable; #[inline] fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> @@ -45,7 +43,7 @@ impl IsHierarchicalForm for BeMutable { } impl IsDynCompatibleForm for BeMutable { - type DynLeaf<'a, D: IsDynType> = QqqMutable; + type DynLeaf<'a, D: IsDynType> = Mutable; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( Spanned(leaf, span): Spanned>, @@ -55,9 +53,9 @@ impl IsDynCompatibleForm for BeMutable { { leaf.emplace_map(|content, emplacer| match ::map_mut(content) { Ok(mapped) => { - // SAFETY: PathExtension::Tightened correctly describes type narrowing + // SAFETY: PathExtension is correct for mapping to a dyn type let mapped_mut = unsafe { - MappedMut::new(mapped, PathExtension::Tightened(D::type_kind()), span) + MappedMut::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) }; Ok(emplacer.emplace(mapped_mut)) } diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index 5015adf5..bd22f6bc 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -1,13 +1,11 @@ use super::*; -pub(crate) type QqqShared = SharedReference; - -impl IsValueContent for QqqShared { +impl IsValueContent for Shared { type Type = X::Type; type Form = BeShared; } -impl<'a, X: IsValueContent> IntoValueContent<'a> for QqqShared +impl<'a, X: IsValueContent> IntoValueContent<'a> for Shared where X: 'static, X::Type: IsHierarchicalType = X>, @@ -22,7 +20,7 @@ where // Note we can't implement this more widely than leaves. // This is because e.g. Content has Shared() in its leaves, // This can't be mapped back to having Shared(Content). -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqShared { +impl<'a, L: IsValueLeaf> FromValueContent<'a> for Shared { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } @@ -33,7 +31,7 @@ pub(crate) struct BeShared; impl IsForm for BeShared {} impl IsHierarchicalForm for BeShared { - type Leaf<'a, T: IsLeafType> = QqqShared; + type Leaf<'a, T: IsLeafType> = Shared; #[inline] fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> @@ -45,7 +43,7 @@ impl IsHierarchicalForm for BeShared { } impl IsDynCompatibleForm for BeShared { - type DynLeaf<'a, D: IsDynType> = QqqShared; + type DynLeaf<'a, D: IsDynType> = Shared; fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( Spanned(leaf, span): Spanned>, @@ -55,9 +53,9 @@ impl IsDynCompatibleForm for BeShared { { leaf.emplace_map(|content, emplacer| match ::map_ref(content) { Ok(mapped) => { - // SAFETY: PathExtension::Tightened correctly describes type narrowing + // SAFETY: PathExtension is correct for mapping to a dyn type let mapped_ref = unsafe { - MappedRef::new(mapped, PathExtension::Tightened(D::type_kind()), span) + MappedRef::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) }; Ok(emplacer.emplace(mapped_ref)) } diff --git a/src/expressions/concepts/forms/simple_mut.rs b/src/expressions/concepts/forms/simple_mut.rs index ff447af5..e7175dd5 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -86,11 +86,11 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and - // PathExtension::Tightened correctly describes type narrowing + // PathExtension correctly describes mapping to a leaf type T let mapped = unsafe { MappedMut::new_unchecked( leaf, - PathExtension::Tightened(T::type_kind()), + PathExtension::TypeNarrowing(T::type_kind()), self.span, ) }; @@ -128,15 +128,15 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and - // PathExtension::Tightened correctly describes type narrowing + // PathExtension correctly describes mapping to a leaf type T let mapped = unsafe { MappedMut::new_unchecked( leaf, - PathExtension::Tightened(T::type_kind()), + PathExtension::TypeNarrowing(T::type_kind()), self.span, ) }; - QqqAssignee(self.emplacer.emplace(mapped)) + Assignee(self.emplacer.emplace(mapped)) } }; let __mapper = __InlineMapper { emplacer, span }; @@ -170,16 +170,15 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and - // PathExtension::Tightened correctly describes type narrowing + // PathExtension correctly describes mapping to a leaf type T let mapped = unsafe { MappedMut::new_unchecked( leaf, - PathExtension::Tightened(T::type_kind()), + PathExtension::TypeNarrowing(T::type_kind()), self.span, ) }; - let mutable_ref: MutableReference<_> = self.emplacer.emplace(mapped); - mutable_ref.into() + self.emplacer.emplace(mapped).into() } }; let __mapper = __InlineMapper { emplacer, span }; diff --git a/src/expressions/concepts/forms/simple_ref.rs b/src/expressions/concepts/forms/simple_ref.rs index c9086171..1d36b72a 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -86,11 +86,11 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and - // PathExtension::Tightened correctly describes type narrowing + // PathExtension correctly describes mapping to a leaf type T let mapped = unsafe { MappedRef::new_unchecked( leaf, - PathExtension::Tightened(T::type_kind()), + PathExtension::TypeNarrowing(T::type_kind()), self.span, ) }; @@ -128,16 +128,15 @@ where leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and - // PathExtension::Tightened correctly describes type narrowing + // PathExtension correctly describes mapping to a leaf type T let mapped = unsafe { MappedRef::new_unchecked( leaf, - PathExtension::Tightened(T::type_kind()), + PathExtension::TypeNarrowing(T::type_kind()), self.span, ) }; - let shared_ref: SharedReference<_> = self.emplacer.emplace(mapped); - shared_ref.into() + self.emplacer.emplace(mapped).into() } }; let __mapper = __InlineMapper { emplacer, span }; diff --git a/src/expressions/evaluation/evaluator.rs b/src/expressions/evaluation/evaluator.rs index 1576b1ad..2c80252b 100644 --- a/src/expressions/evaluation/evaluator.rs +++ b/src/expressions/evaluation/evaluator.rs @@ -149,8 +149,8 @@ pub(crate) enum RequestedValue { Owned(AnyValueOwned), Shared(AnyValueShared), Mutable(AnyValueMutable), - CopyOnWrite(CopyOnWriteValue), - Assignee(AssigneeValue), + CopyOnWrite(AnyValueCopyOnWrite), + Assignee(AnyValueAssignee), // RequestedOwnership::LateBound // ------------------------------- @@ -177,7 +177,7 @@ impl RequestedValue { } #[allow(dead_code)] - pub(crate) fn expect_copy_on_write(self) -> CopyOnWriteValue { + pub(crate) fn expect_copy_on_write(self) -> AnyValueCopyOnWrite { match self { RequestedValue::CopyOnWrite(copy_on_write) => copy_on_write, _ => panic!("expect_copy_on_write() called on non-copy-on-write RequestedValue"), diff --git a/src/expressions/evaluation/node_conversion.rs b/src/expressions/evaluation/node_conversion.rs index 68333d37..ca19336f 100644 --- a/src/expressions/evaluation/node_conversion.rs +++ b/src/expressions/evaluation/node_conversion.rs @@ -32,7 +32,7 @@ impl ExpressionNode { // We return a freely clonable CopyOnWrite in order to delay the clone of the literal if it's not necessary // This allows something like e.g. x[0][5][2] to only clone the innermost value instead of the full multi-dimensional array let shared_cloned = Shared::clone(value); - let cow = CopyOnWriteValue::shared_in_place_of_owned(shared_cloned); + let cow = AnyValueCopyOnWrite::shared_in_place_of_owned(shared_cloned); context.return_returned_value(Spanned( ReturnedValue::CopyOnWrite(cow), *span_range, diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index d88224a3..ae160ee0 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -7,7 +7,7 @@ use super::*; /// which indicates what ownership type to resolve to. pub(crate) enum ArgumentValue { Owned(AnyValueOwned), - CopyOnWrite(CopyOnWriteValue), + CopyOnWrite(AnyValueCopyOnWrite), Mutable(AnyValueMutable), Assignee(AnyValueAssignee), Shared(AnyValueShared), @@ -21,7 +21,7 @@ impl ArgumentValue { } } - pub(crate) fn expect_copy_on_write(self) -> CopyOnWriteValue { + pub(crate) fn expect_copy_on_write(self) -> AnyValueCopyOnWrite { match self { ArgumentValue::CopyOnWrite(value) => value, _ => panic!("expect_copy_on_write() called on a non-copy-on-write ArgumentValue"), @@ -49,65 +49,65 @@ impl ArgumentValue { } } - /// Disables this argument value, releasing any borrow on the RefCell. - /// Returns a `DisabledArgumentValue` which can be cloned and later re-enabled. - pub(crate) fn disable(self) -> DisabledArgumentValue { + /// Deactivates this argument value, releasing any borrow on the RefCell. + /// Returns a `InactiveArgumentValue` which can be cloned and later re-enabled. + pub(crate) fn deactivate(self) -> InactiveArgumentValue { match self { - ArgumentValue::Owned(owned) => DisabledArgumentValue::Owned(owned), + ArgumentValue::Owned(owned) => InactiveArgumentValue::Owned(owned), ArgumentValue::CopyOnWrite(copy_on_write) => { - DisabledArgumentValue::CopyOnWrite(copy_on_write.disable()) + InactiveArgumentValue::CopyOnWrite(copy_on_write.deactivate()) } - ArgumentValue::Mutable(mutable) => DisabledArgumentValue::Mutable(mutable.disable()), + ArgumentValue::Mutable(mutable) => InactiveArgumentValue::Mutable(mutable.deactivate()), ArgumentValue::Assignee(Assignee(mutable)) => { - DisabledArgumentValue::Assignee(mutable.disable()) + InactiveArgumentValue::Assignee(mutable.deactivate()) } - ArgumentValue::Shared(shared) => DisabledArgumentValue::Shared(shared.disable()), + ArgumentValue::Shared(shared) => InactiveArgumentValue::Shared(shared.deactivate()), } } } -/// A disabled argument value that can be safely cloned and dropped. -pub(crate) enum DisabledArgumentValue { +/// An inactive argument value that can be safely cloned and dropped. +pub(crate) enum InactiveArgumentValue { Owned(AnyValueOwned), - CopyOnWrite(DisabledCopyOnWrite), - Mutable(DisabledMutable), - Assignee(DisabledMutable), - Shared(DisabledShared), + CopyOnWrite(InactiveCopyOnWrite), + Mutable(InactiveMutable), + Assignee(InactiveMutable), + Shared(InactiveShared), } -impl Clone for DisabledArgumentValue { +impl Clone for InactiveArgumentValue { fn clone(&self) -> Self { match self { - DisabledArgumentValue::Owned(owned) => DisabledArgumentValue::Owned(owned.clone()), - DisabledArgumentValue::CopyOnWrite(copy_on_write) => { - DisabledArgumentValue::CopyOnWrite(copy_on_write.clone()) + InactiveArgumentValue::Owned(owned) => InactiveArgumentValue::Owned(owned.clone()), + InactiveArgumentValue::CopyOnWrite(copy_on_write) => { + InactiveArgumentValue::CopyOnWrite(copy_on_write.clone()) } - DisabledArgumentValue::Mutable(mutable) => { - DisabledArgumentValue::Mutable(mutable.clone()) + InactiveArgumentValue::Mutable(mutable) => { + InactiveArgumentValue::Mutable(mutable.clone()) } - DisabledArgumentValue::Assignee(assignee) => { - DisabledArgumentValue::Assignee(assignee.clone()) + InactiveArgumentValue::Assignee(assignee) => { + InactiveArgumentValue::Assignee(assignee.clone()) } - DisabledArgumentValue::Shared(shared) => DisabledArgumentValue::Shared(shared.clone()), + InactiveArgumentValue::Shared(shared) => InactiveArgumentValue::Shared(shared.clone()), } } } -impl DisabledArgumentValue { +impl InactiveArgumentValue { /// Re-enables this disabled argument value by re-acquiring any borrow. pub(crate) fn enable(self, span: SpanRange) -> FunctionResult { match self { - DisabledArgumentValue::Owned(owned) => Ok(ArgumentValue::Owned(owned)), - DisabledArgumentValue::CopyOnWrite(copy_on_write) => { - Ok(ArgumentValue::CopyOnWrite(copy_on_write.enable(span)?)) + InactiveArgumentValue::Owned(owned) => Ok(ArgumentValue::Owned(owned)), + InactiveArgumentValue::CopyOnWrite(copy_on_write) => { + Ok(ArgumentValue::CopyOnWrite(copy_on_write.activate(span)?)) } - DisabledArgumentValue::Mutable(inactive) => { + InactiveArgumentValue::Mutable(inactive) => { Ok(ArgumentValue::Mutable(inactive.activate(span)?)) } - DisabledArgumentValue::Assignee(inactive) => { + InactiveArgumentValue::Assignee(inactive) => { Ok(ArgumentValue::Assignee(Assignee(inactive.activate(span)?))) } - DisabledArgumentValue::Shared(inactive) => { + InactiveArgumentValue::Shared(inactive) => { Ok(ArgumentValue::Shared(inactive.activate(span)?)) } } @@ -164,8 +164,7 @@ impl AsRef for ArgumentValue { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum RequestedOwnership { - /// Receives any of Owned, SharedReference or MutableReference, depending on what - /// is available. + /// Receives any of Owned, Shared or Mutable, depending on what is available. /// This can then be used to resolve the value kind, and use the correct one. LateBound, /// A concrete value of the correct type. @@ -297,7 +296,7 @@ impl RequestedOwnership { pub(crate) fn map_from_copy_on_write( &self, - Spanned(cow, span): Spanned, + Spanned(cow, span): Spanned, ) -> FunctionResult> { Ok(Spanned( match self { @@ -331,7 +330,7 @@ impl RequestedOwnership { pub(crate) fn map_from_assignee( &self, - Spanned(assignee, span): Spanned, + Spanned(assignee, span): Spanned, ) -> FunctionResult> { Ok(Spanned( match self { @@ -348,7 +347,7 @@ impl RequestedOwnership { pub(crate) fn map_from_shared( &self, - Spanned(shared, span): Spanned, + Spanned(shared, span): Spanned, ) -> FunctionResult> { Ok(Spanned( match self { @@ -446,7 +445,7 @@ impl ArgumentOwnership { pub(crate) fn map_from_copy_on_write( &self, - Spanned(copy_on_write, span): Spanned, + Spanned(copy_on_write, span): Spanned, ) -> FunctionResult { match self { ArgumentOwnership::Owned => Ok(ArgumentValue::Owned( @@ -479,7 +478,7 @@ impl ArgumentOwnership { pub(crate) fn map_from_shared( &self, - shared: Spanned, + shared: Spanned, ) -> FunctionResult { self.map_from_shared_with_error_reason( shared, @@ -489,13 +488,13 @@ impl ArgumentOwnership { fn map_from_shared_with_error_reason( &self, - Spanned(shared, span): Spanned, + Spanned(shared, span): Spanned, mutable_error: impl FnOnce(SpanRange) -> FunctionError, ) -> FunctionResult { match self { - ArgumentOwnership::Owned => Ok(ArgumentValue::Owned( - Spanned(shared, span).transparent_clone()?, - )), + ArgumentOwnership::Owned => { + Ok(ArgumentValue::Owned(shared.try_transparent_clone(span)?)) + } ArgumentOwnership::CopyOnWrite => Ok(ArgumentValue::CopyOnWrite( CopyOnWrite::shared_in_place_of_shared(shared), )), @@ -516,7 +515,7 @@ impl ArgumentOwnership { pub(crate) fn map_from_assignee( &self, - Spanned(assignee, span): Spanned, + Spanned(assignee, span): Spanned, ) -> FunctionResult { self.map_from_mutable_inner(Spanned(assignee.0, span), false) } @@ -529,9 +528,7 @@ impl ArgumentOwnership { match self { ArgumentOwnership::Owned => { if is_late_bound { - Ok(ArgumentValue::Owned( - Spanned(mutable, span).transparent_clone()?, - )) + Ok(ArgumentValue::Owned(mutable.try_transparent_clone(span)?)) } else { span.ownership_err("An owned value is required, but a mutable reference was received. This indicates a possible bug. If this was intended, use `.clone()` to get an owned value.") } @@ -874,7 +871,7 @@ enum BinaryPath { right: ExpressionNodeId, }, OnRightBranch { - left: Spanned, + left: Spanned, interface: BinaryOperationInterface, }, } @@ -935,7 +932,7 @@ impl EvaluationFrame for BinaryOperationBuilder { let left = Spanned(left, left_span); // Disable left so we can evaluate right without borrow conflicts - let left = left.map(|v| v.disable()); + let left = left.map(|v| v.deactivate()); self.state = BinaryPath::OnRightBranch { left, interface }; context.request_argument_value(self, right, rhs_ownership) @@ -960,7 +957,7 @@ impl EvaluationFrame for BinaryOperationBuilder { // If left and right clash, then the error message should be on the right, not the left // Disable right, then re-enable left first (for intuitive error messages) - let right = right.map(|v| v.disable()); + let right = right.map(|v| v.deactivate()); let left_span = left.1; let right_span = right.1; let left = left.try_map(|v| v.enable(left_span))?; @@ -1034,7 +1031,7 @@ impl EvaluationFrame for ValuePropertyAccessBuilder { .map_from_late_bound(receiver.spanned(source_span))? .spanned(source_span); // Disable receiver so it can be stored in the FunctionValue - let receiver = receiver.map(|v| v.disable()); + let receiver = receiver.map(|v| v.deactivate()); let function_value = FunctionValue { invokable: InvokableFunction::Native(method), disabled_bound_arguments: vec![receiver], @@ -1348,7 +1345,7 @@ enum InvocationPath { ArgumentsPath { function_span: SpanRange, invokable: InvokableFunction, - disabled_evaluated_arguments: Vec>, + disabled_evaluated_arguments: Vec>, }, } @@ -1471,7 +1468,7 @@ impl EvaluationFrame for InvocationBuilder { let argument = value.expect_argument_value(); let argument = Spanned(argument, span); // Disable argument so we can evaluate remaining arguments without borrow conflicts - let argument = argument.map(|v| v.disable()); + let argument = argument.map(|v| v.deactivate()); disabled_evaluated_arguments.push(argument); } }; diff --git a/src/expressions/expression.rs b/src/expressions/expression.rs index 6c3a4829..e9f8a561 100644 --- a/src/expressions/expression.rs +++ b/src/expressions/expression.rs @@ -50,7 +50,7 @@ impl Expression { pub(crate) fn evaluate_shared( &self, interpreter: &mut Interpreter, - ) -> ExecutionResult> { + ) -> ExecutionResult> { Ok(self .evaluate(interpreter, RequestedOwnership::shared())? .expect_shared()) @@ -154,7 +154,7 @@ pub(super) enum Leaf { Variable(VariableReference), TypeProperty(TypeProperty), Discarded(Token![_]), - Value(Spanned), + Value(Spanned), StreamLiteral(StreamLiteral), ParseTemplateLiteral(ParseTemplateLiteral), IfExpression(Box), diff --git a/src/expressions/expression_parsing.rs b/src/expressions/expression_parsing.rs index 4bc53b20..9779be1e 100644 --- a/src/expressions/expression_parsing.rs +++ b/src/expressions/expression_parsing.rs @@ -123,7 +123,7 @@ impl<'a> ExpressionParser<'a> { "true" | "false" => { let bool = input.parse::()?; UnaryAtom::Leaf(Leaf::Value(Spanned( - SharedValue::new_from_owned(bool.value.into_any_value(), None, bool.span.span_range()), + AnyValueShared::new_from_owned(bool.value.into_any_value(), None, bool.span.span_range()), bool.span.span_range(), ))) } @@ -136,7 +136,7 @@ impl<'a> ExpressionParser<'a> { "None" => { let none_ident = input.parse_any_ident()?; // consume the "None" token UnaryAtom::Leaf(Leaf::Value(Spanned( - SharedValue::new_from_owned(().into_any_value(), None, none_ident.span().span_range()), + AnyValueShared::new_from_owned(().into_any_value(), None, none_ident.span().span_range()), none_ident.span().span_range(), ))) } @@ -155,7 +155,7 @@ impl<'a> ExpressionParser<'a> { let lit: syn::Lit = input.parse()?; let span_range = lit.span().span_range(); UnaryAtom::Leaf(Leaf::Value(Spanned( - SharedValue::new_from_owned(AnyValue::for_syn_lit(lit), None, span_range), + AnyValueShared::new_from_owned(AnyValue::for_syn_lit(lit), None, span_range), span_range, ))) }, diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index be59dc96..82c6b99e 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -80,14 +80,45 @@ impl IsArgument for Shared { // } // } -impl + ResolvableArgumentTarget + ?Sized> IsArgument - for Assignee -{ - type ValueType = T::ValueType; +// We have some friction: +// - Assignee wants to take whole parent types e.g. AnyValue or FloatValue, so it +// can replace them +// - The blanket impl of IsArgument for X: FromValueContent<'static, Type = T, Form = F> +// breaks down, because this only works for LeafContent based types (which e.g. have +// Assignee in each leaf, which isn't very useful) +// - But FromValueContent for leaves is required by the Form system +// - So for now, we're stuck implementing IsArgument manually for Assignee for each parent +// X that we need. +// +// TODO[non-leaf-form]: Future work - +// - Rename the Content type alias/associated type to LeafContent or something +// - Make IsValueContent / FromValueContent more flexible, so it can capture the group level +// somehow, and take values with the content at any given level above the input level. + +impl IsArgument for Assignee { + type ValueType = AnyType; + const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Assignee { auto_create: false }; + + fn from_argument(argument: Spanned) -> FunctionResult { + Ok(argument.expect_assignee().0) + } +} + +impl IsArgument for Assignee { + type ValueType = FloatType; + const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Assignee { auto_create: false }; + + fn from_argument(argument: Spanned) -> FunctionResult { + FloatValue::resolve_assignee(argument.expect_assignee(), "This argument") + } +} + +impl IsArgument for Assignee { + type ValueType = IntegerType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Assignee { auto_create: false }; fn from_argument(argument: Spanned) -> FunctionResult { - T::resolve_assignee(argument.expect_assignee(), "This argument") + IntegerValue::resolve_assignee(argument.expect_assignee(), "This argument") } } @@ -122,10 +153,9 @@ impl IsArgument for Mutable { // } // } -impl + ResolvableArgumentTarget + ToOwned> IsArgument - for CopyOnWrite +impl + ResolvableArgumentTarget> IsArgument for CopyOnWrite where - T::Owned: ResolvableOwned, + T: ResolvableOwned, { type ValueType = T::ValueType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::CopyOnWrite; @@ -133,12 +163,7 @@ where fn from_argument(Spanned(value, span): Spanned) -> FunctionResult { value.expect_copy_on_write().map( |v| T::resolve_shared(v.spanned(span), "This argument"), - |v| { - >::resolve_value( - v.spanned(span), - "This argument", - ) - }, + |v| T::resolve_value(v.spanned(span), "This argument"), ) } } @@ -265,7 +290,7 @@ pub(crate) trait ResolvableShared { Ok(unsafe { MappedRef::new( resolved, - PathExtension::Tightened(T::Type::type_kind()), + PathExtension::TypeNarrowing(T::Type::type_kind()), span, ) }) @@ -338,7 +363,7 @@ pub(crate) trait ResolvableMutable { Ok(unsafe { MappedMut::new( resolved, - PathExtension::Tightened(T::Type::type_kind()), + PathExtension::TypeNarrowing(T::Type::type_kind()), span, ) }) diff --git a/src/expressions/type_resolution/outputs.rs b/src/expressions/type_resolution/outputs.rs index 74a66561..b62593c8 100644 --- a/src/expressions/type_resolution/outputs.rs +++ b/src/expressions/type_resolution/outputs.rs @@ -6,7 +6,7 @@ use super::*; /// See also [`RequestedValue`] for values that have been fully evaluated. pub(crate) enum ReturnedValue { Owned(AnyValueOwned), - CopyOnWrite(CopyOnWriteValue), + CopyOnWrite(AnyValueCopyOnWrite), Mutable(AnyValueMutable), Shared(AnyValueShared), } diff --git a/src/expressions/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index 7fe69984..14a75a14 100644 --- a/src/expressions/type_resolution/type_kinds.rs +++ b/src/expressions/type_resolution/type_kinds.rs @@ -91,7 +91,7 @@ impl TypeKind { } } - pub(crate) fn is_tightening_to(&self, other: &Self) -> bool { + pub(crate) fn is_narrowing_to(&self, other: &Self) -> bool { match self.compare_bindings(other) { TypeBindingComparison::Equal => true, TypeBindingComparison::RightDerivesFromLeftButIsNotSubtype => true, @@ -333,7 +333,7 @@ impl TypeProperty { let property_name = self.property.to_string(); if let Some(value) = resolver.resolve_type_property(&property_name) { return ownership.map_from_shared(Spanned( - SharedValue::new_from_owned( + AnyValueShared::new_from_owned( value.into_any_value(), Some(property_name), self.span_range(), @@ -343,7 +343,7 @@ impl TypeProperty { } if let Some(method) = resolver.resolve_method(&property_name) { return ownership.map_from_shared(Spanned( - SharedValue::new_from_owned( + AnyValueShared::new_from_owned( method.into_any_value(), Some(property_name.clone()), self.span_range(), @@ -353,7 +353,7 @@ impl TypeProperty { } if let Some(function) = resolver.resolve_type_function(&property_name) { return ownership.map_from_shared(Spanned( - SharedValue::new_from_owned( + AnyValueShared::new_from_owned( function.into_any_value(), Some(property_name.clone()), self.span_range(), diff --git a/src/expressions/values/any_value.rs b/src/expressions/values/any_value.rs index ceea4b08..880b990a 100644 --- a/src/expressions/values/any_value.rs +++ b/src/expressions/values/any_value.rs @@ -8,6 +8,7 @@ pub(crate) type AnyValueAnyRef<'a> = AnyValueContent<'a, BeAnyRef>; pub(crate) type AnyValueShared = Shared; pub(crate) type AnyValueMutable = Mutable; pub(crate) type AnyValueAssignee = Assignee; +pub(crate) type AnyValueCopyOnWrite = CopyOnWrite; // pub(crate) type AnyValueShared = AnyValueContent<'static, BeShared>; // pub(crate) type AnyValueMutable = AnyValueContent<'static, BeMutable>; // pub(crate) type AnyValueAssignee = AnyValueContent<'static, BeAssignee>; @@ -44,7 +45,7 @@ define_type_features! { impl AnyType, pub(crate) mod value_interface { methods { - fn clone(this: CopyOnWriteValue) -> AnyValue { + fn clone(this: AnyValueCopyOnWrite) -> AnyValue { this.clone_to_owned_infallible() } @@ -64,28 +65,28 @@ define_type_features! { // NOTE: // All value types can be coerced into SharedValue as an input, so this method does actually do something - fn as_ref(this: SharedValue) -> SharedValue { + fn as_ref(this: AnyValueShared) -> AnyValueShared { this } - fn swap(mut a: AssigneeValue, mut b: AssigneeValue) -> () { + fn swap(mut a: AnyValueAssignee, mut b: AnyValueAssignee) -> () { core::mem::swap(a.0.deref_mut(), b.0.deref_mut()); } - fn replace(mut a: AssigneeValue, b: AnyValue) -> AnyValue { + fn replace(mut a: AnyValueAssignee, b: AnyValue) -> AnyValue { core::mem::replace(a.0.deref_mut(), b) } - [context] fn debug(Spanned(this, span_range): Spanned) -> FunctionResult<()> { + [context] fn debug(Spanned(this, span_range): Spanned) -> FunctionResult<()> { let message = this.as_ref_value().concat_recursive(&ConcatBehaviour::debug(span_range), context.interpreter)?; span_range.debug_err(message) } - [context] fn to_debug_string(Spanned(this, span_range): Spanned) -> FunctionResult { + [context] fn to_debug_string(Spanned(this, span_range): Spanned) -> FunctionResult { this.as_ref_value().concat_recursive(&ConcatBehaviour::debug(span_range), context.interpreter) } - [context] fn to_stream(Spanned(input, span_range): Spanned) -> FunctionResult { + [context] fn to_stream(Spanned(input, span_range): Spanned) -> FunctionResult { let interpreter_ptr = context.interpreter as *mut Interpreter; input.map_into( // SAFETY: map_into only calls one of these two closures, @@ -95,7 +96,7 @@ define_type_features! { ) } - [context] fn to_group(Spanned(input, span_range): Spanned) -> FunctionResult { + [context] fn to_group(Spanned(input, span_range): Spanned) -> FunctionResult { let interpreter_ptr = context.interpreter as *mut Interpreter; input.map_into( // SAFETY: map_into only calls one of these two closures, @@ -105,11 +106,11 @@ define_type_features! { ) } - [context] fn to_string(Spanned(input, span_range): Spanned) -> FunctionResult { + [context] fn to_string(Spanned(input, span_range): Spanned) -> FunctionResult { input.as_ref_value().concat_recursive(&ConcatBehaviour::standard(span_range), context.interpreter) } - [context] fn with_span(value: Spanned, spans: AnyRef) -> FunctionResult { + [context] fn with_span(value: Spanned, spans: AnyRef) -> FunctionResult { let mut this = to_stream(context, value)?; let span_to_use = match spans.resolve_content_span_range() { Some(span_range) => span_range.span_from_join_else_start(), @@ -121,7 +122,7 @@ define_type_features! { // TYPE CHECKING // =============================== - fn is_none(this: SharedValue) -> bool { + fn is_none(this: AnyValueShared) -> bool { this.is_none() } diff --git a/src/expressions/values/function.rs b/src/expressions/values/function.rs index ce37e42e..bcaad322 100644 --- a/src/expressions/values/function.rs +++ b/src/expressions/values/function.rs @@ -16,7 +16,7 @@ define_type_features! { #[derive(Clone)] pub(crate) struct FunctionValue { - pub(crate) disabled_bound_arguments: Vec>, + pub(crate) disabled_bound_arguments: Vec>, pub(crate) invokable: InvokableFunction, } diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index fca3309b..70e31b20 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -1,6 +1,6 @@ use super::*; -use std::borrow::{Borrow, ToOwned}; +use std::borrow::Borrow; pub(super) enum VariableState { Uninitialized, @@ -8,16 +8,16 @@ pub(super) enum VariableState { Finished, } -/// Cheaply clonable, non-active content of a variable. -/// By non-active, we mean that the shared/mutable invariants are not required to hold. +/// Cheaply clonable, inactive content of a variable. +/// By inactive, we mean that the shared/mutable aliasing invariants are not required to hold. /// Before use, they have to be activated. This allows more flexibility over how references /// can be used and held, without compromising safety. #[derive(Clone)] pub(crate) enum VariableContent { // Owned, but possibly with pre-existing references Referenceable(Referenceable), - Shared(DisabledShared), - Mutable(DisabledMutable), + Shared(InactiveShared), + Mutable(InactiveMutable), } impl VariableContent { @@ -168,7 +168,6 @@ pub(crate) struct VariableBinding { variable_span: Span, } -#[allow(unused)] impl VariableBinding { /// Gets the cloned expression value /// This only works if the value can be transparently cloned @@ -182,14 +181,9 @@ impl VariableBinding { fn into_mut(self) -> FunctionResult { let span = self.variable_span.span_range(); match self.content { - VariableContent::Referenceable(referenceable) => { - referenceable.new_active_mutable(span).map_err(|_| { - self.variable_span - .ownership_error::(MUTABLE_ERROR_MESSAGE) - }) - } + VariableContent::Referenceable(referenceable) => referenceable.new_active_mutable(span), VariableContent::Mutable(inactive) => inactive.activate(span), - VariableContent::Shared(shared) => self + VariableContent::Shared(_) => self .variable_span .ownership_err(SHARED_TO_MUTABLE_ERROR_MESSAGE), } @@ -198,12 +192,7 @@ impl VariableBinding { fn into_shared(self) -> FunctionResult { let span = self.variable_span.span_range(); match self.content { - VariableContent::Referenceable(referenceable) => { - referenceable.new_active_shared(span).map_err(|_| { - self.variable_span - .ownership_error::(SHARED_ERROR_MESSAGE) - }) - } + VariableContent::Referenceable(referenceable) => referenceable.new_active_shared(span), VariableContent::Mutable(inactive) => inactive.into_shared().activate(span), VariableContent::Shared(inactive) => inactive.activate(span), } @@ -215,15 +204,12 @@ impl VariableBinding { VariableContent::Referenceable(referenceable) => { match referenceable.new_active_mutable(span) { Ok(mutable) => Ok(LateBoundValue::Mutable(mutable)), - Err(_) => { + Err(err) => { // Mutable failed, try shared - let shared = referenceable.new_active_shared(span).map_err(|_| { - self.variable_span - .ownership_error::(SHARED_ERROR_MESSAGE) - })?; + let shared = referenceable.new_active_shared(span)?; Ok(LateBoundValue::Shared(LateBoundSharedValue::new( shared, - self.variable_span.syn_error(SHARED_ERROR_MESSAGE), + err.expect_ownership_error()?, ))) } } @@ -242,6 +228,8 @@ impl VariableBinding { } } +static SHARED_TO_MUTABLE_ERROR_MESSAGE: &str = "Cannot mutate a non-mutable reference"; + pub(crate) struct LateBoundOwnedValue { pub(crate) owned: AnyValueOwned, pub(crate) is_from_last_use: bool, @@ -249,12 +237,12 @@ pub(crate) struct LateBoundOwnedValue { /// A shared value where mutable access failed for a specific reason pub(crate) struct LateBoundSharedValue { - pub(crate) shared: SharedValue, + pub(crate) shared: AnyValueShared, pub(crate) reason_not_mutable: syn::Error, } impl LateBoundSharedValue { - pub(crate) fn new(shared: SharedValue, reason_not_mutable: syn::Error) -> Self { + pub(crate) fn new(shared: AnyValueShared, reason_not_mutable: syn::Error) -> Self { Self { shared, reason_not_mutable, @@ -276,7 +264,7 @@ pub(crate) enum LateBoundValue { /// An owned value that can be converted to any ownership type Owned(LateBoundOwnedValue), /// A copy-on-write value that can be converted to an owned value - CopyOnWrite(CopyOnWriteValue), + CopyOnWrite(AnyValueCopyOnWrite), /// A mutable reference Mutable(AnyValueMutable), /// A shared reference where mutable access failed for a specific reason @@ -352,141 +340,47 @@ impl Deref for LateBoundValue { } } -// ============================================================================ -// Type aliases: Old names → New dynamic reference types -// ============================================================================ - -/// Type alias: `Shared` is now `SharedReference`. -pub(crate) type Shared = SharedReference; - -/// Type alias: `Mutable` is now `MutableReference`. -pub(crate) type Mutable = MutableReference; - -/// Type alias: `DisabledShared` is now `InactiveSharedReference`. -pub(crate) type DisabledShared = InactiveSharedReference; - -/// Type alias: `DisabledMutable` is now `InactiveMutableReference`. -pub(crate) type DisabledMutable = InactiveMutableReference; - -pub(crate) type AssigneeValue = AnyValueAssignee; -pub(crate) type SharedValue = AnyValueShared; - -/// A binding of a unique (mutable) reference to a value. -/// See [`ArgumentOwnership::Assignee`] for more details. -/// -/// If you need span information, wrap with `Spanned>`. -pub(crate) struct Assignee(pub MutableReference); - -impl AssigneeValue { - pub(crate) fn set(&mut self, content: impl IntoAnyValue) { - *self.0 = content.into_any_value(); - } -} - -impl IsValueContent for Assignee { - type Type = X::Type; - type Form = BeAssignee; -} - -impl> IntoValueContent<'static> for Assignee -where - X::Type: IsHierarchicalType = X>, - X::Form: IsHierarchicalForm, - X::Form: LeafAsMutForm, -{ - fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - self.0 - .emplace_map(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer, None)) - } -} - -impl Deref for Assignee { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Assignee { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -pub(crate) static MUTABLE_ERROR_MESSAGE: &str = - "The variable cannot be modified as it is already being modified"; -pub(crate) static SHARED_TO_MUTABLE_ERROR_MESSAGE: &str = - "The variable cannot be modified as it is a shared reference"; - -impl Spanned { - pub(crate) fn transparent_clone(&self) -> FunctionResult { - let value = self.0.as_ref().try_transparent_clone(self.1)?; - Ok(value) - } -} - -impl Spanned { - pub(crate) fn transparent_clone(&self) -> FunctionResult { - let value = self.0.as_ref().try_transparent_clone(self.1)?; - Ok(value) - } -} - -pub(crate) static SHARED_ERROR_MESSAGE: &str = - "The variable cannot be read as it is already being modified"; - -// ============================================================================ -// IntoValueContent impls for the new types (formerly on Shared / Mutable) -// ============================================================================ - -// Note: IsValueContent and IntoValueContent are implemented via QqqShared/QqqMutable -// in the forms system, since SharedReference IS the leaf type now. - // ============================================================================ // CopyOnWrite // ============================================================================ /// Copy-on-write value that can be either owned or shared -pub(crate) struct CopyOnWrite { +pub(crate) struct CopyOnWrite { pub(crate) inner: CopyOnWriteInner, } -pub(crate) enum CopyOnWriteInner { +pub(crate) enum CopyOnWriteInner { /// An owned value that can be used directly - Owned(Owned), + Owned(Owned), /// For use when the CopyOnWrite value effectively represents the owned value (post-clone). /// In this case, returning a Cow is just an optimization and we can always clone infallibly. - SharedWithInfallibleCloning(SharedReference), + SharedWithInfallibleCloning(Shared), /// For use when the CopyOnWrite value represents a pre-cloned read-only value. /// A transparent clone may fail in this case at use time. - SharedWithTransparentCloning(SharedReference), + SharedWithTransparentCloning(Shared), } -impl CopyOnWrite { - pub(crate) fn shared_in_place_of_owned(shared: SharedReference) -> Self { +impl CopyOnWrite { + pub(crate) fn shared_in_place_of_owned(shared: Shared) -> Self { Self { inner: CopyOnWriteInner::SharedWithInfallibleCloning(shared), } } - pub(crate) fn shared_in_place_of_shared(shared: SharedReference) -> Self { + pub(crate) fn shared_in_place_of_shared(shared: Shared) -> Self { Self { inner: CopyOnWriteInner::SharedWithTransparentCloning(shared), } } - pub(crate) fn owned(owned: Owned) -> Self { + pub(crate) fn owned(owned: Owned) -> Self { Self { inner: CopyOnWriteInner::Owned(owned), } } #[allow(unused)] - pub(crate) fn extract_owned( - self, - map: impl FnOnce(T::Owned) -> Result, - ) -> Result { + pub(crate) fn extract_owned(self, map: impl FnOnce(T) -> Result) -> Result { match self.inner { CopyOnWriteInner::Owned(value) => match map(value) { Ok(mapped) => Ok(mapped), @@ -506,11 +400,11 @@ impl CopyOnWrite { } } - pub(crate) fn map( + pub(crate) fn map( self, - map_shared: impl FnOnce(SharedReference) -> FunctionResult>, - map_owned: impl FnOnce(Owned) -> FunctionResult>, - ) -> FunctionResult> { + map_shared: impl FnOnce(Shared) -> FunctionResult>, + map_owned: impl FnOnce(Owned) -> FunctionResult>, + ) -> FunctionResult> { let inner = match self.inner { CopyOnWriteInner::Owned(owned) => CopyOnWriteInner::Owned(map_owned(owned)?), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { @@ -525,8 +419,8 @@ impl CopyOnWrite { pub(crate) fn map_into( self, - map_shared: impl FnOnce(SharedReference) -> U, - map_owned: impl FnOnce(Owned) -> U, + map_shared: impl FnOnce(Shared) -> U, + map_owned: impl FnOnce(Owned) -> U, ) -> U { match self.inner { CopyOnWriteInner::Owned(owned) => map_owned(owned), @@ -535,62 +429,59 @@ impl CopyOnWrite { } } - /// Disables this copy-on-write value, releasing any borrow. - /// Returns a `DisabledCopyOnWrite` which can be cloned and later re-enabled. - pub(crate) fn disable(self) -> DisabledCopyOnWrite { + /// Deactivates this copy-on-write value, releasing any borrow. + /// Returns a `InactiveCopyOnWrite` which can be cloned and later re-activated. + pub(crate) fn deactivate(self) -> InactiveCopyOnWrite { let inner = match self.inner { - CopyOnWriteInner::Owned(owned) => DisabledCopyOnWriteInner::Owned(owned), + CopyOnWriteInner::Owned(owned) => InactiveCopyOnWriteInner::Owned(owned), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - DisabledCopyOnWriteInner::SharedWithInfallibleCloning(shared.disable()) + InactiveCopyOnWriteInner::SharedWithInfallibleCloning(shared.deactivate()) } CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - DisabledCopyOnWriteInner::SharedWithTransparentCloning(shared.disable()) + InactiveCopyOnWriteInner::SharedWithTransparentCloning(shared.deactivate()) } }; - DisabledCopyOnWrite { inner } + InactiveCopyOnWrite { inner } } } /// A disabled copy-on-write value that can be safely cloned and dropped. -pub(crate) struct DisabledCopyOnWrite { - inner: DisabledCopyOnWriteInner, +pub(crate) struct InactiveCopyOnWrite { + inner: InactiveCopyOnWriteInner, } -enum DisabledCopyOnWriteInner { - Owned(Owned), - SharedWithInfallibleCloning(InactiveSharedReference), - SharedWithTransparentCloning(InactiveSharedReference), +enum InactiveCopyOnWriteInner { + Owned(Owned), + SharedWithInfallibleCloning(InactiveShared), + SharedWithTransparentCloning(InactiveShared), } -impl Clone for DisabledCopyOnWrite -where - T::Owned: Clone, -{ +impl Clone for InactiveCopyOnWrite { fn clone(&self) -> Self { let inner = match &self.inner { - DisabledCopyOnWriteInner::Owned(owned) => { - DisabledCopyOnWriteInner::Owned(owned.clone()) + InactiveCopyOnWriteInner::Owned(owned) => { + InactiveCopyOnWriteInner::Owned(owned.clone()) } - DisabledCopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - DisabledCopyOnWriteInner::SharedWithInfallibleCloning(shared.clone()) + InactiveCopyOnWriteInner::SharedWithInfallibleCloning(shared) => { + InactiveCopyOnWriteInner::SharedWithInfallibleCloning(shared.clone()) } - DisabledCopyOnWriteInner::SharedWithTransparentCloning(shared) => { - DisabledCopyOnWriteInner::SharedWithTransparentCloning(shared.clone()) + InactiveCopyOnWriteInner::SharedWithTransparentCloning(shared) => { + InactiveCopyOnWriteInner::SharedWithTransparentCloning(shared.clone()) } }; Self { inner } } } -impl DisabledCopyOnWrite { - /// Re-enables this disabled copy-on-write value by re-acquiring any borrow. - pub(crate) fn enable(self, span: SpanRange) -> FunctionResult> { +impl InactiveCopyOnWrite { + /// Re-activates this inactive copy-on-write value by re-acquiring any borrow. + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { let inner = match self.inner { - DisabledCopyOnWriteInner::Owned(owned) => CopyOnWriteInner::Owned(owned), - DisabledCopyOnWriteInner::SharedWithInfallibleCloning(inactive) => { + InactiveCopyOnWriteInner::Owned(owned) => CopyOnWriteInner::Owned(owned), + InactiveCopyOnWriteInner::SharedWithInfallibleCloning(inactive) => { CopyOnWriteInner::SharedWithInfallibleCloning(inactive.activate(span)?) } - DisabledCopyOnWriteInner::SharedWithTransparentCloning(inactive) => { + InactiveCopyOnWriteInner::SharedWithTransparentCloning(inactive) => { CopyOnWriteInner::SharedWithTransparentCloning(inactive.activate(span)?) } }; @@ -625,13 +516,13 @@ where } } -impl AsRef for CopyOnWrite { +impl AsRef for CopyOnWrite { fn as_ref(&self) -> &T { self } } -impl Deref for CopyOnWrite { +impl Deref for CopyOnWrite { type Target = T; fn deref(&self) -> &T { @@ -669,13 +560,11 @@ impl CopyOnWrite { } /// Converts to shared reference - pub(crate) fn into_shared(self, span: SpanRange) -> SharedValue { + pub(crate) fn into_shared(self, span: SpanRange) -> AnyValueShared { match self.inner { - CopyOnWriteInner::Owned(owned) => SharedValue::new_from_owned(owned, None, span), + CopyOnWriteInner::Owned(owned) => AnyValueShared::new_from_owned(owned, None, span), CopyOnWriteInner::SharedWithInfallibleCloning(shared) => shared, CopyOnWriteInner::SharedWithTransparentCloning(shared) => shared, } } } - -pub(crate) type CopyOnWriteValue = CopyOnWrite; diff --git a/src/interpretation/refs.rs b/src/interpretation/refs.rs index bf8faa57..38efaccc 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -1,7 +1,9 @@ +use std::mem::transmute; + use super::*; /// A flexible type which can either be a reference to a value of type `T`, -/// or an emplaced reference from a [`SharedReference`]. +/// or an emplaced reference from a [`Shared`]. pub(crate) struct AnyRef<'a, T: ?Sized + 'static> { inner: AnyRefInner<'a, T>, } @@ -59,30 +61,60 @@ impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { pub(crate) fn emplace_map( self, - f: impl for<'e> FnOnce(&'e T, &mut AnyRefEmplacer<'a, 'e, T>) -> O, + f: impl for<'m> FnOnce(&'m T, &mut AnyRefEmplacer<'_, 'a, 'm, T>) -> O, ) -> O { - let copied_ref = self.deref() as *const T; - let mut emplacer = AnyRefEmplacer { - inner: Some(self), - encapsulation_lifetime: std::marker::PhantomData, - }; - f( - // SAFETY: The underlying reference is valid for the lifetime of self - // So we can copy it fine - unsafe { &*copied_ref }, - &mut emplacer, - ) + match self.inner { + AnyRefInner::Direct(direct_ref) => { + let raw_ptr = direct_ref as *const T; + let mut emplacer = AnyRefEmplacer { + inner: AnyRefEmplacerState::Direct(DirectRefEmplacerState { + ptr: Some(raw_ptr), + original_lifetime: PhantomData, + }), + }; + // SAFETY: raw_ptr was derived from a valid &'a T, and the original + // reference was consumed by converting to *const T. + f(unsafe { &*raw_ptr }, &mut emplacer) + } + AnyRefInner::Encapsulated(shared) => shared.emplace_map(|x, shared_emplacer| { + let mut emplacer = AnyRefEmplacer { + inner: AnyRefEmplacerState::Encapsulated(shared_emplacer), + }; + f(x, &mut emplacer) + }), + } } } -pub(crate) struct AnyRefEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { - inner: Option>, - encapsulation_lifetime: std::marker::PhantomData<&'e ()>, +pub(crate) struct AnyRefEmplacer<'x, 'a, 'm: 'x, T: 'static + ?Sized> { + inner: AnyRefEmplacerState<'x, 'a, 'm, T>, +} + +enum AnyRefEmplacerState<'x, 'a, 'm: 'x, T: 'static + ?Sized> { + Direct(DirectRefEmplacerState<'a, T>), + Encapsulated(&'x mut SharedEmplacer<'m, T>), +} + +struct DirectRefEmplacerState<'a, T: 'static + ?Sized> { + ptr: Option<*const T>, + original_lifetime: PhantomData<&'a T>, } -impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { +impl<'x, 'a, 'm, T: 'static + ?Sized> AnyRefEmplacer<'x, 'a, 'm, T> { pub(crate) fn revert(&mut self) -> AnyRef<'a, T> { - self.inner.take().expect("Emplacer already consumed") + match &mut self.inner { + AnyRefEmplacerState::Direct(DirectRefEmplacerState { ptr, .. }) => { + let ptr = ptr.take().expect("Emplacer already consumed"); + AnyRef { + // SAFETY: ptr was derived from a valid &'a T and the original + // reference was consumed when creating the emplacer. + inner: AnyRefInner::Direct(unsafe { &*ptr }), + } + } + AnyRefEmplacerState::Encapsulated(emplacer) => AnyRef { + inner: AnyRefInner::Encapsulated(emplacer.revert()), + }, + } } /// Emplaces a mapped shared reference, consuming the emplacer's reference tracking. @@ -91,25 +123,19 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { /// derivation) are validated by the [`MappedRef`] constructor. pub(crate) fn emplace( &mut self, - mapped: MappedRef<'e, V>, + mapped: MappedRef<'m, V>, ) -> AnyRef<'a, V> { - let any_ref = self - .inner - .take() - .expect("You can only emplace to create a new AnyRef value once"); - let (value, path_extension, span) = mapped.into_parts(); - match any_ref.inner { - AnyRefInner::Direct(_) => AnyRef { - // 'e: 'a is guaranteed by the struct constraint, so coercion is valid - inner: AnyRefInner::Direct(value), - }, - AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.emplace_map(|_, emplacer| { - // SAFETY: The value's lifetime ('e) matches the SharedEmplacer's lifetime - // since both are derived from the same underlying data - let remapped = unsafe { MappedRef::new_unchecked(value, path_extension, span) }; - emplacer.emplace(remapped) - })), + match &mut self.inner { + AnyRefEmplacerState::Direct(_) => { + let (value, _, _) = mapped.into_parts(); + // SAFETY: In Direct case, 'm == 'a, so the lifetime of the value is valid for 'a + let value = unsafe { transmute::<&'m V, &'a V>(value) }; + AnyRef { + inner: AnyRefInner::Direct(value), + } + } + AnyRefEmplacerState::Encapsulated(emplacer) => AnyRef { + inner: AnyRefInner::Encapsulated(emplacer.emplace(mapped)), }, } } @@ -139,15 +165,15 @@ impl<'a, T: ?Sized> ToSpannedRef<'a> for &'a T { } } -impl<'a, T: ?Sized> From> for AnyRef<'a, T> { - fn from(value: SharedReference) -> Self { +impl<'a, T: ?Sized> From> for AnyRef<'a, T> { + fn from(value: Shared) -> Self { Self { inner: AnyRefInner::Encapsulated(value), } } } -impl ToSpannedRef<'static> for SharedReference { +impl ToSpannedRef<'static> for Shared { type Target = T; fn into_ref(self) -> AnyRef<'static, Self::Target> { self.into() @@ -159,7 +185,7 @@ impl ToSpannedRef<'static> for SharedReference { enum AnyRefInner<'a, T: 'static + ?Sized> { Direct(&'a T), - Encapsulated(SharedReference), + Encapsulated(Shared), } impl<'a, T: 'static + ?Sized> Deref for AnyRef<'a, T> { @@ -174,7 +200,7 @@ impl<'a, T: 'static + ?Sized> Deref for AnyRef<'a, T> { } /// A flexible type which can either be a mutable reference to a value of type `T`, -/// or an emplaced reference from a [`MutableReference`]. +/// or an emplaced reference from a [`Mutable`]. pub(crate) struct AnyMut<'a, T: 'static + ?Sized> { inner: AnyMutInner<'a, T>, } @@ -240,7 +266,7 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { pub(crate) fn emplace_map( self, - f: impl for<'e> FnOnce(&'e mut T, &mut AnyMutEmplacer<'a, 'e, T>) -> O, + f: impl for<'m> FnOnce(&'m mut T, &mut AnyMutEmplacer<'_, 'a, 'm, T>) -> O, ) -> O { match self.inner { AnyMutInner::Direct(direct_mut) => { @@ -248,52 +274,53 @@ impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { // stores the raw pointer, so no second &mut T exists. let raw_ptr = direct_mut as *mut T; let mut emplacer = AnyMutEmplacer { - inner: Some(AnyMutEmplacerState::Direct(raw_ptr, PhantomData)), - encapsulation_lifetime: std::marker::PhantomData, + inner: AnyMutEmplacerState::Direct(DirectMutEmplacerState { + ptr: Some(raw_ptr), + original_lifetime: PhantomData, + }), }; // SAFETY: raw_ptr was derived from a valid &'a mut T, and the original // reference was consumed by converting to *mut T. f(unsafe { &mut *raw_ptr }, &mut emplacer) } - AnyMutInner::Encapsulated(mut mutable) => { - // MutableReference internally stores NonNull (a raw pointer), - // not &mut T, so there is no aliasing issue. - let raw_ptr = (&mut *mutable) as *mut T; + AnyMutInner::Encapsulated(mutable) => mutable.emplace_map(|x, mut_emplacer| { let mut emplacer = AnyMutEmplacer { - inner: Some(AnyMutEmplacerState::Encapsulated(mutable)), - encapsulation_lifetime: std::marker::PhantomData, + inner: AnyMutEmplacerState::Encapsulated(mut_emplacer), }; - // SAFETY: raw_ptr was derived from the MutableReference which - // uses NonNull internally (not &mut), so no aliasing occurs. - f(unsafe { &mut *raw_ptr }, &mut emplacer) - } + + f(x, &mut emplacer) + }), } } } -pub(crate) struct AnyMutEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { - inner: Option>, - encapsulation_lifetime: std::marker::PhantomData<&'e ()>, +pub(crate) struct AnyMutEmplacer<'x, 'a, 'm: 'x, T: 'static + ?Sized> { + inner: AnyMutEmplacerState<'x, 'a, 'm, T>, +} + +enum AnyMutEmplacerState<'x, 'a, 'm: 'x, T: 'static + ?Sized> { + Direct(DirectMutEmplacerState<'a, T>), + Encapsulated(&'x mut MutableEmplacer<'m, T>), } -/// Stores either a raw pointer (for Direct) or a MutableReference (for Encapsulated), -/// avoiding &mut aliasing that would occur if we stored the full AnyMut. -enum AnyMutEmplacerState<'a, T: 'static + ?Sized> { - Direct(*mut T, PhantomData<&'a mut T>), - Encapsulated(MutableReference), +struct DirectMutEmplacerState<'a, T: 'static + ?Sized> { + ptr: Option<*mut T>, + original_lifetime: PhantomData<&'a mut T>, } -impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { +impl<'x, 'a, 'm, T: 'static + ?Sized> AnyMutEmplacer<'x, 'a, 'm, T> { pub(crate) fn revert(&mut self) -> AnyMut<'a, T> { - let state = self.inner.take().expect("Emplacer already consumed"); - match state { - AnyMutEmplacerState::Direct(ptr, _) => AnyMut { - // SAFETY: ptr was derived from a valid &'a mut T and no other - // &mut T currently exists (the one passed to the closure has ended). - inner: AnyMutInner::Direct(unsafe { &mut *ptr }), - }, - AnyMutEmplacerState::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable), + match &mut self.inner { + AnyMutEmplacerState::Direct(DirectMutEmplacerState { ptr, .. }) => { + let ptr = ptr.take().expect("Emplacer already consumed"); + AnyMut { + // SAFETY: ptr was derived from a valid &'a mut T and no other + // &mut T currently exists (the one passed to the closure has ended). + inner: AnyMutInner::Direct(unsafe { &mut *ptr }), + } + } + AnyMutEmplacerState::Encapsulated(emplacer) => AnyMut { + inner: AnyMutInner::Encapsulated(emplacer.revert()), }, } } @@ -304,25 +331,19 @@ impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { /// derivation) are validated by the [`MappedMut`] constructor. pub(crate) fn emplace( &mut self, - mapped: MappedMut<'e, V>, + mapped: MappedMut<'m, V>, ) -> AnyMut<'a, V> { - let state = self - .inner - .take() - .expect("You can only emplace to create a new AnyMut value once"); - let (value, path_extension, span) = mapped.into_parts(); - match state { - AnyMutEmplacerState::Direct(_, _) => AnyMut { - // 'e: 'a is guaranteed by the struct constraint, so coercion is valid - inner: AnyMutInner::Direct(value), - }, - AnyMutEmplacerState::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.emplace_map(|_, emplacer| { - // SAFETY: The value's lifetime ('e) matches the MutableEmplacer's lifetime - // since both are derived from the same underlying data - let remapped = unsafe { MappedMut::new_unchecked(value, path_extension, span) }; - emplacer.emplace(remapped) - })), + match &mut self.inner { + AnyMutEmplacerState::Direct(_) => { + let (value, _, _) = mapped.into_parts(); + // SAFETY: In Direct case, 'm == 'a, so the lifetime of the value is valid for 'a + let value = unsafe { transmute::<&'m mut V, &'a mut V>(value) }; + AnyMut { + inner: AnyMutInner::Direct(value), + } + } + AnyMutEmplacerState::Encapsulated(emplacer) => AnyMut { + inner: AnyMutInner::Encapsulated(emplacer.emplace(mapped)), }, } } @@ -342,8 +363,8 @@ impl<'a, T: ?Sized + 'static> IntoAnyMut<'a> for &'a mut T { } } -impl<'a, T: ?Sized> From> for AnyMut<'a, T> { - fn from(value: MutableReference) -> Self { +impl<'a, T: ?Sized> From> for AnyMut<'a, T> { + fn from(value: Mutable) -> Self { Self { inner: AnyMutInner::Encapsulated(value), } @@ -352,7 +373,7 @@ impl<'a, T: ?Sized> From> for AnyMut<'a, T> { enum AnyMutInner<'a, T: 'static + ?Sized> { Direct(&'a mut T), - Encapsulated(MutableReference), + Encapsulated(Mutable), } impl<'a, T: 'static + ?Sized> Deref for AnyMut<'a, T> { diff --git a/src/interpretation/variable.rs b/src/interpretation/variable.rs index 2c05e064..93600c40 100644 --- a/src/interpretation/variable.rs +++ b/src/interpretation/variable.rs @@ -161,7 +161,7 @@ impl VariableReference { pub(crate) fn resolve_shared( &self, interpreter: &mut Interpreter, - ) -> FunctionResult { + ) -> FunctionResult { Ok(self .resolve_concrete(interpreter, ArgumentOwnership::Shared)? .expect_shared()) diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs index c3d17e28..68af8048 100644 --- a/src/misc/dynamic_references/mutable_reference.rs +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -2,19 +2,20 @@ use std::mem::transmute; use super::*; -pub(crate) struct MutableReference(pub(super) ReferenceCore); +/// Represents an active `&mut T` reference, to something derived from a Referenceable root. +pub(crate) struct Mutable(pub(super) ReferenceCore); -impl MutableReference { - pub(crate) fn deactivate(self) -> InactiveMutableReference { +impl Mutable { + pub(crate) fn deactivate(self) -> InactiveMutable { self.0.core.data_mut().deactivate_reference(self.0.id); - InactiveMutableReference(self.0) + InactiveMutable(self.0) } /// A powerful map method which lets you place the resultant mapped reference inside /// a structure arbitrarily. pub(crate) fn emplace_map( mut self, - f: impl for<'e> FnOnce(&'e mut T, &mut MutableEmplacer<'e, T>) -> O, + f: impl for<'a> FnOnce(&'a mut T, &mut MutableEmplacer<'a, T>) -> O, ) -> O { // SAFETY: The validity + safety invariants are upheld by `ReferenceableCore` // ... assuming this id is marked as a mutable reference for the duration. @@ -32,8 +33,8 @@ impl MutableReference { /// making this method itself safe. pub(crate) fn map( self, - f: impl for<'r> FnOnce(&'r mut T) -> MappedMut<'r, V>, - ) -> MutableReference { + f: impl for<'a> FnOnce(&'a mut T) -> MappedMut<'a, V>, + ) -> Mutable { self.emplace_map(move |input, emplacer| emplacer.emplace(f(input))) } @@ -42,8 +43,8 @@ impl MutableReference { /// The unsafe PathExtension assertion is confined to the [`MappedMut::new`] constructor. pub(crate) fn try_map( self, - f: impl for<'r> FnOnce(&'r mut T) -> Result, E>, - ) -> Result, (E, MutableReference)> { + f: impl for<'a> FnOnce(&'a mut T) -> Result, E>, + ) -> Result, (E, Mutable)> { self.emplace_map(|input, emplacer| match f(input) { Ok(mapped) => Ok(emplacer.emplace(mapped)), Err(e) => Err((e, emplacer.revert())), @@ -51,7 +52,7 @@ impl MutableReference { } } -impl MutableReference { +impl Mutable { /// Creates a new MutableReference from an owned value, wrapping it in a Referenceable. pub(crate) fn new_from_owned( value: AnyValue, @@ -65,31 +66,14 @@ impl MutableReference { } } -impl MutableReference { - /// Disables this mutable reference (bridge for old `disable()` API). - /// Equivalent to `deactivate()` in the new naming. - pub(crate) fn disable(self) -> InactiveMutableReference { - self.deactivate() - } - - /// Converts this mutable reference into a shared reference. - /// Bridge for old `into_shared()` API. - pub(crate) fn into_shared(self) -> SharedReference { - let span = self - .0 - .core - .data_mut() - .for_reference(self.0.id) - .creation_span; - let inactive = self.deactivate(); - let inactive_shared = inactive.into_shared(); - inactive_shared - .activate(span) - .expect("Converting mutable to shared should always succeed since we just released the mutable borrow") +impl Mutable { + pub(crate) fn into_shared(self) -> Shared { + self.0.core.data_mut().make_shared(self.0.id); + Shared(self.0) } } -impl Deref for MutableReference { +impl Deref for Mutable { type Target = T; fn deref(&self) -> &Self::Target { @@ -102,19 +86,19 @@ impl Deref for MutableReference { } } -impl AsRef for MutableReference { +impl AsRef for Mutable { fn as_ref(&self) -> &T { self } } -impl AsMut for MutableReference { +impl AsMut for Mutable { fn as_mut(&mut self) -> &mut T { self } } -impl DerefMut for MutableReference { +impl DerefMut for Mutable { fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: See the rustdoc on dynamic_references/mod.rs for full details. // To summarize: @@ -125,29 +109,33 @@ impl DerefMut for MutableReference { } } -pub(crate) struct InactiveMutableReference(pub(super) ReferenceCore); +/// Represents an inactive but *valid* mutable reference. +/// +/// It can't currently deref into a `&mut T`, but can after being activated, when +/// the aliasing rules get checked and enforced. +pub(crate) struct InactiveMutable(pub(super) ReferenceCore); -impl Clone for InactiveMutableReference { +impl Clone for InactiveMutable { fn clone(&self) -> Self { - InactiveMutableReference(self.0.clone()) + InactiveMutable(self.0.clone()) } } -impl InactiveMutableReference { - pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { +impl InactiveMutable { + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { self.0 .core .data_mut() .activate_mutable_reference(self.0.id, span)?; - Ok(MutableReference(self.0)) + Ok(Mutable(self.0)) } - pub(crate) fn into_shared(self) -> InactiveSharedReference { + pub(crate) fn into_shared(self) -> InactiveShared { // As an inactive reference, we are free to map between them // ... we could even enable the other way around, but that'd likely allow breaking // application invariants which we want to respect. self.0.core.data_mut().make_shared(self.0.id); - InactiveSharedReference(self.0) + InactiveShared(self.0) } } @@ -199,25 +187,25 @@ impl<'a, V: ?Sized> MappedMut<'a, V> { } } -pub(crate) struct MutableEmplacer<'e, T: ?Sized>(EmplacerCore<'e, T>); +pub(crate) struct MutableEmplacer<'a, T: ?Sized>(EmplacerCore<'a, T>); -impl<'e, T: ?Sized> MutableEmplacer<'e, T> { - pub(crate) fn revert(&mut self) -> MutableReference { - MutableReference(self.0.revert()) +impl<'a, T: ?Sized> MutableEmplacer<'a, T> { + pub(crate) fn revert(&mut self) -> Mutable { + Mutable(self.0.revert()) } /// Emplaces a mapped mutable reference, consuming the emplacer's reference tracking. /// /// This is safe because all preconditions (correct PathExtension and valid reference /// derivation) are validated by the [`MappedMut`] constructor. - pub(crate) fn emplace( + pub(crate) fn emplace<'e: 'a, V: 'static + ?Sized>( &mut self, mapped: MappedMut<'e, V>, - ) -> MutableReference { + ) -> Mutable { let (value, path_extension, span) = mapped.into_parts(); // SAFETY: The pointer is from a valid reference (guaranteed by MappedMut constructor), // and the PathExtension was validated by the MappedMut constructor. let pointer = unsafe { NonNull::new_unchecked(value as *mut V) }; - unsafe { MutableReference(self.0.emplace_unchecked(pointer, path_extension, span)) } + unsafe { Mutable(self.0.emplace_unchecked(pointer, path_extension, span)) } } } diff --git a/src/misc/dynamic_references/reference_core.rs b/src/misc/dynamic_references/reference_core.rs index 7285f914..ed30dea9 100644 --- a/src/misc/dynamic_references/reference_core.rs +++ b/src/misc/dynamic_references/reference_core.rs @@ -91,7 +91,8 @@ impl<'e, T: ?Sized> EmplacerCore<'e, T> { } /// SAFETY: - /// - The caller must ensure that the pointer is derived from the original content + /// - The caller must ensure that the pointer is derived from the original content, + /// or more specifically, it lives at least as long as that content /// - The caller must ensure that the PathExtension is correct pub(crate) unsafe fn emplace_unchecked( &mut self, diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 31de0f01..6af5c6bc 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -12,31 +12,25 @@ impl Referenceable { } } - pub(crate) fn new_inactive_shared(&self) -> InactiveSharedReference { - InactiveSharedReference(ReferenceCore::new_root( + pub(crate) fn new_inactive_shared(&self) -> InactiveShared { + InactiveShared(ReferenceCore::new_root( self.core.clone(), ReferenceKind::InactiveShared, )) } - pub(crate) fn new_inactive_mutable(&self) -> InactiveMutableReference { - InactiveMutableReference(ReferenceCore::new_root( + pub(crate) fn new_inactive_mutable(&self) -> InactiveMutable { + InactiveMutable(ReferenceCore::new_root( self.core.clone(), ReferenceKind::InactiveMutable, )) } - pub(crate) fn new_active_shared( - &self, - span: SpanRange, - ) -> FunctionResult> { + pub(crate) fn new_active_shared(&self, span: SpanRange) -> FunctionResult> { self.new_inactive_shared().activate(span) } - pub(crate) fn new_active_mutable( - &self, - span: SpanRange, - ) -> FunctionResult> { + pub(crate) fn new_active_mutable(&self, span: SpanRange) -> FunctionResult> { self.new_inactive_mutable().activate(span) } @@ -236,7 +230,7 @@ impl ReferenceableData { let parent_type = specifier.bound_type_kind(); match last_path_part { PathPart::Value { bound_as } => { - if !bound_as.is_tightening_to(&parent_type) { + if !bound_as.is_narrowing_to(&parent_type) { panic!( "Invalid path extension: cannot derive {} from {}", parent_type.source_name(), @@ -253,12 +247,12 @@ impl ReferenceableData { bound_as: child_bound_as, }); } - (PathPart::Value { bound_as }, PathExtension::Tightened(new_bound_as)) => { - if bound_as.is_tightening_to(&new_bound_as) { + (PathPart::Value { bound_as }, PathExtension::TypeNarrowing(new_bound_as)) => { + if bound_as.is_narrowing_to(&new_bound_as) { *bound_as = new_bound_as; } else { panic!( - "Invalid path extension: cannot derive {} from {}", + "Invalid path extension: cannot narrow to {} from {}", new_bound_as.source_name(), bound_as.source_name() ); @@ -450,8 +444,8 @@ impl ReferencePath { pub(crate) enum PathExtension { /// Extends the path with a child reference (e.g. .x or [0]) Child(ChildSpecifier, TypeKind), - /// Extends the path with a value of a certain type (e.g. dereferencing a pointer) - Tightened(TypeKind), + /// Extends the path with a value of a certain type + TypeNarrowing(TypeKind), } #[derive(PartialEq, Eq, Clone)] diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs index 245ba601..fc1d842e 100644 --- a/src/misc/dynamic_references/shared_reference.rs +++ b/src/misc/dynamic_references/shared_reference.rs @@ -2,25 +2,26 @@ use std::mem::transmute; use super::*; -pub(crate) struct SharedReference(pub(super) ReferenceCore); +/// Represents an active `&T` reference, to something derived from a Referenceable root. +pub(crate) struct Shared(pub(super) ReferenceCore); -impl Clone for SharedReference { +impl Clone for Shared { fn clone(&self) -> Self { - SharedReference(self.0.clone()) + Shared(self.0.clone()) } } -impl SharedReference { - pub(crate) fn deactivate(self) -> InactiveSharedReference { +impl Shared { + pub(crate) fn deactivate(self) -> InactiveShared { self.0.core.data_mut().deactivate_reference(self.0.id); - InactiveSharedReference(self.0) + InactiveShared(self.0) } /// A powerful map method which lets you place the resultant mapped reference inside /// a structure arbitrarily. pub(crate) fn emplace_map( self, - f: impl for<'e> FnOnce(&'e T, &mut SharedEmplacer<'e, T>) -> O, + f: impl for<'a> FnOnce(&'a T, &mut SharedEmplacer<'a, T>) -> O, ) -> O { // SAFETY: The validity + safety invariants are upheld by `ReferenceableCore` // ... assuming this id is marked as a shared reference for the duration. @@ -38,8 +39,8 @@ impl SharedReference { /// making this method itself safe. pub(crate) fn map( self, - f: impl for<'r> FnOnce(&'r T) -> MappedRef<'r, V>, - ) -> SharedReference { + f: impl for<'a> FnOnce(&'a T) -> MappedRef<'a, V>, + ) -> Shared { self.emplace_map(move |input, emplacer| emplacer.emplace(f(input))) } @@ -48,8 +49,8 @@ impl SharedReference { /// The unsafe PathExtension assertion is confined to the [`MappedRef::new`] constructor. pub(crate) fn try_map( self, - f: impl for<'r> FnOnce(&'r T) -> Result, E>, - ) -> Result, (E, SharedReference)> { + f: impl for<'a> FnOnce(&'a T) -> Result, E>, + ) -> Result, (E, Shared)> { self.emplace_map(|input, emplacer| match f(input) { Ok(mapped) => Ok(emplacer.emplace(mapped)), Err(e) => Err((e, emplacer.revert())), @@ -57,7 +58,7 @@ impl SharedReference { } } -impl SharedReference { +impl Shared { /// Creates a new SharedReference from an owned value, wrapping it in a Referenceable. /// Uses a placeholder name and span for the Referenceable root. pub(crate) fn new_from_owned( @@ -77,15 +78,7 @@ impl SharedReference { } } -impl SharedReference { - /// Disables this shared reference (bridge for old `disable()` API). - /// Equivalent to `deactivate()` in the new naming. - pub(crate) fn disable(self) -> InactiveSharedReference { - self.deactivate() - } -} - -impl Deref for SharedReference { +impl Deref for Shared { type Target = T; fn deref(&self) -> &Self::Target { @@ -98,27 +91,31 @@ impl Deref for SharedReference { } } -impl AsRef for SharedReference { +impl AsRef for Shared { fn as_ref(&self) -> &T { self } } -pub(crate) struct InactiveSharedReference(pub(super) ReferenceCore); +/// Represents an inactive but *valid* shared reference. +/// +/// It can't currently deref into a `&T`, but can after being activated, when +/// the aliasing rules get checked and enforced. +pub(crate) struct InactiveShared(pub(super) ReferenceCore); -impl Clone for InactiveSharedReference { +impl Clone for InactiveShared { fn clone(&self) -> Self { - InactiveSharedReference(self.0.clone()) + InactiveShared(self.0.clone()) } } -impl InactiveSharedReference { - pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { +impl InactiveShared { + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { self.0 .core .data_mut() .activate_shared_reference(self.0.id, span)?; - Ok(SharedReference(self.0)) + Ok(Shared(self.0)) } } @@ -166,25 +163,25 @@ impl<'a, V: ?Sized> MappedRef<'a, V> { } } -pub(crate) struct SharedEmplacer<'e, T: ?Sized>(EmplacerCore<'e, T>); +pub(crate) struct SharedEmplacer<'a, T: ?Sized>(EmplacerCore<'a, T>); -impl<'e, T: ?Sized> SharedEmplacer<'e, T> { - pub(crate) fn revert(&mut self) -> SharedReference { - SharedReference(self.0.revert()) +impl<'a, T: ?Sized> SharedEmplacer<'a, T> { + pub(crate) fn revert(&mut self) -> Shared { + Shared(self.0.revert()) } /// Emplaces a mapped shared reference, consuming the emplacer's reference tracking. /// /// This is safe because all preconditions (correct PathExtension and valid reference /// derivation) are validated by the [`MappedRef`] constructor. - pub(crate) fn emplace( + pub(crate) fn emplace<'e: 'a, V: 'static + ?Sized>( &mut self, mapped: MappedRef<'e, V>, - ) -> SharedReference { + ) -> Shared { let (value, path_extension, span) = mapped.into_parts(); // SAFETY: The pointer is from a valid reference (guaranteed by MappedRef constructor), // and the PathExtension was validated by the MappedRef constructor. let pointer = unsafe { NonNull::new_unchecked(value as *const V as *mut V) }; - unsafe { SharedReference(self.0.emplace_unchecked(pointer, path_extension, span)) } + unsafe { Shared(self.0.emplace_unchecked(pointer, path_extension, span)) } } } diff --git a/src/misc/errors.rs b/src/misc/errors.rs index 27c04977..2a373e8b 100644 --- a/src/misc/errors.rs +++ b/src/misc/errors.rs @@ -143,6 +143,15 @@ impl FunctionError { _ => Err(self), } } + + pub(crate) fn expect_ownership_error(self) -> Result { + match self.0.inner.as_ref() { + ExecutionInterruptInner::Error(ExecutionError(ErrorKind::Ownership, _)) => { + Ok(self.0.expect_error().convert_to_syn_error()) + } + _ => panic!("Expected ownership error"), + } + } } pub(crate) trait FunctionResultExt { From e53715edaf004343e5aff73e9b57476e41e38e1d Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 28 Feb 2026 16:59:56 +0000 Subject: [PATCH 43/49] tests: Update error output --- .../expressions/array_place_destructure_multiple_muts.stderr | 5 ++++- tests/compilation_failures/expressions/swap_itself.stderr | 1 - .../functions/ref_argument_cannot_be_mutated.stderr | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/compilation_failures/expressions/array_place_destructure_multiple_muts.stderr b/tests/compilation_failures/expressions/array_place_destructure_multiple_muts.stderr index 58d88c8c..21e440fd 100644 --- a/tests/compilation_failures/expressions/array_place_destructure_multiple_muts.stderr +++ b/tests/compilation_failures/expressions/array_place_destructure_multiple_muts.stderr @@ -1,4 +1,7 @@ -error: The variable cannot be read as it is already being modified +error: Cannot create a shared reference because it clashes with an existing mutable reference: + This reference : [*active*] &arr (of type any) + Other reference: [*active*] &mut arr (of type any) + Reason : mutation may be observed from the other active reference, which breaks aliasing rules --> tests/compilation_failures/expressions/array_place_destructure_multiple_muts.rs:6:13 | 6 | arr[arr[1]] = 3; diff --git a/tests/compilation_failures/expressions/swap_itself.stderr b/tests/compilation_failures/expressions/swap_itself.stderr index de6412af..a15b6faf 100644 --- a/tests/compilation_failures/expressions/swap_itself.stderr +++ b/tests/compilation_failures/expressions/swap_itself.stderr @@ -2,7 +2,6 @@ error: Cannot create a mutable reference because it clashes with an existing ref This reference : [*active*] &mut a (of type any) Other reference: [*active*] &mut a (of type any) Reason : mutation may be observed from the other active reference, which breaks aliasing rules - --> tests/compilation_failures/expressions/swap_itself.rs:6:16 | 6 | a.swap(a); diff --git a/tests/compilation_failures/functions/ref_argument_cannot_be_mutated.stderr b/tests/compilation_failures/functions/ref_argument_cannot_be_mutated.stderr index 185a9ee0..8b72a038 100644 --- a/tests/compilation_failures/functions/ref_argument_cannot_be_mutated.stderr +++ b/tests/compilation_failures/functions/ref_argument_cannot_be_mutated.stderr @@ -1,4 +1,4 @@ -error: The variable cannot be modified as it is a shared reference +error: Cannot mutate a non-mutable reference --> tests/compilation_failures/functions/ref_argument_cannot_be_mutated.rs:5:48 | 5 | let my_array_push = |arr: &any, value| arr.push(value); From 8b6178283f2cd523be42ab523fa5df1917b5867c Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 28 Feb 2026 18:32:55 +0000 Subject: [PATCH 44/49] refactor: Unify `CopyOnWrite` --- plans/TODO.md | 2 +- src/expressions/concepts/content.rs | 4 +- src/expressions/concepts/form.rs | 2 +- src/expressions/concepts/forms/argument.rs | 2 +- .../concepts/forms/copy_on_write.rs | 359 +++++++++++++----- src/expressions/concepts/forms/late_bound.rs | 2 +- src/expressions/concepts/forms/shared.rs | 1 + src/expressions/concepts/mapping.rs | 3 + src/expressions/type_resolution/arguments.rs | 21 +- src/interpretation/bindings.rs | 233 +----------- 10 files changed, 284 insertions(+), 345 deletions(-) diff --git a/plans/TODO.md b/plans/TODO.md index eba29e01..0d7cd05d 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -300,7 +300,7 @@ Suddenly dawned on me - my Disabled arguments might not be safe. - [x] Rename `DisabledArgumentValue` and `DisabledCopyOnWrite` to `Inactive__` and their method from `enable` to `activate` and ditto with `disable -> deactivate` - [ ] See what else can be deleted from `bindings.rs` - [ ] Unify LateBound into `QqqLateBound` - - [ ] Unify CopyOnWrite into `QqqCopyOnWrite` + - [x] Unify CopyOnWrite into `QqqCopyOnWrite` - [ ] Add various tests: - [ ] Stretching different error messages - [ ] Showing I can do e.g. `x.a += x.b` diff --git a/src/expressions/concepts/content.rs b/src/expressions/concepts/content.rs index 16ae7a21..4b42b108 100644 --- a/src/expressions/concepts/content.rs +++ b/src/expressions/concepts/content.rs @@ -180,7 +180,7 @@ where fn clone_to_owned_transparently<'r>( &'r self, span_range: SpanRange, - ) -> ExecutionResult> + ) -> FunctionResult> where 'a: 'r, Self::Form: LeafAsRefForm, @@ -189,7 +189,7 @@ where map_via_leaf! { input: &'r (Content<'a, Self::Type, Self::Form>) = self, state: SpanRange | let span_range = span_range, - fn map_leaf(leaf) -> (ExecutionResult>) { + fn map_leaf(leaf) -> (FunctionResult>) { F::leaf_clone_to_owned_transparently(leaf, span_range) } } diff --git a/src/expressions/concepts/form.rs b/src/expressions/concepts/form.rs index a7113215..bda41811 100644 --- a/src/expressions/concepts/form.rs +++ b/src/expressions/concepts/form.rs @@ -39,7 +39,7 @@ pub(crate) trait LeafAsRefForm: IsHierarchicalForm { fn leaf_clone_to_owned_transparently<'r, 'a: 'r, T: IsLeafType>( leaf: &'r Self::Leaf<'a, T>, error_span: SpanRange, - ) -> ExecutionResult { + ) -> FunctionResult { let type_kind = T::type_kind(); if type_kind.supports_transparent_cloning() { Ok(Self::leaf_clone_to_owned_infallible(leaf)) diff --git a/src/expressions/concepts/forms/argument.rs b/src/expressions/concepts/forms/argument.rs index 5c260ced..85b1dcb4 100644 --- a/src/expressions/concepts/forms/argument.rs +++ b/src/expressions/concepts/forms/argument.rs @@ -2,7 +2,7 @@ use super::*; pub(crate) enum Argument { Owned(L), - CopyOnWrite(QqqCopyOnWrite), + CopyOnWrite(CopyOnWrite), Mutable(Mutable), Assignee(Assignee), Shared(Shared), diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index 7bec58b9..92fbc418 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -1,6 +1,6 @@ use super::*; -pub(crate) enum QqqCopyOnWrite { +pub(crate) enum CopyOnWrite { /// An owned value that can be used directly Owned(Owned), /// For use when the CopyOnWrite value effectively represents the owned value (post-clone). @@ -10,30 +10,190 @@ pub(crate) enum QqqCopyOnWrite { /// A transparent clone may fail in this case at use time. SharedWithTransparentCloning(Shared), } - -impl IsValueContent for QqqCopyOnWrite { - type Type = L::Type; +impl IsValueContent for CopyOnWrite { + type Type = X::Type; type Form = BeCopyOnWrite; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqCopyOnWrite { +impl<'a, X: Clone + IsValueContent> IntoValueContent<'a> for CopyOnWrite +where + X: 'static, + X::Type: IsHierarchicalType = X>, + X: IsValueContent + IsSelfValueContent<'static> + for<'b> IntoValueContent<'b>, +{ fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self + match self { + CopyOnWrite::Owned(owned) => { + BeCopyOnWrite::new_owned(owned) + } + CopyOnWrite::SharedWithInfallibleCloning(shared) => { + BeCopyOnWrite::new_shared_in_place_of_owned(shared) + } + CopyOnWrite::SharedWithTransparentCloning(shared) => { + BeCopyOnWrite::new_shared_in_place_of_shared(shared) + } + } } } -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqCopyOnWrite { +impl<'a, L: IsValueLeaf> FromValueContent<'a> for CopyOnWrite { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } } +impl CopyOnWrite { + pub(crate) fn shared_in_place_of_owned(shared: Shared) -> Self { + CopyOnWrite::SharedWithInfallibleCloning(shared) + } + + pub(crate) fn shared_in_place_of_shared(shared: Shared) -> Self { + CopyOnWrite::SharedWithTransparentCloning(shared) + } + + pub(crate) fn owned(owned: Owned) -> Self { + CopyOnWrite::Owned(owned) + } + + pub(crate) fn acts_as_shared_reference(&self) -> bool { + match &self { + CopyOnWrite::Owned { .. } => false, + CopyOnWrite::SharedWithInfallibleCloning { .. } => false, + CopyOnWrite::SharedWithTransparentCloning { .. } => true, + } + } + + pub(crate) fn map( + self, + map_shared: impl FnOnce(Shared) -> FunctionResult>, + map_owned: impl FnOnce(Owned) -> FunctionResult>, + ) -> FunctionResult> { + Ok(match self { + CopyOnWrite::Owned(owned) => CopyOnWrite::Owned(map_owned(owned)?), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { + CopyOnWrite::SharedWithInfallibleCloning(map_shared(shared)?) + } + CopyOnWrite::SharedWithTransparentCloning(shared) => { + CopyOnWrite::SharedWithTransparentCloning(map_shared(shared)?) + } + }) + } + + pub(crate) fn map_into( + self, + map_shared: impl FnOnce(Shared) -> U, + map_owned: impl FnOnce(Owned) -> U, + ) -> U { + match self { + CopyOnWrite::Owned(owned) => map_owned(owned), + CopyOnWrite::SharedWithInfallibleCloning(shared) => map_shared(shared), + CopyOnWrite::SharedWithTransparentCloning(shared) => map_shared(shared), + } + } + + /// Deactivates this copy-on-write value, releasing any borrow. + /// Returns a `InactiveCopyOnWrite` which can be cloned and later re-activated. + pub(crate) fn deactivate(self) -> InactiveCopyOnWrite { + match self { + CopyOnWrite::Owned(owned) => InactiveCopyOnWrite::Owned(owned), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { + InactiveCopyOnWrite::SharedWithInfallibleCloning(shared.deactivate()) + } + CopyOnWrite::SharedWithTransparentCloning(shared) => { + InactiveCopyOnWrite::SharedWithTransparentCloning(shared.deactivate()) + } + } + } +} + +impl CopyOnWrite { + /// Converts to shared reference + pub(crate) fn into_shared(self, span: SpanRange) -> Shared { + match self { + CopyOnWrite::Owned(owned) => Shared::new_from_owned(owned, None, span), + CopyOnWrite::SharedWithInfallibleCloning(shared) => shared, + CopyOnWrite::SharedWithTransparentCloning(shared) => shared, + } + } + + /// Converts to owned, using transparent clone for shared values where cloning was not requested + /// Note - there's a more general `IsSelfValueContent::clone_to_owned_transparently` + /// but this is re-implemented here as a specialization for efficiency + pub(crate) fn clone_to_owned_transparently( + self, + span: SpanRange, + ) -> FunctionResult { + match self { + CopyOnWrite::Owned(owned) => Ok(owned), + CopyOnWrite::SharedWithInfallibleCloning(shared) => Ok(shared.infallible_clone()), + CopyOnWrite::SharedWithTransparentCloning(shared) => { + let value = shared.as_ref().try_transparent_clone(span)?; + Ok(value) + } + } + } +} + +/// A disabled copy-on-write value that can be safely cloned and dropped. +pub(crate) enum InactiveCopyOnWrite { + Owned(Owned), + SharedWithInfallibleCloning(InactiveShared), + SharedWithTransparentCloning(InactiveShared), +} + +impl Clone for InactiveCopyOnWrite { + fn clone(&self) -> Self { + match self { + InactiveCopyOnWrite::Owned(owned) => InactiveCopyOnWrite::Owned(owned.clone()), + InactiveCopyOnWrite::SharedWithInfallibleCloning(shared) => { + InactiveCopyOnWrite::SharedWithInfallibleCloning(shared.clone()) + } + InactiveCopyOnWrite::SharedWithTransparentCloning(shared) => { + InactiveCopyOnWrite::SharedWithTransparentCloning(shared.clone()) + } + } + } +} + +impl InactiveCopyOnWrite { + /// Re-activates this inactive copy-on-write value by re-acquiring any borrow. + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { + Ok(match self { + InactiveCopyOnWrite::Owned(owned) => CopyOnWrite::Owned(owned), + InactiveCopyOnWrite::SharedWithInfallibleCloning(inactive) => { + CopyOnWrite::SharedWithInfallibleCloning(inactive.activate(span)?) + } + InactiveCopyOnWrite::SharedWithTransparentCloning(inactive) => { + CopyOnWrite::SharedWithTransparentCloning(inactive.activate(span)?) + } + }) + } +} + +impl AsRef for CopyOnWrite { + fn as_ref(&self) -> &T { + self + } +} + +impl Deref for CopyOnWrite { + type Target = T; + + fn deref(&self) -> &T { + match self { + CopyOnWrite::Owned(owned) => owned, + CopyOnWrite::SharedWithInfallibleCloning(shared) => shared.as_ref(), + CopyOnWrite::SharedWithTransparentCloning(shared) => shared.as_ref(), + } + } +} + #[derive(Copy, Clone)] pub(crate) struct BeCopyOnWrite; impl IsForm for BeCopyOnWrite {} impl IsHierarchicalForm for BeCopyOnWrite { - type Leaf<'a, T: IsLeafType> = QqqCopyOnWrite; + type Leaf<'a, T: IsLeafType> = CopyOnWrite; #[inline] fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> @@ -51,7 +211,7 @@ impl BeCopyOnWrite { map_via_leaf! { input: (Content<'a, C::Type, BeOwned>) = owned.into_content(), fn map_leaf(leaf) -> (Content<'a, T, BeCopyOnWrite>) { - QqqCopyOnWrite::Owned(leaf) + CopyOnWrite::Owned(leaf) } } } @@ -62,7 +222,7 @@ impl BeCopyOnWrite { map_via_leaf! { input: (Content<'a, C::Type, BeShared>) = shared.into_content(), fn map_leaf(leaf) -> (Content<'a, T, BeCopyOnWrite>) { - QqqCopyOnWrite::SharedWithInfallibleCloning(leaf) + CopyOnWrite::SharedWithInfallibleCloning(leaf) } } } @@ -73,7 +233,7 @@ impl BeCopyOnWrite { map_via_leaf! { input: (Content<'a, C::Type, BeShared>) = shared.into_content(), fn map_leaf(leaf) -> (Content<'a, T, BeCopyOnWrite>) { - QqqCopyOnWrite::SharedWithTransparentCloning(leaf) + CopyOnWrite::SharedWithTransparentCloning(leaf) } } } @@ -85,12 +245,12 @@ impl MapFromArgument for BeCopyOnWrite { fn from_argument_value( Spanned(value, span): Spanned, ) -> FunctionResult> { - match value.expect_copy_on_write().inner { - CopyOnWriteInner::Owned(owned) => Ok(BeCopyOnWrite::new_owned(owned)), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { + match value.expect_copy_on_write() { + CopyOnWrite::Owned(owned) => Ok(BeCopyOnWrite::new_owned(owned)), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { Ok(BeCopyOnWrite::new_shared_in_place_of_owned(shared)) } - CopyOnWriteInner::SharedWithTransparentCloning(shared) => { + CopyOnWrite::SharedWithTransparentCloning(shared) => { Ok(BeCopyOnWrite::new_shared_in_place_of_shared(shared)) } } @@ -100,9 +260,9 @@ impl MapFromArgument for BeCopyOnWrite { impl LeafAsRefForm for BeCopyOnWrite { fn leaf_as_ref<'r, 'a: 'r, T: IsLeafType>(leaf: &'r Self::Leaf<'a, T>) -> &'r T::Leaf { match leaf { - QqqCopyOnWrite::Owned(owned) => owned, - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => shared, - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => shared, + CopyOnWrite::Owned(owned) => owned, + CopyOnWrite::SharedWithInfallibleCloning(shared) => shared, + CopyOnWrite::SharedWithTransparentCloning(shared) => shared, } } @@ -110,11 +270,11 @@ impl LeafAsRefForm for BeCopyOnWrite { leaf: &'r Self::Leaf<'a, T>, ) -> T::Leaf { match leaf { - QqqCopyOnWrite::Owned(owned) => owned.clone(), - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => { + CopyOnWrite::Owned(owned) => owned.clone(), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { BeShared::leaf_clone_to_owned_infallible::(shared) } - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => { + CopyOnWrite::SharedWithTransparentCloning(shared) => { BeShared::leaf_clone_to_owned_infallible::(shared) } } @@ -123,13 +283,13 @@ impl LeafAsRefForm for BeCopyOnWrite { fn leaf_clone_to_owned_transparently<'r, 'a: 'r, T: IsLeafType>( leaf: &'r Self::Leaf<'a, T>, error_span: SpanRange, - ) -> ExecutionResult { + ) -> FunctionResult { match leaf { - QqqCopyOnWrite::Owned(owned) => Ok(owned.clone()), - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => { + CopyOnWrite::Owned(owned) => Ok(owned.clone()), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { Ok(BeShared::leaf_clone_to_owned_infallible::(shared)) } - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => { + CopyOnWrite::SharedWithTransparentCloning(shared) => { BeShared::leaf_clone_to_owned_transparently::(shared, error_span) } } @@ -149,9 +309,9 @@ where input: (Content<'a, Self::Type, Self::Form>) = self, fn map_leaf(leaf) -> (AnyLevelCopyOnWrite<'a, T>) { match leaf { - QqqCopyOnWrite::Owned(owned) => AnyLevelCopyOnWrite::Owned(owned), - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => AnyLevelCopyOnWrite::SharedWithInfallibleCloning(shared), - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => AnyLevelCopyOnWrite::SharedWithTransparentCloning(shared), + CopyOnWrite::Owned(owned) => AnyLevelCopyOnWrite::Owned(owned), + CopyOnWrite::SharedWithInfallibleCloning(shared) => AnyLevelCopyOnWrite::SharedWithInfallibleCloning(shared), + CopyOnWrite::SharedWithTransparentCloning(shared) => AnyLevelCopyOnWrite::SharedWithTransparentCloning(shared), } } } @@ -165,9 +325,9 @@ where input: (Content<'a, Self::Type, Self::Form>) = self, fn map_leaf(leaf) -> (Content<'static, T, BeOwned>) { match leaf { - QqqCopyOnWrite::Owned(owned) => owned, - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), + CopyOnWrite::Owned(owned) => owned, + CopyOnWrite::SharedWithInfallibleCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), + CopyOnWrite::SharedWithTransparentCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), } } } @@ -185,9 +345,9 @@ where state: SpanRange | let error_span = error_span, fn map_leaf(leaf) -> (ExecutionResult>) { Ok(match leaf { - QqqCopyOnWrite::Owned(owned) => owned, - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => BeShared::leaf_clone_to_owned_transparently::(&shared, error_span)?, + CopyOnWrite::Owned(owned) => owned, + CopyOnWrite::SharedWithInfallibleCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), + CopyOnWrite::SharedWithTransparentCloning(shared) => BeShared::leaf_clone_to_owned_transparently::(&shared, error_span)?, }) } } @@ -198,59 +358,58 @@ where input: &'r (Content<'a, Self::Type, Self::Form>) = self, fn map_leaf(leaf) -> (bool) { match leaf { - QqqCopyOnWrite::Owned(_) => false, - QqqCopyOnWrite::SharedWithInfallibleCloning(_) => false, - QqqCopyOnWrite::SharedWithTransparentCloning(_) => true, + CopyOnWrite::Owned(_) => false, + CopyOnWrite::SharedWithInfallibleCloning(_) => false, + CopyOnWrite::SharedWithTransparentCloning(_) => true, } } } } + // // TODO - Find alternative implementation or replace + // fn map(self, mapper: M) -> ExecutionResult> + // where + // 'a: 'static, + // M: OwnedTypeMapper + RefTypeMapper, + // M: TypeMapper, // u32 is temporary to see if we can make it work without adding generics to `map_via_leaf!` + // Self: Sized, + // Self: IsValueContent, // Temporary to see if we can make it work without adding generics to `map_via_leaf!` + // { + // Ok(match self.into_any_level_copy_on_write() { + // AnyLevelCopyOnWrite::Owned(owned) => BeCopyOnWrite::new_owned(mapper.map_owned(owned)?), + // AnyLevelCopyOnWrite::SharedWithInfallibleCloning(shared) => { + // // // Apply map, get Shared> + // // let shared = map_via_leaf! { + // // input: (Content<'a, Self::Type, BeShared>) = shared, + // // fn map_leaf(leaf) -> (ExecutionResult>>) { + // // leaf.try_map(|value_ref| { + // // let from_ref = value_ref.into_any(); + // // inner_map_ref(from_ref) // &Content - // TODO - Find alternative implementation or replace - fn map(self, mapper: M) -> ExecutionResult> - where - 'a: 'static, - M: OwnedTypeMapper + RefTypeMapper, - M: TypeMapper, // u32 is temporary to see if we can make it work without adding generics to `map_via_leaf!` - Self: Sized, - Self: IsValueContent, // Temporary to see if we can make it work without adding generics to `map_via_leaf!` - { - Ok(match self.into_any_level_copy_on_write() { - AnyLevelCopyOnWrite::Owned(owned) => BeCopyOnWrite::new_owned(mapper.map_owned(owned)?), - AnyLevelCopyOnWrite::SharedWithInfallibleCloning(shared) => { - // // Apply map, get Shared> - // let shared = map_via_leaf! { - // input: (Content<'a, Self::Type, BeShared>) = shared, - // fn map_leaf(leaf) -> (ExecutionResult>>) { - // leaf.try_map(|value_ref| { - // let from_ref = value_ref.into_any(); - // inner_map_ref(from_ref) // &Content - - // // If inner_map_ref returned a Content<'a, M::TTo, BeRef> - // // then we'd need to move the leaf map inside here, but it'd work the same - // }) - // } - // }?; - // // Migrate Shared into leaf - // let shared_content = shared.replace(|content, emplacer| { - // // TODO - use two lifetimes here - // // ---- - // map_via_leaf! { - // input: &'r (Content<'a, AnyType, BeOwned>) = content, - // state: | <'e> SharedEmplacer<'e, AnyValue, AnyValue> | let emplacer = emplacer, - // fn map_leaf(leaf) -> (Content<'static, T, BeShared>) { - // emplacer.emplace(leaf) - // } - // } - // }); - // BeCopyOnWrite::new_shared_in_place_of_owned::(shared_content) - todo!() - } - AnyLevelCopyOnWrite::SharedWithTransparentCloning(shared) => { - todo!() - } - }) - } + // // // If inner_map_ref returned a Content<'a, M::TTo, BeRef> + // // // then we'd need to move the leaf map inside here, but it'd work the same + // // }) + // // } + // // }?; + // // // Migrate Shared into leaf + // // let shared_content = shared.replace(|content, emplacer| { + // // // TODO - use two lifetimes here + // // // ---- + // // map_via_leaf! { + // // input: &'r (Content<'a, AnyType, BeOwned>) = content, + // // state: | <'e> SharedEmplacer<'e, AnyValue, AnyValue> | let emplacer = emplacer, + // // fn map_leaf(leaf) -> (Content<'static, T, BeShared>) { + // // emplacer.emplace(leaf) + // // } + // // } + // // }); + // // BeCopyOnWrite::new_shared_in_place_of_owned::(shared_content) + // todo!() + // } + // AnyLevelCopyOnWrite::SharedWithTransparentCloning(shared) => { + // todo!() + // } + // }) + // } } fn inner_map_ref<'a>( @@ -297,26 +456,26 @@ pub(crate) trait TypeMapper { type TFrom: IsHierarchicalType; type TTo: IsHierarchicalType; } -pub(crate) trait OwnedTypeMapper: TypeMapper { - fn map_owned<'a>( - self, - owned_value: Content<'a, Self::TFrom, BeOwned>, - ) -> ExecutionResult>; -} +// pub(crate) trait OwnedTypeMapper: TypeMapper { +// fn map_owned<'a>( +// self, +// owned_value: Content<'a, Self::TFrom, BeOwned>, +// ) -> ExecutionResult>; +// } -pub(crate) trait RefTypeMapper: TypeMapper { - fn map_ref<'a>( - self, - ref_value: Content<'a, Self::TFrom, BeRef>, - ) -> ExecutionResult<&'a Content<'a, Self::TTo, BeOwned>>; -} +// pub(crate) trait RefTypeMapper: TypeMapper { +// fn map_ref<'a>( +// self, +// ref_value: Content<'a, Self::TFrom, BeRef>, +// ) -> ExecutionResult<&'a Content<'a, Self::TTo, BeOwned>>; +// } -pub(crate) trait MutTypeMapper: TypeMapper { - fn map_mut<'a>( - self, - mut_value: Content<'a, Self::TFrom, BeMut>, - ) -> ExecutionResult<&'a mut Content<'a, Self::TTo, BeOwned>>; -} +// pub(crate) trait MutTypeMapper: TypeMapper { +// fn map_mut<'a>( +// self, +// mut_value: Content<'a, Self::TFrom, BeMut>, +// ) -> ExecutionResult<&'a mut Content<'a, Self::TTo, BeOwned>>; +// } impl<'a, C: IsSelfValueContent<'a>> IsSelfCopyOnWriteContent<'a> for C where diff --git a/src/expressions/concepts/forms/late_bound.rs b/src/expressions/concepts/forms/late_bound.rs index 22f45a18..9236e02a 100644 --- a/src/expressions/concepts/forms/late_bound.rs +++ b/src/expressions/concepts/forms/late_bound.rs @@ -4,7 +4,7 @@ pub(crate) enum QqqLateBound { /// An owned value that can be converted to any ownership type Owned(QqqLateBoundOwned), /// A copy-on-write value that can be converted to an owned value - CopyOnWrite(QqqCopyOnWrite), + CopyOnWrite(CopyOnWrite), /// A mutable reference Mutable(Mutable), /// A shared reference where mutable access failed for a specific reason diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index bd22f6bc..330d2065 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -8,6 +8,7 @@ impl IsValueContent for Shared { impl<'a, X: IsValueContent> IntoValueContent<'a> for Shared where X: 'static, + X: IsSelfValueContent<'static>, X::Type: IsHierarchicalType = X>, X::Form: IsHierarchicalForm, X::Form: LeafAsRefForm, diff --git a/src/expressions/concepts/mapping.rs b/src/expressions/concepts/mapping.rs index 6b58a307..a7ea5a14 100644 --- a/src/expressions/concepts/mapping.rs +++ b/src/expressions/concepts/mapping.rs @@ -146,6 +146,9 @@ macro_rules! __map_via_leaf_to_parent { ($output:ident -> ExecutionResult>) => { Ok($t::into_parent($output?)) }; + ($output:ident -> FunctionResult>) => { + Ok($t::into_parent($output?)) + }; ($output:ident -> Result, $err:ty>) => { Ok($t::into_parent($output?)) }; diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index 82c6b99e..12ef60b1 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -153,17 +153,24 @@ impl IsArgument for Mutable { // } // } -impl + ResolvableArgumentTarget> IsArgument for CopyOnWrite -where - T: ResolvableOwned, -{ - type ValueType = T::ValueType; +// See the "We have some friction" post above +impl IsArgument for CopyOnWrite { + type ValueType = AnyType; + const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::CopyOnWrite; + + fn from_argument(Spanned(value, _): Spanned) -> FunctionResult { + Ok(value.expect_copy_on_write()) + } +} + +impl IsArgument for CopyOnWrite { + type ValueType = AnyType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::CopyOnWrite; fn from_argument(Spanned(value, span): Spanned) -> FunctionResult { value.expect_copy_on_write().map( - |v| T::resolve_shared(v.spanned(span), "This argument"), - |v| T::resolve_value(v.spanned(span), "This argument"), + |v| FloatValue::resolve_shared(v.spanned(span), "This argument"), + |v| FloatValue::resolve_value(v.spanned(span), "This argument"), ) } } diff --git a/src/interpretation/bindings.rs b/src/interpretation/bindings.rs index 70e31b20..c62a24f7 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/bindings.rs @@ -1,7 +1,5 @@ use super::*; -use std::borrow::Borrow; - pub(super) enum VariableState { Uninitialized, Value(VariableContent), @@ -163,7 +161,7 @@ impl VariableState { } #[derive(Clone)] -pub(crate) struct VariableBinding { +struct VariableBinding { content: VariableContent, variable_span: Span, } @@ -339,232 +337,3 @@ impl Deref for LateBoundValue { self.as_value() } } - -// ============================================================================ -// CopyOnWrite -// ============================================================================ - -/// Copy-on-write value that can be either owned or shared -pub(crate) struct CopyOnWrite { - pub(crate) inner: CopyOnWriteInner, -} - -pub(crate) enum CopyOnWriteInner { - /// An owned value that can be used directly - Owned(Owned), - /// For use when the CopyOnWrite value effectively represents the owned value (post-clone). - /// In this case, returning a Cow is just an optimization and we can always clone infallibly. - SharedWithInfallibleCloning(Shared), - /// For use when the CopyOnWrite value represents a pre-cloned read-only value. - /// A transparent clone may fail in this case at use time. - SharedWithTransparentCloning(Shared), -} - -impl CopyOnWrite { - pub(crate) fn shared_in_place_of_owned(shared: Shared) -> Self { - Self { - inner: CopyOnWriteInner::SharedWithInfallibleCloning(shared), - } - } - - pub(crate) fn shared_in_place_of_shared(shared: Shared) -> Self { - Self { - inner: CopyOnWriteInner::SharedWithTransparentCloning(shared), - } - } - - pub(crate) fn owned(owned: Owned) -> Self { - Self { - inner: CopyOnWriteInner::Owned(owned), - } - } - - #[allow(unused)] - pub(crate) fn extract_owned(self, map: impl FnOnce(T) -> Result) -> Result { - match self.inner { - CopyOnWriteInner::Owned(value) => match map(value) { - Ok(mapped) => Ok(mapped), - Err(other) => Err(Self { - inner: CopyOnWriteInner::Owned(other), - }), - }, - other => Err(Self { inner: other }), - } - } - - pub(crate) fn acts_as_shared_reference(&self) -> bool { - match &self.inner { - CopyOnWriteInner::Owned { .. } => false, - CopyOnWriteInner::SharedWithInfallibleCloning { .. } => false, - CopyOnWriteInner::SharedWithTransparentCloning { .. } => true, - } - } - - pub(crate) fn map( - self, - map_shared: impl FnOnce(Shared) -> FunctionResult>, - map_owned: impl FnOnce(Owned) -> FunctionResult>, - ) -> FunctionResult> { - let inner = match self.inner { - CopyOnWriteInner::Owned(owned) => CopyOnWriteInner::Owned(map_owned(owned)?), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - CopyOnWriteInner::SharedWithInfallibleCloning(map_shared(shared)?) - } - CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - CopyOnWriteInner::SharedWithTransparentCloning(map_shared(shared)?) - } - }; - Ok(CopyOnWrite { inner }) - } - - pub(crate) fn map_into( - self, - map_shared: impl FnOnce(Shared) -> U, - map_owned: impl FnOnce(Owned) -> U, - ) -> U { - match self.inner { - CopyOnWriteInner::Owned(owned) => map_owned(owned), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => map_shared(shared), - CopyOnWriteInner::SharedWithTransparentCloning(shared) => map_shared(shared), - } - } - - /// Deactivates this copy-on-write value, releasing any borrow. - /// Returns a `InactiveCopyOnWrite` which can be cloned and later re-activated. - pub(crate) fn deactivate(self) -> InactiveCopyOnWrite { - let inner = match self.inner { - CopyOnWriteInner::Owned(owned) => InactiveCopyOnWriteInner::Owned(owned), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - InactiveCopyOnWriteInner::SharedWithInfallibleCloning(shared.deactivate()) - } - CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - InactiveCopyOnWriteInner::SharedWithTransparentCloning(shared.deactivate()) - } - }; - InactiveCopyOnWrite { inner } - } -} - -/// A disabled copy-on-write value that can be safely cloned and dropped. -pub(crate) struct InactiveCopyOnWrite { - inner: InactiveCopyOnWriteInner, -} - -enum InactiveCopyOnWriteInner { - Owned(Owned), - SharedWithInfallibleCloning(InactiveShared), - SharedWithTransparentCloning(InactiveShared), -} - -impl Clone for InactiveCopyOnWrite { - fn clone(&self) -> Self { - let inner = match &self.inner { - InactiveCopyOnWriteInner::Owned(owned) => { - InactiveCopyOnWriteInner::Owned(owned.clone()) - } - InactiveCopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - InactiveCopyOnWriteInner::SharedWithInfallibleCloning(shared.clone()) - } - InactiveCopyOnWriteInner::SharedWithTransparentCloning(shared) => { - InactiveCopyOnWriteInner::SharedWithTransparentCloning(shared.clone()) - } - }; - Self { inner } - } -} - -impl InactiveCopyOnWrite { - /// Re-activates this inactive copy-on-write value by re-acquiring any borrow. - pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { - let inner = match self.inner { - InactiveCopyOnWriteInner::Owned(owned) => CopyOnWriteInner::Owned(owned), - InactiveCopyOnWriteInner::SharedWithInfallibleCloning(inactive) => { - CopyOnWriteInner::SharedWithInfallibleCloning(inactive.activate(span)?) - } - InactiveCopyOnWriteInner::SharedWithTransparentCloning(inactive) => { - CopyOnWriteInner::SharedWithTransparentCloning(inactive.activate(span)?) - } - }; - Ok(CopyOnWrite { inner }) - } -} - -impl IsValueContent for CopyOnWrite { - type Type = X::Type; - type Form = BeCopyOnWrite; -} - -impl IntoValueContent<'static> for CopyOnWrite -where - X: IsValueContent + IsSelfValueContent<'static> + IntoValueContent<'static>, - X::Type: IsHierarchicalType = X>, -{ - fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - match self.inner { - CopyOnWriteInner::Owned(owned) => { - AnyLevelCopyOnWrite::::Owned(owned).into_copy_on_write() - } - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - AnyLevelCopyOnWrite::::SharedWithInfallibleCloning(shared.into_content()) - .into_copy_on_write() - } - CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - AnyLevelCopyOnWrite::::SharedWithTransparentCloning(shared.into_content()) - .into_copy_on_write() - } - } - } -} - -impl AsRef for CopyOnWrite { - fn as_ref(&self) -> &T { - self - } -} - -impl Deref for CopyOnWrite { - type Target = T; - - fn deref(&self) -> &T { - match self.inner { - CopyOnWriteInner::Owned(ref owned) => (*owned).borrow(), - CopyOnWriteInner::SharedWithInfallibleCloning(ref shared) => shared.as_ref(), - CopyOnWriteInner::SharedWithTransparentCloning(ref shared) => shared.as_ref(), - } - } -} - -impl CopyOnWrite { - /// Converts to owned, cloning if necessary - pub(crate) fn clone_to_owned_infallible(self) -> AnyValueOwned { - match self.inner { - CopyOnWriteInner::Owned(owned) => owned, - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => shared.infallible_clone(), - CopyOnWriteInner::SharedWithTransparentCloning(shared) => shared.infallible_clone(), - } - } - - /// Converts to owned, using transparent clone for shared values where cloning was not requested - pub(crate) fn clone_to_owned_transparently( - self, - span: SpanRange, - ) -> FunctionResult { - match self.inner { - CopyOnWriteInner::Owned(owned) => Ok(owned), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => Ok(shared.infallible_clone()), - CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - let value = shared.as_ref().try_transparent_clone(span)?; - Ok(value) - } - } - } - - /// Converts to shared reference - pub(crate) fn into_shared(self, span: SpanRange) -> AnyValueShared { - match self.inner { - CopyOnWriteInner::Owned(owned) => AnyValueShared::new_from_owned(owned, None, span), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => shared, - CopyOnWriteInner::SharedWithTransparentCloning(shared) => shared, - } - } -} From eec576cd799829be6380479609711142b4cc165b Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 1 Mar 2026 20:23:04 +0000 Subject: [PATCH 45/49] refactor: Unify `LateBound` --- plans/TODO.md | 4 +- .../concepts/forms/copy_on_write.rs | 6 +- src/expressions/concepts/forms/late_bound.rs | 104 ++++++++++-- src/expressions/evaluation/evaluator.rs | 8 +- src/expressions/evaluation/value_frames.rs | 24 +-- src/expressions/values/any_value.rs | 1 + src/interpretation/interpreter.rs | 4 +- src/interpretation/mod.rs | 4 +- src/interpretation/variable.rs | 2 +- .../{bindings.rs => variable_state.rs} | 155 +++--------------- 10 files changed, 131 insertions(+), 181 deletions(-) rename src/interpretation/{bindings.rs => variable_state.rs} (60%) diff --git a/plans/TODO.md b/plans/TODO.md index 0d7cd05d..9d62ea27 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -298,8 +298,8 @@ Suddenly dawned on me - my Disabled arguments might not be safe. - [x] Move `Assignee` out of bindings. To e.g. `dynamic_references` - [x] Rename `PathExtension::Tightening` to `PathExtension::TypeNarrowing` - [x] Rename `DisabledArgumentValue` and `DisabledCopyOnWrite` to `Inactive__` and their method from `enable` to `activate` and ditto with `disable -> deactivate` -- [ ] See what else can be deleted from `bindings.rs` - - [ ] Unify LateBound into `QqqLateBound` +- [x] See what else can be deleted from `bindings.rs` + - [x] Unify LateBound into `LateBound` - [x] Unify CopyOnWrite into `QqqCopyOnWrite` - [ ] Add various tests: - [ ] Stretching different error messages diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index 92fbc418..0b80e81f 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -23,9 +23,7 @@ where { fn into_content(self) -> Content<'a, Self::Type, Self::Form> { match self { - CopyOnWrite::Owned(owned) => { - BeCopyOnWrite::new_owned(owned) - } + CopyOnWrite::Owned(owned) => BeCopyOnWrite::new_owned(owned), CopyOnWrite::SharedWithInfallibleCloning(shared) => { BeCopyOnWrite::new_shared_in_place_of_owned(shared) } @@ -158,7 +156,7 @@ impl Clone for InactiveCopyOnWrite { impl InactiveCopyOnWrite { /// Re-activates this inactive copy-on-write value by re-acquiring any borrow. pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { - Ok(match self { + Ok(match self { InactiveCopyOnWrite::Owned(owned) => CopyOnWrite::Owned(owned), InactiveCopyOnWrite::SharedWithInfallibleCloning(inactive) => { CopyOnWrite::SharedWithInfallibleCloning(inactive.activate(span)?) diff --git a/src/expressions/concepts/forms/late_bound.rs b/src/expressions/concepts/forms/late_bound.rs index 9236e02a..2628b357 100644 --- a/src/expressions/concepts/forms/late_bound.rs +++ b/src/expressions/concepts/forms/late_bound.rs @@ -1,49 +1,117 @@ use super::*; -pub(crate) enum QqqLateBound { +/// A temporary, flexible form that can resolve to any concrete ownership type. +/// +/// Sometimes, a value can be accessed, but we don't yet know *how* we need to access it. +/// In this case, we attempt to load it with the most powerful access we can have, and convert it later. +/// +/// ## Example of requirement +/// For example, if we have `x[a].method()`, we first need to resolve the type of `x[a]` to know +/// whether the method needs `x[a]` to be a shared reference, mutable reference or an owned value. +/// +/// So instead, we take the most powerful access we can have for `x[a]`, and convert it later. +pub(crate) enum LateBound { /// An owned value that can be converted to any ownership type - Owned(QqqLateBoundOwned), + Owned(LateBoundOwned), /// A copy-on-write value that can be converted to an owned value CopyOnWrite(CopyOnWrite), /// A mutable reference Mutable(Mutable), /// A shared reference where mutable access failed for a specific reason - Shared(QqqLateBoundShared), + Shared(LateBoundShared), } -impl IsValueContent for QqqLateBound { +impl LateBound { + pub(crate) fn new_shared(shared: Shared, reason_not_mutable: syn::Error) -> Self { + LateBound::Shared(LateBoundShared { + shared, + reason_not_mutable, + }) + } + + /// Maps the late-bound value through the appropriate accessor function. + /// + /// If the mutable mapping fails with a retryable reason, we fall back to `map_shared`. + /// This allows operations that don't need mutable access (like reading a non-existent + /// key from an object) to still work, with the mutable error preserved as `reason_not_mutable`. + pub(crate) fn map_any( + self, + map_shared: impl FnOnce(Shared) -> FunctionResult>, + map_mutable: impl FnOnce(Mutable) -> Result, (FunctionError, Mutable)>, + map_owned: impl FnOnce(T) -> FunctionResult, + ) -> FunctionResult { + Ok(match self { + LateBound::Owned(owned) => LateBound::Owned(LateBoundOwned { + owned: map_owned(owned.owned)?, + is_from_last_use: owned.is_from_last_use, + }), + LateBound::CopyOnWrite(copy_on_write) => { + LateBound::CopyOnWrite(copy_on_write.map(map_shared, map_owned)?) + } + LateBound::Mutable(mutable) => match map_mutable(mutable) { + Ok(mapped) => LateBound::Mutable(mapped), + Err((error, recovered_mutable)) => { + // Check if this error can be caught for fallback to shared access + let reason_not_mutable = error.into_caught_mutable_map_attempt_error()?; + LateBound::new_shared( + map_shared(recovered_mutable.into_shared())?, + reason_not_mutable, + ) + } + }, + LateBound::Shared(LateBoundShared { + shared, + reason_not_mutable, + }) => LateBound::new_shared(map_shared(shared)?, reason_not_mutable), + }) + } +} + +impl Spanned { + pub(crate) fn resolve(self, ownership: ArgumentOwnership) -> FunctionResult { + ownership.map_from_late_bound(self) + } +} + +impl Deref for LateBound { + type Target = T; + + fn deref(&self) -> &Self::Target { + match self { + LateBound::Owned(owned) => &owned.owned, + LateBound::CopyOnWrite(cow) => cow.as_ref(), + LateBound::Mutable(mutable) => mutable.as_ref(), + LateBound::Shared(shared) => shared.shared.as_ref(), + } + } +} + +impl IsValueContent for LateBound { type Type = L::Type; type Form = BeLateBound; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqLateBound { +impl<'a, L: IsValueLeaf> IntoValueContent<'a> for LateBound { fn into_content(self) -> Content<'a, Self::Type, Self::Form> { self } } -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqLateBound { +impl<'a, L: IsValueLeaf> FromValueContent<'a> for LateBound { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } } -/// Universal value type that can resolve to any concrete ownership type. -/// -/// Sometimes, a value can be accessed, but we don't yet know *how* we need to access it. -/// In this case, we attempt to load it with the most powerful access we can have, and convert it later. +/// A temporary, flexible form that can resolve to any concrete ownership type. /// -/// ## Example of requirement -/// For example, if we have `x[a].method()`, we first need to resolve the type of `x[a]` to know -/// whether the method needs `x[a]` to be a shared reference, mutable reference or an owned value. -/// -/// So instead, we take the most powerful access we can have for `x[a]`, and convert it later. +/// See [`LateBound`] for more details. #[derive(Copy, Clone)] pub(crate) struct BeLateBound; impl IsForm for BeLateBound {} impl IsHierarchicalForm for BeLateBound { - type Leaf<'a, T: IsLeafType> = QqqLateBound; + type Leaf<'a, T: IsLeafType> = LateBound; #[inline] fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> @@ -54,13 +122,13 @@ impl IsHierarchicalForm for BeLateBound { } } -pub(crate) struct QqqLateBoundOwned { +pub(crate) struct LateBoundOwned { pub(crate) owned: O, pub(crate) is_from_last_use: bool, } /// A shared value where mutable access failed for a specific reason -pub(crate) struct QqqLateBoundShared { +pub(crate) struct LateBoundShared { pub(crate) shared: Shared, pub(crate) reason_not_mutable: syn::Error, } diff --git a/src/expressions/evaluation/evaluator.rs b/src/expressions/evaluation/evaluator.rs index 2c80252b..0f7b8dba 100644 --- a/src/expressions/evaluation/evaluator.rs +++ b/src/expressions/evaluation/evaluator.rs @@ -154,7 +154,7 @@ pub(crate) enum RequestedValue { // RequestedOwnership::LateBound // ------------------------------- - LateBound(LateBoundValue), + LateBound(AnyValueLateBound), // Marks completion of an assignment frame // --------------------------------------- @@ -191,7 +191,7 @@ impl RequestedValue { } } - pub(crate) fn expect_late_bound(self) -> LateBoundValue { + pub(crate) fn expect_late_bound(self) -> AnyValueLateBound { match self { RequestedValue::LateBound(late_bound) => late_bound, _ => panic!("expect_late_bound() called on non-late-bound RequestedValue"), @@ -287,7 +287,7 @@ impl Spanned { } #[inline] - pub(crate) fn expect_late_bound(self) -> Spanned { + pub(crate) fn expect_late_bound(self) -> Spanned { self.map(|v| v.expect_late_bound()) } @@ -476,7 +476,7 @@ impl<'a> Context<'a, ReturnsValue> { pub(super) fn return_late_bound( self, - late_bound: Spanned, + late_bound: Spanned, ) -> ExecutionResult { let value = self.request.map_from_late_bound(late_bound)?; Ok(NextAction::return_requested(value)) diff --git a/src/expressions/evaluation/value_frames.rs b/src/expressions/evaluation/value_frames.rs index ae160ee0..10ffd94a 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -211,7 +211,7 @@ impl RequestedOwnership { pub(crate) fn map_from_late_bound( &self, - Spanned(late_bound, span): Spanned, + Spanned(late_bound, span): Spanned, ) -> FunctionResult> { Ok(match self { RequestedOwnership::LateBound => RequestedValue::LateBound(late_bound), @@ -281,7 +281,7 @@ impl RequestedOwnership { Ok(Spanned( match self { RequestedOwnership::LateBound => { - RequestedValue::LateBound(LateBoundValue::Owned(LateBoundOwnedValue { + RequestedValue::LateBound(LateBound::Owned(LateBoundOwned { owned: value, is_from_last_use: false, })) @@ -301,7 +301,7 @@ impl RequestedOwnership { Ok(Spanned( match self { RequestedOwnership::LateBound => { - RequestedValue::LateBound(LateBoundValue::CopyOnWrite(cow)) + RequestedValue::LateBound(AnyValueLateBound::CopyOnWrite(cow)) } RequestedOwnership::Concrete(requested) => { Self::item_from_argument(requested.map_from_copy_on_write(Spanned(cow, span))?) @@ -318,7 +318,7 @@ impl RequestedOwnership { Ok(Spanned( match self { RequestedOwnership::LateBound => { - RequestedValue::LateBound(LateBoundValue::Mutable(mutable)) + RequestedValue::LateBound(AnyValueLateBound::Mutable(mutable)) } RequestedOwnership::Concrete(requested) => { Self::item_from_argument(requested.map_from_mutable(Spanned(mutable, span))?) @@ -335,7 +335,7 @@ impl RequestedOwnership { Ok(Spanned( match self { RequestedOwnership::LateBound => { - RequestedValue::LateBound(LateBoundValue::Mutable(assignee.0)) + RequestedValue::LateBound(AnyValueLateBound::Mutable(assignee.0)) } RequestedOwnership::Concrete(requested) => { Self::item_from_argument(requested.map_from_assignee(Spanned(assignee, span))?) @@ -352,7 +352,7 @@ impl RequestedOwnership { Ok(Spanned( match self { RequestedOwnership::LateBound => RequestedValue::LateBound( - LateBoundValue::CopyOnWrite(CopyOnWrite::shared_in_place_of_shared(shared)), + AnyValueLateBound::CopyOnWrite(CopyOnWrite::shared_in_place_of_shared(shared)), ), RequestedOwnership::Concrete(requested) => { Self::item_from_argument(requested.map_from_shared(Spanned(shared, span))?) @@ -419,20 +419,20 @@ pub(crate) enum ArgumentOwnership { impl ArgumentOwnership { pub(crate) fn map_from_late_bound( &self, - Spanned(late_bound, span): Spanned, + Spanned(late_bound, span): Spanned, ) -> FunctionResult { match late_bound { - LateBoundValue::Owned(owned) => self.map_from_owned_with_is_last_use( + LateBound::Owned(owned) => self.map_from_owned_with_is_last_use( Spanned(owned.owned, span), owned.is_from_last_use, ), - LateBoundValue::CopyOnWrite(copy_on_write) => { + LateBound::CopyOnWrite(copy_on_write) => { self.map_from_copy_on_write(Spanned(copy_on_write, span)) } - LateBoundValue::Mutable(mutable) => { + LateBound::Mutable(mutable) => { self.map_from_mutable_inner(Spanned(mutable, span), true) } - LateBoundValue::Shared(late_bound_shared) => self.map_from_shared_with_error_reason( + LateBound::Shared(late_bound_shared) => self.map_from_shared_with_error_reason( Spanned(late_bound_shared.shared, span), |_| { FunctionError::new(ExecutionInterrupt::ownership_error( @@ -910,7 +910,7 @@ impl EvaluationFrame for BinaryOperationBuilder { // Check for lazy evaluation first (short-circuit operators) // Use operator span for type errors since the error is about the operation's requirements - let left_value = Spanned(left.as_value(), left_span); + let left_value = Spanned(&*left, left_span); if let Some(result) = self.operation.lazy_evaluate(left_value)? { // For short-circuit, the result span is just the left operand's span // (the right operand was never evaluated) diff --git a/src/expressions/values/any_value.rs b/src/expressions/values/any_value.rs index 880b990a..7dd20b02 100644 --- a/src/expressions/values/any_value.rs +++ b/src/expressions/values/any_value.rs @@ -9,6 +9,7 @@ pub(crate) type AnyValueShared = Shared; pub(crate) type AnyValueMutable = Mutable; pub(crate) type AnyValueAssignee = Assignee; pub(crate) type AnyValueCopyOnWrite = CopyOnWrite; +pub(crate) type AnyValueLateBound = LateBound; // pub(crate) type AnyValueShared = AnyValueContent<'static, BeShared>; // pub(crate) type AnyValueMutable = AnyValueContent<'static, BeMutable>; // pub(crate) type AnyValueAssignee = AnyValueContent<'static, BeAssignee>; diff --git a/src/interpretation/interpreter.rs b/src/interpretation/interpreter.rs index 19868f66..a6ef8239 100644 --- a/src/interpretation/interpreter.rs +++ b/src/interpretation/interpreter.rs @@ -251,7 +251,7 @@ impl Interpreter { &mut self, variable: &VariableReference, ownership: RequestedOwnership, - ) -> FunctionResult> { + ) -> FunctionResult> { let reference = self.scope_definitions.references.get(variable.id); let (definition, span, is_final) = ( reference.definition, @@ -512,7 +512,7 @@ impl RuntimeScope { is_final: bool, ownership: RequestedOwnership, blocked_from_mutation: Option, - ) -> FunctionResult> { + ) -> FunctionResult> { self.variables .get_mut(&definition_id) .expect("Variable data not found in scope") diff --git a/src/interpretation/mod.rs b/src/interpretation/mod.rs index 23deb67d..ce92f88f 100644 --- a/src/interpretation/mod.rs +++ b/src/interpretation/mod.rs @@ -1,4 +1,3 @@ -mod bindings; mod input_handler; mod interpret_traits; mod interpreter; @@ -9,10 +8,10 @@ mod parse_template_stream; mod refs; mod source_stream; mod variable; +mod variable_state; // Marked as use for expression sub-modules to use with a `use super::*` statement use crate::internal_prelude::*; -pub(crate) use bindings::*; pub(crate) use input_handler::*; pub(crate) use interpret_traits::*; pub(crate) use interpreter::*; @@ -23,3 +22,4 @@ pub(crate) use parse_template_stream::*; pub(crate) use refs::*; pub(crate) use source_stream::*; pub(crate) use variable::*; +pub(crate) use variable_state::*; diff --git a/src/interpretation/variable.rs b/src/interpretation/variable.rs index 93600c40..d4004b92 100644 --- a/src/interpretation/variable.rs +++ b/src/interpretation/variable.rs @@ -144,7 +144,7 @@ impl VariableReference { pub(crate) fn resolve_late_bound( &self, interpreter: &mut Interpreter, - ) -> FunctionResult> { + ) -> FunctionResult> { interpreter.resolve(self, RequestedOwnership::LateBound) } diff --git a/src/interpretation/bindings.rs b/src/interpretation/variable_state.rs similarity index 60% rename from src/interpretation/bindings.rs rename to src/interpretation/variable_state.rs index c62a24f7..fdbb9dd4 100644 --- a/src/interpretation/bindings.rs +++ b/src/interpretation/variable_state.rs @@ -71,7 +71,7 @@ impl VariableState { is_final: bool, ownership: RequestedOwnership, blocked_from_mutation: Option, - ) -> FunctionResult> { + ) -> FunctionResult> { let span_range = variable_span.span_range(); // If blocked from mutation, we technically could allow is_final to work and @@ -90,7 +90,7 @@ impl VariableState { return variable_span.control_flow_err("The final usage of a variable cannot be assigned to. You can use `let _ = ..` to discard a value."); } return Ok(Spanned( - LateBoundValue::Owned(LateBoundOwnedValue { + LateBound::Owned(LateBoundOwned { owned, is_from_last_use: true, }), @@ -122,7 +122,7 @@ impl VariableState { RequestedOwnership::LateBound => binding.into_late_bound(), RequestedOwnership::Concrete(ownership) => match ownership { ArgumentOwnership::Owned => binding.into_transparently_cloned().map(|owned| { - LateBoundValue::Owned(LateBoundOwnedValue { + LateBound::Owned(LateBoundOwned { owned, is_from_last_use: false, }) @@ -130,26 +130,26 @@ impl VariableState { ArgumentOwnership::Shared => binding .into_shared() .map(CopyOnWrite::shared_in_place_of_shared) - .map(LateBoundValue::CopyOnWrite), + .map(AnyValueLateBound::CopyOnWrite), ArgumentOwnership::Assignee { .. } => { - binding.into_mut().map(LateBoundValue::Mutable) + binding.into_mut().map(AnyValueLateBound::Mutable) } - ArgumentOwnership::Mutable => binding.into_mut().map(LateBoundValue::Mutable), + ArgumentOwnership::Mutable => binding.into_mut().map(AnyValueLateBound::Mutable), ArgumentOwnership::CopyOnWrite | ArgumentOwnership::AsIs => binding .into_shared() .map(CopyOnWrite::shared_in_place_of_shared) - .map(LateBoundValue::CopyOnWrite), + .map(AnyValueLateBound::CopyOnWrite), }, }; let late_bound = if let Some(mutation_block_reason) = blocked_from_mutation { match resolved { - Ok(LateBoundValue::Mutable(mutable)) => { + Ok(AnyValueLateBound::Mutable(mutable)) => { let reason_not_mutable = variable_span .syn_error(mutation_block_reason.error_message("mutate this variable")); - Ok(LateBoundValue::Shared(LateBoundSharedValue::new( + Ok(LateBound::new_shared( mutable.into_shared(), reason_not_mutable, - ))) + )) } x => x, } @@ -196,144 +196,27 @@ impl VariableBinding { } } - fn into_late_bound(self) -> FunctionResult { + fn into_late_bound(self) -> FunctionResult { let span = self.variable_span.span_range(); match self.content { VariableContent::Referenceable(referenceable) => { match referenceable.new_active_mutable(span) { - Ok(mutable) => Ok(LateBoundValue::Mutable(mutable)), + Ok(mutable) => Ok(AnyValueLateBound::Mutable(mutable)), Err(err) => { // Mutable failed, try shared let shared = referenceable.new_active_shared(span)?; - Ok(LateBoundValue::Shared(LateBoundSharedValue::new( - shared, - err.expect_ownership_error()?, - ))) + Ok(LateBound::new_shared(shared, err.expect_ownership_error()?)) } } } - VariableContent::Mutable(inactive) => { - Ok(LateBoundValue::Mutable(inactive.activate(span)?)) - } - VariableContent::Shared(inactive) => { - Ok(LateBoundValue::Shared(LateBoundSharedValue::new( - inactive.activate(span)?, - self.variable_span - .syn_error(SHARED_TO_MUTABLE_ERROR_MESSAGE), - ))) - } - } - } -} - -static SHARED_TO_MUTABLE_ERROR_MESSAGE: &str = "Cannot mutate a non-mutable reference"; - -pub(crate) struct LateBoundOwnedValue { - pub(crate) owned: AnyValueOwned, - pub(crate) is_from_last_use: bool, -} - -/// A shared value where mutable access failed for a specific reason -pub(crate) struct LateBoundSharedValue { - pub(crate) shared: AnyValueShared, - pub(crate) reason_not_mutable: syn::Error, -} - -impl LateBoundSharedValue { - pub(crate) fn new(shared: AnyValueShared, reason_not_mutable: syn::Error) -> Self { - Self { - shared, - reason_not_mutable, - } - } -} - -/// Universal value type that can resolve to any concrete ownership type. -/// -/// Sometimes, a value can be accessed, but we don't yet know *how* we need to access it. -/// In this case, we attempt to load it with the most powerful access we can have, and convert it later. -/// -/// ## Example of requirement -/// For example, if we have `x[a].method()`, we first need to resolve the type of `x[a]` to know -/// whether the method needs `x[a]` to be a shared reference, mutable reference or an owned value. -/// -/// So instead, we take the most powerful access we can have for `x[a]`, and convert it later. -pub(crate) enum LateBoundValue { - /// An owned value that can be converted to any ownership type - Owned(LateBoundOwnedValue), - /// A copy-on-write value that can be converted to an owned value - CopyOnWrite(AnyValueCopyOnWrite), - /// A mutable reference - Mutable(AnyValueMutable), - /// A shared reference where mutable access failed for a specific reason - Shared(LateBoundSharedValue), -} - -impl Spanned { - pub(crate) fn resolve(self, ownership: ArgumentOwnership) -> FunctionResult { - ownership.map_from_late_bound(self) - } -} - -impl LateBoundValue { - /// Maps the late-bound value through the appropriate accessor function. - /// - /// If the mutable mapping fails with a retryable reason, we fall back to `map_shared`. - /// This allows operations that don't need mutable access (like reading a non-existent - /// key from an object) to still work, with the mutable error preserved as `reason_not_mutable`. - pub(crate) fn map_any( - self, - map_shared: impl FnOnce(AnyValueShared) -> FunctionResult, - map_mutable: impl FnOnce( - AnyValueMutable, - ) -> Result, - map_owned: impl FnOnce(AnyValueOwned) -> FunctionResult, - ) -> FunctionResult { - Ok(match self { - LateBoundValue::Owned(owned) => LateBoundValue::Owned(LateBoundOwnedValue { - owned: map_owned(owned.owned)?, - is_from_last_use: owned.is_from_last_use, - }), - LateBoundValue::CopyOnWrite(copy_on_write) => { - LateBoundValue::CopyOnWrite(copy_on_write.map(map_shared, map_owned)?) - } - LateBoundValue::Mutable(mutable) => match map_mutable(mutable) { - Ok(mapped) => LateBoundValue::Mutable(mapped), - Err((error, recovered_mutable)) => { - // Check if this error can be caught for fallback to shared access - let reason_not_mutable = error.into_caught_mutable_map_attempt_error()?; - let shared = recovered_mutable.into_shared(); - let mapped_shared = map_shared(shared)?; - LateBoundValue::Shared(LateBoundSharedValue::new( - mapped_shared, - reason_not_mutable, - )) - } - }, - LateBoundValue::Shared(LateBoundSharedValue { - shared, - reason_not_mutable, - }) => LateBoundValue::Shared(LateBoundSharedValue::new( - map_shared(shared)?, - reason_not_mutable, + VariableContent::Mutable(inactive) => Ok(LateBound::Mutable(inactive.activate(span)?)), + VariableContent::Shared(inactive) => Ok(LateBound::new_shared( + inactive.activate(span)?, + self.variable_span + .syn_error(SHARED_TO_MUTABLE_ERROR_MESSAGE), )), - }) - } - - pub(crate) fn as_value(&self) -> &AnyValue { - match self { - LateBoundValue::Owned(owned) => &owned.owned, - LateBoundValue::CopyOnWrite(cow) => cow.as_ref(), - LateBoundValue::Mutable(mutable) => mutable.as_ref(), - LateBoundValue::Shared(shared) => shared.shared.as_ref(), } } } -impl Deref for LateBoundValue { - type Target = AnyValue; - - fn deref(&self) -> &Self::Target { - self.as_value() - } -} +static SHARED_TO_MUTABLE_ERROR_MESSAGE: &str = "Cannot mutate a non-mutable reference"; From 5f53bb1480015318a43c59e5f83dab8cd25f6508 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 1 Mar 2026 20:26:55 +0000 Subject: [PATCH 46/49] fix: Remove extra new line from error message --- src/misc/dynamic_references/referenceable.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 6af5c6bc..aaf73b5c 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -158,7 +158,7 @@ impl ReferenceableData { self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveMutable)); let _ = write!(error_message, "\nOther reference: "); let _ = self.display_path(&mut error_message, other_id, None); - let _ = write!(error_message, "\nReason : {}\n", error_reason); + let _ = write!(error_message, "\nReason : {}", error_reason); return data.creation_span.ownership_err(error_message); } } @@ -200,7 +200,7 @@ impl ReferenceableData { self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveShared)); let _ = write!(error_message, "\nOther reference: "); let _ = self.display_path(&mut error_message, other_id, None); - let _ = write!(error_message, "\nReason : {}\n", error_reason); + let _ = write!(error_message, "\nReason : {}", error_reason); return data.creation_span.ownership_err(error_message); } } From 2e320e0bb28984c8752c47d12a91d67c3869d815 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 1 Mar 2026 21:26:05 +0000 Subject: [PATCH 47/49] tests: Add tests for dynamic references --- plans/TODO.md | 12 ++++--- src/expressions/closures.rs | 1 - src/expressions/values/array.rs | 7 ++-- src/expressions/values/none.rs | 4 +++ src/misc/dynamic_references/referenceable.rs | 20 +++++++---- ...ctive_ref_attempt_mutate_its_descendent.rs | 13 ++++++++ ...e_ref_attempt_mutate_its_descendent.stderr | 8 +++++ ...nactive_mut_attempt_mutate_its_ancestor.rs | 8 +++++ ...ive_mut_attempt_mutate_its_ancestor.stderr | 8 +++++ ...nactive_ref_attempt_mutate_its_ancestor.rs | 11 +++++++ ...ive_ref_attempt_mutate_its_ancestor.stderr | 8 +++++ tests/expressions.rs | 4 +++ tests/references.rs | 33 +++++++++++++++++++ 13 files changed, 123 insertions(+), 14 deletions(-) create mode 100644 tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs create mode 100644 tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.stderr create mode 100644 tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs create mode 100644 tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr create mode 100644 tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs create mode 100644 tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr create mode 100644 tests/references.rs diff --git a/plans/TODO.md b/plans/TODO.md index 9d62ea27..23b1e3bc 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -251,11 +251,11 @@ Moved to [2026-01-types-and-forms.md](./2026-01-types-and-forms.md). - [ ] Salvage half-baked `FunctionValue` changes to allow invocation - [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` - [ ] Add tests for iterable methods -- [ ] Add `array.sort`, `array.sort_by` - [ ] Look into if a closure like `|| { }()` can evade `attempt` block statue mutation checks. Maybe lean into it as a way to avoid them, and mention it in the error message - [ ] Resolve all `TODO[functions]` Possible punted: +- [ ] Add `array.sort`, `array.sort_by` - [ ] Allow destructuring shared and mutable variables and arguments - [ ] Support optional arguments in closures - [ ] Support for `move()` expressions in closures. @@ -301,13 +301,15 @@ Suddenly dawned on me - my Disabled arguments might not be safe. - [x] See what else can be deleted from `bindings.rs` - [x] Unify LateBound into `LateBound` - [x] Unify CopyOnWrite into `QqqCopyOnWrite` -- [ ] Add various tests: - - [ ] Stretching different error messages - - [ ] Showing I can do e.g. `x.a += x.b` - - [ ] Show that `let my_arr = [[]]; my_arr[0].push(my_arr.pop())` gives a suitable error +- [x] Add various tests: + - [x] Stretching different error messages + - [x] Showing I can do e.g. `x.a += x.b` + - [x] Show that `let my_arr = [[]]; my_arr[0].push(my_arr.pop())` gives a suitable error ### Other ideas +- [ ] Make it so that we disable the parent whilst resolving a property/index + We could imagine a world where we are more clever over our mutation: * Note that I can reference `x.a` and `x.b` separately, or `x[0]` and `x[1]` but in that case, can't mutate `x` itself to create new fields. * Or we use a `ExpandableVec` which allocates a `Vec>` and doesn't move the inner chunks; allowing us to add new fields without breaking pointers to existing ones. diff --git a/src/expressions/closures.rs b/src/expressions/closures.rs index 1bff2d1c..7eb38710 100644 --- a/src/expressions/closures.rs +++ b/src/expressions/closures.rs @@ -33,7 +33,6 @@ impl ClosureExpression { pub(crate) struct ClosureValue { definition: Rc, closed_references: Vec<(VariableDefinitionId, VariableContent)>, - // TODO[functions]: Add closed_values from moves here } impl PartialEq for ClosureValue { diff --git a/src/expressions/values/array.rs b/src/expressions/values/array.rs index d40ade4c..4d2cbe01 100644 --- a/src/expressions/values/array.rs +++ b/src/expressions/values/array.rs @@ -161,9 +161,12 @@ define_type_features! { impl ArrayType, pub(crate) mod array_interface { methods { - fn push(mut this: Mutable, item: AnyValue) -> FunctionResult<()> { + fn push(mut this: Mutable, item: AnyValue) -> (){ this.items.push(item); - Ok(()) + } + + fn pop(mut this: Mutable) -> AnyValue { + this.items.pop().unwrap_or(none()) } [context] fn to_stream_grouped(this: ArrayValue) -> FunctionResult { diff --git a/src/expressions/values/none.rs b/src/expressions/values/none.rs index 0f79e10a..2106cebe 100644 --- a/src/expressions/values/none.rs +++ b/src/expressions/values/none.rs @@ -9,6 +9,10 @@ define_leaf_type! { dyn_impls: {}, } +pub(crate) const fn none() -> AnyValue { + AnyValue::None(()) +} + impl ResolvableArgumentTarget for () { type ValueType = NoneType; } diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index aaf73b5c..411510ac 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -227,7 +227,7 @@ impl ReferenceableData { let last_path_part = data.path.parts.last_mut().expect("path is non-empty"); match (last_path_part, path_extension) { (last_path_part, PathExtension::Child(specifier, child_bound_as)) => { - let parent_type = specifier.bound_type_kind(); + let parent_type = specifier.parent_type_kind(); match last_path_part { PathPart::Value { bound_as } => { if !bound_as.is_narrowing_to(&parent_type) { @@ -378,6 +378,13 @@ enum PathComparison { impl PathComparison { fn error_comparing_mutable_with_other(self, other_is_active: bool) -> Option<&'static str> { + // We are looking to start actively mutating a reference (the "Left" reference), + // with a pre-existing other reference around (the "Right" reference). + // The method protects against the various ways this may break invariants of the Right: + // - If the other reference is active, it must not overlap with any mutable reference + // (i.e. the aliasing rules must be respected) + // - If the other reference is inactive, observable mutation may happen, but + // Right must remain pointing at valid memory for its type Some(match self { PathComparison::Divergent => return None, PathComparison::Overlapping => { @@ -394,7 +401,7 @@ impl PathComparison { TypeBindingComparison::LeftDerivesFromRightButIsNotSubtype, ) | PathComparison::ReferencesEqual(TypeBindingComparison::Incomparable) => { - "mutation may invalidate the other reference with an incompatible type" + "mutation may invalidate the other reference by setting it to an incompatible type" } // Mutable reference is a descendent of the other reference PathComparison::ReferencesEqual(TypeBindingComparison::Equal) @@ -461,7 +468,7 @@ pub(crate) enum ChildSpecifier { } impl ChildSpecifier { - fn bound_type_kind(&self) -> TypeKind { + fn parent_type_kind(&self) -> TypeKind { match self { ChildSpecifier::ArrayChild(_) => ArrayType::type_kind(), ChildSpecifier::ObjectChild(_) => ObjectType::type_kind(), @@ -505,23 +512,24 @@ impl PathPart { (ChildSpecifier::ArrayChild(_), ChildSpecifier::ArrayChild(_)) => { PathPartComparison::Divergent } + (ChildSpecifier::ArrayChild(_), _) => PathPartComparison::Incompatible, (ChildSpecifier::ObjectChild(a), ChildSpecifier::ObjectChild(b)) if a == b => { PathPartComparison::IdenticalChildReference } (ChildSpecifier::ObjectChild(_), ChildSpecifier::ObjectChild(_)) => { PathPartComparison::Divergent } - _ => PathPartComparison::Incompatible, + (ChildSpecifier::ObjectChild(_), _) => PathPartComparison::Incompatible, }, (PathPart::Child(a), PathPart::Value { bound_as }) => { - if a.bound_type_kind() == *bound_as { + if bound_as.is_narrowing_to(&a.parent_type_kind()) { PathPartComparison::LeftIsDescendent } else { PathPartComparison::Incompatible } } (PathPart::Value { bound_as }, PathPart::Child(b)) => { - if b.bound_type_kind() == *bound_as { + if bound_as.is_narrowing_to(&b.parent_type_kind()) { PathPartComparison::RightIsDescendent } else { PathPartComparison::Incompatible diff --git a/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs new file mode 100644 index 00000000..29e87fdc --- /dev/null +++ b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs @@ -0,0 +1,13 @@ +use preinterpret::*; + +fn main() { + let _ = run! { + let x = %{ a: 1 }; + // Technically x could be inactive here during the resolution + // of its index, but for now, it's an error. + x[{ + x.a += 1; + "a" + }] = 3; + }; +} \ No newline at end of file diff --git a/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.stderr b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.stderr new file mode 100644 index 00000000..2aa888c1 --- /dev/null +++ b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.stderr @@ -0,0 +1,8 @@ +error: Cannot create a shared reference because it clashes with an existing mutable reference: + This reference : [*active*] &x (of type any) + Other reference: [*active*] &mut x (of type any) + Reason : mutation may be observed from the other active reference, which breaks aliasing rules + --> tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs:9:13 + | +9 | x.a += 1; + | ^ diff --git a/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs new file mode 100644 index 00000000..d1131b6e --- /dev/null +++ b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs @@ -0,0 +1,8 @@ +use preinterpret::*; + +fn main() { + let _ = run!{ + let my_arr = [[]]; + my_arr[0].push(my_arr.pop()) + }; +} \ No newline at end of file diff --git a/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr new file mode 100644 index 00000000..0faeb961 --- /dev/null +++ b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr @@ -0,0 +1,8 @@ +error: Cannot create a mutable reference because it clashes with an existing reference: + This reference : [*active*] &mut my_arr (of type any) + Other reference: [inactive] &mut my_arr[0] (of type any) + Reason : mutation may invalidate the other descendent reference + --> tests/compilation_failures/references/with_active_mut_attempt_mutate_its_ancestor.rs:6:24 + | +6 | my_arr[0].push(my_arr.pop()) + | ^^^^^^ diff --git a/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs new file mode 100644 index 00000000..c7b3b69b --- /dev/null +++ b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs @@ -0,0 +1,11 @@ +use preinterpret::*; + +fn main() { + let _ = run! { + let x = %{ a: 1 }; + let f = |x_field: &any| { + x.b = "new!!"; + }; + f(x.a); + }; +} \ No newline at end of file diff --git a/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr new file mode 100644 index 00000000..8e37c29e --- /dev/null +++ b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr @@ -0,0 +1,8 @@ +error: Cannot create a mutable reference because it clashes with an existing reference: + This reference : [*active*] &mut x (of type any) + Other reference: [inactive] &x.a (of type any) + Reason : mutation may invalidate the other descendent reference + --> tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs:7:13 + | +7 | x.b = "new!!"; + | ^ diff --git a/tests/expressions.rs b/tests/expressions.rs index effbae4e..4e2cecc1 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -632,6 +632,10 @@ fn test_method_calls() { ); // Push returns None assert_eq!(run!([1, 2, 3].as_mut().push(4).to_debug_string()), "None"); + // Pop returns last value + assert_eq!(run!([1, 2].as_mut().pop().to_debug_string()), "2"); + // Pop empty returns None + assert_eq!(run!([].as_mut().pop().to_debug_string()), "None"); // Converting to mut and then to shared works assert_eq!(run!([].as_mut().len().to_debug_string()), "0usize"); assert_eq!( diff --git a/tests/references.rs b/tests/references.rs new file mode 100644 index 00000000..3cc5081b --- /dev/null +++ b/tests/references.rs @@ -0,0 +1,33 @@ +#![allow(clippy::assertions_on_constants)] + +#[path = "helpers/prelude.rs"] +mod prelude; +use prelude::*; + +#[test] +#[cfg_attr(miri, ignore = "incompatible with miri")] +fn test_core_compilation_failures() { + if !should_run_ui_tests() { + // Some of the outputs are different on nightly, so don't test these + return; + } + let t = trybuild::TestCases::new(); + t.compile_fail("tests/compilation_failures/references/*.rs"); +} + +#[test] +fn test_can_use_distinct_paths() { + run! { + let x = %{ + a: 1, + b: 2, + }; + x.a += x.b; + %[_].assert_eq(x.a, 3); + }; + run! { + let arr = [0, 1, 2, 3]; + arr[0] += arr[1]; + %[_].assert_eq(arr[0], 1); + }; +} From 64ffbad3132618dc44e71993ebd404af0d2e0725 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 1 Mar 2026 21:32:25 +0000 Subject: [PATCH 48/49] fix: Fix filename in stderr file --- .../with_inactive_mut_attempt_mutate_its_ancestor.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr index 0faeb961..cbcadc52 100644 --- a/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr +++ b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr @@ -2,7 +2,7 @@ error: Cannot create a mutable reference because it clashes with an existing ref This reference : [*active*] &mut my_arr (of type any) Other reference: [inactive] &mut my_arr[0] (of type any) Reason : mutation may invalidate the other descendent reference - --> tests/compilation_failures/references/with_active_mut_attempt_mutate_its_ancestor.rs:6:24 + --> tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs:6:24 | 6 | my_arr[0].push(my_arr.pop()) | ^^^^^^ From 9c8f2661d77b677039ab6dd9df3eef8d296c9c52 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 1 Mar 2026 22:31:26 +0000 Subject: [PATCH 49/49] fix: Markups --- plans/TODO.md | 2 +- .../concepts/forms/copy_on_write.rs | 2 +- src/expressions/type_resolution/arguments.rs | 2 +- src/interpretation/variable_state.rs | 12 +++---- src/misc/dynamic_references/referenceable.rs | 32 +++++++++---------- ...tive_ref_attempt_mutate_its_descendant.rs} | 0 ..._ref_attempt_mutate_its_descendant.stderr} | 2 +- ...ive_mut_attempt_mutate_its_ancestor.stderr | 2 +- ...ive_ref_attempt_mutate_its_ancestor.stderr | 2 +- 9 files changed, 28 insertions(+), 28 deletions(-) rename tests/compilation_failures/references/{with_active_ref_attempt_mutate_its_descendent.rs => with_active_ref_attempt_mutate_its_descendant.rs} (100%) rename tests/compilation_failures/references/{with_active_ref_attempt_mutate_its_descendent.stderr => with_active_ref_attempt_mutate_its_descendant.stderr} (92%) diff --git a/plans/TODO.md b/plans/TODO.md index 23b1e3bc..2a8ada46 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -261,7 +261,7 @@ Possible punted: - [ ] Support for `move()` expressions in closures. - `move(x)` / `move(a.b.as_ref())` / `move(a.b.as_mut())` => we hoist up the content into the previous frame (effectively temporarily change `current_frame_id` to be the parent in the `FlowAnalysisState` - pretty easy). - These can be an anonymous definition in the root frame of the closure, which is referenced inline. --[ ] Possibly - not require `Clone` on iterators: +- [ ] Possibly - not require `Clone` on iterators: - Make `TryClone -> Result` ## Fix broken "Disabled" abstraction diff --git a/src/expressions/concepts/forms/copy_on_write.rs b/src/expressions/concepts/forms/copy_on_write.rs index 0b80e81f..e2561d3b 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -132,7 +132,7 @@ impl CopyOnWrite { } } -/// A disabled copy-on-write value that can be safely cloned and dropped. +/// An inactive copy-on-write value that can be safely cloned and dropped. pub(crate) enum InactiveCopyOnWrite { Owned(Owned), SharedWithInfallibleCloning(InactiveShared), diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index 12ef60b1..e2e38ae7 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -164,7 +164,7 @@ impl IsArgument for CopyOnWrite { } impl IsArgument for CopyOnWrite { - type ValueType = AnyType; + type ValueType = FloatType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::CopyOnWrite; fn from_argument(Spanned(value, span): Spanned) -> FunctionResult { diff --git a/src/interpretation/variable_state.rs b/src/interpretation/variable_state.rs index fdbb9dd4..54f8ee77 100644 --- a/src/interpretation/variable_state.rs +++ b/src/interpretation/variable_state.rs @@ -30,8 +30,8 @@ impl VariableContent { } } -const UNITIALIZED_ERR: &str = "Cannot resolve uninitialized variable. This shouldn't be possible, because all variables are set on first use."; -const FINISHED_ERR: &str = "Cannot resolve finished variable. This shouldn't be possible, because is_final should be marked correctly. If you see this error, please report a bug to preinterpret on github with a reproduction case."; +const UNINITIALIZED_ERR: &str = "Cannot resolve uninitialized variable. This shouldn't be possible, because all variables are set on first use."; +const FINISHED_ERR: &str = "Cannot resolve finished variable. This shouldn't be possible, because is_final should be marked correctly. If you see this error, please report a bug to preinterpret on GitHub with a reproduction case."; impl VariableState { pub(crate) fn define(&mut self, value: VariableContent) { @@ -52,13 +52,13 @@ impl VariableState { if is_final && blocked_from_mutation.is_none() { let content = std::mem::replace(self, VariableState::Finished); match content { - VariableState::Uninitialized => panic!("{}", UNITIALIZED_ERR), + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), VariableState::Value(content) => content, VariableState::Finished => panic!("{}", FINISHED_ERR), } } else { match self { - VariableState::Uninitialized => panic!("{}", UNITIALIZED_ERR), + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), VariableState::Value(content) => content.clone(), VariableState::Finished => panic!("{}", FINISHED_ERR), } @@ -80,7 +80,7 @@ impl VariableState { let content = if is_final && blocked_from_mutation.is_none() { let content = std::mem::replace(self, VariableState::Finished); match content { - VariableState::Uninitialized => panic!("{}", UNITIALIZED_ERR), + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), VariableState::Value(content) => match content.into_owned_as_only_owner() { Ok(owned) => { if matches!( @@ -109,7 +109,7 @@ impl VariableState { } } else { match self { - VariableState::Uninitialized => panic!("{}", UNITIALIZED_ERR), + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), VariableState::Value(content) => content.clone(), VariableState::Finished => panic!("{}", FINISHED_ERR), } diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs index 411510ac..d22b4f0c 100644 --- a/src/misc/dynamic_references/referenceable.rs +++ b/src/misc/dynamic_references/referenceable.rs @@ -362,12 +362,12 @@ enum PathComparison { /// but they are not identical or in an ancestor/descendant relationship. /// e.g. left = x[0..10], right = x[5..15] Overlapping, - /// The right path is a descendent of the left path. + /// The right path is a descendant of the left path. /// e.g. right = left.x or right = left[0]["key"] - RightIsDescendent, - /// The left path is a descendent of the right path. + RightIsDescendant, + /// The left path is a descendant of the right path. /// e.g. left = right.x or left = right[0]["key"] - LeftIsDescendent, + LeftIsDescendant, /// The left and right path refer to the same leaf value. /// But they may be in different forms. e.g. left = &any and right = &integer ReferencesEqual(TypeBindingComparison), @@ -390,8 +390,8 @@ impl PathComparison { PathComparison::Overlapping => { "mutation may invalidate the other overlapping reference" } - PathComparison::RightIsDescendent => { - "mutation may invalidate the other descendent reference" + PathComparison::RightIsDescendant => { + "mutation may invalidate the other descendant reference" } PathComparison::ReferencesEqual(TypeBindingComparison::RightIsSubtypeOfLeft) | PathComparison::ReferencesEqual( @@ -403,10 +403,10 @@ impl PathComparison { | PathComparison::ReferencesEqual(TypeBindingComparison::Incomparable) => { "mutation may invalidate the other reference by setting it to an incompatible type" } - // Mutable reference is a descendent of the other reference + // Mutable reference is a descendant of the other reference PathComparison::ReferencesEqual(TypeBindingComparison::Equal) | PathComparison::ReferencesEqual(TypeBindingComparison::LeftIsSubtypeOfRight) - | PathComparison::LeftIsDescendent => { + | PathComparison::LeftIsDescendant => { if other_is_active { "mutation may be observed from the other active reference, which breaks aliasing rules" } else { @@ -436,8 +436,8 @@ impl ReferencePath { PathPartComparison::Divergent => PathComparison::Divergent, PathPartComparison::IdenticalChildReference => continue, PathPartComparison::OverlappingChildReference => PathComparison::Overlapping, - PathPartComparison::RightIsDescendent => PathComparison::RightIsDescendent, - PathPartComparison::LeftIsDescendent => PathComparison::LeftIsDescendent, + PathPartComparison::RightIsDescendant => PathComparison::RightIsDescendant, + PathPartComparison::LeftIsDescendant => PathComparison::LeftIsDescendant, PathPartComparison::ReferencesEqual(inner) => { PathComparison::ReferencesEqual(inner) } @@ -488,12 +488,12 @@ enum PathPartComparison { /// e.g. left = x[0..10], right = x[5..15] #[allow(unused)] // Kept for future, and to ensure we have the correct abstraction OverlappingChildReference, - /// The right path is a descendent of the left path. + /// The right path is a descendant of the left path. /// e.g. right = left.x or right = left[0]["key"] - RightIsDescendent, - /// The left path is a descendent of the right path. + RightIsDescendant, + /// The left path is a descendant of the right path. /// e.g. left = right.x or left = right[0]["key"] - LeftIsDescendent, + LeftIsDescendant, /// The left and right path refer to the same leaf value. /// But they may be in different forms. e.g. left = &any and right = &integer ReferencesEqual(TypeBindingComparison), @@ -523,14 +523,14 @@ impl PathPart { }, (PathPart::Child(a), PathPart::Value { bound_as }) => { if bound_as.is_narrowing_to(&a.parent_type_kind()) { - PathPartComparison::LeftIsDescendent + PathPartComparison::LeftIsDescendant } else { PathPartComparison::Incompatible } } (PathPart::Value { bound_as }, PathPart::Child(b)) => { if bound_as.is_narrowing_to(&b.parent_type_kind()) { - PathPartComparison::RightIsDescendent + PathPartComparison::RightIsDescendant } else { PathPartComparison::Incompatible } diff --git a/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.rs similarity index 100% rename from tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs rename to tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.rs diff --git a/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.stderr b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.stderr similarity index 92% rename from tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.stderr rename to tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.stderr index 2aa888c1..2a474bd3 100644 --- a/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.stderr +++ b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.stderr @@ -2,7 +2,7 @@ error: Cannot create a shared reference because it clashes with an existing muta This reference : [*active*] &x (of type any) Other reference: [*active*] &mut x (of type any) Reason : mutation may be observed from the other active reference, which breaks aliasing rules - --> tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendent.rs:9:13 + --> tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.rs:9:13 | 9 | x.a += 1; | ^ diff --git a/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr index cbcadc52..81913905 100644 --- a/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr +++ b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr @@ -1,7 +1,7 @@ error: Cannot create a mutable reference because it clashes with an existing reference: This reference : [*active*] &mut my_arr (of type any) Other reference: [inactive] &mut my_arr[0] (of type any) - Reason : mutation may invalidate the other descendent reference + Reason : mutation may invalidate the other descendant reference --> tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs:6:24 | 6 | my_arr[0].push(my_arr.pop()) diff --git a/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr index 8e37c29e..60626c9d 100644 --- a/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr +++ b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr @@ -1,7 +1,7 @@ error: Cannot create a mutable reference because it clashes with an existing reference: This reference : [*active*] &mut x (of type any) Other reference: [inactive] &x.a (of type any) - Reason : mutation may invalidate the other descendent reference + Reason : mutation may invalidate the other descendant reference --> tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs:7:13 | 7 | x.b = "new!!";