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/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/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 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 89162502..2a8ada46 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 @@ -204,42 +203,125 @@ 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 - * 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) -- [ ] 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 - * 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 - * 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 -- [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` +- [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] `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 + - [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` +- [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 + - [x] Closures expose an interface, and a means of invocation, and are wired into the invocation logic +- [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 +- [x] Close over variables in ancestor scopes +- [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 +- [ ] Iterable methods: + - [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: + - [x] An iterator trait where Interpreter is passed at next time. + - [x] Make `to_string` for iterator return `Iterator[?]` + - [x] Create new iterator trait `PreinterpretIterator` and `IntoPreinterpretIterator` with `.next(&mut Interpreter)` + - [x] Blanket implement it for `Iterator + Clone` + - [x] Then replace e.g. for loop impl with it. + - [x] Create `Map` and `Filter` types on top of it, to be able to implement `map` and `filter` + - [ ] See if new iterators on iterator value can be fixed to be lazy + - [ ] Salvage half-baked `FunctionValue` changes to allow invocation + - [ ] Add `iterable.map`, `iterable.filter`, `iterable.flatten`, `iterable.flatmap` + - [ ] Add tests for iterable methods +- [ ] 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. + - `move(x)` / `move(a.b.as_ref())` / `move(a.b.as_mut())` => we hoist up the content into the previous frame (effectively temporarily change `current_frame_id` to be the parent in the `FlowAnalysisState` - pretty easy). + - These can be an anonymous definition in the root frame of the closure, which is referenced inline. +- [ ] Possibly - not require `Clone` on iterators: + - Make `TryClone -> Result` + +## Fix broken "Disabled" abstraction + +### Background + +Suddenly dawned on me - my Disabled arguments might not be safe. +* Imagine if I get `my_arr = [[]]` a Shared `my_arr[0]` then do `my_arr.pop()` +* Then we enable `my_arr[0]` and get a use after free(!!). e.g. `my_arr[0].push(my_arr.pop())`. + +### Task list + +- [x] Write up model in `dynamic_references/mod.rs` +- [x] Add structure for `dynamic_references` +- [x] Create Referenceable, and Reference types +- [x] Create error messages for rule breaks in `referenceable.rs` +- [x] Add the following to `ReferenceCore` and maybe others: + - [x] Emplacing + - [x] Map, Try map + - [x] Ability to map deeper. Should take a `PathExtension` and a new span. +- [x] Try replacing existing RefCell based abstractions with new custom dynamic references, and report on what breaks: + - `pub(crate) type Referenceable = Rc>;` becomes `pub(crate) type Referenceable = dynamic_references::Referenceable` + - `Shared` / `SharedSubRcRefCell` - both become type aliases for `SharedReference` + - `DisabledShared` becomes `InactiveSharedReference` + - `Mutable` / `MutableSubRcRefCell` - becomes type alias for `MutableReference` + - `DisabledMutable` becomes `InactiveMutableReference` +- [x] Replace all the aliases: + - [x] Remove `Shared`, `Mutable`, `SharedValue`, `AssigneeValue` + - [x] Move `type Shared` and `type Mutable` alongside `SharedReference` / `MutableReference` + - [x] Remove references to `MutableReference` and `SharedReference` outside these aliases + - [x] Rename `SharedReference -> Shared` and `MutableReference -> Mutable` + - [x] Rename `DisabledShared` -> `InactiveShared` and same for `Mutable` + - [x] Remove `type AssigneeValue` and `type SharedValue` + - [x] Move `Assignee` out of bindings. To e.g. `dynamic_references` + - [x] Rename `PathExtension::Tightening` to `PathExtension::TypeNarrowing` + - [x] Rename `DisabledArgumentValue` and `DisabledCopyOnWrite` to `Inactive__` and their method from `enable` to `activate` and ditto with `disable -> deactivate` +- [x] See what else can be deleted from `bindings.rs` + - [x] Unify LateBound into `LateBound` + - [x] Unify CopyOnWrite into `QqqCopyOnWrite` +- [x] Add various tests: + - [x] Stretching different error messages + - [x] Showing I can do e.g. `x.a += x.b` + - [x] Show that `let my_arr = [[]]; my_arr[0].push(my_arr.pop())` gives a suitable error + +### Other ideas + +- [ ] Make it so that we disable the parent whilst resolving a property/index + +We could imagine a world where we are more clever over our mutation: +* Note that I can reference `x.a` and `x.b` separately, or `x[0]` and `x[1]` but in that case, can't mutate `x` itself to create new fields. + * Or we use a `ExpandableVec` which allocates a `Vec>` and doesn't move the inner chunks; allowing us to add new fields without breaking pointers to existing ones. + Maybe some SmallVec or SegVec like library has this feature? Stores an initial chunk `[_; N]` and then a `Vec>` + +- [ ] Consider if IteratorValue, Object and Array should have `Item = DisabledReturnedValue` + - [ ] And add `iter()` and `iter_mut()` methods +- [ ] Allow variables to be marked owned / shared / mutable + - [ ] ... and maybe no marking accepts any? + ... so e.g. push_one_and_return_mut can have `let y = arr; y` without erroring + - [ ] Else - improve the errors so that the conversion error has some hint from the target explaining that the target can be changed to allow + shared/mutable instead. ## Parser - Methods using closures @@ -272,7 +354,7 @@ input.repeated( ) ``` - [ ] `input.any_group(|inner| { })` -- [ ] `input.group('()', |inner| { })` +- [ ] `input.group("()", |inner| { })` - [ ] `input.transparent_group(|inner| { })` ## Parser - Better Types for Tokens @@ -428,8 +510,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 @@ -439,6 +522,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 @@ -465,7 +549,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 +570,9 @@ 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 +- [ ] 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 macro_rules! macro somehow... I'm not sure how to get that span though. @@ -496,8 +583,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..7eb38710 --- /dev/null +++ b/src/expressions/closures.rs @@ -0,0 +1,332 @@ +use super::*; + +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, + ) -> FunctionResult> { + 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)) + } +} + +#[derive(Clone)] +pub(crate) struct ClosureValue { + definition: Rc, + closed_references: Vec<(VariableDefinitionId, VariableContent)>, +} + +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, + ) -> FunctionResult> { + let definition = &*self.definition; + + 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); + } + + for (pattern, Spanned(arg, arg_span)) in + definition.argument_definitions.iter().zip(arguments) + { + match (pattern, arg) { + (pattern, ArgumentValue::Owned(owned)) => { + pattern + .handle_destructure(context.interpreter, owned) + .expect_no_interrupts()?; + } + (Pattern::Discarded(_), _) => {} + (Pattern::Variable(variable), ArgumentValue::Shared(shared)) => { + context.interpreter.define_variable( + variable.definition.id, + VariableContent::Shared(shared.deactivate()), + ); + } + (_, 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.deactivate()), + ); + } + (_, 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( + context.interpreter, + RequestedOwnership::Concrete(ArgumentOwnership::AsIs), + ) + .expect_no_interrupts()?; + + 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 body_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, context.output_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 ClosureDefinition { + frame_id: FrameId, + scope_id: ScopeId, + required_argument_count: usize, + argument_ownerships: Vec, + argument_definitions: Vec, + body: Expression, + span_range: SpanRange, +} + +impl ParseSource for ClosureDefinition { + fn parse(input: SourceParser) -> ParseResult { + 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. + // 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(); + + 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(), + required_argument_count: argument_ownerships.len(), + argument_ownerships, + argument_definitions, + body, + span_range: SpanRange::new_between(start_span, end_span), + }) + } + + 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_closure_frame(self.frame_id, self.scope_id); + 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); + 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, +} + +// They're clearer with a common prefix By +#[allow(clippy::enum_variant_names)] +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: _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(()) + } +} diff --git a/src/expressions/concepts/content.rs b/src/expressions/concepts/content.rs index d33e97c7..4b42b108 100644 --- a/src/expressions/concepts/content.rs +++ b/src/expressions/concepts/content.rs @@ -2,8 +2,7 @@ use super::*; /// Shorthand for representing the form F of a type T with a particular lifetime 'a. pub(crate) type Content<'a, T, F> = ::Content<'a, F>; -pub(crate) type DynContent<'a, D, F> = - ::DynLeaf<'a, ::DynContent>; +pub(crate) type DynContent<'a, D, F> = ::DynLeaf<'a, D>; /// For types which have an associated value (type and form) pub(crate) trait IsValueContent { @@ -69,18 +68,6 @@ where { self.upcast() } - - fn into_referenceable(self) -> Content<'a, Self::Type, BeReferenceable> - where - Self: IsValueContent
, - { - map_via_leaf! { - input: (Content<'a, Self::Type, Self::Form>) = self.into_content(), - fn map_leaf(leaf) -> (Content<'a, T, BeReferenceable>) { - Rc::new(RefCell::new(leaf)) - } - } - } } impl<'a, C> Spanned @@ -92,7 +79,7 @@ where pub(crate) fn downcast_resolve>( self, resolution_target: &str, - ) -> ExecutionResult + ) -> FunctionResult where ::Type: DowncastFrom, { @@ -107,11 +94,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, @@ -193,7 +180,7 @@ where fn clone_to_owned_transparently<'r>( &'r self, span_range: SpanRange, - ) -> ExecutionResult> + ) -> FunctionResult> where 'a: 'r, Self::Form: LeafAsRefForm, @@ -202,7 +189,7 @@ where map_via_leaf! { input: &'r (Content<'a, Self::Type, Self::Form>) = self, state: SpanRange | let span_range = span_range, - fn map_leaf(leaf) -> (ExecutionResult>) { + fn map_leaf(leaf) -> (FunctionResult>) { F::leaf_clone_to_owned_transparently(leaf, span_range) } } @@ -225,8 +212,8 @@ impl< { type ValueType = T; const OWNERSHIP: ArgumentOwnership = F::ARGUMENT_OWNERSHIP; - fn from_argument(Spanned(value, span_range): Spanned) -> ExecutionResult { - let ownership_mapped = F::from_argument_value(value)?; + fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { + 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)) } @@ -240,7 +227,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 f4b8e4fb..bda41811 100644 --- a/src/expressions/concepts/form.rs +++ b/src/expressions/concepts/form.rs @@ -18,6 +18,13 @@ pub(crate) trait IsHierarchicalForm: IsForm { type Leaf<'a, T: IsLeafType>: IsValueContent + IntoValueContent<'a> + FromValueContent<'a>; + + /// Proof of covariance: shrinks the lifetime of a leaf from `'a` to `'b`. + /// Implementors should use the identity function `{ leaf }` as the body. + /// This will only compile if the leaf type is genuinely covariant in `'a`. + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b; } pub(crate) trait LeafAsRefForm: IsHierarchicalForm { @@ -32,14 +39,14 @@ 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)) } 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() )) } } @@ -53,25 +60,24 @@ pub(crate) trait IsDynCompatibleForm: IsHierarchicalForm { /// The container for a dyn Trait based type. /// The DynLeaf can be similar to the standard leaf, but must be /// able to support an unsized D. - type DynLeaf<'a, D: 'static + ?Sized>; + type DynLeaf<'a, D: IsDynType>; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + leaf: Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn; + T::Leaf: CastDyn; } pub(crate) trait MapFromArgument: IsHierarchicalForm { const ARGUMENT_OWNERSHIP: ArgumentOwnership; fn from_argument_value( - value: ArgumentValue, - ) -> ExecutionResult>; + value: Spanned, + ) -> FunctionResult>; } pub(crate) trait MapIntoReturned: IsHierarchicalForm { - fn into_returned_value( - value: Content<'static, AnyType, Self>, - ) -> ExecutionResult; + fn into_returned_value(value: Content<'static, AnyType, Self>) + -> FunctionResult; } diff --git a/src/expressions/concepts/forms/any_mut.rs b/src/expressions/concepts/forms/any_mut.rs index 5bbaf984..bf08c3ce 100644 --- a/src/expressions/concepts/forms/any_mut.rs +++ b/src/expressions/concepts/forms/any_mut.rs @@ -22,20 +22,34 @@ pub(crate) struct BeAnyMut; impl IsForm for BeAnyMut {} impl IsHierarchicalForm for BeAnyMut { type Leaf<'a, T: IsLeafType> = AnyMut<'a, T::Leaf>; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeAnyMut { - type DynLeaf<'a, D: 'static + ?Sized> = AnyMut<'a, D>; + type DynLeaf<'a, D: IsDynType> = AnyMut<'a, D::DynContent>; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.emplace_map(|content, emplacer| match ::map_mut(content) { + Ok(mapped) => { + // SAFETY: PathExtension is correct for mapping to a dyn type + let mapped_mut = unsafe { + MappedMut::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_mut)) + } + Err(_this) => Err(emplacer.revert()), }) } } @@ -56,11 +70,12 @@ impl MapFromArgument for BeAnyMut { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; fn from_argument_value( - value: ArgumentValue, - ) -> ExecutionResult> { - Ok(value - .expect_mutable() - .0 - .replace(|inner, emplacer| inner.as_mut_value().into_mutable_any_mut(emplacer))) + Spanned(value, span): Spanned, + ) -> FunctionResult> { + Ok(value.expect_mutable().emplace_map(|inner, emplacer| { + inner + .as_mut_value() + .into_mutable_any_mut(emplacer, Some(span)) + })) } } diff --git a/src/expressions/concepts/forms/any_ref.rs b/src/expressions/concepts/forms/any_ref.rs index 3e9220cd..e073e31a 100644 --- a/src/expressions/concepts/forms/any_ref.rs +++ b/src/expressions/concepts/forms/any_ref.rs @@ -23,20 +23,34 @@ impl IsForm for BeAnyRef {} impl IsHierarchicalForm for BeAnyRef { type Leaf<'a, T: IsLeafType> = crate::internal_prelude::AnyRef<'a, T::Leaf>; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeAnyRef { - type DynLeaf<'a, D: 'static + ?Sized> = crate::internal_prelude::AnyRef<'a, D>; + type DynLeaf<'a, D: IsDynType> = crate::internal_prelude::AnyRef<'a, D::DynContent>; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_ref(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.emplace_map(|content, emplacer| match ::map_ref(content) { + Ok(mapped) => { + // SAFETY: PathExtension is correct for mapping to a dyn type + let mapped_ref = unsafe { + MappedRef::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_ref)) + } + Err(_this) => Err(emplacer.revert()), }) } } @@ -51,11 +65,12 @@ impl MapFromArgument for BeAnyRef { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; fn from_argument_value( - value: ArgumentValue, - ) -> ExecutionResult> { - Ok(value - .expect_shared() - .0 - .replace(|inner, emplacer| inner.as_ref_value().into_shared_any_ref(emplacer))) + Spanned(value, span): Spanned, + ) -> FunctionResult> { + Ok(value.expect_shared().emplace_map(|inner, emplacer| { + inner + .as_ref_value() + .into_shared_any_ref(emplacer, Some(span)) + })) } } diff --git a/src/expressions/concepts/forms/argument.rs b/src/expressions/concepts/forms/argument.rs index a2ac0e4d..85b1dcb4 100644 --- a/src/expressions/concepts/forms/argument.rs +++ b/src/expressions/concepts/forms/argument.rs @@ -2,10 +2,10 @@ use super::*; pub(crate) enum Argument { Owned(L), - CopyOnWrite(QqqCopyOnWrite), - Mutable(QqqMutable), - Assignee(QqqAssignee), - Shared(QqqShared), + CopyOnWrite(CopyOnWrite), + Mutable(Mutable), + Assignee(Assignee), + Shared(Shared), } impl IsValueContent for Argument { @@ -31,14 +31,22 @@ impl IsForm for BeArgument {} impl IsHierarchicalForm for BeArgument { type Leaf<'a, T: IsLeafType> = Argument; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl MapFromArgument for BeArgument { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::AsIs; fn from_argument_value( - value: ArgumentValue, - ) -> ExecutionResult> { + Spanned(value, _span): Spanned, + ) -> FunctionResult> { todo!("Argument") } } diff --git a/src/expressions/concepts/forms/assignee.rs b/src/expressions/concepts/forms/assignee.rs index 1a20eef6..9973b56c 100644 --- a/src/expressions/concepts/forms/assignee.rs +++ b/src/expressions/concepts/forms/assignee.rs @@ -1,45 +1,90 @@ use super::*; -pub(crate) struct QqqAssignee(pub(crate) MutableSubRcRefCell); +/// A binding of a unique (mutable) reference to a value. +/// See [`ArgumentOwnership::Assignee`] for more details. +/// +/// If you need span information, wrap with `Spanned>`. +pub(crate) struct Assignee(pub(crate) Mutable); -impl IsValueContent for QqqAssignee { - type Type = L::Type; +impl AnyValueAssignee { + pub(crate) fn set(&mut self, content: impl IntoAnyValue) { + *self.0 = content.into_any_value(); + } +} + +impl IsValueContent for Assignee { + type Type = X::Type; type Form = BeAssignee; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqAssignee { +impl<'a, X: IsValueContent> IntoValueContent<'a> for Assignee +where + X: 'static, + X::Type: IsHierarchicalType = X>, + X::Form: IsHierarchicalForm, + X::Form: LeafAsMutForm, +{ fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self + self.0 + .emplace_map(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer, None)) } } -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqAssignee { +impl<'a, L: IsValueLeaf> FromValueContent<'a> for Assignee { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } } +impl Deref for Assignee { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Assignee { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + #[derive(Copy, Clone)] pub(crate) struct BeAssignee; impl IsForm for BeAssignee {} impl IsHierarchicalForm for BeAssignee { - type Leaf<'a, T: IsLeafType> = QqqAssignee; + type Leaf<'a, T: IsLeafType> = Assignee; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeAssignee { - type DynLeaf<'a, D: 'static + ?Sized> = QqqAssignee; + type DynLeaf<'a, D: IsDynType> = Assignee; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { leaf.0 - .replace(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(QqqAssignee(emplacer.emplace(mapped))), - Err(this) => Err(QqqAssignee(emplacer.emplace(this))), + .emplace_map(|content, emplacer| match ::map_mut(content) { + Ok(mapped) => { + // SAFETY: PathExtension is correct for mapping to a dyn type + let mapped_mut = unsafe { + MappedMut::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) + }; + Ok(Assignee(emplacer.emplace(mapped_mut))) + } + Err(_this) => Err(Assignee(emplacer.revert())), }) } } @@ -61,8 +106,8 @@ impl MapFromArgument for BeAssignee { ArgumentOwnership::Assignee { auto_create: false }; fn from_argument_value( - value: ArgumentValue, - ) -> ExecutionResult> { + 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 2e6ea9a0..e2561d3b 100644 --- a/src/expressions/concepts/forms/copy_on_write.rs +++ b/src/expressions/concepts/forms/copy_on_write.rs @@ -1,39 +1,205 @@ use super::*; -pub(crate) enum QqqCopyOnWrite { +pub(crate) enum CopyOnWrite { /// An owned value that can be used directly Owned(Owned), /// For use when the CopyOnWrite value effectively represents the owned value (post-clone). /// In this case, returning a Cow is just an optimization and we can always clone infallibly. - SharedWithInfallibleCloning(QqqShared), + SharedWithInfallibleCloning(Shared), /// For use when the CopyOnWrite value represents a pre-cloned read-only value. /// A transparent clone may fail in this case at use time. - SharedWithTransparentCloning(QqqShared), + SharedWithTransparentCloning(Shared), } - -impl IsValueContent for QqqCopyOnWrite { - type Type = L::Type; +impl IsValueContent for CopyOnWrite { + type Type = X::Type; type Form = BeCopyOnWrite; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqCopyOnWrite { +impl<'a, X: Clone + IsValueContent> IntoValueContent<'a> for CopyOnWrite +where + X: 'static, + X::Type: IsHierarchicalType = X>, + X: IsValueContent + IsSelfValueContent<'static> + for<'b> IntoValueContent<'b>, +{ fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self + match self { + CopyOnWrite::Owned(owned) => BeCopyOnWrite::new_owned(owned), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { + BeCopyOnWrite::new_shared_in_place_of_owned(shared) + } + CopyOnWrite::SharedWithTransparentCloning(shared) => { + BeCopyOnWrite::new_shared_in_place_of_shared(shared) + } + } } } -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqCopyOnWrite { +impl<'a, L: IsValueLeaf> FromValueContent<'a> for CopyOnWrite { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } } +impl CopyOnWrite { + pub(crate) fn shared_in_place_of_owned(shared: Shared) -> Self { + CopyOnWrite::SharedWithInfallibleCloning(shared) + } + + pub(crate) fn shared_in_place_of_shared(shared: Shared) -> Self { + CopyOnWrite::SharedWithTransparentCloning(shared) + } + + pub(crate) fn owned(owned: Owned) -> Self { + CopyOnWrite::Owned(owned) + } + + pub(crate) fn acts_as_shared_reference(&self) -> bool { + match &self { + CopyOnWrite::Owned { .. } => false, + CopyOnWrite::SharedWithInfallibleCloning { .. } => false, + CopyOnWrite::SharedWithTransparentCloning { .. } => true, + } + } + + pub(crate) fn map( + self, + map_shared: impl FnOnce(Shared) -> FunctionResult>, + map_owned: impl FnOnce(Owned) -> FunctionResult>, + ) -> FunctionResult> { + Ok(match self { + CopyOnWrite::Owned(owned) => CopyOnWrite::Owned(map_owned(owned)?), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { + CopyOnWrite::SharedWithInfallibleCloning(map_shared(shared)?) + } + CopyOnWrite::SharedWithTransparentCloning(shared) => { + CopyOnWrite::SharedWithTransparentCloning(map_shared(shared)?) + } + }) + } + + pub(crate) fn map_into( + self, + map_shared: impl FnOnce(Shared) -> U, + map_owned: impl FnOnce(Owned) -> U, + ) -> U { + match self { + CopyOnWrite::Owned(owned) => map_owned(owned), + CopyOnWrite::SharedWithInfallibleCloning(shared) => map_shared(shared), + CopyOnWrite::SharedWithTransparentCloning(shared) => map_shared(shared), + } + } + + /// Deactivates this copy-on-write value, releasing any borrow. + /// Returns a `InactiveCopyOnWrite` which can be cloned and later re-activated. + pub(crate) fn deactivate(self) -> InactiveCopyOnWrite { + match self { + CopyOnWrite::Owned(owned) => InactiveCopyOnWrite::Owned(owned), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { + InactiveCopyOnWrite::SharedWithInfallibleCloning(shared.deactivate()) + } + CopyOnWrite::SharedWithTransparentCloning(shared) => { + InactiveCopyOnWrite::SharedWithTransparentCloning(shared.deactivate()) + } + } + } +} + +impl CopyOnWrite { + /// Converts to shared reference + pub(crate) fn into_shared(self, span: SpanRange) -> Shared { + match self { + CopyOnWrite::Owned(owned) => Shared::new_from_owned(owned, None, span), + CopyOnWrite::SharedWithInfallibleCloning(shared) => shared, + CopyOnWrite::SharedWithTransparentCloning(shared) => shared, + } + } + + /// Converts to owned, using transparent clone for shared values where cloning was not requested + /// Note - there's a more general `IsSelfValueContent::clone_to_owned_transparently` + /// but this is re-implemented here as a specialization for efficiency + pub(crate) fn clone_to_owned_transparently( + self, + span: SpanRange, + ) -> FunctionResult { + match self { + CopyOnWrite::Owned(owned) => Ok(owned), + CopyOnWrite::SharedWithInfallibleCloning(shared) => Ok(shared.infallible_clone()), + CopyOnWrite::SharedWithTransparentCloning(shared) => { + let value = shared.as_ref().try_transparent_clone(span)?; + Ok(value) + } + } + } +} + +/// An inactive copy-on-write value that can be safely cloned and dropped. +pub(crate) enum InactiveCopyOnWrite { + Owned(Owned), + SharedWithInfallibleCloning(InactiveShared), + SharedWithTransparentCloning(InactiveShared), +} + +impl Clone for InactiveCopyOnWrite { + fn clone(&self) -> Self { + match self { + InactiveCopyOnWrite::Owned(owned) => InactiveCopyOnWrite::Owned(owned.clone()), + InactiveCopyOnWrite::SharedWithInfallibleCloning(shared) => { + InactiveCopyOnWrite::SharedWithInfallibleCloning(shared.clone()) + } + InactiveCopyOnWrite::SharedWithTransparentCloning(shared) => { + InactiveCopyOnWrite::SharedWithTransparentCloning(shared.clone()) + } + } + } +} + +impl InactiveCopyOnWrite { + /// Re-activates this inactive copy-on-write value by re-acquiring any borrow. + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { + Ok(match self { + InactiveCopyOnWrite::Owned(owned) => CopyOnWrite::Owned(owned), + InactiveCopyOnWrite::SharedWithInfallibleCloning(inactive) => { + CopyOnWrite::SharedWithInfallibleCloning(inactive.activate(span)?) + } + InactiveCopyOnWrite::SharedWithTransparentCloning(inactive) => { + CopyOnWrite::SharedWithTransparentCloning(inactive.activate(span)?) + } + }) + } +} + +impl AsRef for CopyOnWrite { + fn as_ref(&self) -> &T { + self + } +} + +impl Deref for CopyOnWrite { + type Target = T; + + fn deref(&self) -> &T { + match self { + CopyOnWrite::Owned(owned) => owned, + CopyOnWrite::SharedWithInfallibleCloning(shared) => shared.as_ref(), + CopyOnWrite::SharedWithTransparentCloning(shared) => shared.as_ref(), + } + } +} + #[derive(Copy, Clone)] pub(crate) struct BeCopyOnWrite; impl IsForm for BeCopyOnWrite {} impl IsHierarchicalForm for BeCopyOnWrite { - type Leaf<'a, T: IsLeafType> = QqqCopyOnWrite; + type Leaf<'a, T: IsLeafType> = CopyOnWrite; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl BeCopyOnWrite { @@ -43,7 +209,7 @@ impl BeCopyOnWrite { map_via_leaf! { input: (Content<'a, C::Type, BeOwned>) = owned.into_content(), fn map_leaf(leaf) -> (Content<'a, T, BeCopyOnWrite>) { - QqqCopyOnWrite::Owned(leaf) + CopyOnWrite::Owned(leaf) } } } @@ -54,7 +220,7 @@ impl BeCopyOnWrite { map_via_leaf! { input: (Content<'a, C::Type, BeShared>) = shared.into_content(), fn map_leaf(leaf) -> (Content<'a, T, BeCopyOnWrite>) { - QqqCopyOnWrite::SharedWithInfallibleCloning(leaf) + CopyOnWrite::SharedWithInfallibleCloning(leaf) } } } @@ -65,7 +231,7 @@ impl BeCopyOnWrite { map_via_leaf! { input: (Content<'a, C::Type, BeShared>) = shared.into_content(), fn map_leaf(leaf) -> (Content<'a, T, BeCopyOnWrite>) { - QqqCopyOnWrite::SharedWithTransparentCloning(leaf) + CopyOnWrite::SharedWithTransparentCloning(leaf) } } } @@ -75,14 +241,14 @@ impl MapFromArgument for BeCopyOnWrite { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::CopyOnWrite; fn from_argument_value( - value: ArgumentValue, - ) -> ExecutionResult> { - match value.expect_copy_on_write().inner { - CopyOnWriteInner::Owned(owned) => Ok(BeCopyOnWrite::new_owned(owned)), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { + Spanned(value, span): Spanned, + ) -> FunctionResult> { + match value.expect_copy_on_write() { + CopyOnWrite::Owned(owned) => Ok(BeCopyOnWrite::new_owned(owned)), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { Ok(BeCopyOnWrite::new_shared_in_place_of_owned(shared)) } - CopyOnWriteInner::SharedWithTransparentCloning(shared) => { + CopyOnWrite::SharedWithTransparentCloning(shared) => { Ok(BeCopyOnWrite::new_shared_in_place_of_shared(shared)) } } @@ -92,9 +258,9 @@ impl MapFromArgument for BeCopyOnWrite { impl LeafAsRefForm for BeCopyOnWrite { fn leaf_as_ref<'r, 'a: 'r, T: IsLeafType>(leaf: &'r Self::Leaf<'a, T>) -> &'r T::Leaf { match leaf { - QqqCopyOnWrite::Owned(owned) => owned, - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => shared, - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => shared, + CopyOnWrite::Owned(owned) => owned, + CopyOnWrite::SharedWithInfallibleCloning(shared) => shared, + CopyOnWrite::SharedWithTransparentCloning(shared) => shared, } } @@ -102,11 +268,11 @@ impl LeafAsRefForm for BeCopyOnWrite { leaf: &'r Self::Leaf<'a, T>, ) -> T::Leaf { match leaf { - QqqCopyOnWrite::Owned(owned) => owned.clone(), - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => { + CopyOnWrite::Owned(owned) => owned.clone(), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { BeShared::leaf_clone_to_owned_infallible::(shared) } - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => { + CopyOnWrite::SharedWithTransparentCloning(shared) => { BeShared::leaf_clone_to_owned_infallible::(shared) } } @@ -115,13 +281,13 @@ impl LeafAsRefForm for BeCopyOnWrite { fn leaf_clone_to_owned_transparently<'r, 'a: 'r, T: IsLeafType>( leaf: &'r Self::Leaf<'a, T>, error_span: SpanRange, - ) -> ExecutionResult { + ) -> FunctionResult { match leaf { - QqqCopyOnWrite::Owned(owned) => Ok(owned.clone()), - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => { + CopyOnWrite::Owned(owned) => Ok(owned.clone()), + CopyOnWrite::SharedWithInfallibleCloning(shared) => { Ok(BeShared::leaf_clone_to_owned_infallible::(shared)) } - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => { + CopyOnWrite::SharedWithTransparentCloning(shared) => { BeShared::leaf_clone_to_owned_transparently::(shared, error_span) } } @@ -141,9 +307,9 @@ where input: (Content<'a, Self::Type, Self::Form>) = self, fn map_leaf(leaf) -> (AnyLevelCopyOnWrite<'a, T>) { match leaf { - QqqCopyOnWrite::Owned(owned) => AnyLevelCopyOnWrite::Owned(owned), - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => AnyLevelCopyOnWrite::SharedWithInfallibleCloning(shared), - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => AnyLevelCopyOnWrite::SharedWithTransparentCloning(shared), + CopyOnWrite::Owned(owned) => AnyLevelCopyOnWrite::Owned(owned), + CopyOnWrite::SharedWithInfallibleCloning(shared) => AnyLevelCopyOnWrite::SharedWithInfallibleCloning(shared), + CopyOnWrite::SharedWithTransparentCloning(shared) => AnyLevelCopyOnWrite::SharedWithTransparentCloning(shared), } } } @@ -157,9 +323,9 @@ where input: (Content<'a, Self::Type, Self::Form>) = self, fn map_leaf(leaf) -> (Content<'static, T, BeOwned>) { match leaf { - QqqCopyOnWrite::Owned(owned) => owned, - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), + CopyOnWrite::Owned(owned) => owned, + CopyOnWrite::SharedWithInfallibleCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), + CopyOnWrite::SharedWithTransparentCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), } } } @@ -177,9 +343,9 @@ where state: SpanRange | let error_span = error_span, fn map_leaf(leaf) -> (ExecutionResult>) { Ok(match leaf { - QqqCopyOnWrite::Owned(owned) => owned, - QqqCopyOnWrite::SharedWithInfallibleCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), - QqqCopyOnWrite::SharedWithTransparentCloning(shared) => BeShared::leaf_clone_to_owned_transparently::(&shared, error_span)?, + CopyOnWrite::Owned(owned) => owned, + CopyOnWrite::SharedWithInfallibleCloning(shared) => BeShared::leaf_clone_to_owned_infallible::(&shared), + CopyOnWrite::SharedWithTransparentCloning(shared) => BeShared::leaf_clone_to_owned_transparently::(&shared, error_span)?, }) } } @@ -190,59 +356,58 @@ where input: &'r (Content<'a, Self::Type, Self::Form>) = self, fn map_leaf(leaf) -> (bool) { match leaf { - QqqCopyOnWrite::Owned(_) => false, - QqqCopyOnWrite::SharedWithInfallibleCloning(_) => false, - QqqCopyOnWrite::SharedWithTransparentCloning(_) => true, + CopyOnWrite::Owned(_) => false, + CopyOnWrite::SharedWithInfallibleCloning(_) => false, + CopyOnWrite::SharedWithTransparentCloning(_) => true, } } } } + // // TODO - Find alternative implementation or replace + // fn map(self, mapper: M) -> ExecutionResult> + // where + // 'a: 'static, + // M: OwnedTypeMapper + RefTypeMapper, + // M: TypeMapper, // u32 is temporary to see if we can make it work without adding generics to `map_via_leaf!` + // Self: Sized, + // Self: IsValueContent, // Temporary to see if we can make it work without adding generics to `map_via_leaf!` + // { + // Ok(match self.into_any_level_copy_on_write() { + // AnyLevelCopyOnWrite::Owned(owned) => BeCopyOnWrite::new_owned(mapper.map_owned(owned)?), + // AnyLevelCopyOnWrite::SharedWithInfallibleCloning(shared) => { + // // // Apply map, get Shared> + // // let shared = map_via_leaf! { + // // input: (Content<'a, Self::Type, BeShared>) = shared, + // // fn map_leaf(leaf) -> (ExecutionResult>>) { + // // leaf.try_map(|value_ref| { + // // let from_ref = value_ref.into_any(); + // // inner_map_ref(from_ref) // &Content - // TODO - Find alternative implementation or replace - fn map(self, mapper: M) -> ExecutionResult> - where - 'a: 'static, - M: OwnedTypeMapper + RefTypeMapper, - M: TypeMapper, // u32 is temporary to see if we can make it work without adding generics to `map_via_leaf!` - Self: Sized, - Self: IsValueContent, // Temporary to see if we can make it work without adding generics to `map_via_leaf!` - { - Ok(match self.into_any_level_copy_on_write() { - AnyLevelCopyOnWrite::Owned(owned) => BeCopyOnWrite::new_owned(mapper.map_owned(owned)?), - AnyLevelCopyOnWrite::SharedWithInfallibleCloning(shared) => { - // // Apply map, get Shared> - // let shared = map_via_leaf! { - // input: (Content<'a, Self::Type, BeShared>) = shared, - // fn map_leaf(leaf) -> (ExecutionResult>>) { - // leaf.try_map(|value_ref| { - // let from_ref = value_ref.into_any(); - // inner_map_ref(from_ref) // &Content - - // // If inner_map_ref returned a Content<'a, M::TTo, BeRef> - // // then we'd need to move the leaf map inside here, but it'd work the same - // }) - // } - // }?; - // // Migrate Shared into leaf - // let shared_content = shared.replace(|content, emplacer| { - // // TODO - use two lifetimes here - // // ---- - // map_via_leaf! { - // input: &'r (Content<'a, AnyType, BeOwned>) = content, - // state: | <'e> SharedEmplacer<'e, AnyValue, AnyValue> | let emplacer = emplacer, - // fn map_leaf(leaf) -> (Content<'static, T, BeShared>) { - // emplacer.emplace(leaf) - // } - // } - // }); - // BeCopyOnWrite::new_shared_in_place_of_owned::(shared_content) - todo!() - } - AnyLevelCopyOnWrite::SharedWithTransparentCloning(shared) => { - todo!() - } - }) - } + // // // If inner_map_ref returned a Content<'a, M::TTo, BeRef> + // // // then we'd need to move the leaf map inside here, but it'd work the same + // // }) + // // } + // // }?; + // // // Migrate Shared into leaf + // // let shared_content = shared.replace(|content, emplacer| { + // // // TODO - use two lifetimes here + // // // ---- + // // map_via_leaf! { + // // input: &'r (Content<'a, AnyType, BeOwned>) = content, + // // state: | <'e> SharedEmplacer<'e, AnyValue, AnyValue> | let emplacer = emplacer, + // // fn map_leaf(leaf) -> (Content<'static, T, BeShared>) { + // // emplacer.emplace(leaf) + // // } + // // } + // // }); + // // BeCopyOnWrite::new_shared_in_place_of_owned::(shared_content) + // todo!() + // } + // AnyLevelCopyOnWrite::SharedWithTransparentCloning(shared) => { + // todo!() + // } + // }) + // } } fn inner_map_ref<'a>( @@ -289,26 +454,26 @@ pub(crate) trait TypeMapper { type TFrom: IsHierarchicalType; type TTo: IsHierarchicalType; } -pub(crate) trait OwnedTypeMapper: TypeMapper { - fn map_owned<'a>( - self, - owned_value: Content<'a, Self::TFrom, BeOwned>, - ) -> ExecutionResult>; -} +// pub(crate) trait OwnedTypeMapper: TypeMapper { +// fn map_owned<'a>( +// self, +// owned_value: Content<'a, Self::TFrom, BeOwned>, +// ) -> ExecutionResult>; +// } -pub(crate) trait RefTypeMapper: TypeMapper { - fn map_ref<'a>( - self, - ref_value: Content<'a, Self::TFrom, BeRef>, - ) -> ExecutionResult<&'a Content<'a, Self::TTo, BeOwned>>; -} +// pub(crate) trait RefTypeMapper: TypeMapper { +// fn map_ref<'a>( +// self, +// ref_value: Content<'a, Self::TFrom, BeRef>, +// ) -> ExecutionResult<&'a Content<'a, Self::TTo, BeOwned>>; +// } -pub(crate) trait MutTypeMapper: TypeMapper { - fn map_mut<'a>( - self, - mut_value: Content<'a, Self::TFrom, BeMut>, - ) -> ExecutionResult<&'a mut Content<'a, Self::TTo, BeOwned>>; -} +// pub(crate) trait MutTypeMapper: TypeMapper { +// fn map_mut<'a>( +// self, +// mut_value: Content<'a, Self::TFrom, BeMut>, +// ) -> ExecutionResult<&'a mut Content<'a, Self::TTo, BeOwned>>; +// } impl<'a, C: IsSelfValueContent<'a>> IsSelfCopyOnWriteContent<'a> for C where diff --git a/src/expressions/concepts/forms/late_bound.rs b/src/expressions/concepts/forms/late_bound.rs index 0395e1ca..2628b357 100644 --- a/src/expressions/concepts/forms/late_bound.rs +++ b/src/expressions/concepts/forms/late_bound.rs @@ -1,58 +1,134 @@ use super::*; -pub(crate) enum QqqLateBound { +/// A temporary, flexible form that can resolve to any concrete ownership type. +/// +/// Sometimes, a value can be accessed, but we don't yet know *how* we need to access it. +/// In this case, we attempt to load it with the most powerful access we can have, and convert it later. +/// +/// ## Example of requirement +/// For example, if we have `x[a].method()`, we first need to resolve the type of `x[a]` to know +/// whether the method needs `x[a]` to be a shared reference, mutable reference or an owned value. +/// +/// So instead, we take the most powerful access we can have for `x[a]`, and convert it later. +pub(crate) enum LateBound { /// An owned value that can be converted to any ownership type - Owned(QqqLateBoundOwned), + Owned(LateBoundOwned), /// A copy-on-write value that can be converted to an owned value - CopyOnWrite(QqqCopyOnWrite), + CopyOnWrite(CopyOnWrite), /// A mutable reference - Mutable(QqqMutable), + Mutable(Mutable), /// A shared reference where mutable access failed for a specific reason - Shared(QqqLateBoundShared), + Shared(LateBoundShared), +} + +impl LateBound { + pub(crate) fn new_shared(shared: Shared, reason_not_mutable: syn::Error) -> Self { + LateBound::Shared(LateBoundShared { + shared, + reason_not_mutable, + }) + } + + /// Maps the late-bound value through the appropriate accessor function. + /// + /// If the mutable mapping fails with a retryable reason, we fall back to `map_shared`. + /// This allows operations that don't need mutable access (like reading a non-existent + /// key from an object) to still work, with the mutable error preserved as `reason_not_mutable`. + pub(crate) fn map_any( + self, + map_shared: impl FnOnce(Shared) -> FunctionResult>, + map_mutable: impl FnOnce(Mutable) -> Result, (FunctionError, Mutable)>, + map_owned: impl FnOnce(T) -> FunctionResult, + ) -> FunctionResult { + Ok(match self { + LateBound::Owned(owned) => LateBound::Owned(LateBoundOwned { + owned: map_owned(owned.owned)?, + is_from_last_use: owned.is_from_last_use, + }), + LateBound::CopyOnWrite(copy_on_write) => { + LateBound::CopyOnWrite(copy_on_write.map(map_shared, map_owned)?) + } + LateBound::Mutable(mutable) => match map_mutable(mutable) { + Ok(mapped) => LateBound::Mutable(mapped), + Err((error, recovered_mutable)) => { + // Check if this error can be caught for fallback to shared access + let reason_not_mutable = error.into_caught_mutable_map_attempt_error()?; + LateBound::new_shared( + map_shared(recovered_mutable.into_shared())?, + reason_not_mutable, + ) + } + }, + LateBound::Shared(LateBoundShared { + shared, + reason_not_mutable, + }) => LateBound::new_shared(map_shared(shared)?, reason_not_mutable), + }) + } } -impl IsValueContent for QqqLateBound { +impl Spanned { + pub(crate) fn resolve(self, ownership: ArgumentOwnership) -> FunctionResult { + ownership.map_from_late_bound(self) + } +} + +impl Deref for LateBound { + type Target = T; + + fn deref(&self) -> &Self::Target { + match self { + LateBound::Owned(owned) => &owned.owned, + LateBound::CopyOnWrite(cow) => cow.as_ref(), + LateBound::Mutable(mutable) => mutable.as_ref(), + LateBound::Shared(shared) => shared.shared.as_ref(), + } + } +} + +impl IsValueContent for LateBound { type Type = L::Type; type Form = BeLateBound; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqLateBound { +impl<'a, L: IsValueLeaf> IntoValueContent<'a> for LateBound { fn into_content(self) -> Content<'a, Self::Type, Self::Form> { self } } -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqLateBound { +impl<'a, L: IsValueLeaf> FromValueContent<'a> for LateBound { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } } -/// Universal value type that can resolve to any concrete ownership type. -/// -/// Sometimes, a value can be accessed, but we don't yet know *how* we need to access it. -/// In this case, we attempt to load it with the most powerful access we can have, and convert it later. +/// A temporary, flexible form that can resolve to any concrete ownership type. /// -/// ## Example of requirement -/// For example, if we have `x[a].method()`, we first need to resolve the type of `x[a]` to know -/// whether the method needs `x[a]` to be a shared reference, mutable reference or an owned value. -/// -/// So instead, we take the most powerful access we can have for `x[a]`, and convert it later. +/// See [`LateBound`] for more details. #[derive(Copy, Clone)] pub(crate) struct BeLateBound; impl IsForm for BeLateBound {} impl IsHierarchicalForm for BeLateBound { - type Leaf<'a, T: IsLeafType> = QqqLateBound; + type Leaf<'a, T: IsLeafType> = LateBound; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } -pub(crate) struct QqqLateBoundOwned { +pub(crate) struct LateBoundOwned { pub(crate) owned: O, pub(crate) is_from_last_use: bool, } /// A shared value where mutable access failed for a specific reason -pub(crate) struct QqqLateBoundShared { - pub(crate) shared: QqqShared, +pub(crate) struct LateBoundShared { + pub(crate) shared: Shared, pub(crate) reason_not_mutable: syn::Error, } diff --git a/src/expressions/concepts/forms/mod.rs b/src/expressions/concepts/forms/mod.rs index 3f42e697..17102ad6 100644 --- a/src/expressions/concepts/forms/mod.rs +++ b/src/expressions/concepts/forms/mod.rs @@ -9,7 +9,6 @@ mod copy_on_write; mod late_bound; mod mutable; mod owned; -mod referenceable; mod shared; mod simple_mut; mod simple_ref; @@ -22,7 +21,6 @@ pub(crate) use copy_on_write::*; pub(crate) use late_bound::*; pub(crate) use mutable::*; pub(crate) use owned::*; -pub(crate) use referenceable::*; pub(crate) use shared::*; pub(crate) use simple_mut::*; pub(crate) use simple_ref::*; diff --git a/src/expressions/concepts/forms/mutable.rs b/src/expressions/concepts/forms/mutable.rs index b17e1c43..e26fcf36 100644 --- a/src/expressions/concepts/forms/mutable.rs +++ b/src/expressions/concepts/forms/mutable.rs @@ -1,19 +1,26 @@ use super::*; -pub(crate) type QqqMutable = MutableSubRcRefCell; - -impl IsValueContent for QqqMutable { - type Type = L::Type; +impl IsValueContent for Mutable { + type Type = X::Type; type Form = BeMutable; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqMutable { +impl<'a, X: IsValueContent> IntoValueContent<'a> for Mutable +where + X: 'static, + X::Type: IsHierarchicalType = X>, + X::Form: IsHierarchicalForm, + X::Form: LeafAsMutForm, +{ fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self + self.emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer, None)) } } -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqMutable { +// Note we can't implement this more widely than leaves. +// This is because e.g. Content has Mutable() in its leaves, +// This can't be mapped back to having Mutable(Content). +impl<'a, L: IsValueLeaf> FromValueContent<'a> for Mutable { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } @@ -24,21 +31,35 @@ pub(crate) struct BeMutable; impl IsForm for BeMutable {} impl IsHierarchicalForm for BeMutable { - type Leaf<'a, T: IsLeafType> = QqqMutable; + type Leaf<'a, T: IsLeafType> = Mutable; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeMutable { - type DynLeaf<'a, D: 'static + ?Sized> = QqqMutable; + type DynLeaf<'a, D: IsDynType> = Mutable; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_mut(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.emplace_map(|content, emplacer| match ::map_mut(content) { + Ok(mapped) => { + // SAFETY: PathExtension is correct for mapping to a dyn type + let mapped_mut = unsafe { + MappedMut::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_mut)) + } + Err(_) => Err(emplacer.revert()), }) } } @@ -59,9 +80,11 @@ impl MapFromArgument for BeMutable { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; fn from_argument_value( - value: ArgumentValue, - ) -> ExecutionResult> { - Ok(value.expect_mutable().into_content()) + Spanned(value, span): Spanned, + ) -> FunctionResult> { + Ok(value + .expect_mutable() + .emplace_map(|inner, emplacer| inner.as_mut_value().into_mutable(emplacer, Some(span)))) } } diff --git a/src/expressions/concepts/forms/owned.rs b/src/expressions/concepts/forms/owned.rs index 5095ac42..5a575a03 100644 --- a/src/expressions/concepts/forms/owned.rs +++ b/src/expressions/concepts/forms/owned.rs @@ -18,16 +18,24 @@ impl IsForm for BeOwned {} impl IsHierarchicalForm for BeOwned { type Leaf<'a, T: IsLeafType> = T::Leaf; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeOwned { - type DynLeaf<'a, D: 'static + ?Sized> = Box; + type DynLeaf<'a, D: IsDynType> = Box; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, _span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { ::map_boxed(Box::new(leaf)).map_err(|boxed| *boxed) } @@ -49,8 +57,8 @@ impl MapFromArgument for BeOwned { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Owned; fn from_argument_value( - value: ArgumentValue, - ) -> ExecutionResult> { + Spanned(value, _span): Spanned, + ) -> FunctionResult> { Ok(value.expect_owned()) } } @@ -58,7 +66,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/referenceable.rs b/src/expressions/concepts/forms/referenceable.rs deleted file mode 100644 index 47b7b622..00000000 --- a/src/expressions/concepts/forms/referenceable.rs +++ /dev/null @@ -1,33 +0,0 @@ -use super::*; - -pub(crate) type Referenceable = Rc>; - -impl IsValueContent for Referenceable { - type Type = L::Type; - type Form = BeReferenceable; -} - -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for Referenceable { - fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self - } -} - -impl<'a, L: IsValueLeaf> FromValueContent<'a> for Referenceable { - fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { - content - } -} - -/// Roughly equivalent to an owned, but wrapped so that it can be turned into a Shared/Mutable easily. -/// This is useful for the content of variables. -/// -/// Note that Referenceable form does not support dyn casting, because Rc> cannot be -/// directly cast to Rc>. -#[derive(Copy, Clone)] -pub(crate) struct BeReferenceable; -impl IsForm for BeReferenceable {} - -impl IsHierarchicalForm for BeReferenceable { - type Leaf<'a, T: IsLeafType> = Referenceable; -} diff --git a/src/expressions/concepts/forms/shared.rs b/src/expressions/concepts/forms/shared.rs index 106cf736..330d2065 100644 --- a/src/expressions/concepts/forms/shared.rs +++ b/src/expressions/concepts/forms/shared.rs @@ -1,19 +1,27 @@ use super::*; -pub(crate) type QqqShared = SharedSubRcRefCell; - -impl IsValueContent for QqqShared { - type Type = L::Type; +impl IsValueContent for Shared { + type Type = X::Type; type Form = BeShared; } -impl<'a, L: IsValueLeaf> IntoValueContent<'a> for QqqShared { +impl<'a, X: IsValueContent> IntoValueContent<'a> for Shared +where + X: 'static, + X: IsSelfValueContent<'static>, + X::Type: IsHierarchicalType = X>, + X::Form: IsHierarchicalForm, + X::Form: LeafAsRefForm, +{ fn into_content(self) -> Content<'a, Self::Type, Self::Form> { - self + self.emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer, None)) } } -impl<'a, L: IsValueLeaf> FromValueContent<'a> for QqqShared { +// Note we can't implement this more widely than leaves. +// This is because e.g. Content has Shared() in its leaves, +// This can't be mapped back to having Shared(Content). +impl<'a, L: IsValueLeaf> FromValueContent<'a> for Shared { fn from_content(content: Content<'a, Self::Type, Self::Form>) -> Self { content } @@ -24,21 +32,35 @@ pub(crate) struct BeShared; impl IsForm for BeShared {} impl IsHierarchicalForm for BeShared { - type Leaf<'a, T: IsLeafType> = QqqShared; + type Leaf<'a, T: IsLeafType> = Shared; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeShared { - type DynLeaf<'a, D: 'static + ?Sized> = QqqShared; + type DynLeaf<'a, D: IsDynType> = Shared; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { - leaf.replace(|content, emplacer| match ::map_ref(content) { - Ok(mapped) => Ok(emplacer.emplace(mapped)), - Err(this) => Err(emplacer.emplace(this)), + leaf.emplace_map(|content, emplacer| match ::map_ref(content) { + Ok(mapped) => { + // SAFETY: PathExtension is correct for mapping to a dyn type + let mapped_ref = unsafe { + MappedRef::new(mapped, PathExtension::TypeNarrowing(D::type_kind()), span) + }; + Ok(emplacer.emplace(mapped_ref)) + } + Err(_) => Err(emplacer.revert()), }) } } @@ -53,16 +75,18 @@ impl MapFromArgument for BeShared { const ARGUMENT_OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; fn from_argument_value( - value: ArgumentValue, - ) -> ExecutionResult> { - Ok(value.expect_shared().into_content()) + Spanned(value, span): Spanned, + ) -> FunctionResult> { + Ok(value + .expect_shared() + .emplace_map(|inner, emplacer| inner.as_ref_value().into_shared(emplacer, Some(span)))) } } // impl MapIntoReturned for BeShared { // fn into_returned_value( // content: Content<'static, AnyType, Self>, -// ) -> ExecutionResult { +// ) -> FunctionResult { // todo!("Return shared") // } // } diff --git a/src/expressions/concepts/forms/simple_mut.rs b/src/expressions/concepts/forms/simple_mut.rs index fcd4bbe4..e7175dd5 100644 --- a/src/expressions/concepts/forms/simple_mut.rs +++ b/src/expressions/concepts/forms/simple_mut.rs @@ -26,16 +26,24 @@ impl IsForm for BeMut {} impl IsHierarchicalForm for BeMut { type Leaf<'a, T: IsLeafType> = &'a mut T::Leaf; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeMut { - type DynLeaf<'a, D: 'static + ?Sized> = &'a mut D; + type DynLeaf<'a, D: IsDynType> = &'a mut D::DynContent; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, _span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { ::map_mut(leaf) } @@ -52,15 +60,17 @@ where Self: IsValueContent, Self::Type: IsHierarchicalType = Self>, { - fn into_mutable<'b, T: 'static>( + fn into_mutable<'o, 'b, T: 'static>( self, emplacer: &'b mut MutableEmplacer<'a, T>, - ) -> Content<'static, Self::Type, BeMutable> + span: Option, + ) -> Content<'o, Self::Type, BeMutable> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut MutableEmplacer<'e2, X>, + span: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeMutable>; @@ -75,23 +85,34 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { self.emplacer.emplace_unchecked(leaf) } + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension correctly describes mapping to a leaf type T + let mapped = unsafe { + MappedMut::new_unchecked( + leaf, + PathExtension::TypeNarrowing(T::type_kind()), + self.span, + ) + }; + self.emplacer.emplace(mapped) } }; - let __mapper = __InlineMapper { emplacer }; - ::map_with::(__mapper, self) + let __mapper = __InlineMapper { emplacer, span }; + let content = ::map_with::(__mapper, self); + ::covariant::(content) } - fn into_assignee<'b, T: 'static>( + fn into_assignee<'o, 'b, T: 'static>( self, emplacer: &'b mut MutableEmplacer<'a, T>, - ) -> Content<'static, Self::Type, BeAssignee> + span: Option, + ) -> Content<'o, Self::Type, BeAssignee> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut MutableEmplacer<'e2, X>, + span: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAssignee>; @@ -106,23 +127,34 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { QqqAssignee(self.emplacer.emplace_unchecked(leaf)) } + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension correctly describes mapping to a leaf type T + let mapped = unsafe { + MappedMut::new_unchecked( + leaf, + PathExtension::TypeNarrowing(T::type_kind()), + self.span, + ) + }; + Assignee(self.emplacer.emplace(mapped)) } }; - let __mapper = __InlineMapper { emplacer }; - ::map_with::(__mapper, self) + let __mapper = __InlineMapper { emplacer, span }; + let content = ::map_with::(__mapper, self); + ::covariant::(content) } - fn into_mutable_any_mut<'b, T: 'static>( + fn into_mutable_any_mut<'o, 'b, T: 'static>( self, emplacer: &'b mut MutableEmplacer<'a, T>, - ) -> Content<'static, Self::Type, BeAnyMut> + span: Option, + ) -> Content<'o, Self::Type, BeAnyMut> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut MutableEmplacer<'e2, X>, + span: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAnyMut>; @@ -137,12 +169,21 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { Mutable(self.emplacer.emplace_unchecked(leaf)).into() } + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension correctly describes mapping to a leaf type T + let mapped = unsafe { + MappedMut::new_unchecked( + leaf, + PathExtension::TypeNarrowing(T::type_kind()), + self.span, + ) + }; + self.emplacer.emplace(mapped).into() } }; - let __mapper = __InlineMapper { emplacer }; - ::map_with::(__mapper, self) + let __mapper = __InlineMapper { emplacer, span }; + let content = ::map_with::(__mapper, self); + ::covariant::(content) } } diff --git a/src/expressions/concepts/forms/simple_ref.rs b/src/expressions/concepts/forms/simple_ref.rs index d466b492..1d36b72a 100644 --- a/src/expressions/concepts/forms/simple_ref.rs +++ b/src/expressions/concepts/forms/simple_ref.rs @@ -26,16 +26,24 @@ impl IsForm for BeRef {} impl IsHierarchicalForm for BeRef { type Leaf<'a, T: IsLeafType> = &'a T::Leaf; + + #[inline] + fn covariant_leaf<'a, 'b, T: IsLeafType>(leaf: Self::Leaf<'a, T>) -> Self::Leaf<'b, T> + where + 'a: 'b, + { + leaf + } } impl IsDynCompatibleForm for BeRef { - type DynLeaf<'a, D: 'static + ?Sized> = &'a D; + type DynLeaf<'a, D: IsDynType> = &'a D::DynContent; - fn leaf_to_dyn<'a, T: IsLeafType, D: ?Sized + 'static>( - leaf: Self::Leaf<'a, T>, + fn leaf_to_dyn<'a, T: IsLeafType, D: IsDynType>( + Spanned(leaf, _span): Spanned>, ) -> Result, Content<'a, T, Self>> where - T::Leaf: CastDyn, + T::Leaf: CastDyn, { ::map_ref(leaf) } @@ -52,17 +60,19 @@ where Self: IsValueContent, Self::Type: IsHierarchicalType = Self>, { - fn into_shared<'b, T: 'static>( + fn into_shared<'o, 'b, T: 'static>( self, emplacer: &'b mut SharedEmplacer<'a, T>, - ) -> Content<'static, Self::Type, BeShared> + span: Option, + ) -> Content<'o, Self::Type, BeShared> where Self: Sized, { - struct __InlineMapper<'b, 'e2, X: 'static> { - emplacer: &'b mut SharedEmplacer<'e2, X>, + struct __InlineMapper<'b, 'e, X: 'static> { + emplacer: &'b mut SharedEmplacer<'e, X>, + span: Option, } - impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { + impl<'b, 'e, X> LeafMapper for __InlineMapper<'b, 'e, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeShared>; fn to_parent_output<'a, T: IsChildType>( @@ -75,23 +85,34 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { self.emplacer.emplace_unchecked(leaf) } + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension correctly describes mapping to a leaf type T + let mapped = unsafe { + MappedRef::new_unchecked( + leaf, + PathExtension::TypeNarrowing(T::type_kind()), + self.span, + ) + }; + self.emplacer.emplace(mapped) } }; - let __mapper = __InlineMapper { emplacer }; - ::map_with::(__mapper, self) + let __mapper = __InlineMapper { emplacer, span }; + let content = ::map_with::(__mapper, self); + ::covariant::(content) } - fn into_shared_any_ref<'b, T: 'static>( + fn into_shared_any_ref<'o, 'b, T: 'static>( self, emplacer: &'b mut SharedEmplacer<'a, T>, - ) -> Content<'static, Self::Type, BeAnyRef> + span: Option, + ) -> Content<'o, Self::Type, BeAnyRef> where Self: Sized, { struct __InlineMapper<'b, 'e2, X: 'static> { emplacer: &'b mut SharedEmplacer<'e2, X>, + span: Option, } impl<'b, 'e2, X> LeafMapper for __InlineMapper<'b, 'e2, X> { type Output<'a, T: IsHierarchicalType> = Content<'static, T, BeAnyRef>; @@ -106,12 +127,21 @@ where self, leaf: ::Leaf<'l, T>, ) -> Self::Output<'l, T> { - // SAFETY: 'l = 'a = 'e so this is valid - unsafe { Shared(self.emplacer.emplace_unchecked(leaf)).into() } + // SAFETY: 'l = 'a = 'e2 so the lifetime transmute is valid, and + // PathExtension correctly describes mapping to a leaf type T + let mapped = unsafe { + MappedRef::new_unchecked( + leaf, + PathExtension::TypeNarrowing(T::type_kind()), + self.span, + ) + }; + self.emplacer.emplace(mapped).into() } }; - let __mapper = __InlineMapper { emplacer }; - ::map_with::(__mapper, self) + let __mapper = __InlineMapper { emplacer, span }; + let content = ::map_with::(__mapper, self); + ::covariant::(content) } } diff --git a/src/expressions/concepts/mapping.rs b/src/expressions/concepts/mapping.rs index 6b58a307..a7ea5a14 100644 --- a/src/expressions/concepts/mapping.rs +++ b/src/expressions/concepts/mapping.rs @@ -146,6 +146,9 @@ macro_rules! __map_via_leaf_to_parent { ($output:ident -> ExecutionResult>) => { Ok($t::into_parent($output?)) }; + ($output:ident -> FunctionResult>) => { + Ok($t::into_parent($output?)) + }; ($output:ident -> Result, $err:ty>) => { Ok($t::into_parent($output?)) }; diff --git a/src/expressions/concepts/type_traits.rs b/src/expressions/concepts/type_traits.rs index e1bd9f26..02b09942 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; @@ -42,6 +42,15 @@ pub(crate) trait IsHierarchicalType: IsType { fn content_to_leaf_kind( content: &Self::Content<'_, F>, ) -> Self::LeafKind; + + /// Shrinks the lifetime of a content value from `'a` to `'b`. + /// This is safe because all forms' leaf types are covariant in `'a`, + /// as enforced by [`IsHierarchicalForm::covariant_leaf`]. + fn covariant<'a, 'b, F: IsHierarchicalForm>( + content: Self::Content<'a, F>, + ) -> Self::Content<'b, F> + where + 'a: 'b; } pub(crate) trait IsLeafType: @@ -57,7 +66,6 @@ pub(crate) trait IsLeafType: + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> - + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> + for<'a> IsHierarchicalType = ::Leaf<'a, Self>> @@ -86,7 +94,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) => { @@ -94,8 +102,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(), )); } }; @@ -106,22 +114,23 @@ pub(crate) trait DowncastFrom: IsHierarchicalType { pub(crate) trait DynResolveFrom: IsDynType { fn downcast_from<'a, F: IsHierarchicalForm + IsDynCompatibleForm>( content: Content<'a, T, F>, + span: SpanRange, ) -> Result, Content<'a, T, F>>; fn resolve<'a, F: IsHierarchicalForm + IsDynCompatibleForm>( content: Content<'a, T, F>, span_range: SpanRange, resolution_target: &str, - ) -> ExecutionResult> { - let content = match Self::downcast_from(content) { + ) -> FunctionResult> { + let content = match Self::downcast_from(content, span_range) { Ok(c) => c, Err(existing) => { let leaf_kind = T::content_to_leaf_kind::(&existing); 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(), )); } }; @@ -144,7 +153,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,6 +186,11 @@ macro_rules! impl_type_feature_resolver { None } + 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) + } + 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) @@ -343,10 +357,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 @@ -377,7 +387,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; @@ -386,7 +396,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)) @@ -454,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> { @@ -471,7 +495,6 @@ macro_rules! define_parent_type { } } - impl<'a, F: IsHierarchicalForm> Copy for $content<'a, F> where $( Content<'a, $variant_type, F>: Copy ),* @@ -495,12 +518,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 { @@ -529,9 +552,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(), )* } } @@ -572,7 +595,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)* })* }, @@ -584,7 +607,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)) @@ -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 { @@ -661,11 +694,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 { @@ -711,11 +744,17 @@ macro_rules! define_leaf_type { pub(crate) use define_leaf_type; -pub(crate) struct DynMapper(std::marker::PhantomData); +pub(crate) struct DynMapper { + _phantom: std::marker::PhantomData, + pub(crate) span: SpanRange, +} -impl DynMapper { - pub(crate) const fn new() -> Self { - Self(std::marker::PhantomData) +impl DynMapper { + pub(crate) fn new(span: SpanRange) -> Self { + Self { + _phantom: std::marker::PhantomData, + span, + } } } @@ -769,10 +808,10 @@ 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_display_name: $articled_display_name:literal, + articled_value_name: $articled_value_name:literal, ) => { #[derive(Copy, Clone)] $type_def_vis struct $type_def; @@ -781,7 +820,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) @@ -801,53 +840,53 @@ macro_rules! define_dyn_type { } impl IsDynType for $type_def { - type DynContent = $dyn_type; + type DynContent = $dyn_content; } impl_type_feature_resolver! { impl TypeFeatureResolver for $type_def: [$type_def] } - impl IsDynLeaf for $dyn_type { + impl IsDynLeaf for $dyn_content { type Type = $type_def; } - impl IsArgument for Box<$dyn_type> { + impl IsArgument for Box<$dyn_content> { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Owned; - fn from_argument(Spanned(value, span_range): Spanned) -> ExecutionResult { - let form_mapped = BeOwned::from_argument_value(value)?; + fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { + let form_mapped = BeOwned::from_argument_value(Spanned(value, span_range))?; <$type_def as DynResolveFrom>::resolve(form_mapped, span_range, "This argument") } } - impl<'a> IsArgument for AnyRef<'a, $dyn_type> { + impl<'a> IsArgument for AnyRef<'a, $dyn_content> { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; - fn from_argument(Spanned(value, span_range): Spanned) -> ExecutionResult { - let form_mapped = BeAnyRef::from_argument_value(value)?; + fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { + let form_mapped = BeAnyRef::from_argument_value(Spanned(value, span_range))?; <$type_def as DynResolveFrom>::resolve(form_mapped, span_range, "This argument") } } - impl<'a> IsArgument for AnyMut<'a, $dyn_type> { + impl<'a> IsArgument for AnyMut<'a, $dyn_content> { type ValueType = $type_def; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; - fn from_argument(Spanned(value, span_range): Spanned) -> ExecutionResult { - let form_mapped = BeAnyMut::from_argument_value(value)?; + fn from_argument(Spanned(value, span_range): Spanned) -> FunctionResult { + let form_mapped = BeAnyMut::from_argument_value(Spanned(value, span_range))?; <$type_def as DynResolveFrom>::resolve(form_mapped, span_range, "This argument") } } impl DynResolveFrom for $type_def { - fn downcast_from<'a, F: IsHierarchicalForm + IsDynCompatibleForm>(content: Content<'a, T, F>) -> Result, Content<'a, T, F>> { - T::map_with::<'a, F, _>(DynMapper::<$dyn_type>::new(), content) + fn downcast_from<'a, F: IsHierarchicalForm + IsDynCompatibleForm>(content: Content<'a, T, F>, span: SpanRange) -> Result, Content<'a, T, F>> { + T::map_with::<'a, F, _>(DynMapper::<$type_def>::new(span), content) } } - impl LeafMapper for DynMapper<$dyn_type> { - type Output<'a, T: IsHierarchicalType> = Result, Content<'a, T, F>>; + impl LeafMapper for DynMapper<$type_def> { + type Output<'a, T: IsHierarchicalType> = Result, Content<'a, T, F>>; fn to_parent_output<'a, T: IsChildType>( output: Self::Output<'a, T>, @@ -862,7 +901,7 @@ macro_rules! define_dyn_type { self, leaf: F::Leaf<'a, T>, ) -> Self::Output<'a, T> { - F::leaf_to_dyn(leaf) + F::leaf_to_dyn(Spanned(leaf, self.span)) } } }; diff --git a/src/expressions/control_flow.rs b/src/expressions/control_flow.rs index 1080af25..ee1f22d1 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) } } @@ -379,10 +375,15 @@ 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_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); @@ -407,9 +408,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) } } @@ -527,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() } }) } @@ -538,7 +538,10 @@ 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, @@ -618,7 +621,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/equality.rs b/src/expressions/equality.rs index 4f3b78f2..5b095550 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"), } @@ -50,7 +48,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; @@ -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; @@ -106,7 +96,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 } @@ -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) @@ -177,15 +162,19 @@ 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) } #[inline] - fn leaf_values_not_equal(&mut self, _lhs: &T, _rhs: &T) -> ExecutionResult { + fn leaf_values_not_equal( + &mut self, + _lhs: &T, + _rhs: &T, + ) -> FunctionResult { Ok(false) } @@ -193,14 +182,14 @@ 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 {}", path_str, - lhs.articled_kind(), + lhs.kind().articled_value_name(), path_str, - rhs.articled_kind() + rhs.kind().articled_value_name(), ))) } @@ -213,9 +202,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() ))) } @@ -224,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) } @@ -249,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); @@ -274,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)) } @@ -350,9 +331,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 { @@ -362,9 +343,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 } => { @@ -424,7 +405,7 @@ impl EqualityContext for DebugEquality { } #[inline] - fn leaf_values_not_equal( + fn leaf_values_not_equal( &mut self, lhs: &T, rhs: &T, @@ -509,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); @@ -566,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/control_flow_analysis.rs b/src/expressions/evaluation/control_flow_analysis.rs index e26f3782..9603f92c 100644 --- a/src/expressions/evaluation/control_flow_analysis.rs +++ b/src/expressions/evaluation/control_flow_analysis.rs @@ -45,11 +45,13 @@ pub(in super::super) fn control_flow_visit( ExpressionNode::Property { node, .. } => { stack.push(*node); } - ExpressionNode::MethodCall { - node, parameters, .. + ExpressionNode::Invocation { + invokable, + invocation: Invocation { parameters, .. }, + .. } => { stack.push_reversed(parameters.iter().copied()); - stack.push(*node); // This is a stack so this executes first + stack.push(*invokable); // This is a stack so this executes first } ExpressionNode::Index { node, index, .. } => { stack.push_reversed([*node, *index]); @@ -120,6 +122,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), } } } diff --git a/src/expressions/evaluation/evaluator.rs b/src/expressions/evaluation/evaluator.rs index 0348ec2d..0f7b8dba 100644 --- a/src/expressions/evaluation/evaluator.rs +++ b/src/expressions/evaluation/evaluator.rs @@ -149,12 +149,12 @@ pub(crate) enum RequestedValue { Owned(AnyValueOwned), Shared(AnyValueShared), Mutable(AnyValueMutable), - CopyOnWrite(CopyOnWriteValue), - Assignee(AssigneeValue), + CopyOnWrite(AnyValueCopyOnWrite), + Assignee(AnyValueAssignee), // RequestedOwnership::LateBound // ------------------------------- - LateBound(LateBoundValue), + LateBound(AnyValueLateBound), // Marks completion of an assignment frame // --------------------------------------- @@ -176,6 +176,14 @@ impl RequestedValue { } } + #[allow(dead_code)] + 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"), + } + } + pub(super) fn expect_assignee(self) -> AnyValueAssignee { match self { RequestedValue::Assignee(assignee) => assignee, @@ -183,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"), @@ -229,19 +237,27 @@ impl RequestedValue { pub(crate) fn expect_any_value_and_map( self, - map_shared: impl FnOnce(AnyValueShared) -> ExecutionResult, - map_mutable: impl FnOnce(AnyValueMutable) -> ExecutionResult, - map_owned: impl FnOnce(AnyValueOwned) -> ExecutionResult, - ) -> ExecutionResult { + map_shared: impl FnOnce(AnyValueShared) -> FunctionResult, + map_mutable: impl FnOnce( + AnyValueMutable, + ) -> 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)?) } 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)?) @@ -271,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()) } @@ -337,6 +353,15 @@ 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, + node: ExpressionNodeId, + ) -> NextAction { + self.request_argument_value(handler, node, ArgumentOwnership::CopyOnWrite) + } + pub(super) fn request_shared>( self, handler: H, @@ -451,7 +476,7 @@ impl<'a> Context<'a, ReturnsValue> { pub(super) fn return_late_bound( self, - late_bound: Spanned, + late_bound: Spanned, ) -> ExecutionResult { let value = self.request.map_from_late_bound(late_bound)?; Ok(NextAction::return_requested(value)) diff --git a/src/expressions/evaluation/node_conversion.rs b/src/expressions/evaluation/node_conversion.rs index 8ceb680d..ca19336f 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) })?, @@ -33,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, @@ -43,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) => { @@ -81,6 +80,11 @@ impl ExpressionNode { parse_expression.evaluate_spanned(interpreter, ownership) })? } + Leaf::ClosureExpression(closure_expression) => { + context.evaluate(|interpreter, ownership| { + Ok(closure_expression.evaluate_spanned(interpreter, ownership)?) + })? + } } } ExpressionNode::Grouped { delim_span, inner } => { @@ -118,11 +122,10 @@ impl ExpressionNode { equals_token, value, } => AssignmentBuilder::start(context, *assignee, *equals_token, *value), - ExpressionNode::MethodCall { - node, - method, - parameters, - } => MethodCallBuilder::start(context, *node, method.clone(), parameters), + 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..10ffd94a 100644 --- a/src/expressions/evaluation/value_frames.rs +++ b/src/expressions/evaluation/value_frames.rs @@ -7,7 +7,7 @@ use super::*; /// which indicates what ownership type to resolve to. pub(crate) enum ArgumentValue { Owned(AnyValueOwned), - CopyOnWrite(CopyOnWriteValue), + CopyOnWrite(AnyValueCopyOnWrite), Mutable(AnyValueMutable), Assignee(AnyValueAssignee), Shared(AnyValueShared), @@ -21,7 +21,7 @@ impl ArgumentValue { } } - pub(crate) fn expect_copy_on_write(self) -> CopyOnWriteValue { + pub(crate) fn expect_copy_on_write(self) -> AnyValueCopyOnWrite { match self { ArgumentValue::CopyOnWrite(value) => value, _ => panic!("expect_copy_on_write() called on a non-copy-on-write ArgumentValue"), @@ -48,6 +48,70 @@ impl ArgumentValue { _ => panic!("expect_shared() called on a non-shared ArgumentValue"), } } + + /// 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) => InactiveArgumentValue::Owned(owned), + ArgumentValue::CopyOnWrite(copy_on_write) => { + InactiveArgumentValue::CopyOnWrite(copy_on_write.deactivate()) + } + ArgumentValue::Mutable(mutable) => InactiveArgumentValue::Mutable(mutable.deactivate()), + ArgumentValue::Assignee(Assignee(mutable)) => { + InactiveArgumentValue::Assignee(mutable.deactivate()) + } + ArgumentValue::Shared(shared) => InactiveArgumentValue::Shared(shared.deactivate()), + } + } +} + +/// An inactive argument value that can be safely cloned and dropped. +pub(crate) enum InactiveArgumentValue { + Owned(AnyValueOwned), + CopyOnWrite(InactiveCopyOnWrite), + Mutable(InactiveMutable), + Assignee(InactiveMutable), + Shared(InactiveShared), +} + +impl Clone for InactiveArgumentValue { + fn clone(&self) -> Self { + match self { + InactiveArgumentValue::Owned(owned) => InactiveArgumentValue::Owned(owned.clone()), + InactiveArgumentValue::CopyOnWrite(copy_on_write) => { + InactiveArgumentValue::CopyOnWrite(copy_on_write.clone()) + } + InactiveArgumentValue::Mutable(mutable) => { + InactiveArgumentValue::Mutable(mutable.clone()) + } + InactiveArgumentValue::Assignee(assignee) => { + InactiveArgumentValue::Assignee(assignee.clone()) + } + InactiveArgumentValue::Shared(shared) => InactiveArgumentValue::Shared(shared.clone()), + } + } +} + +impl InactiveArgumentValue { + /// Re-enables this disabled argument value by re-acquiring any borrow. + pub(crate) fn enable(self, span: SpanRange) -> FunctionResult { + match self { + InactiveArgumentValue::Owned(owned) => Ok(ArgumentValue::Owned(owned)), + InactiveArgumentValue::CopyOnWrite(copy_on_write) => { + Ok(ArgumentValue::CopyOnWrite(copy_on_write.activate(span)?)) + } + InactiveArgumentValue::Mutable(inactive) => { + Ok(ArgumentValue::Mutable(inactive.activate(span)?)) + } + InactiveArgumentValue::Assignee(inactive) => { + Ok(ArgumentValue::Assignee(Assignee(inactive.activate(span)?))) + } + InactiveArgumentValue::Shared(inactive) => { + Ok(ArgumentValue::Shared(inactive.activate(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; @@ -133,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. @@ -168,29 +198,35 @@ impl RequestedOwnership { } } - pub(crate) fn map_none(self, span: SpanRange) -> ExecutionResult> { + pub(crate) fn is_assignee_request(&self) -> bool { + matches!( + self, + RequestedOwnership::Concrete(ArgumentOwnership::Assignee { .. }) + ) + } + + 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> { - Ok(Spanned( - match self { - RequestedOwnership::LateBound => RequestedValue::LateBound(late_bound), - RequestedOwnership::Concrete(_) => { - panic!("Returning a late-bound reference when concrete ownership was requested") - } - }, - span, - )) + Spanned(late_bound, span): Spanned, + ) -> FunctionResult> { + 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( &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)), @@ -205,7 +241,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)), @@ -220,7 +256,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)), @@ -241,11 +277,11 @@ impl RequestedOwnership { pub(crate) fn map_from_owned( &self, Spanned(value, span): Spanned, - ) -> ExecutionResult> { + ) -> FunctionResult> { Ok(Spanned( match self { RequestedOwnership::LateBound => { - RequestedValue::LateBound(LateBoundValue::Owned(LateBoundOwnedValue { + RequestedValue::LateBound(LateBound::Owned(LateBoundOwned { owned: value, is_from_last_use: false, })) @@ -260,12 +296,12 @@ impl RequestedOwnership { pub(crate) fn map_from_copy_on_write( &self, - Spanned(cow, span): Spanned, - ) -> ExecutionResult> { + Spanned(cow, span): Spanned, + ) -> FunctionResult> { Ok(Spanned( match self { RequestedOwnership::LateBound => { - RequestedValue::LateBound(LateBoundValue::CopyOnWrite(cow)) + RequestedValue::LateBound(AnyValueLateBound::CopyOnWrite(cow)) } RequestedOwnership::Concrete(requested) => { Self::item_from_argument(requested.map_from_copy_on_write(Spanned(cow, span))?) @@ -278,11 +314,11 @@ impl RequestedOwnership { pub(crate) fn map_from_mutable( &self, Spanned(mutable, span): Spanned, - ) -> ExecutionResult> { + ) -> FunctionResult> { 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))?) @@ -294,12 +330,12 @@ impl RequestedOwnership { pub(crate) fn map_from_assignee( &self, - Spanned(assignee, span): Spanned, - ) -> ExecutionResult> { + Spanned(assignee, span): Spanned, + ) -> FunctionResult> { Ok(Spanned( match self { RequestedOwnership::LateBound => { - RequestedValue::LateBound(LateBoundValue::Mutable(assignee.0)) + RequestedValue::LateBound(AnyValueLateBound::Mutable(assignee.0)) } RequestedOwnership::Concrete(requested) => { Self::item_from_argument(requested.map_from_assignee(Spanned(assignee, span))?) @@ -311,12 +347,12 @@ impl RequestedOwnership { pub(crate) fn map_from_shared( &self, - Spanned(shared, span): Spanned, - ) -> ExecutionResult> { + Spanned(shared, span): Spanned, + ) -> FunctionResult> { Ok(Spanned( match self { RequestedOwnership::LateBound => RequestedValue::LateBound( - LateBoundValue::CopyOnWrite(CopyOnWrite::shared_in_place_of_shared(shared)), + AnyValueLateBound::CopyOnWrite(CopyOnWrite::shared_in_place_of_shared(shared)), ), RequestedOwnership::Concrete(requested) => { Self::item_from_argument(requested.map_from_shared(Spanned(shared, span))?) @@ -383,41 +419,47 @@ pub(crate) enum ArgumentOwnership { impl ArgumentOwnership { pub(crate) fn map_from_late_bound( &self, - Spanned(late_bound, span): Spanned, - ) -> ExecutionResult { + 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(Spanned(late_bound_shared.shared, span), |_| { - ExecutionInterrupt::ownership_error(late_bound_shared.reason_not_mutable) - }), + LateBound::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 { + Spanned(copy_on_write, span): Spanned, + ) -> FunctionResult { match self { ArgumentOwnership::Owned => Ok(ArgumentValue::Owned( copy_on_write.clone_to_owned_transparently(span)?, )), - ArgumentOwnership::Shared => Ok(ArgumentValue::Shared(copy_on_write.into_shared())), + ArgumentOwnership::Shared => Ok(ArgumentValue::Shared(copy_on_write.into_shared(span))), ArgumentOwnership::Mutable => { if copy_on_write.acts_as_shared_reference() { span.ownership_err("A mutable reference is required, but a shared reference was received, this indicates a possible bug as the updated value won't be accessible. To proceed regardless, use `.clone()` to get a mutable reference to a cloned value.") } else { Ok(ArgumentValue::Mutable(Mutable::new_from_owned( copy_on_write.clone_to_owned_transparently(span)?, + None, + span, ))) } } @@ -436,8 +478,8 @@ impl ArgumentOwnership { pub(crate) fn map_from_shared( &self, - shared: Spanned, - ) -> ExecutionResult { + shared: Spanned, + ) -> 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."), @@ -446,13 +488,13 @@ impl ArgumentOwnership { fn map_from_shared_with_error_reason( &self, - Spanned(shared, span): Spanned, - mutable_error: impl FnOnce(SpanRange) -> ExecutionInterrupt, - ) -> ExecutionResult { + 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), )), @@ -467,14 +509,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 { + Spanned(assignee, span): Spanned, + ) -> FunctionResult { self.map_from_mutable_inner(Spanned(assignee.0, span), false) } @@ -482,13 +524,11 @@ impl ArgumentOwnership { &self, Spanned(mutable, span): Spanned, is_late_bound: bool, - ) -> ExecutionResult { + ) -> FunctionResult { 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.") } @@ -504,10 +544,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) } @@ -515,15 +552,15 @@ 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 => { 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.") @@ -531,7 +568,9 @@ impl ArgumentOwnership { span.ownership_err("An owned value cannot be assigned to.") } } - ArgumentOwnership::Shared => Ok(ArgumentValue::Shared(Shared::new_from_owned(owned))), + ArgumentOwnership::Shared => Ok(ArgumentValue::Shared(Shared::new_from_owned( + owned, None, span, + ))), } } } @@ -547,7 +586,7 @@ pub(super) enum AnyValueFrame { IndexAccess(ValueIndexAccessBuilder), Range(RangeBuilder), Assignment(AssignmentBuilder), - MethodCall(MethodCallBuilder), + Invocation(InvocationBuilder), } impl AnyValueFrame { @@ -566,7 +605,7 @@ 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), } } } @@ -705,6 +744,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(), @@ -787,7 +833,7 @@ impl EvaluationFrame for UnaryOperationBuilder { fn handle_next( self, - context: ValueContext, + mut context: ValueContext, operand: Spanned, ) -> ExecutionResult { let operand = operand.expect_late_bound(); @@ -800,14 +846,17 @@ 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!( "The {} operator is not supported for {}", self.operation, - operand.articled_kind(), + operand.kind().articled_value_name(), )) } } @@ -822,7 +871,7 @@ enum BinaryPath { right: ExpressionNodeId, }, OnRightBranch { - left: Spanned, + left: Spanned, interface: BinaryOperationInterface, }, } @@ -861,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) @@ -880,12 +929,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.deactivate()); self.state = BinaryPath::OnRightBranch { left, interface }; context.request_argument_value(self, right, rhs_ownership) @@ -894,30 +941,28 @@ 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(), )); } } } } - 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.deactivate()); + 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); } @@ -936,13 +981,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) } } @@ -958,31 +998,73 @@ 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 + 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_value_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 + .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.deactivate()); + let function_value = FunctionValue { + invokable: InvokableFunction::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() + "`{}` is not a method on {}, and the {} type does not support fields", + property_name, + source_kind.articled_value_name(), + source_kind.source_type_name(), )); }; let ctx = PropertyAccessCallContext { property: &self.access, + output_span_range: result_span, }; let auto_create = context.requested_ownership().requests_auto_create(); - // 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), )?; - // 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)) } } @@ -1044,7 +1126,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() )); }; @@ -1065,14 +1147,19 @@ 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(); - // 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) @@ -1080,7 +1167,6 @@ impl EvaluationFrame for ValueIndexAccessBuilder { }, |owned| (interface.owned_access)(ctx, owned, index), )?; - let result_span = SpanRange::new_between(source_span, self.access.span_range()); context.return_not_necessarily_matching_requested(Spanned(result, result_span))? } }) @@ -1248,30 +1334,31 @@ impl EvaluationFrame for AssignmentBuilder { } } -pub(super) struct MethodCallBuilder { - method: MethodAccess, +pub(super) struct InvocationBuilder { + parentheses: Parentheses, unevaluated_parameters_stack: Vec<(ExpressionNodeId, ArgumentOwnership)>, - state: MethodCallPath, + state: InvocationPath, } -enum MethodCallPath { - CallerPath, +enum InvocationPath { + InvokablePath, ArgumentsPath { - method: MethodInterface, - disabled_evaluated_arguments_including_caller: Vec>, + function_span: SpanRange, + invokable: InvokableFunction, + disabled_evaluated_arguments: Vec>, }, } -impl MethodCallBuilder { +impl InvocationBuilder { pub(super) fn start( context: ValueContext, - caller: ExpressionNodeId, - method: MethodAccess, - parameters: &[ExpressionNodeId], + invokable: ExpressionNodeId, + invocation: &Invocation, ) -> NextAction { let frame = Self { - method, - unevaluated_parameters_stack: parameters + parentheses: invocation.parentheses, + unevaluated_parameters_stack: invocation + .parameters .iter() .rev() .map(|x| { @@ -1279,17 +1366,17 @@ impl MethodCallBuilder { (*x, ArgumentOwnership::Owned) }) .collect(), - state: MethodCallPath::CallerPath, + state: InvocationPath::InvokablePath, }; - context.request_late_bound(frame, caller) + context.request_owned(frame, invokable) } } -impl EvaluationFrame for MethodCallBuilder { +impl EvaluationFrame for InvocationBuilder { type ReturnType = ReturnsValue; fn into_any(self) -> AnyValueFrame { - AnyValueFrame::MethodCall(self) + AnyValueFrame::Invocation(self) } fn handle_next( @@ -1297,95 +1384,92 @@ impl EvaluationFrame for MethodCallBuilder { 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 = caller - .kind() - .feature_resolver() - .resolve_method(self.method.method.to_string().as_str()); - let method = match method { - Some(m) => m, - None => { - return self.method.method.type_err(format!( - "The method {} does not exist on {}", - self.method.method, - caller.articled_kind(), - )) - } - }; - let non_caller_arguments = self.unevaluated_parameters_stack.len(); - let (argument_ownerships, min_arguments) = method.argument_ownerships(); + InvocationPath::InvokablePath => { + let function_span = span; + let function = value.expect_owned(); + + let function = function + .into_content() + .spanned(function_span) + .downcast_resolve::("An invoked value")?; + + 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) = invokable.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 + + 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 { - return self.method.method.type_err(format!( - "The method {} expects {} non-self argument/s, but {} were provided", - self.method.method, - if non_caller_min_arguments == non_caller_max_arguments { - (non_caller_min_arguments).to_string() + let expected_arguments = if unbound_min_arguments == unbound_max_arguments { + if unbound_min_arguments == 1 { + "1 argument".to_string() } else { - format!( - "{} to {}", - (non_caller_min_arguments), - (non_caller_max_arguments) - ) - }, - non_caller_arguments, + 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, )); } - let caller = - argument_ownerships[0].map_from_late_bound(Spanned(caller, caller_span))?; - let mut caller = Spanned(caller, caller_span); // We skip 1 to ignore the caller - let non_self_argument_ownerships: iter::Skip< + let unbound_argument_ownerships: iter::Skip< std::slice::Iter<'_, ArgumentOwnership>, - > = argument_ownerships.iter().skip(1); + > = 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(non_self_argument_ownerships) + .zip(unbound_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()); - unsafe { - // SAFETY: We enable it again before use - caller.to_mut().disable(); - } - params.push(caller); - params - }, - method, + self.state = InvocationPath::ArgumentsPath { + function_span, + invokable, + disabled_evaluated_arguments: disabled_bound_arguments, }; } - MethodCallPath::ArgumentsPath { - ref mut disabled_evaluated_arguments_including_caller, + 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_including_caller.push(argument); + let argument = Spanned(argument, span); + // Disable argument so we can evaluate remaining arguments without borrow conflicts + let argument = argument.map(|v| v.deactivate()); + disabled_evaluated_arguments.push(argument); } }; // Now plan the next action @@ -1394,31 +1478,35 @@ impl EvaluationFrame for MethodCallBuilder { 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: mut arguments, - method, + let (arguments, invokable, function_span) = match self.state { + InvocationPath::InvokablePath => unreachable!("Already updated above"), + InvocationPath::ArgumentsPath { + invokable, + 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()?; - } - } - (arguments, method) + let arguments = disabled_evaluated_arguments + .into_iter() + .map(|arg| { + let span = arg.1; + arg.try_map(|v| v.enable(span)) + }) + .collect::>>()?; + (arguments, invokable, function_span) } }; - let mut call_context = MethodCallContext { - output_span_range: self.method.span_range(), + let mut call_context = FunctionCallContext { + output_span_range: SpanRange::new_between( + function_span, + self.parentheses.close(), + ), interpreter: context.interpreter(), }; - let output = method.execute(arguments, &mut call_context)?; + let output = invokable.invoke(arguments, &mut call_context)?; context.return_returned_value(output)? } }) diff --git a/src/expressions/expression.rs b/src/expressions/expression.rs index 3912aa90..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()) @@ -122,10 +122,9 @@ pub(super) enum ExpressionNode { node: ExpressionNodeId, access: PropertyAccess, }, - MethodCall { - node: ExpressionNodeId, - method: MethodAccess, - parameters: Vec, + Invocation { + invokable: ExpressionNodeId, + invocation: Invocation, }, Index { node: ExpressionNodeId, @@ -144,13 +143,18 @@ 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), Variable(VariableReference), TypeProperty(TypeProperty), Discarded(Token![_]), - Value(Spanned), + Value(Spanned), StreamLiteral(StreamLiteral), ParseTemplateLiteral(ParseTemplateLiteral), IfExpression(Box), @@ -159,26 +163,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 +181,8 @@ impl Leaf { | Leaf::Discarded(_) | Leaf::Value(_) | Leaf::StreamLiteral(_) - | Leaf::ParseTemplateLiteral(_) => false, + | Leaf::ParseTemplateLiteral(_) + | Leaf::ClosureExpression(_) => false, } } } diff --git a/src/expressions/expression_block.rs b/src/expressions/expression_block.rs index cacdfcca..a46c22e7 100644 --- a/src/expressions/expression_block.rs +++ b/src/expressions/expression_block.rs @@ -29,13 +29,16 @@ impl HasSpanRange for EmbeddedExpression { } } -impl Interpret for EmbeddedExpression { - fn interpret(&self, interpreter: &mut Interpreter) -> ExecutionResult<()> { - let value = self.content.evaluate_shared(interpreter)?; - value.as_ref_value().output_to( - Grouping::Flattened, - &mut ToStreamContext::new(interpreter.output(self)?, self.span_range()), - ) +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() } } @@ -68,17 +71,22 @@ 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()), - ) + value + .as_ref_value() + .output_to( + Grouping::Flattened, + &mut ToStreamContext::new(output, self.span_range()), + ) + .into_execution_result() } } @@ -212,7 +220,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)?; @@ -332,6 +340,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/expression_parsing.rs b/src/expressions/expression_parsing.rs index 2fc27fdf..9779be1e 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() == '!' { @@ -121,7 +123,7 @@ impl<'a> ExpressionParser<'a> { "true" | "false" => { let bool = input.parse::()?; UnaryAtom::Leaf(Leaf::Value(Spanned( - SharedValue::new_from_owned(bool.value.into_any_value()), + AnyValueShared::new_from_owned(bool.value.into_any_value(), None, bool.span.span_range()), bool.span.span_range(), ))) } @@ -134,7 +136,7 @@ impl<'a> ExpressionParser<'a> { "None" => { let none_ident = input.parse_any_ident()?; // consume the "None" token UnaryAtom::Leaf(Leaf::Value(Spanned( - SharedValue::new_from_owned(().into_any_value()), + AnyValueShared::new_from_owned(().into_any_value(), None, none_ident.span().span_range()), none_ident.span().span_range(), ))) } @@ -153,7 +155,7 @@ impl<'a> ExpressionParser<'a> { let lit: syn::Lit = input.parse()?; let span_range = lit.span().span_range(); UnaryAtom::Leaf(Leaf::Value(Spanned( - SharedValue::new_from_owned(AnyValue::for_syn_lit(lit)), + AnyValueShared::new_from_owned(AnyValue::for_syn_lit(lit), None, span_range), span_range, ))) }, @@ -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 { .. }, .. @@ -207,19 +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()?; - 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('=') { @@ -260,7 +259,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 +334,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) } @@ -375,21 +374,19 @@ impl<'a> ExpressionParser<'a> { .nodes .add_node(ExpressionNode::Property { node, access }), }, - NodeExtension::MethodCall(method) => { + NodeExtension::Invocation(invocation) => { if self.streams.is_current_empty() { self.streams.exit_group(None)?; - let node = self.nodes.add_node(ExpressionNode::MethodCall { - node, - method, - parameters: Vec::new(), + let node = self.nodes.add_node(ExpressionNode::Invocation { + invokable: node, + invocation, }); WorkItem::TryParseAndApplyExtension { node } } else { self.push_stack_frame( - ExpressionStackFrame::NonEmptyMethodCallParametersList { + ExpressionStackFrame::NonEmptyInvocationParametersList { node, - method, - parameters: Vec::new(), + invocation, }, ) } @@ -448,18 +445,16 @@ 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 = self.nodes.add_node(ExpressionNode::Invocation { + invokable: source, + invocation, }); WorkItem::TryParseAndApplyExtension { node } } @@ -919,13 +914,12 @@ 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, + invocation: Invocation, }, /// An incomplete unary prefix operation /// NB: unary postfix operations such as `as` casting go straight to ExtendableNode @@ -977,7 +971,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, @@ -1038,8 +1032,8 @@ 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), AssignmentOperation(Token![=]), EndOfStreamOrGroup, @@ -1053,8 +1047,8 @@ 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, NodeExtension::EndOfStreamOrGroup => OperatorPrecendence::MIN, NodeExtension::AssignmentOperation(_) => OperatorPrecendence::AssignExtension, @@ -1069,8 +1063,8 @@ impl NodeExtension { extension @ (NodeExtension::PostfixOperation { .. } | NodeExtension::BinaryOperation { .. } | 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..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; @@ -16,6 +17,7 @@ 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::*; diff --git a/src/expressions/operations.rs b/src/expressions/operations.rs index 271198be..3c7433e6 100644 --- a/src/expressions/operations.rs +++ b/src/expressions/operations.rs @@ -85,23 +85,24 @@ 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(|| { + .ok_or_else(|| -> FunctionError { self.type_error(format!( "The {} operator is not supported for {} operand", self, - input.articled_kind(), + input.kind().articled_value_name(), )) })?; 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 @@ -325,7 +326,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(), )), } } @@ -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()) @@ -501,19 +502,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/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/statements.rs b/src/expressions/statements.rs index e0d4fca3..55187bcf 100644 --- a/src/expressions/statements.rs +++ b/src/expressions/statements.rs @@ -369,9 +369,13 @@ impl EmitStatement { interpreter: &mut Interpreter, ) -> ExecutionResult<()> { let Spanned(value, span) = self.expression.evaluate_owned(interpreter)?; - value.as_ref_value().output_to( - Grouping::Flattened, - &mut ToStreamContext::new(interpreter.output(&self.emit)?, span), - ) + 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() } } diff --git a/src/expressions/type_resolution/arguments.rs b/src/expressions/type_resolution/arguments.rs index 4e272095..e2e38ae7 100644 --- a/src/expressions/type_resolution/arguments.rs +++ b/src/expressions/type_resolution/arguments.rs @@ -33,12 +33,12 @@ 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, articled_expected_value_kind, - value.articled_kind() + value.kind().articled_value_name(), )) } } @@ -46,24 +46,25 @@ 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) } } -impl + ResolvableArgumentTarget + ?Sized> IsArgument for Shared { - type ValueType = T::ValueType; +impl IsArgument for Shared { + type ValueType = AnyType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Shared; - fn from_argument(argument: Spanned) -> ExecutionResult { - T::resolve_shared(argument.expect_shared(), "This argument") + fn from_argument(argument: Spanned) -> FunctionResult { + let Spanned(shared, _span) = argument.expect_shared(); + Ok(shared) } } @@ -79,23 +80,55 @@ impl + ResolvableArgumentTarget + ?Sized> IsArgume // } // } -impl + ResolvableArgumentTarget + ?Sized> IsArgument - for Assignee -{ - type ValueType = T::ValueType; +// We have some friction: +// - Assignee wants to take whole parent types e.g. AnyValue or FloatValue, so it +// can replace them +// - The blanket impl of IsArgument for X: FromValueContent<'static, Type = T, Form = F> +// breaks down, because this only works for LeafContent based types (which e.g. have +// Assignee in each leaf, which isn't very useful) +// - But FromValueContent for leaves is required by the Form system +// - So for now, we're stuck implementing IsArgument manually for Assignee for each parent +// X that we need. +// +// TODO[non-leaf-form]: Future work - +// - Rename the Content type alias/associated type to LeafContent or something +// - Make IsValueContent / FromValueContent more flexible, so it can capture the group level +// somehow, and take values with the content at any given level above the input level. + +impl IsArgument for Assignee { + type ValueType = AnyType; + const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Assignee { auto_create: false }; + + fn from_argument(argument: Spanned) -> FunctionResult { + Ok(argument.expect_assignee().0) + } +} + +impl IsArgument for Assignee { + type ValueType = FloatType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Assignee { auto_create: false }; - fn from_argument(argument: Spanned) -> ExecutionResult { - T::resolve_assignee(argument.expect_assignee(), "This argument") + fn from_argument(argument: Spanned) -> FunctionResult { + FloatValue::resolve_assignee(argument.expect_assignee(), "This argument") } } -impl + ResolvableArgumentTarget + ?Sized> IsArgument for Mutable { - type ValueType = T::ValueType; +impl IsArgument for Assignee { + type ValueType = IntegerType; + const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Assignee { auto_create: false }; + + fn from_argument(argument: Spanned) -> FunctionResult { + IntegerValue::resolve_assignee(argument.expect_assignee(), "This argument") + } +} + +impl IsArgument for Mutable { + type ValueType = AnyType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::Mutable; - fn from_argument(argument: Spanned) -> ExecutionResult { - T::resolve_mutable(argument.expect_mutable(), "This argument") + fn from_argument(argument: Spanned) -> FunctionResult { + let Spanned(mutable, _span) = argument.expect_mutable(); + Ok(mutable) } } @@ -120,23 +153,24 @@ impl + ResolvableArgumentTarget + ?Sized> IsArgum // } // } -impl + ResolvableArgumentTarget + ToOwned> IsArgument - for CopyOnWrite -where - T::Owned: ResolvableOwned, -{ - type ValueType = T::ValueType; +// See the "We have some friction" post above +impl IsArgument for CopyOnWrite { + type ValueType = AnyType; const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::CopyOnWrite; - fn from_argument(Spanned(value, span): Spanned) -> ExecutionResult { + fn from_argument(Spanned(value, _): Spanned) -> FunctionResult { + Ok(value.expect_copy_on_write()) + } +} + +impl IsArgument for CopyOnWrite { + type ValueType = FloatType; + const OWNERSHIP: ArgumentOwnership = ArgumentOwnership::CopyOnWrite; + + fn from_argument(Spanned(value, span): Spanned) -> FunctionResult { value.expect_copy_on_write().map( - |v| T::resolve_shared(v.spanned(span), "This argument"), - |v| { - >::resolve_value( - v.spanned(span), - "This argument", - ) - }, + |v| FloatValue::resolve_shared(v.spanned(span), "This argument"), + |v| FloatValue::resolve_value(v.spanned(span), "This argument"), ) } } @@ -145,7 +179,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 +187,28 @@ 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> { +impl + ?Sized + 'static> ResolveAs> + for Spanned +{ + 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 +216,15 @@ 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> { +impl + ?Sized + 'static> ResolveAs> + for Spanned +{ + fn resolve_as(self, resolution_target: &str) -> FunctionResult> { T::resolve_mutable(self, resolution_target) } } @@ -194,7 +232,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 +240,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) } } @@ -211,13 +249,13 @@ pub(crate) trait ResolvableArgumentTarget { type ValueType: TypeData; } -pub(crate) trait ResolvableOwned: Sized { - fn resolve_from_value(value: T, context: ResolutionContext) -> ExecutionResult; +pub(crate) trait ResolvableOwned: Sized { + 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 +264,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, @@ -235,29 +273,42 @@ pub(crate) trait ResolvableOwned: Sized { } } -pub(crate) trait ResolvableShared { - fn resolve_from_ref<'a>(value: &'a T, context: ResolutionContext) -> ExecutionResult<&'a Self>; +pub(crate) trait ResolvableShared { + fn resolve_from_ref<'a>(value: &'a T, context: ResolutionContext) -> FunctionResult<&'a Self>; /// The `resolution_target` should be capitalized, e.g. "This argument" or "The value destructed with an object pattern" fn resolve_shared( Spanned(value, span): Spanned>, resolution_target: &str, - ) -> ExecutionResult> { - value.try_map(|v| { - Self::resolve_from_ref( - v, - ResolutionContext { - span_range: &span, - resolution_target, - }, - ) - }) + ) -> FunctionResult> + where + Self: 'static, + { + value + .try_map(|v| { + let resolved = Self::resolve_from_ref( + v, + ResolutionContext { + span_range: &span, + resolution_target, + }, + )?; + // SAFETY: Tightened(T::Type) correctly describes type narrowing + Ok(unsafe { + MappedRef::new( + resolved, + PathExtension::TypeNarrowing(T::Type::type_kind()), + span, + ) + }) + }) + .map_err(|(err, _)| err) } 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 { @@ -270,7 +321,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, @@ -283,16 +334,16 @@ pub(crate) trait ResolvableShared { } } -pub(crate) trait ResolvableMutable { +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, @@ -302,22 +353,35 @@ pub(crate) trait ResolvableMutable { fn resolve_mutable( Spanned(value, span): Spanned>, resolution_target: &str, - ) -> ExecutionResult> { - value.try_map(|v| { - Self::resolve_from_mut( - v, - ResolutionContext { - span_range: &span, - resolution_target, - }, - ) - }) + ) -> FunctionResult> + where + Self: 'static, + { + value + .try_map(|v| { + let resolved = Self::resolve_from_mut( + v, + ResolutionContext { + span_range: &span, + resolution_target, + }, + )?; + // SAFETY: Tightened(T::Type) correctly describes type narrowing + Ok(unsafe { + MappedMut::new( + resolved, + PathExtension::TypeNarrowing(T::Type::type_kind()), + span, + ) + }) + }) + .map_err(|(err, _)| err) } 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 { @@ -330,7 +394,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, @@ -348,7 +412,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) } } @@ -356,7 +420,7 @@ impl ResolvableShared for AnyValue { fn resolve_from_ref<'a>( value: &'a AnyValue, _context: ResolutionContext, - ) -> ExecutionResult<&'a Self> { + ) -> FunctionResult<&'a Self> { Ok(value) } } @@ -364,7 +428,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) } } @@ -379,7 +443,7 @@ macro_rules! impl_resolvable_argument_for { fn resolve_from_value( $value: AnyValue, $context: ResolutionContext, - ) -> ExecutionResult { + ) -> FunctionResult { $body } } @@ -388,7 +452,7 @@ macro_rules! impl_resolvable_argument_for { fn resolve_from_ref<'a>( $value: &'a AnyValue, $context: ResolutionContext, - ) -> ExecutionResult<&'a Self> { + ) -> FunctionResult<&'a Self> { $body } } @@ -397,7 +461,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 } } @@ -405,46 +469,3 @@ macro_rules! impl_resolvable_argument_for { } pub(crate) use impl_resolvable_argument_for; - -macro_rules! impl_delegated_resolvable_argument_for { - (($value:ident: $delegate:ty) -> $type:ty { $expr:expr }) => { - impl ResolvableArgumentTarget for $type { - type ValueType = <$delegate as ResolvableArgumentTarget>::ValueType; - } - - impl ResolvableOwned for $type { - fn resolve_from_value( - input_value: Value, - context: ResolutionContext, - ) -> ExecutionResult { - 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, - ) -> ExecutionResult<&'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, - ) -> ExecutionResult<&'a mut Self> { - let $value: &mut $delegate = - ResolvableMutable::::resolve_from_mut(input_value, context)?; - Ok(&mut $expr) - } - } - }; -} - -pub(crate) use impl_delegated_resolvable_argument_for; diff --git a/src/expressions/type_resolution/interface_macros.rs b/src/expressions/type_resolution/interface_macros.rs index 0786469f..f8b1c46f 100644 --- a/src/expressions/type_resolution/interface_macros.rs +++ b/src/expressions/type_resolution/interface_macros.rs @@ -49,21 +49,21 @@ macro_rules! generate_binary_interface { }; } -macro_rules! generate_method_interface { +macro_rules! generate_function_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_function_interface`: ", $($arg)*)); }; } @@ -155,9 +155,9 @@ macro_rules! parse_arg_types { #[allow(unused)] pub(crate) fn apply_fn0( - f: fn(&mut MethodCallContext) -> R, - context: &mut MethodCallContext, -) -> ExecutionResult + f: fn(&mut FunctionCallContext) -> R, + context: &mut FunctionCallContext, +) -> FunctionResult where R: IsReturnable, { @@ -166,10 +166,10 @@ where } pub(crate) fn apply_fn1( - f: fn(&mut MethodCallContext, A) -> R, + f: fn(&mut FunctionCallContext, A) -> R, a: Spanned, - context: &mut MethodCallContext, -) -> ExecutionResult + context: &mut FunctionCallContext, +) -> FunctionResult where A: IsArgument, R: IsReturnable, @@ -179,11 +179,11 @@ 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, -) -> ExecutionResult + context: &mut FunctionCallContext, +) -> FunctionResult where A: IsArgument, B: IsArgument, @@ -199,11 +199,11 @@ 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, -) -> ExecutionResult + context: &mut FunctionCallContext, +) -> FunctionResult where A: IsArgument, B: IsArgument, @@ -213,12 +213,12 @@ 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, -) -> ExecutionResult + context: &mut FunctionCallContext, +) -> FunctionResult where A: IsArgument, B: IsArgument, @@ -236,12 +236,12 @@ 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, -) -> ExecutionResult + context: &mut FunctionCallContext, +) -> FunctionResult where A: IsArgument, B: IsArgument, @@ -258,13 +258,13 @@ 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, -) -> ExecutionResult + context: &mut FunctionCallContext, +) -> 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>, ctx: PropertyAccessCallContext, source: &'a AnyValue, -) -> ExecutionResult<&'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) -> ExecutionResult<&'b mut AnyValue>, + f: for<'b> fn( + PropertyAccessCallContext, + &'b mut S, + bool, + ) -> FunctionResult>, ctx: PropertyAccessCallContext, source: &'a mut AnyValue, auto_create: bool, -) -> ExecutionResult<&'a mut AnyValue> { +) -> FunctionResult> { let source = S::resolve_from_mut( source, ResolutionContext::new(&ctx.property.span_range(), "The property access source"), @@ -338,10 +342,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 +362,11 @@ pub(crate) fn apply_index_shared<'a, S: ResolvableShared + ?Sized + 'a IndexAccessCallContext, &'b S, Spanned, - ) -> ExecutionResult<&'b AnyValue>, + ) -> FunctionResult>, ctx: IndexAccessCallContext, source: &'a AnyValue, index: Spanned, -) -> ExecutionResult<&'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, - ) -> ExecutionResult<&'b mut AnyValue>, + ) -> FunctionResult>, ctx: IndexAccessCallContext, source: &'a mut AnyValue, index: Spanned, auto_create: bool, -) -> ExecutionResult<&'a mut AnyValue> { +) -> FunctionResult> { let source = S::resolve_from_mut( source, ResolutionContext::new(&ctx.access.span_range(), "The index access source"), @@ -390,11 +395,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"), @@ -402,20 +407,20 @@ 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 } } -#[derive(Clone, Copy)] pub(crate) struct UnaryOperationCallContext<'a> { pub operation: &'a UnaryOperation, + pub interpreter: &'a mut Interpreter, } #[derive(Clone, Copy)] @@ -425,11 +430,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) } } @@ -438,21 +443,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 +473,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,104 +486,131 @@ 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() -> &'static FunctionInterface { + &parse_arg_types!([CALLBACK: generate_function_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 + } + )* + } - $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 method_definitions { + #[allow(unused)] + use super::*; + $( + pub(crate) fn $method_name() -> &'static FunctionInterface { + &parse_arg_types!([CALLBACK: generate_function_interface![methods::$method_name]] $($method_args)*) + } + )* + } + )? - $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_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_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 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)*) + } + )* + } + )? + + $( + 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 { #[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> $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> $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 { @@ -590,11 +627,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> $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> $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 { @@ -608,22 +645,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<&'static FunctionInterface> { + Some(match method_name { + $( + stringify!($method_name) => method_definitions::$method_name(), + )* + _ => return None, + }) + } + )? + + $( + #[allow(unreachable_code)] + fn resolve_type_function(function_name: &str) -> Option<&'static FunctionInterface> { + 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)*)? } } }; @@ -648,6 +699,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/outputs.rs b/src/expressions/type_resolution/outputs.rs index cd823619..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), } @@ -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() - } -} diff --git a/src/expressions/type_resolution/type_data.rs b/src/expressions/type_resolution/type_data.rs index c7877739..c678c584 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<&'static FunctionInterface>; /// 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<&'static FunctionInterface>; + /// 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<&'static FunctionInterface> { 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<&'static FunctionInterface> { + None + } + /// Returns the property access interface for this type, if supported. fn resolve_own_property_access() -> Option { None @@ -73,90 +83,91 @@ pub(crate) trait TypeData { } #[allow(unused)] -pub(crate) enum MethodInterface { +#[derive(Clone)] +pub(crate) enum FunctionInterface { Arity0 { - method: fn(&mut MethodCallContext) -> ExecutionResult, + method: fn(&mut FunctionCallContext) -> FunctionResult, argument_ownership: [ArgumentOwnership; 0], }, Arity1 { method: - fn(&mut MethodCallContext, Spanned) -> ExecutionResult, + fn(&mut FunctionCallContext, Spanned) -> FunctionResult, argument_ownership: [ArgumentOwnership; 1], }, /// 1 argument, 1 optional argument Arity1PlusOptional1 { method: fn( - &mut MethodCallContext, + &mut FunctionCallContext, Spanned, Option>, - ) -> ExecutionResult, + ) -> FunctionResult, argument_ownership: [ArgumentOwnership; 2], }, Arity2 { method: fn( - &mut MethodCallContext, + &mut FunctionCallContext, Spanned, Spanned, - ) -> ExecutionResult, + ) -> FunctionResult, argument_ownership: [ArgumentOwnership; 2], }, Arity2PlusOptional1 { method: fn( - &mut MethodCallContext, + &mut FunctionCallContext, Spanned, Spanned, Option>, - ) -> ExecutionResult, + ) -> FunctionResult, argument_ownership: [ArgumentOwnership; 3], }, Arity3 { method: fn( - &mut MethodCallContext, + &mut FunctionCallContext, Spanned, Spanned, Spanned, - ) -> ExecutionResult, + ) -> FunctionResult, argument_ownership: [ArgumentOwnership; 3], }, Arity3PlusOptional1 { method: fn( - &mut MethodCallContext, + &mut FunctionCallContext, Spanned, Spanned, Spanned, Option>, - ) -> ExecutionResult, + ) -> FunctionResult, argument_ownership: [ArgumentOwnership; 4], }, ArityAny { method: fn( - &mut MethodCallContext, + &mut FunctionCallContext, Vec>, - ) -> ExecutionResult, + ) -> FunctionResult, argument_ownership: Vec, }, } -impl MethodInterface { - pub(crate) fn execute( +impl FunctionInterface { + pub(crate) fn invoke( &self, arguments: Vec>, - context: &mut MethodCallContext, - ) -> ExecutionResult> { + context: &mut FunctionCallContext, + ) -> FunctionResult> { 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 +184,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 +207,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 +230,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 +238,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), } @@ -257,7 +268,7 @@ impl MethodInterface { pub(crate) struct UnaryOperationInterface { pub method: - fn(UnaryOperationCallContext, Spanned) -> ExecutionResult, + fn(UnaryOperationCallContext, Spanned) -> FunctionResult, pub argument_ownership: ArgumentOwnership, } @@ -266,10 +277,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)) @@ -285,7 +300,7 @@ pub(crate) struct BinaryOperationInterface { BinaryOperationCallContext, Spanned, Spanned, - ) -> ExecutionResult, + ) -> FunctionResult, pub lhs_ownership: ArgumentOwnership, pub rhs_ownership: ArgumentOwnership, } @@ -296,7 +311,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 }, @@ -323,25 +338,30 @@ 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) -> ExecutionResult<&'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, - ) -> ExecutionResult<&'a mut AnyValue>, + ) -> FunctionResult>, /// Extract a property from an owned value. - pub owned_access: fn(PropertyAccessCallContext, AnyValue) -> ExecutionResult, + pub owned_access: fn(PropertyAccessCallContext, AnyValue) -> FunctionResult, } // ============================================================================ @@ -352,29 +372,32 @@ 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, - ) -> ExecutionResult<&'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, - ) -> ExecutionResult<&'a mut AnyValue>, + ) -> FunctionResult>, /// 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/type_resolution/type_kinds.rs b/src/expressions/type_resolution/type_kinds.rs index 7c27e743..14a75a14 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; } @@ -33,12 +33,15 @@ 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, } } } // 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), @@ -60,11 +63,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(), } } @@ -87,8 +90,96 @@ impl TypeKind { TypeKind::Dyn(dyn_kind) => dyn_kind.source_name(), } } + + pub(crate) fn is_narrowing_to(&self, other: &Self) -> bool { + match self.compare_bindings(other) { + TypeBindingComparison::Equal => true, + TypeBindingComparison::RightDerivesFromLeftButIsNotSubtype => true, + TypeBindingComparison::RightIsSubtypeOfLeft => true, + TypeBindingComparison::LeftDerivesFromRightButIsNotSubtype => false, + TypeBindingComparison::LeftIsSubtypeOfRight => false, + TypeBindingComparison::Incomparable => false, + TypeBindingComparison::Incompatible => false, + } + } + + /// This is the subtyping ordering. + /// - I define that X <= Y if Y is a subtype of X. + /// - Think "X is shallower than Y in the enum representation" + /// + /// This must be accurate so that the Dynamic References works correctly. + /// e.g. if I have a `a: &mut AnyValue` and a `b: &mut IntegerValue` pointing + /// to the same memory, then I can't use a to change to a String. + pub(crate) fn compare_bindings(&self, other: &Self) -> TypeBindingComparison { + if self == other { + return TypeBindingComparison::Equal; + } + match (self, other) { + // Dyns are not binding-comparable to other dyns + (TypeKind::Dyn(_), TypeKind::Dyn(_)) => TypeBindingComparison::Incomparable, + // Assuming the values are compatible, a dyn can be derived from any leaf/parent, + // but not the other way around (at present at least) + (TypeKind::Dyn(_), _) => TypeBindingComparison::LeftDerivesFromRightButIsNotSubtype, + (_, TypeKind::Dyn(_)) => TypeBindingComparison::RightDerivesFromLeftButIsNotSubtype, + // All non-any-values are strict subtypes of AnyValue + (TypeKind::Parent(ParentTypeKind::Value(_)), _) => { + TypeBindingComparison::RightIsSubtypeOfLeft + } + (_, TypeKind::Parent(ParentTypeKind::Value(_))) => { + TypeBindingComparison::LeftIsSubtypeOfRight + } + // Integer types + ( + TypeKind::Parent(ParentTypeKind::Integer(_)), + TypeKind::Leaf(AnyValueLeafKind::Integer(_)), + ) => TypeBindingComparison::RightIsSubtypeOfLeft, + ( + TypeKind::Leaf(AnyValueLeafKind::Integer(_)), + TypeKind::Parent(ParentTypeKind::Integer(_)), + ) => TypeBindingComparison::LeftIsSubtypeOfRight, + (TypeKind::Parent(ParentTypeKind::Integer(_)), _) => { + TypeBindingComparison::Incompatible + } + (_, TypeKind::Parent(ParentTypeKind::Integer(_))) => { + TypeBindingComparison::Incompatible + } + // Float types + ( + TypeKind::Parent(ParentTypeKind::Float(_)), + TypeKind::Leaf(AnyValueLeafKind::Float(_)), + ) => TypeBindingComparison::RightIsSubtypeOfLeft, + ( + TypeKind::Leaf(AnyValueLeafKind::Float(_)), + TypeKind::Parent(ParentTypeKind::Float(_)), + ) => TypeBindingComparison::LeftIsSubtypeOfRight, + (TypeKind::Parent(ParentTypeKind::Float(_)), _) => TypeBindingComparison::Incompatible, + (_, TypeKind::Parent(ParentTypeKind::Float(_))) => TypeBindingComparison::Incompatible, + // Non-equal leaf types are incomparable + (TypeKind::Leaf(_), TypeKind::Leaf(_)) => TypeBindingComparison::Incompatible, + } + } +} + +// TODO[non-leaf-form]: This abstraction isn't quite right. +// We probably need to reference whether the form we're looking at +// is compatible with some other form. +pub(crate) enum TypeBindingComparison { + Equal, + // e.g. U8Value is a subtype of IntegerValue + RightIsSubtypeOfLeft, + // e.g. dyn IterableValue derives from ArrayValue, but isn't a subtype of it. + RightDerivesFromLeftButIsNotSubtype, + // e.g. U8Value is a subtype of IntegerValue + LeftIsSubtypeOfRight, + // e.g. dyn IterableValue derives from ArrayValue, but isn't a subtype of it. + LeftDerivesFromRightButIsNotSubtype, + Incomparable, + // Indicates that the types are incompatible. + // Such a comparison shouldn't arise between valid references. + Incompatible, } +#[derive(PartialEq, Eq, Clone, Copy)] pub(crate) enum ParentTypeKind { Value(AnyValueTypeKind), Integer(IntegerTypeKind), @@ -96,11 +187,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(), } } @@ -121,14 +212,15 @@ impl ParentTypeKind { } } +#[derive(PartialEq, Eq, Clone, Copy)] pub(crate) enum DynTypeKind { Iterable, } 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, } } @@ -235,21 +327,45 @@ 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 resolved_property = resolver.resolve_type_property(&self.property.to_string()); - match resolved_property { - Some(value) => ownership.map_from_shared(Spanned( - SharedValue::new_from_owned(value.into_any_value()), + let property_name = self.property.to_string(); + if let Some(value) = resolver.resolve_type_property(&property_name) { + return ownership.map_from_shared(Spanned( + AnyValueShared::new_from_owned( + value.into_any_value(), + Some(property_name), + self.span_range(), + ), + self.span_range(), + )); + } + if let Some(method) = resolver.resolve_method(&property_name) { + return ownership.map_from_shared(Spanned( + AnyValueShared::new_from_owned( + method.into_any_value(), + Some(property_name.clone()), + self.span_range(), + ), + self.span_range(), + )); + } + if let Some(function) = resolver.resolve_type_function(&property_name) { + return ownership.map_from_shared(Spanned( + AnyValueShared::new_from_owned( + function.into_any_value(), + Some(property_name.clone()), + self.span_range(), + ), self.span_range(), - )), - None => self.type_err(format!( - "Type '{}' has no property named '{}'", - self.source_type.kind.source_name(), - self.property, - )), + )); } + 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..7dd20b02 100644 --- a/src/expressions/values/any_value.rs +++ b/src/expressions/values/any_value.rs @@ -8,6 +8,8 @@ pub(crate) type AnyValueAnyRef<'a> = AnyValueContent<'a, BeAnyRef>; pub(crate) type AnyValueShared = Shared; pub(crate) type AnyValueMutable = Mutable; pub(crate) type AnyValueAssignee = Assignee; +pub(crate) type AnyValueCopyOnWrite = CopyOnWrite; +pub(crate) type AnyValueLateBound = LateBound; // pub(crate) type AnyValueShared = AnyValueContent<'static, BeShared>; // pub(crate) type AnyValueMutable = AnyValueContent<'static, BeMutable>; // pub(crate) type AnyValueAssignee = AnyValueContent<'static, BeAssignee>; @@ -33,22 +35,24 @@ define_parent_type! { Range => RangeType, Iterator => IteratorType, Parser => ParserType, + Function => FunctionType, + PreinterpretApi => PreinterpretApiType, }, - type_name: "value", - articled_display_name: "any value", + type_name: "any", + articled_value_name: "any", } define_type_features! { impl AnyType, pub(crate) mod value_interface { - pub(crate) mod methods { - fn clone(this: CopyOnWriteValue) -> AnyValue { + methods { + fn clone(this: AnyValueCopyOnWrite) -> AnyValue { 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::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(), @@ -62,46 +66,52 @@ 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) } - 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(), @@ -113,61 +123,61 @@ define_type_features! { // TYPE CHECKING // =============================== - fn is_none(this: SharedValue) -> bool { + fn is_none(this: AnyValueShared) -> bool { this.is_none() } // 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) } } - pub(crate) mod unary_operations { - fn cast_to_string(Spanned(input, span_range): Spanned) -> ExecutionResult { - input.as_ref_value().concat_recursive(&ConcatBehaviour::standard(span_range)) + unary_operations { + [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) } } - 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()) } @@ -249,11 +259,11 @@ 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.", - self.articled_kind() + self.kind().articled_value_name(), )); } Ok(self.clone()) @@ -303,6 +313,9 @@ 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 {}, } } } @@ -312,18 +325,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) + } } } } @@ -333,20 +349,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 @@ -362,7 +379,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) => { @@ -393,7 +410,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)?, @@ -404,13 +421,21 @@ 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(()) } - 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) } @@ -418,7 +443,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 { @@ -429,21 +455,34 @@ 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 { + write!(output, "parser[{:?}]", parser).unwrap(); + } else { + return behaviour + .error_span_range + .type_err("Parsers cannot be output to a string"); + } } - AnyValueContent::Parser(_) => { - return behaviour - .error_span_range - .type_err("Parsers cannot be output to a string"); + AnyValueContent::Function(_) => { + 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(_) @@ -453,58 +492,65 @@ 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); } + AnyValueContent::PreinterpretApi(not_a_value) => match *not_a_value {}, } Ok(()) } } 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 } } @@ -523,15 +569,15 @@ 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 eaa02b03..4d2cbe01 100644 --- a/src/expressions/values/array.rs +++ b/src/expressions/values/array.rs @@ -5,14 +5,14 @@ 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 { + 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 = @@ -59,47 +59,11 @@ impl ArrayValue { }) } - pub(super) fn index_mut( - &mut self, - Spanned(index, span_range): Spanned, - ) -> ExecutionResult<&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, - ) -> ExecutionResult<&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, is_exclusive: bool, - ) -> ExecutionResult { + ) -> FunctionResult { match index { AnyValueContent::Integer(int) => { self.resolve_valid_index_from_integer(Spanned(int, span_range), is_exclusive) @@ -112,7 +76,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 +105,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, ) } } @@ -194,22 +160,27 @@ impl_resolvable_argument_for! { define_type_features! { impl ArrayType, pub(crate) mod array_interface { - pub(crate) mod methods { - fn push(mut this: Mutable, item: AnyValue) -> ExecutionResult<()> { + methods { + fn push(mut this: Mutable, item: AnyValue) -> (){ this.items.push(item); - Ok(()) } - [context] fn to_stream_grouped(this: ArrayValue) -> StreamOutput [ignore_type_assertion!] { + fn pop(mut this: Mutable) -> AnyValue { + this.items.pop().unwrap_or(none()) + } + + [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) + }) } } - pub(crate) mod unary_operations { - [context] fn cast_singleton_to_value(Spanned(mut this, span): Spanned) -> ExecutionResult { + unary_operations { + [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", @@ -218,7 +189,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 @@ -229,11 +200,51 @@ define_type_features! { } } index_access(ArrayValue) { - fn shared(source: &'a ArrayValue, index: Spanned) { - source.index_ref(index) + [ctx] fn shared(source: &'a ArrayValue, Spanned(index, span_range): Spanned) { + match index { + AnyValueContent::Integer(integer) => { + let idx = source.resolve_valid_index_from_integer(Spanned(integer, span_range), false)?; + // SAFETY: ArrayChild correctly describes navigating to an array element + Ok(unsafe { + MappedRef::new( + &source.items[idx], + PathExtension::Child( + ChildSpecifier::ArrayChild(idx), + AnyType::type_kind(), + ), + ctx.output_span_range, + ) + }) + } + AnyValueContent::Range(..) => { + // TODO[slice-support] Temporary until we add slice types - we error here + span_range.ownership_err("Currently, a range-indexed array must be owned. Use `.take()` or `.clone()` before indexing [..]") + } + _ => span_range.type_err("The index must be an integer or a range"), + } } - fn mutable(source: &'a mut ArrayValue, index: Spanned, _auto_create: bool) { - source.index_mut(index) + [ctx] fn mutable(source: &'a mut ArrayValue, Spanned(index, span_range): Spanned, _auto_create: bool) { + match index { + AnyValueContent::Integer(integer) => { + let idx = source.resolve_valid_index_from_integer(Spanned(integer, span_range), false)?; + // SAFETY: ArrayChild correctly describes navigating to an array element + Ok(unsafe { + MappedMut::new( + &mut source.items[idx], + PathExtension::Child( + ChildSpecifier::ArrayChild(idx), + AnyType::type_kind(), + ), + ctx.output_span_range, + ) + }) + } + AnyValueContent::Range(..) => { + // TODO[slice-support] Temporary until we add slice types - we error here + span_range.ownership_err("Currently, a range-indexed array must be owned. Use `.take()` or `.clone()` before indexing [..]") + } + _ => span_range.type_err("The index must be an integer or a range"), + } } fn owned(source: ArrayValue, index: Spanned) { source.into_indexed(index) diff --git a/src/expressions/values/boolean.rs b/src/expressions/values/boolean.rs index adce8579..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: {}, } @@ -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..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: {}, } @@ -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..974c8a50 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>; @@ -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; @@ -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,10 +211,8 @@ define_type_features! { } } } - pub(crate) mod unary_operations { - } - pub(crate) mod binary_operations { - fn add(left: FloatValue, right: Spanned) -> ExecutionResult { + binary_operations { + 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), @@ -222,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), @@ -234,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), @@ -246,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), @@ -258,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), @@ -270,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), @@ -282,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), @@ -290,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), @@ -298,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), @@ -306,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), @@ -314,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 c3b21c33..df31f779 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 { @@ -148,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: {}, } @@ -162,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) @@ -176,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)), @@ -184,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,11 +201,11 @@ 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), - other => context.err($articled_display_name, other), + other => context.err($type_def::ARTICLED_VALUE_NAME, other), } } } @@ -218,10 +214,10 @@ 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($articled_display_name, other), + other => context.err($type_def::ARTICLED_VALUE_NAME, other), } } } @@ -230,10 +226,10 @@ 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($articled_display_name, other), + other => context.err($type_def::ARTICLED_VALUE_NAME, other), } } } @@ -242,10 +238,10 @@ 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($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 360feb4f..a4cf3608 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: {}, } @@ -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; @@ -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 { @@ -193,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") } } @@ -206,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 new file mode 100644 index 00000000..bcaad322 --- /dev/null +++ b/src/expressions/values/function.rs @@ -0,0 +1,130 @@ +use super::*; + +define_leaf_type! { + pub(crate) FunctionType => AnyType(AnyValueContent::Function), + content: FunctionValue, + kind: pub(crate) FunctionValueKind, + type_name: "fn", + articled_value_name: "a function", + dyn_impls: {}, +} + +define_type_features! { + impl FunctionType, + pub(crate) mod function_value_interface {} +} + +#[derive(Clone)] +pub(crate) struct FunctionValue { + pub(crate) disabled_bound_arguments: Vec>, + pub(crate) invokable: InvokableFunction, +} + +#[derive(Clone)] +pub(crate) enum InvokableFunction { + Native(&'static FunctionInterface), + // TODO[closures]: Add closed_variables: Vec to Closure + Closure(ClosureValue), +} + +impl Eq for InvokableFunction {} + +impl PartialEq for InvokableFunction { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (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, + ) -> FunctionResult> { + match self { + InvokableFunction::Native(interface) => interface.invoke(arguments, context), + InvokableFunction::Closure(closure) => closure.invoke(arguments, context), + } + } +} + +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 { + ctx.values_equal() + } else { + ctx.leaf_values_not_equal("left_anon_function", "right_anon_function") + } + } +} + +impl IsValueContent for &'static FunctionInterface { + type Type = FunctionType; + type Form = BeOwned; +} + +impl IntoValueContent<'static> for &'static FunctionInterface { + fn into_content(self) -> Content<'static, Self::Type, Self::Form> { + FunctionValue { + invokable: InvokableFunction::Native(self), + disabled_bound_arguments: vec![], + } + } +} + +impl IsValueContent for ClosureValue { + type Type = FunctionType; + type Form = BeOwned; +} + +impl IntoValueContent<'static> for ClosureValue { + fn into_content(self) -> Content<'static, Self::Type, Self::Form> { + FunctionValue { + invokable: InvokableFunction::Closure(self), + disabled_bound_arguments: vec![], + } + } +} diff --git a/src/expressions/values/integer.rs b/src/expressions/values/integer.rs index 532f267b..f4fa7ca3 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>; @@ -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; @@ -206,12 +206,8 @@ 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 { - [context] fn add(left: Spanned, right: Spanned) -> ExecutionResult { + binary_operations { + [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), @@ -229,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), @@ -251,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), @@ -273,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), @@ -295,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), @@ -317,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)), @@ -339,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)), @@ -361,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)), @@ -383,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), @@ -405,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), @@ -427,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), @@ -449,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), @@ -467,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), @@ -485,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), @@ -503,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), @@ -521,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), @@ -596,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") } } @@ -609,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 ef053838..ed848ce4 100644 --- a/src/expressions/values/integer_subtypes.rs +++ b/src/expressions/values/integer_subtypes.rs @@ -8,11 +8,9 @@ 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> { + 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), @@ -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 { @@ -181,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: {}, } @@ -195,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) @@ -209,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)), @@ -217,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,11 +234,11 @@ 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), - other => context.err($articled_display_name, other), + other => context.err($type_def::ARTICLED_VALUE_NAME, other), } } } @@ -251,10 +247,10 @@ 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($articled_display_name, other), + other => context.err($type_def::ARTICLED_VALUE_NAME, other), } } } @@ -263,10 +259,10 @@ 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($articled_display_name, other), + other => context.err($type_def::ARTICLED_VALUE_NAME, other), } } } @@ -275,10 +271,10 @@ 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($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 0a38b75a..da3f162b 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: {}, } @@ -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,13 +109,13 @@ 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(|| { + 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_display_name() + kind.articled_value_name() )) }) } @@ -124,10 +124,8 @@ impl Spanned { define_type_features! { impl UntypedIntegerType, pub(crate) mod untyped_integer_interface { - pub(crate) mod methods { - } - pub(crate) mod unary_operations { - fn neg(Spanned(value, span): Spanned) -> ExecutionResult { + unary_operations { + fn neg(Spanned(value, span): Spanned) -> FunctionResult { let input = value.into_fallback(); match input.checked_neg() { Some(negated) => Ok(UntypedInteger::from_fallback(negated)), @@ -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 { @@ -253,7 +249,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") } } @@ -266,7 +262,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())) @@ -274,10 +270,7 @@ impl ResolvableOwned for UntypedIntegerFallback { } impl ResolvableOwned for UntypedInteger { - fn resolve_from_value( - value: IntegerValue, - context: ResolutionContext, - ) -> ExecutionResult { + 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/iterable.rs b/src/expressions/values/iterable.rs index 5a87f161..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!( @@ -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; @@ -19,56 +19,59 @@ pub(crate) type IterableAnyRef<'a> = AnyRef<'a, dyn IsIterable>; define_type_features! { impl IterableType, pub(crate) mod iterable_interface { - pub(crate) mod methods { - fn into_iter(this: IterableValue) -> ExecutionResult { + methods { + 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); } Ok(vec) } } - pub(crate) mod unary_operations { - fn cast_into_iterator(this: IterableValue) -> ExecutionResult { + unary_operations { + fn cast_into_iterator(this: IterableValue) -> FunctionResult { 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..81283eba 100644 --- a/src/expressions/values/iterator.rs +++ b/src/expressions/values/iterator.rs @@ -5,46 +5,48 @@ 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 { + 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))) + Self::new(Box::new(iterator)) } - pub(crate) fn new_custom(iterator: Box>) -> Self { - Self::new(IteratorValueInner::Other(iterator)) + /// Returns the inner BoxedIterator box (for creating Map/Filter wrappers). + pub(crate) fn into_inner(self) -> ValueIterator { + self.inner } - pub(crate) fn len(&self, error_span_range: SpanRange) -> ExecutionResult { - let (min, max) = self.size_hint(); - if max == Some(min) { - Ok(min) + 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 { - error_span_range.value_err("Iterator has an inexact length") - } - } - - pub(crate) fn singleton_value(mut self) -> Option { - let first = self.next()?; - if self.next().is_none() { - 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,92 +119,60 @@ impl IteratorValue { &self, output: &mut String, behaviour: &ConcatBehaviour, - ) -> ExecutionResult<()> { - Self::any_iterator_to_string( - self.clone(), - output, - behaviour, - "[]", - "[ ", - "]", - true, - ) - } - - 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); - } + interpreter: &mut Interpreter, + ) -> FunctionResult<()> { + if behaviour.use_debug_literal_syntax { + any_items_to_string( + &mut self.clone(), + output, + behaviour, + "[]", + "[ ", + "]", + true, + interpreter, + ) + } else { + output.push_str("Iterator[?]"); + Ok(()) } - 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) } } @@ -216,129 +186,78 @@ 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), - } - } - ctx.iteration_limit_exceeded(MAX_ITERATIONS) - } -} - -#[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(), + if std::ptr::eq(self, other) { + ctx.values_equal() + } else { + ctx.leaf_values_not_equal(&"Iterator[?]", &"Iterator[?]") } } } -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 { - pub(crate) mod methods { - fn next(mut this: Mutable) -> AnyValue { - match this.next() { - Some(value) => value, - None => ().into_any_value(), + methods { + [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))) } } - pub(crate) mod 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), + unary_operations { + [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"), } } } - 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..421d2e0a 100644 --- a/src/expressions/values/mod.rs +++ b/src/expressions/values/mod.rs @@ -5,6 +5,7 @@ mod character; mod float; mod float_subtypes; mod float_untyped; +mod function; mod integer; mod integer_subtypes; mod integer_untyped; @@ -13,6 +14,7 @@ mod iterator; mod none; mod object; mod parser; +mod preinterpret; mod range; mod stream; mod string; @@ -25,6 +27,7 @@ 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::*; @@ -33,6 +36,7 @@ pub(crate) use iterator::*; 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..2106cebe 100644 --- a/src/expressions/values/none.rs +++ b/src/expressions/values/none.rs @@ -5,17 +5,20 @@ 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: {}, } +pub(crate) const fn none() -> AnyValue { + AnyValue::None(()) +} + impl ResolvableArgumentTarget for () { type ValueType = NoneType; } 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), @@ -23,24 +26,25 @@ impl ResolvableOwned for () { } } -define_optional_object! { - pub(crate) struct SettingsInputs { - iteration_limit: usize => (DEFAULT_ITERATION_LIMIT_STR, "The new iteration limit"), +/// 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 { - 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..e927826f 100644 --- a/src/expressions/values/object.rs +++ b/src/expressions/values/object.rs @@ -5,14 +5,14 @@ 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 { + 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)) } @@ -72,47 +72,30 @@ impl ObjectValue { } } - pub(super) fn index_mut( - &mut self, - index: Spanned, - auto_create: bool, - ) -> ExecutionResult<&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> { - 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) - } - pub(super) fn property_mut( &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(); - 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(static_none_ref()), + } } fn mut_entry( &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, @@ -125,8 +108,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!( + "There is no pre-existing entry with key `{}` available to mutate", + entry.into_key() + )); } } }) @@ -136,7 +121,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 @@ -174,7 +160,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 { @@ -212,7 +198,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) { @@ -272,35 +258,83 @@ impl IntoValueContent<'static> for BTreeMap { define_type_features! { impl ObjectType, pub(crate) mod object_interface { - pub(crate) mod methods { - [context] fn zip(this: ObjectValue) -> ExecutionResult { + methods { + [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) } } - pub(crate) mod unary_operations { - } - pub(crate) mod binary_operations {} 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) @@ -343,7 +377,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() { diff --git a/src/expressions/values/parser.rs b/src/expressions/values/parser.rs index 492c6ad5..5a6f4497 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: {}, } @@ -53,16 +53,16 @@ impl Spanned> { pub(crate) fn parser<'i>( &self, interpreter: &'i mut Interpreter, - ) -> ExecutionResult> { + ) -> FunctionResult> { let Spanned(handle, span) = self; 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) } @@ -70,24 +70,24 @@ impl Spanned> { fn parser<'a>( this: Spanned>, - context: &'a mut MethodCallContext, -) -> ExecutionResult> { + context: &'a mut FunctionCallContext, +) -> FunctionResult> { this.parser(context.interpreter) } define_type_features! { impl ParserType, pub(crate) mod parser_interface { - pub(crate) mod methods { + methods { // 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,81 +182,76 @@ 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)?) } } - pub(crate) mod unary_operations { - } - pub(crate) mod binary_operations {} - interface_items { - } } } @@ -360,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/preinterpret.rs b/src/expressions/values/preinterpret.rs new file mode 100644 index 00000000..bec7c554 --- /dev/null +++ b/src/expressions/values/preinterpret.rs @@ -0,0 +1,28 @@ +use super::*; + +define_leaf_type! { + pub(crate) PreinterpretApiType => AnyType(AnyValueContent::PreinterpretApi), + content: PreinterpretApiValue, + kind: pub(crate) PreinterpretApiKind, + type_name: "preinterpret", + articled_value_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(new_limit.0); + } + + [context] fn set_recursion_limit(new_limit: OptionalSuffix) { + context.interpreter.set_recursion_limit(new_limit.0); + } + } + } +} diff --git a/src/expressions/values/range.rs b/src/expressions/values/range.rs index d20da791..9ef583d8 100644 --- a/src/expressions/values/range.rs +++ b/src/expressions/values/range.rs @@ -7,14 +7,14 @@ 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 { + 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(); @@ -184,7 +201,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..", @@ -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, @@ -398,15 +415,12 @@ impl_resolvable_argument_for! { define_type_features! { impl RangeType, pub(crate) mod range_interface { - pub(crate) mod methods { - } - pub(crate) mod unary_operations { - [context] fn cast_via_iterator(Spanned(this, span): Spanned) -> ExecutionResult { + unary_operations { + [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) } } - pub(crate) mod binary_operations {} interface_items { fn resolve_own_unary_operation(operation: &UnaryOperation) -> Option { Some(match operation { @@ -440,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")?; @@ -455,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()))) @@ -501,9 +511,7 @@ impl IterableRangeOf { } impl ResolvableRange for UntypedInteger { - fn resolve( - definition: IterableRangeOf, - ) -> ExecutionResult>> { + fn resolve(definition: IterableRangeOf) -> FunctionResult { match definition { IterableRangeOf::RangeFromTo { start, dots, end } => { let start = start.into_fallback(); @@ -534,7 +542,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 fc856de3..e4d3dc3e 100644 --- a/src/expressions/values/stream.rs +++ b/src/expressions/values/stream.rs @@ -5,14 +5,14 @@ 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 { + 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()) } } @@ -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() @@ -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,39 +227,42 @@ 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()) } } - pub(crate) mod unary_operations { - [context] fn cast_coerced_to_value(Spanned(this, span): Spanned) -> ExecutionResult { + unary_operations { + [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) } } - pub(crate) mod binary_operations { + binary_operations { fn add(mut lhs: OutputStream, rhs: OutputStream) -> OutputStream { rhs.append_into(&mut lhs); lhs @@ -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() @@ -548,10 +548,13 @@ impl Interpret for ConcatenatedStreamLiteral { string_to_literal(str, self, ident_span)?.into_any_value() } }; - value.as_ref_value().output_to( - Grouping::Flattened, - &mut ToStreamContext::new(interpreter.output(self)?, self.span_range()), - ) + 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 99124508..4be261a4 100644 --- a/src/expressions/values/string.rs +++ b/src/expressions/values/string.rs @@ -5,14 +5,14 @@ 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 { + 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,9 +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)) + error_source + .value_error::(format!("`{}` is not a valid ident: {:?}", str, err)) })?; Ok(ident.with_span(span)) } @@ -57,9 +58,10 @@ 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!("`{}` is not a valid literal: {:?}", str, err)) + error_source + .value_error::(format!("`{}` is not a valid literal: {:?}", str, err)) })?; Ok(literal.with_span(span)) } @@ -67,30 +69,30 @@ pub(crate) fn string_to_literal( define_type_features! { impl StringType, pub(crate) mod string_interface { - pub(crate) mod methods { + methods { // ================== // 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()) } @@ -141,12 +143,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 @@ -228,7 +230,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/expressions/values/unsupported_literal.rs b/src/expressions/values/unsupported_literal.rs index 071a4b8e..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: {}, } @@ -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/extensions/errors_and_spans.rs b/src/extensions/errors_and_spans.rs index f3ce267e..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() } } @@ -457,11 +478,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/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..93d60f59 100644 --- a/src/internal_prelude.rs +++ b/src/internal_prelude.rs @@ -1,11 +1,11 @@ pub(crate) use proc_macro2::{extra::*, *}; pub(crate) use quote::ToTokens; pub(crate) use std::{ - borrow::Borrow, - borrow::Cow, - cell::{Ref, RefCell, RefMut}, + borrow::{Borrow, Cow}, + cell::{RefCell, RefMut}, collections::{BTreeMap, HashMap, HashSet}, fmt::Debug, + fmt::Write, iter, marker::PhantomData, ops::{Deref, DerefMut}, @@ -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/bindings.rs b/src/interpretation/bindings.rs deleted file mode 100644 index a5378fa9..00000000 --- a/src/interpretation/bindings.rs +++ /dev/null @@ -1,678 +0,0 @@ -use super::*; - -use std::borrow::{Borrow, ToOwned}; -use std::cell::*; -use std::rc::Rc; - -pub(super) enum VariableState { - Uninitialized, - Value(Rc>), - Finished, -} - -impl VariableState { - pub(crate) fn define(&mut self, value: AnyValue) { - match self { - content @ VariableState::Uninitialized => { - *content = VariableState::Value(Rc::new(RefCell::new(value))); - } - VariableState::Value(_) => panic!("Cannot define existing variable"), - VariableState::Finished => panic!("Cannot define finished variable"), - } - } - - pub(crate) fn resolve( - &mut self, - variable_span: Span, - is_final: bool, - 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 - // 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 = 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) => { - if matches!( - ownership, - RequestedOwnership::Concrete(ArgumentOwnership::Assignee { .. }) - ) { - return variable_span.control_flow_err("The final usage of a variable cannot be assigned to. You can use `let _ = ..` to discard a value."); - } - return Ok(Spanned( - LateBoundValue::Owned(LateBoundOwnedValue { - owned: ref_cell.into_inner(), - is_from_last_use: true, - }), - span_range, - )); - } - // It's currently referenced, 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, - }, - VariableState::Finished => panic!("{}", FINISHED_ERR), - } - } else { - match self { - VariableState::Uninitialized => panic!("{}", UNITIALIZED_ERR), - VariableState::Value(ref_cell) => Rc::clone(ref_cell), - VariableState::Finished => panic!("{}", FINISHED_ERR), - } - }; - let binding = VariableBinding { - data: value_rc, - variable_span, - }; - let resolved = match ownership { - RequestedOwnership::LateBound => binding.into_late_bound(), - RequestedOwnership::Concrete(ownership) => match ownership { - ArgumentOwnership::Owned => binding.into_transparently_cloned().map(|owned| { - LateBoundValue::Owned(LateBoundOwnedValue { - owned, - is_from_last_use: false, - }) - }), - ArgumentOwnership::Shared => binding - .into_shared() - .map(CopyOnWrite::shared_in_place_of_shared) - .map(LateBoundValue::CopyOnWrite), - ArgumentOwnership::Assignee { .. } => { - binding.into_mut().map(LateBoundValue::Mutable) - } - ArgumentOwnership::Mutable => binding.into_mut().map(LateBoundValue::Mutable), - ArgumentOwnership::CopyOnWrite | ArgumentOwnership::AsIs => binding - .into_shared() - .map(CopyOnWrite::shared_in_place_of_shared) - .map(LateBoundValue::CopyOnWrite), - }, - }; - let late_bound = if let Some(mutation_block_reason) = blocked_from_mutation { - match resolved { - Ok(LateBoundValue::Mutable(mutable)) => { - let reason_not_mutable = variable_span - .syn_error(mutation_block_reason.error_message("mutate this variable")); - Ok(LateBoundValue::Shared(LateBoundSharedValue::new( - mutable.into_shared(), - reason_not_mutable, - ))) - } - x => x, - } - } else { - resolved - }?; - Ok(Spanned(late_bound, span_range)) - } -} - -#[derive(Clone)] -pub(crate) struct VariableBinding { - data: Rc>, - variable_span: Span, -} - -#[allow(unused)] -impl VariableBinding { - /// Gets the cloned expression value - /// This only works if the value can be transparently cloned - pub(crate) fn into_transparently_cloned(self) -> ExecutionResult { - 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 { - MutableValue::new_from_variable(self).map_err(ExecutionInterrupt::ownership_error) - } - - fn into_shared(self) -> ExecutionResult { - SharedValue::new_from_variable(self).map_err(ExecutionInterrupt::ownership_error) - } - - 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()?; - Ok(LateBoundValue::Shared(LateBoundSharedValue::new( - shared, - reason_not_mutable, - ))) - } - } - } -} - -pub(crate) struct LateBoundOwnedValue { - pub(crate) owned: AnyValueOwned, - pub(crate) is_from_last_use: bool, -} - -/// A shared value where mutable access failed for a specific reason -pub(crate) struct LateBoundSharedValue { - pub(crate) shared: SharedValue, - pub(crate) reason_not_mutable: syn::Error, -} - -impl LateBoundSharedValue { - pub(crate) fn new(shared: SharedValue, reason_not_mutable: syn::Error) -> Self { - Self { - shared, - reason_not_mutable, - } - } -} - -/// Universal value type that can resolve to any concrete ownership type. -/// -/// Sometimes, a value can be accessed, but we don't yet know *how* we need to access it. -/// In this case, we attempt to load it with the most powerful access we can have, and convert it later. -/// -/// ## Example of requirement -/// For example, if we have `x[a].method()`, we first need to resolve the type of `x[a]` to know -/// whether the method needs `x[a]` to be a shared reference, mutable reference or an owned value. -/// -/// So instead, we take the most powerful access we can have for `x[a]`, and convert it later. -pub(crate) enum LateBoundValue { - /// An owned value that can be converted to any ownership type - Owned(LateBoundOwnedValue), - /// A copy-on-write value that can be converted to an owned value - CopyOnWrite(CopyOnWriteValue), - /// A mutable reference - Mutable(AnyValueMutable), - /// A shared reference where mutable access failed for a specific reason - Shared(LateBoundSharedValue), -} - -impl Spanned { - pub(crate) fn resolve(self, ownership: ArgumentOwnership) -> ExecutionResult { - ownership.map_from_late_bound(self) - } -} - -impl LateBoundValue { - pub(crate) fn map_any( - self, - map_shared: impl FnOnce(AnyValueShared) -> ExecutionResult, - map_mutable: impl FnOnce(AnyValueMutable) -> ExecutionResult, - map_owned: impl FnOnce(AnyValueOwned) -> ExecutionResult, - ) -> ExecutionResult { - 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) => LateBoundValue::Mutable(map_mutable(mutable)?), - LateBoundValue::Shared(LateBoundSharedValue { - shared, - reason_not_mutable, - }) => LateBoundValue::Shared(LateBoundSharedValue::new( - map_shared(shared)?, - reason_not_mutable, - )), - }) - } - - pub(crate) fn as_value(&self) -> &AnyValue { - match self { - LateBoundValue::Owned(owned) => &owned.owned, - LateBoundValue::CopyOnWrite(cow) => cow.as_ref(), - LateBoundValue::Mutable(mutable) => mutable.as_ref(), - LateBoundValue::Shared(shared) => shared.shared.as_ref(), - } - } -} - -impl Deref for LateBoundValue { - type Target = AnyValue; - - fn deref(&self) -> &Self::Target { - self.as_value() - } -} - -pub(crate) type MutableValue = AnyValueMutable; -pub(crate) type AssigneeValue = AnyValueAssignee; - -/// A binding of a unique (mutable) reference to a value. -/// See [`ArgumentOwnership::Assignee`] for more details. -/// -/// If you need span information, wrap with `Spanned>`. -pub(crate) struct Assignee(pub Mutable); - -impl AssigneeValue { - pub(crate) fn set(&mut self, content: impl IntoAnyValue) { - *self.0 .0 = content.into_any_value(); - } -} - -impl IsValueContent for Assignee { - type Type = AnyType; - type Form = BeAssignee; -} - -impl IntoValueContent<'static> for Assignee { - fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - self.0 - .0 - .replace(|inner, emplacer| inner.as_mut_value().into_assignee(emplacer)) - } -} - -impl Deref for Assignee { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Assignee { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// A simple wrapper for a mutable reference to a value. -/// -/// Can be destructured as: `Mutable(cell): Mutable` -/// -/// If you need span information, wrap with `Spanned>`. -pub(crate) struct Mutable(pub(crate) MutableSubRcRefCell); - -impl Mutable { - pub(crate) fn into_shared(self) -> Shared { - Shared(self.0.into_shared()) - } - - #[allow(unused)] - pub(crate) fn map( - self, - value_map: impl for<'a> FnOnce(&'a mut T) -> &'a mut V, - ) -> Mutable { - Mutable(self.0.map(value_map)) - } - - 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)?)) - } -} - -pub(crate) static MUTABLE_ERROR_MESSAGE: &str = - "The variable cannot be modified as it is already being modified"; - -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(); - } - - /// 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<()> { - self.0 - .0 - .enable() - .map_err(|_| self.1.ownership_error(MUTABLE_ERROR_MESSAGE)) - } -} - -impl Spanned { - pub(crate) fn transparent_clone(&self) -> ExecutionResult { - let value = self.0.as_ref().try_transparent_clone(self.1)?; - Ok(value) - } -} - -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), - )?)) - } -} - -impl IsValueContent for Mutable { - type Type = AnyType; - type Form = BeMutable; -} - -impl IntoValueContent<'static> for Mutable { - 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)) - } - - 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)?)) - } - - pub(crate) fn map(self, value_map: impl FnOnce(&T) -> &V) -> Shared { - Shared(self.0.map(value_map)) - } -} - -pub(crate) static SHARED_ERROR_MESSAGE: &str = - "The variable cannot be read as it is already being modified"; - -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(); - } - - /// 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<()> { - self.0 - .0 - .enable() - .map_err(|_| self.1.ownership_error(SHARED_ERROR_MESSAGE)) - } -} - -impl Spanned { - pub(crate) fn transparent_clone(&self) -> ExecutionResult { - let value = self.0.as_ref().try_transparent_clone(self.1)?; - Ok(value) - } -} - -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()) - } - - 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 { - type Type = AnyType; - type Form = BeShared; -} - -impl IntoValueContent<'static> for Shared { - fn into_content(self) -> Content<'static, Self::Type, Self::Form> { - self.0 - .replace(|inner, emplacer| inner.as_ref_value().into_shared(emplacer)) - } -} - -impl AsRef for Shared { - fn as_ref(&self) -> &T { - &self.0 - } -} - -impl Deref for Shared { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// Copy-on-write value that can be either owned or shared -pub(crate) struct CopyOnWrite { - pub(crate) inner: CopyOnWriteInner, -} - -pub(crate) enum CopyOnWriteInner { - /// An owned value that can be used directly - Owned(Owned), - /// For use when the CopyOnWrite value effectively represents the owned value (post-clone). - /// In this case, returning a Cow is just an optimization and we can always clone infallibly. - SharedWithInfallibleCloning(Shared), - /// For use when the CopyOnWrite value represents a pre-cloned read-only value. - /// A transparent clone may fail in this case at use time. - SharedWithTransparentCloning(Shared), -} - -impl CopyOnWrite { - pub(crate) fn shared_in_place_of_owned(shared: Shared) -> Self { - Self { - inner: CopyOnWriteInner::SharedWithInfallibleCloning(shared), - } - } - - pub(crate) fn shared_in_place_of_shared(shared: Shared) -> Self { - Self { - inner: CopyOnWriteInner::SharedWithTransparentCloning(shared), - } - } - - pub(crate) fn owned(owned: Owned) -> Self { - Self { - inner: CopyOnWriteInner::Owned(owned), - } - } - - #[allow(unused)] - pub(crate) fn extract_owned( - self, - map: impl FnOnce(T::Owned) -> Result, - ) -> Result { - match self.inner { - CopyOnWriteInner::Owned(value) => match map(value) { - Ok(mapped) => Ok(mapped), - Err(other) => Err(Self { - inner: CopyOnWriteInner::Owned(other), - }), - }, - other => Err(Self { inner: other }), - } - } - - pub(crate) fn acts_as_shared_reference(&self) -> bool { - match &self.inner { - CopyOnWriteInner::Owned { .. } => false, - CopyOnWriteInner::SharedWithInfallibleCloning { .. } => false, - CopyOnWriteInner::SharedWithTransparentCloning { .. } => true, - } - } - - pub(crate) fn map( - self, - map_shared: impl FnOnce(Shared) -> ExecutionResult>, - map_owned: impl FnOnce(Owned) -> ExecutionResult>, - ) -> ExecutionResult> { - 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), - } - } -} - -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(_) => {} - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - shared.spanned(self.1).disable() - } - CopyOnWriteInner::SharedWithTransparentCloning(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.inner { - CopyOnWriteInner::Owned(_) => Ok(()), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => { - shared.spanned(self.1).enable() - } - CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - shared.spanned(self.1).enable() - } - } - } -} - -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, - ) -> ExecutionResult { - match self.inner { - CopyOnWriteInner::Owned(owned) => Ok(owned), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => Ok(shared.infallible_clone()), - CopyOnWriteInner::SharedWithTransparentCloning(shared) => { - let value = shared.as_ref().try_transparent_clone(span)?; - Ok(value) - } - } - } - - /// Converts to shared reference - pub(crate) fn into_shared(self) -> SharedValue { - match self.inner { - CopyOnWriteInner::Owned(owned) => SharedValue::new_from_owned(owned), - CopyOnWriteInner::SharedWithInfallibleCloning(shared) => shared, - CopyOnWriteInner::SharedWithTransparentCloning(shared) => shared, - } - } -} - -pub(crate) type CopyOnWriteValue = CopyOnWrite; diff --git a/src/interpretation/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 8416046a..a6ef8239 100644 --- a/src/interpretation/interpreter.rs +++ b/src/interpretation/interpreter.rs @@ -2,7 +2,8 @@ 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)>, output_handler: OutputHandler, @@ -10,17 +11,25 @@ pub(crate) struct Interpreter { } impl Interpreter { - pub(crate) fn new(scope_definitions: ScopeDefinitions) -> Self { - let root_scope_id = scope_definitions.root_scope; + 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(), scope_definitions, scopes: vec![], + call_depth: 0, no_mutation_above: vec![], 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 { + root_frame: root_frame_id, + }, + ) + .expect("The root scope can always be entered"); interpreter } @@ -36,8 +45,23 @@ 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) -> ExecutionResult<()> { + 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_scope_starting_with_revertible_segment( @@ -48,7 +72,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,11 +139,24 @@ impl Interpreter { } } - fn enter_scope_inner(&mut self, id: ScopeId, check_parent: bool) { + fn enter_scope_inner(&mut self, id: ScopeId, scope_kind: ScopeKind) -> ExecutionResult<()> { let new_scope = self.scope_definitions.scopes.get(id); - if check_parent { - assert!(new_scope.parent == Some(self.current_scope_id())); - } + match &scope_kind { + ScopeKind::Root { root_frame } => { + assert!(new_scope.parent.is_none()); + assert_eq!(new_scope.frame, *root_frame); + } + ScopeKind::FunctionBoundary { span, .. } => { + assert!(new_scope.parent.is_none()); + 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())); + } + }; let variables = { let mut map = HashMap::new(); for definition_id in new_scope.definitions.iter() { @@ -127,7 +164,12 @@ impl Interpreter { } map }; - self.scopes.push(RuntimeScope { id, variables }); + self.scopes.push(RuntimeScope { + id, + scope_kind, + variables, + }); + Ok(()) } pub(crate) fn exit_scope(&mut self, scope_id: ScopeId) { @@ -137,7 +179,13 @@ 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 let ScopeKind::FunctionBoundary { .. } = scope.scope_kind { + self.call_depth -= 1; + } } pub(crate) fn catch_control_flow( @@ -163,28 +211,64 @@ impl Interpreter { } } - pub(crate) fn define_variable(&mut self, definition_id: VariableDefinitionId, value: AnyValue) { + pub(crate) fn define_variable( + &mut self, + definition_id: VariableDefinitionId, + 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, VariableContent)> { + 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_content(is_final_reference, is_blocked_from_mutation); + (closure_definition_id, reference) + }) + .collect() } pub(crate) fn resolve( &mut self, variable: &VariableReference, ownership: RequestedOwnership, - ) -> ExecutionResult> { + ) -> FunctionResult> { let reference = self.scope_definitions.references.get(variable.id); let (definition, span, is_final) = ( reference.definition, 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), _ => {} } @@ -192,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>( @@ -208,16 +290,20 @@ 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_recursion_limit(&mut self, limit: usize) { + self.config.recursion_limit = limit; + } + // 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, @@ -238,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 @@ -261,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()) } @@ -291,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) @@ -303,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) @@ -320,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. @@ -343,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. @@ -361,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, @@ -396,15 +493,16 @@ pub(crate) enum AttemptOutcome { struct RuntimeScope { id: ScopeId, + scope_kind: ScopeKind, variables: HashMap, } impl RuntimeScope { - fn define_variable(&mut self, definition_id: VariableDefinitionId, value: AnyValue) { + 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( @@ -414,7 +512,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") @@ -425,36 +523,36 @@ impl RuntimeScope { pub(crate) struct IterationCounter<'a, S: HasSpanRange> { span_source: &'a S, count: usize, - iteration_limit: Option, + iteration_limit: usize, } 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<()> { - 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)); - } + 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)); } Ok(()) } } pub(crate) struct InterpreterConfig { - iteration_limit: Option, + iteration_limit: usize, + recursion_limit: usize, } pub(crate) const DEFAULT_ITERATION_LIMIT: usize = 1000; -pub(crate) const DEFAULT_ITERATION_LIMIT_STR: &str = "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, + recursion_limit: STACK_DEPTH_LIMIT, } } } diff --git a/src/interpretation/mod.rs b/src/interpretation/mod.rs index adb537e7..ce92f88f 100644 --- a/src/interpretation/mod.rs +++ b/src/interpretation/mod.rs @@ -1,5 +1,3 @@ -mod bindings; -mod control_flow_pass; mod input_handler; mod interpret_traits; mod interpreter; @@ -8,14 +6,12 @@ mod output_parse_utilities; mod output_stream; mod parse_template_stream; mod refs; -mod source_parsing; 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::*; -use control_flow_pass::*; pub(crate) use input_handler::*; pub(crate) use interpret_traits::*; pub(crate) use interpreter::*; @@ -24,6 +20,6 @@ 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::*; +pub(crate) use variable_state::*; 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/refs.rs b/src/interpretation/refs.rs index 216638ef..38efaccc 100644 --- a/src/interpretation/refs.rs +++ b/src/interpretation/refs.rs @@ -9,77 +9,135 @@ pub(crate) struct AnyRef<'a, T: ?Sized + 'static> { } impl<'a, T: ?Sized + 'static> AnyRef<'a, T> { + /// Maps this `AnyRef` using a closure that returns a [`MappedRef`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedRef::new`] constructor. #[allow(unused)] - pub(crate) fn map(self, f: impl for<'r> FnOnce(&'r T) -> &'r S) -> AnyRef<'a, S> { + pub(crate) fn map( + self, + f: impl for<'r> FnOnce(&'r T) -> MappedRef<'r, S>, + ) -> AnyRef<'a, S> { match self.inner { - AnyRefInner::Direct(value) => AnyRef { - inner: AnyRefInner::Direct(f(value)), - }, + AnyRefInner::Direct(value) => { + let (value, _path_extension, _span) = f(value).into_parts(); + AnyRef { + inner: AnyRefInner::Direct(value), + } + } AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.map(|x| f(x))), + inner: AnyRefInner::Encapsulated(shared.map(f)), }, } } + /// Maps this `AnyRef` using a closure that returns an optional [`MappedRef`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedRef::new`] constructor. #[allow(unused)] - pub(crate) fn map_optional( + pub(crate) fn map_optional( self, - f: impl for<'r> FnOnce(&'r T) -> Option<&'r S>, + f: impl for<'r> FnOnce(&'r T) -> Option>, ) -> Option> { Some(match self.inner { - AnyRefInner::Direct(value) => AnyRef { - inner: AnyRefInner::Direct(f(value)?), - }, + AnyRefInner::Direct(value) => { + let (value, _path_extension, _span) = f(value)?.into_parts(); + AnyRef { + inner: AnyRefInner::Direct(value), + } + } AnyRefInner::Encapsulated(shared) => AnyRef { - inner: AnyRefInner::Encapsulated(shared.map_optional(f)?), + inner: AnyRefInner::Encapsulated(shared.emplace_map(|input, emplacer| match f( + input, + ) { + Some(mapped) => Some(emplacer.emplace(mapped)), + None => { + let _ = emplacer.revert(); + None + } + })?), }, }) } - pub(crate) fn replace( + pub(crate) fn emplace_map( self, - f: impl for<'e> FnOnce(&'e T, &mut AnyRefEmplacer<'a, 'e, T>) -> O, + f: impl for<'m> FnOnce(&'m T, &mut AnyRefEmplacer<'_, 'a, 'm, T>) -> O, ) -> O { - let copied_ref = self.deref() as *const T; - let mut emplacer = AnyRefEmplacer { - inner: Some(self), - encapsulation_lifetime: std::marker::PhantomData, - }; - f( - // SAFETY: The underlying reference is valid for the lifetime of self - // So we can copy it fine - unsafe { &*copied_ref }, - &mut emplacer, - ) + match self.inner { + AnyRefInner::Direct(direct_ref) => { + let raw_ptr = direct_ref as *const T; + let mut emplacer = AnyRefEmplacer { + inner: AnyRefEmplacerState::Direct(DirectRefEmplacerState { + ptr: Some(raw_ptr), + original_lifetime: PhantomData, + }), + }; + // SAFETY: raw_ptr was derived from a valid &'a T, and the original + // reference was consumed by converting to *const T. + f(unsafe { &*raw_ptr }, &mut emplacer) + } + AnyRefInner::Encapsulated(shared) => shared.emplace_map(|x, shared_emplacer| { + let mut emplacer = AnyRefEmplacer { + inner: AnyRefEmplacerState::Encapsulated(shared_emplacer), + }; + f(x, &mut emplacer) + }), + } } } -pub(crate) struct AnyRefEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { - inner: Option>, - encapsulation_lifetime: std::marker::PhantomData<&'e ()>, +pub(crate) struct AnyRefEmplacer<'x, 'a, 'm: 'x, T: 'static + ?Sized> { + inner: AnyRefEmplacerState<'x, 'a, 'm, T>, +} + +enum AnyRefEmplacerState<'x, 'a, 'm: 'x, T: 'static + ?Sized> { + Direct(DirectRefEmplacerState<'a, T>), + Encapsulated(&'x mut SharedEmplacer<'m, T>), } -impl<'a, 'e: 'a, T: 'static + ?Sized> AnyRefEmplacer<'a, 'e, T> { - pub(crate) fn emplace(&mut self, value: &'e V) -> AnyRef<'a, V> { - unsafe { - // SAFETY: The lifetime 'e is equal to the &'e content argument in replace - // So this guarantees that the returned reference is valid as long as the AnyRef exists - self.emplace_unchecked(value) +struct DirectRefEmplacerState<'a, T: 'static + ?Sized> { + ptr: Option<*const T>, + original_lifetime: PhantomData<&'a T>, +} + +impl<'x, 'a, 'm, T: 'static + ?Sized> AnyRefEmplacer<'x, 'a, 'm, T> { + pub(crate) fn revert(&mut self) -> AnyRef<'a, T> { + match &mut self.inner { + AnyRefEmplacerState::Direct(DirectRefEmplacerState { ptr, .. }) => { + let ptr = ptr.take().expect("Emplacer already consumed"); + AnyRef { + // SAFETY: ptr was derived from a valid &'a T and the original + // reference was consumed when creating the emplacer. + inner: AnyRefInner::Direct(unsafe { &*ptr }), + } + } + AnyRefEmplacerState::Encapsulated(emplacer) => AnyRef { + inner: AnyRefInner::Encapsulated(emplacer.revert()), + }, } } - // SAFETY: - // * The caller must ensure that the value's lifetime is derived from the original content - pub(crate) unsafe fn emplace_unchecked( + /// Emplaces a mapped shared reference, consuming the emplacer's reference tracking. + /// + /// This is safe because all preconditions (correct PathExtension and valid reference + /// derivation) are validated by the [`MappedRef`] constructor. + pub(crate) fn emplace( &mut self, - value: &V, + mapped: MappedRef<'m, V>, ) -> AnyRef<'a, V> { - self.inner - .take() - .expect("You can only emplace to create a new AnyRef value once") - .map(|_| - // SAFETY: As defined in the rustdoc above - unsafe { transmute::<&V, &'static V>(value) }) + match &mut self.inner { + AnyRefEmplacerState::Direct(_) => { + let (value, _, _) = mapped.into_parts(); + // SAFETY: In Direct case, 'm == 'a, so the lifetime of the value is valid for 'a + let value = unsafe { transmute::<&'m V, &'a V>(value) }; + AnyRef { + inner: AnyRefInner::Direct(value), + } + } + AnyRefEmplacerState::Encapsulated(emplacer) => AnyRef { + inner: AnyRefInner::Encapsulated(emplacer.emplace(mapped)), + }, + } } } @@ -110,7 +168,7 @@ impl<'a, T: ?Sized> ToSpannedRef<'a> for &'a T { impl<'a, T: ?Sized> From> for AnyRef<'a, T> { fn from(value: Shared) -> Self { Self { - inner: AnyRefInner::Encapsulated(value.0), + inner: AnyRefInner::Encapsulated(value), } } } @@ -127,7 +185,7 @@ impl ToSpannedRef<'static> for Shared { enum AnyRefInner<'a, T: 'static + ?Sized> { Direct(&'a T), - Encapsulated(SharedSubRcRefCell), + Encapsulated(Shared), } impl<'a, T: 'static + ?Sized> Deref for AnyRef<'a, T> { @@ -156,81 +214,138 @@ impl<'a, T: ?Sized> From<&'a mut T> for AnyMut<'a, T> { } impl<'a, T: ?Sized + 'static> AnyMut<'a, T> { + /// Maps this `AnyMut` using a closure that returns a [`MappedMut`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedMut::new`] constructor. #[allow(unused)] - pub(crate) fn map( + pub(crate) fn map( self, - f: impl for<'r> FnOnce(&'r mut T) -> &'r mut S, + f: impl for<'r> FnOnce(&'r mut T) -> MappedMut<'r, S>, ) -> AnyMut<'a, S> { match self.inner { - AnyMutInner::Direct(value) => AnyMut { - inner: AnyMutInner::Direct(f(value)), - }, + AnyMutInner::Direct(value) => { + let (value, _path_extension, _span) = f(value).into_parts(); + AnyMut { + inner: AnyMutInner::Direct(value), + } + } AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.map(|x| f(x))), + inner: AnyMutInner::Encapsulated(mutable.map(f)), }, } } + /// Maps this `AnyMut` using a closure that returns an optional [`MappedMut`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedMut::new`] constructor. #[allow(unused)] - pub(crate) fn map_optional( + pub(crate) fn map_optional( self, - f: impl for<'r> FnOnce(&'r mut T) -> Option<&'r mut S>, + f: impl for<'r> FnOnce(&'r mut T) -> Option>, ) -> Option> { Some(match self.inner { - AnyMutInner::Direct(value) => AnyMut { - inner: AnyMutInner::Direct(f(value)?), - }, + AnyMutInner::Direct(value) => { + let (value, _path_extension, _span) = f(value)?.into_parts(); + AnyMut { + inner: AnyMutInner::Direct(value), + } + } AnyMutInner::Encapsulated(mutable) => AnyMut { - inner: AnyMutInner::Encapsulated(mutable.map_optional(f)?), + inner: AnyMutInner::Encapsulated(mutable.emplace_map( + |input, emplacer| match f(input) { + Some(mapped) => Some(emplacer.emplace(mapped)), + None => { + let _ = emplacer.revert(); + None + } + }, + )?), }, }) } - pub(crate) fn replace( - mut self, - f: impl for<'e> FnOnce(&'e mut T, &mut AnyMutEmplacer<'a, 'e, T>) -> O, + pub(crate) fn emplace_map( + self, + f: impl for<'m> FnOnce(&'m mut T, &mut AnyMutEmplacer<'_, 'a, 'm, T>) -> O, ) -> O { - let copied_mut = self.deref_mut() as *mut T; - let mut emplacer = AnyMutEmplacer { - inner: Some(self), - encapsulation_lifetime: std::marker::PhantomData, - }; - f( - // SAFETY: We are cloning a mutable reference here, but it is safe because: - // - What it's pointing at still lives, inside emplacer.inner - // - No other "mutable reference" is created except at encapsulation time - unsafe { &mut *copied_mut }, - &mut emplacer, - ) + match self.inner { + AnyMutInner::Direct(direct_mut) => { + // Convert to raw pointer to avoid &mut aliasing: the emplacer + // stores the raw pointer, so no second &mut T exists. + let raw_ptr = direct_mut as *mut T; + let mut emplacer = AnyMutEmplacer { + inner: AnyMutEmplacerState::Direct(DirectMutEmplacerState { + ptr: Some(raw_ptr), + original_lifetime: PhantomData, + }), + }; + // SAFETY: raw_ptr was derived from a valid &'a mut T, and the original + // reference was consumed by converting to *mut T. + f(unsafe { &mut *raw_ptr }, &mut emplacer) + } + AnyMutInner::Encapsulated(mutable) => mutable.emplace_map(|x, mut_emplacer| { + let mut emplacer = AnyMutEmplacer { + inner: AnyMutEmplacerState::Encapsulated(mut_emplacer), + }; + + f(x, &mut emplacer) + }), + } } } -pub(crate) struct AnyMutEmplacer<'a, 'e: 'a, T: 'static + ?Sized> { - inner: Option>, - encapsulation_lifetime: std::marker::PhantomData<&'e ()>, +pub(crate) struct AnyMutEmplacer<'x, 'a, 'm: 'x, T: 'static + ?Sized> { + inner: AnyMutEmplacerState<'x, 'a, 'm, T>, +} + +enum AnyMutEmplacerState<'x, 'a, 'm: 'x, T: 'static + ?Sized> { + Direct(DirectMutEmplacerState<'a, T>), + Encapsulated(&'x mut MutableEmplacer<'m, T>), } -impl<'a, 'e: 'a, T: 'static + ?Sized> AnyMutEmplacer<'a, 'e, T> { - pub(crate) fn emplace(&mut self, value: &'e mut V) -> AnyMut<'a, V> { - unsafe { - // SAFETY: The lifetime 'e is equal to the &'e content argument in replace - // So this guarantees that the returned reference is valid as long as the AnyMut exists - self.emplace_unchecked(value) +struct DirectMutEmplacerState<'a, T: 'static + ?Sized> { + ptr: Option<*mut T>, + original_lifetime: PhantomData<&'a mut T>, +} + +impl<'x, 'a, 'm, T: 'static + ?Sized> AnyMutEmplacer<'x, 'a, 'm, T> { + pub(crate) fn revert(&mut self) -> AnyMut<'a, T> { + match &mut self.inner { + AnyMutEmplacerState::Direct(DirectMutEmplacerState { ptr, .. }) => { + let ptr = ptr.take().expect("Emplacer already consumed"); + AnyMut { + // SAFETY: ptr was derived from a valid &'a mut T and no other + // &mut T currently exists (the one passed to the closure has ended). + inner: AnyMutInner::Direct(unsafe { &mut *ptr }), + } + } + AnyMutEmplacerState::Encapsulated(emplacer) => AnyMut { + inner: AnyMutInner::Encapsulated(emplacer.revert()), + }, } } - // SAFETY: - // * The caller must ensure that the value's lifetime is derived from the original content - pub(crate) unsafe fn emplace_unchecked( + /// Emplaces a mapped mutable reference, consuming the emplacer's reference tracking. + /// + /// This is safe because all preconditions (correct PathExtension and valid reference + /// derivation) are validated by the [`MappedMut`] constructor. + pub(crate) fn emplace( &mut self, - value: &mut V, + mapped: MappedMut<'m, V>, ) -> AnyMut<'a, V> { - self.inner - .take() - .expect("You can only emplace to create a new AnyMut value once") - .map(|_| - // SAFETY: As defined in the rustdoc above - unsafe { transmute::<&mut V, &'static mut V>(value) }) + match &mut self.inner { + AnyMutEmplacerState::Direct(_) => { + let (value, _, _) = mapped.into_parts(); + // SAFETY: In Direct case, 'm == 'a, so the lifetime of the value is valid for 'a + let value = unsafe { transmute::<&'m mut V, &'a mut V>(value) }; + AnyMut { + inner: AnyMutInner::Direct(value), + } + } + AnyMutEmplacerState::Encapsulated(emplacer) => AnyMut { + inner: AnyMutInner::Encapsulated(emplacer.emplace(mapped)), + }, + } } } @@ -251,14 +366,14 @@ impl<'a, T: ?Sized + 'static> IntoAnyMut<'a> for &'a mut T { impl<'a, T: ?Sized> From> for AnyMut<'a, T> { fn from(value: Mutable) -> Self { Self { - inner: AnyMutInner::Encapsulated(value.0), + inner: AnyMutInner::Encapsulated(value), } } } enum AnyMutInner<'a, T: 'static + ?Sized> { Direct(&'a mut T), - Encapsulated(MutableSubRcRefCell), + Encapsulated(Mutable), } impl<'a, T: 'static + ?Sized> Deref for AnyMut<'a, T> { diff --git a/src/interpretation/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 72ee8431..d4004b92 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) } } @@ -66,7 +66,14 @@ 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, + VariableContent::Referenceable(Referenceable::new( + value_source.into_any_value(), + Some(self.ident.to_string()), + self.ident.span_range(), + )), + ); } } @@ -121,20 +128,23 @@ 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)?; - value.as_ref_value().output_to( - grouping, - &mut ToStreamContext::new(interpreter.output(self)?, self.span_range()), - ) + 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() } pub(crate) fn resolve_late_bound( &self, interpreter: &mut Interpreter, - ) -> ExecutionResult> { + ) -> FunctionResult> { interpreter.resolve(self, RequestedOwnership::LateBound) } @@ -142,7 +152,7 @@ impl VariableReference { &self, interpreter: &mut Interpreter, ownership: ArgumentOwnership, - ) -> ExecutionResult { + ) -> FunctionResult { interpreter .resolve(self, RequestedOwnership::Concrete(ownership))? .resolve(ownership) @@ -151,7 +161,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/interpretation/variable_state.rs b/src/interpretation/variable_state.rs new file mode 100644 index 00000000..54f8ee77 --- /dev/null +++ b/src/interpretation/variable_state.rs @@ -0,0 +1,222 @@ +use super::*; + +pub(super) enum VariableState { + Uninitialized, + Value(VariableContent), + Finished, +} + +/// Cheaply clonable, inactive content of a variable. +/// By inactive, we mean that the shared/mutable aliasing invariants are not required to hold. +/// Before use, they have to be activated. This allows more flexibility over how references +/// can be used and held, without compromising safety. +#[derive(Clone)] +pub(crate) enum VariableContent { + // Owned, but possibly with pre-existing references + Referenceable(Referenceable), + Shared(InactiveShared), + Mutable(InactiveMutable), +} + +impl VariableContent { + fn into_owned_as_only_owner(self) -> Result, VariableContent> { + match self { + VariableContent::Referenceable(data) => match data.try_into_inner() { + Ok(owned) => Ok(owned), + Err(original) => Err(VariableContent::Referenceable(original)), + }, + other => Err(other), + } + } +} + +const UNINITIALIZED_ERR: &str = "Cannot resolve uninitialized variable. This shouldn't be possible, because all variables are set on first use."; +const FINISHED_ERR: &str = "Cannot resolve finished variable. This shouldn't be possible, because is_final should be marked correctly. If you see this error, please report a bug to preinterpret on GitHub with a reproduction case."; + +impl VariableState { + pub(crate) fn define(&mut self, value: VariableContent) { + match self { + content @ VariableState::Uninitialized => { + *content = VariableState::Value(value); + } + VariableState::Value(_) => panic!("Cannot define existing variable"), + VariableState::Finished => panic!("Cannot define finished variable"), + } + } + + pub(crate) fn resolve_content( + &mut self, + is_final: bool, + blocked_from_mutation: Option, + ) -> VariableContent { + if is_final && blocked_from_mutation.is_none() { + let content = std::mem::replace(self, VariableState::Finished); + match content { + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), + VariableState::Value(content) => content, + VariableState::Finished => panic!("{}", FINISHED_ERR), + } + } else { + match self { + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), + VariableState::Value(content) => content.clone(), + VariableState::Finished => panic!("{}", FINISHED_ERR), + } + } + } + + pub(crate) fn resolve( + &mut self, + variable_span: Span, + is_final: bool, + ownership: RequestedOwnership, + blocked_from_mutation: Option, + ) -> FunctionResult> { + let span_range = variable_span.span_range(); + + // If blocked from mutation, we technically could allow is_final to work and + // return a fully owned value without observable mutation, + // but it's likely confusingly inconsistent, so it's better to just block it entirely. + let content = if is_final && blocked_from_mutation.is_none() { + let content = std::mem::replace(self, VariableState::Finished); + match content { + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), + VariableState::Value(content) => match content.into_owned_as_only_owner() { + Ok(owned) => { + if matches!( + ownership, + RequestedOwnership::Concrete(ArgumentOwnership::Assignee { .. }) + ) { + return variable_span.control_flow_err("The final usage of a variable cannot be assigned to. You can use `let _ = ..` to discard a value."); + } + return Ok(Spanned( + LateBound::Owned(LateBoundOwned { + owned, + is_from_last_use: true, + }), + span_range, + )); + } + // It's currently referenced elsewhere, proceed with normal late-bound resolution. + // e.g. + // * `let x = %[]; x.assert_eq(x, %[]);` - the final `x` resolves to a shared reference + // * `let x = %[]; x.assert_eq(x + %[], %[]);` - errors because the final `x` is shared but it needs to be owned + // Or, it could be captured somewhere else, e.g. in a closure: + // * `let x = %[]; let f = || x; let y = x;` - the final `x` resolves to a shared reference + Err(content) => content, + }, + VariableState::Finished => panic!("{}", FINISHED_ERR), + } + } else { + match self { + VariableState::Uninitialized => panic!("{}", UNINITIALIZED_ERR), + VariableState::Value(content) => content.clone(), + VariableState::Finished => panic!("{}", FINISHED_ERR), + } + }; + let binding = VariableBinding { + content, + variable_span, + }; + let resolved = match ownership { + RequestedOwnership::LateBound => binding.into_late_bound(), + RequestedOwnership::Concrete(ownership) => match ownership { + ArgumentOwnership::Owned => binding.into_transparently_cloned().map(|owned| { + LateBound::Owned(LateBoundOwned { + owned, + is_from_last_use: false, + }) + }), + ArgumentOwnership::Shared => binding + .into_shared() + .map(CopyOnWrite::shared_in_place_of_shared) + .map(AnyValueLateBound::CopyOnWrite), + ArgumentOwnership::Assignee { .. } => { + binding.into_mut().map(AnyValueLateBound::Mutable) + } + ArgumentOwnership::Mutable => binding.into_mut().map(AnyValueLateBound::Mutable), + ArgumentOwnership::CopyOnWrite | ArgumentOwnership::AsIs => binding + .into_shared() + .map(CopyOnWrite::shared_in_place_of_shared) + .map(AnyValueLateBound::CopyOnWrite), + }, + }; + let late_bound = if let Some(mutation_block_reason) = blocked_from_mutation { + match resolved { + Ok(AnyValueLateBound::Mutable(mutable)) => { + let reason_not_mutable = variable_span + .syn_error(mutation_block_reason.error_message("mutate this variable")); + Ok(LateBound::new_shared( + mutable.into_shared(), + reason_not_mutable, + )) + } + x => x, + } + } else { + resolved + }?; + Ok(Spanned(late_bound, span_range)) + } +} + +#[derive(Clone)] +struct VariableBinding { + content: VariableContent, + variable_span: Span, +} + +impl VariableBinding { + /// Gets the cloned expression value + /// This only works if the value can be transparently cloned + pub(crate) fn into_transparently_cloned(self) -> FunctionResult { + let span_range = self.variable_span.span_range(); + let shared = self.into_shared()?; + let value = shared.as_ref().try_transparent_clone(span_range)?; + Ok(value) + } + + fn into_mut(self) -> FunctionResult { + let span = self.variable_span.span_range(); + match self.content { + VariableContent::Referenceable(referenceable) => referenceable.new_active_mutable(span), + VariableContent::Mutable(inactive) => inactive.activate(span), + VariableContent::Shared(_) => self + .variable_span + .ownership_err(SHARED_TO_MUTABLE_ERROR_MESSAGE), + } + } + + fn into_shared(self) -> FunctionResult { + let span = self.variable_span.span_range(); + match self.content { + VariableContent::Referenceable(referenceable) => referenceable.new_active_shared(span), + VariableContent::Mutable(inactive) => inactive.into_shared().activate(span), + VariableContent::Shared(inactive) => inactive.activate(span), + } + } + + fn into_late_bound(self) -> FunctionResult { + let span = self.variable_span.span_range(); + match self.content { + VariableContent::Referenceable(referenceable) => { + match referenceable.new_active_mutable(span) { + Ok(mutable) => Ok(AnyValueLateBound::Mutable(mutable)), + Err(err) => { + // Mutable failed, try shared + let shared = referenceable.new_active_shared(span)?; + Ok(LateBound::new_shared(shared, err.expect_ownership_error()?)) + } + } + } + VariableContent::Mutable(inactive) => Ok(LateBound::Mutable(inactive.activate(span)?)), + VariableContent::Shared(inactive) => Ok(LateBound::new_shared( + inactive.activate(span)?, + self.variable_span + .syn_error(SHARED_TO_MUTABLE_ERROR_MESSAGE), + )), + } + } +} + +static SHARED_TO_MUTABLE_ERROR_MESSAGE: &str = "Cannot mutate a non-mutable reference"; diff --git a/src/lib.rs b/src/lib.rs index aa43efe6..0278afe6 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::*; @@ -430,7 +431,8 @@ 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()?; let output_stream = interpreter.complete(); @@ -459,9 +461,13 @@ 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(); @@ -582,9 +588,13 @@ 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/arena.rs b/src/misc/arena.rs index cdc172c5..f4794dbc 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(), @@ -81,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/src/misc/dynamic_references/mod.rs b/src/misc/dynamic_references/mod.rs new file mode 100644 index 00000000..c07ac467 --- /dev/null +++ b/src/misc/dynamic_references/mod.rs @@ -0,0 +1,99 @@ +//! ## Overview +//! +//! We wish to define a dynamic reference model which upholds the rules of rust +//! references when those references are active, but which is flexible to the +//! needs of the dynamic preinterpret language. +//! +//! In other words, it should catch real bugs, with clear error messages, but +//! not get in the way of things you might want to do in a dynamic language. +//! +//! ## Model +//! +//! Our model is: +//! - When calling a method, all references in arguments must +//! be in an active mode which upholds the rust reference invariants. +//! - This ensures that Rust methods can be called without UB +//! - We do this for user-defined methods to, for consistency, and to allow +//! sensible interface design which matches expectations. +//! - Inside a user-defined method, we don't really want users caring about +//! what a variable points to, as it adds a strictness which could be +//! frustrating without a helpful compiler pointing the way. So, +//! when variables/references aren't in active use, we relax the invariants +//! to a weaker "InactiveReference" which upholds validity invariants +//! (i.e. a reference can't point to a value which isn't of the correct type) +//! but doesn't maintain mutability-related safety invariants (i.e. a +//! shared reference does not prevent mutation if it is inactive). +//! +//! As some examples, it should: +//! - Allow x.a: &mut AnyValue and x.b: &mut AnyValue to exist as active mutable +//! references at the same time +//! - Prevent x: &mut AnyValue and x.a: &AnyValue from existing as active references +//! at the same time +//! - Allow x: &mut Integer to mutate x whilst a currently unused variable +//! y: &Integer exists in preinterpret, but not as an active rust reference. +//! +//! ## Implementation +//! +//! We define a concept of a [`ReferencePath`] with a partial order, roughly +//! signalling a depth/subtyping relation to do with mutable ownership. +//! It is more strictly defined on the ReferencePath docs. +//! +//! There are three reference kinds, each wraps a `*mut T` for ease, and each +//! has the *validity* invariant that the pointer will always point to memory +//! representing the type `T`: +//! - `InactiveReference` +//! - Represents an inactive shared/mutable, or disabled mutable during a two-phase borrow +//! - "Active" `Shared` +//! - Implements `Deref` +//! - The existence of this grants the following safety-invariants: +//! - T is immutable +//! - "Active" `Mutable` +//! - Implemented `DerefMut` +//! - The existence of this implies the following safety-invariants: +//! - No other reference can mutate T +//! +//! This gives rise to the following rules: +//! (a) Can't create a Mutable from an InactiveReference if there is: +//! (a.1) A strictly deeper reference of any kind; which could end up pointing to invalid memory +//! (a.2) Any equal or shallower active reference; as that could break the other reference's safety invariant +//! (b) Can't create an Shared from an InactiveReference if there is: +//! (b.1) Any equal or deeper ActiveMutable, as they could break our safety invariant +//! (b.2) (NOTE: There can't be a strictly shallower ActiveMutable, as that would contravene a.1) +//! +//! Basically, a.1 and a.2 are our two key invariants, and all our actions must preserve them. +//! +//! We also need to be able to map/deepen references, e.g. map x to x.a or &any to &integer. +//! We _could_ do this from/via/to InactiveReference with pointer mappings, and a rule to check (a.1) +//! against the resultant state. This would allow us to map references with a present active +//! mutable on a different path. But it's painful to write and reason about, and we don't really +//! need it. +//! +//! Instead, we map via Active references, and then deactivate when we stop actively using a reference. +//! +//! Okay, but then -- how do we create `&mut x.a` and `&mut x.b` at the same time? As follows: +//! - Start with InactiveReference(x) +//! - Duplicate InactiveReference(x), activate to MutableReference(x), map to MutableReference(x.a), deactivate to InactiveReference(x.a) +//! - Duplicate InactiveReference(x), activate to MutableReference(x), map to MutableReference(x.b), deactivate to InactiveReference(x.b) +//! - Activate both into Mutable references +//! +//! ## Previous Model +//! +//! Was based off an `Rc>` which we could use to create Active/Mutable references, and deactivate them. +//! However, it didn't respect (a.1) and it didn't permit mutiple mutable references on different paths. + +mod mutable_reference; +mod reference_core; +mod referenceable; +mod shared_reference; + +// Prelude for everything under dynamic_references +use crate::internal_prelude::*; +use reference_core::*; +use slotmap::{new_key_type, SlotMap}; +use std::cell::UnsafeCell; +use std::mem::ManuallyDrop; +use std::ptr::NonNull; + +pub(crate) use mutable_reference::*; +pub(crate) use referenceable::*; +pub(crate) use shared_reference::*; diff --git a/src/misc/dynamic_references/mutable_reference.rs b/src/misc/dynamic_references/mutable_reference.rs new file mode 100644 index 00000000..68af8048 --- /dev/null +++ b/src/misc/dynamic_references/mutable_reference.rs @@ -0,0 +1,211 @@ +use std::mem::transmute; + +use super::*; + +/// Represents an active `&mut T` reference, to something derived from a Referenceable root. +pub(crate) struct Mutable(pub(super) ReferenceCore); + +impl Mutable { + pub(crate) fn deactivate(self) -> InactiveMutable { + self.0.core.data_mut().deactivate_reference(self.0.id); + InactiveMutable(self.0) + } + + /// A powerful map method which lets you place the resultant mapped reference inside + /// a structure arbitrarily. + pub(crate) fn emplace_map( + mut self, + f: impl for<'a> FnOnce(&'a mut T, &mut MutableEmplacer<'a, T>) -> O, + ) -> O { + // SAFETY: The validity + safety invariants are upheld by `ReferenceableCore` + // ... assuming this id is marked as a mutable reference for the duration. + // The emplacer ensures that this is upheld whilst it is alive; via either: + // - Delegating to the created MutableReference if it is emplaced + // - Surviving until Drop at the end of this method if it is not emplaced + let copied_mut = unsafe { self.0.pointer.as_mut() }; + let mut emplacer = MutableEmplacer(self.0.into_emplacer()); + f(copied_mut, &mut emplacer) + } + + /// Maps this mutable reference using a closure that returns a [`MappedMut`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedMut::new`] constructor, + /// making this method itself safe. + pub(crate) fn map( + self, + f: impl for<'a> FnOnce(&'a mut T) -> MappedMut<'a, V>, + ) -> Mutable { + self.emplace_map(move |input, emplacer| emplacer.emplace(f(input))) + } + + /// Fallible version of [`map`](Self::map) that returns the original reference on error. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedMut::new`] constructor. + pub(crate) fn try_map( + self, + f: impl for<'a> FnOnce(&'a mut T) -> Result, E>, + ) -> Result, (E, Mutable)> { + self.emplace_map(|input, emplacer| match f(input) { + Ok(mapped) => Ok(emplacer.emplace(mapped)), + Err(e) => Err((e, emplacer.revert())), + }) + } +} + +impl Mutable { + /// Creates a new MutableReference from an owned value, wrapping it in a Referenceable. + pub(crate) fn new_from_owned( + value: AnyValue, + root_name: Option, + span_range: SpanRange, + ) -> Self { + let referenceable = Referenceable::new(value, root_name, span_range); + referenceable + .new_active_mutable(span_range) + .expect("Freshly created referenceable must be borrowable as mutable") + } +} + +impl Mutable { + pub(crate) fn into_shared(self) -> Shared { + self.0.core.data_mut().make_shared(self.0.id); + Shared(self.0) + } +} + +impl Deref for Mutable { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: See the rustdoc on dynamic_references/mod.rs for full details. + // To summarize: + // - Provided by the safety invariants of `ReferenceCore` and `ReferenceableCore`. + // - The pointer is guaranteed to be valid and properly aligned for the duration of the reference's lifetime. + // - The reference kind checks ensure that it is not mutably aliased while active. + unsafe { self.0.pointer.as_ref() } + } +} + +impl AsRef for Mutable { + fn as_ref(&self) -> &T { + self + } +} + +impl AsMut for Mutable { + fn as_mut(&mut self) -> &mut T { + self + } +} + +impl DerefMut for Mutable { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: See the rustdoc on dynamic_references/mod.rs for full details. + // To summarize: + // - Provided by the safety invariants of `ReferenceCore` and `ReferenceableCore`. + // - The pointer is guaranteed to be valid and properly aligned for the duration of the reference's lifetime. + // - The reference kind checks ensure that it is not aliased while active. + unsafe { self.0.pointer.as_mut() } + } +} + +/// Represents an inactive but *valid* mutable reference. +/// +/// It can't currently deref into a `&mut T`, but can after being activated, when +/// the aliasing rules get checked and enforced. +pub(crate) struct InactiveMutable(pub(super) ReferenceCore); + +impl Clone for InactiveMutable { + fn clone(&self) -> Self { + InactiveMutable(self.0.clone()) + } +} + +impl InactiveMutable { + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { + self.0 + .core + .data_mut() + .activate_mutable_reference(self.0.id, span)?; + Ok(Mutable(self.0)) + } + + pub(crate) fn into_shared(self) -> InactiveShared { + // As an inactive reference, we are free to map between them + // ... we could even enable the other way around, but that'd likely allow breaking + // application invariants which we want to respect. + self.0.core.data_mut().make_shared(self.0.id); + InactiveShared(self.0) + } +} + +/// A mapped mutable reference bundled with its [`PathExtension`] and span. +/// +/// Constructing this is unsafe because the caller must ensure the +/// [`PathExtension`] correctly describes the relationship between the +/// source and mapped reference. +pub(crate) struct MappedMut<'a, V: ?Sized> { + value: &'a mut V, + path_extension: PathExtension, + span: Option, +} + +impl<'a, V: ?Sized> MappedMut<'a, V> { + /// SAFETY: The caller must ensure that the PathExtension correctly describes + /// the navigation from the source reference to this mapped reference. + /// An overly-specific PathExtension may cause safety issues. + pub(crate) unsafe fn new( + value: &'a mut V, + path_extension: PathExtension, + span: SpanRange, + ) -> Self { + Self { + value, + path_extension, + span: Some(span), + } + } + + /// SAFETY: In addition to the safety requirements of [`new`](Self::new), the caller + /// must ensure that the reference lifetime is valid for the target lifetime `'a`. + /// This is needed when the compiler cannot prove the lifetime relationship + /// (e.g. in LeafMapper implementations where `'l` and `'e` cannot be unified). + pub(crate) unsafe fn new_unchecked<'any>( + value: &'any mut V, + path_extension: PathExtension, + span: Option, + ) -> Self { + Self { + value: unsafe { transmute::<&'any mut V, &'a mut V>(value) }, + path_extension, + span, + } + } + + pub(crate) fn into_parts(self) -> (&'a mut V, PathExtension, Option) { + (self.value, self.path_extension, self.span) + } +} + +pub(crate) struct MutableEmplacer<'a, T: ?Sized>(EmplacerCore<'a, T>); + +impl<'a, T: ?Sized> MutableEmplacer<'a, T> { + pub(crate) fn revert(&mut self) -> Mutable { + Mutable(self.0.revert()) + } + + /// Emplaces a mapped mutable reference, consuming the emplacer's reference tracking. + /// + /// This is safe because all preconditions (correct PathExtension and valid reference + /// derivation) are validated by the [`MappedMut`] constructor. + pub(crate) fn emplace<'e: 'a, V: 'static + ?Sized>( + &mut self, + mapped: MappedMut<'e, V>, + ) -> Mutable { + let (value, path_extension, span) = mapped.into_parts(); + // SAFETY: The pointer is from a valid reference (guaranteed by MappedMut constructor), + // and the PathExtension was validated by the MappedMut constructor. + let pointer = unsafe { NonNull::new_unchecked(value as *mut V) }; + unsafe { Mutable(self.0.emplace_unchecked(pointer, path_extension, span)) } + } +} diff --git a/src/misc/dynamic_references/reference_core.rs b/src/misc/dynamic_references/reference_core.rs new file mode 100644 index 00000000..ed30dea9 --- /dev/null +++ b/src/misc/dynamic_references/reference_core.rs @@ -0,0 +1,125 @@ +use super::*; + +pub(super) struct ReferenceCore { + pub(super) pointer: NonNull, + pub(super) id: LocalReferenceId, + pub(super) core: Rc>, +} + +impl ReferenceCore { + pub(super) fn new_root( + core: Rc>, + reference_kind: ReferenceKind, + ) -> Self { + let new_id = { + let mut data_mut = core.data_mut(); + let creation_span = data_mut.root_span(); + data_mut.new_reference(TrackedReference { + path: ReferencePath::leaf(AnyType::type_kind()), + reference_kind, + creation_span, + }) + }; + Self { + pointer: core.root(), + id: new_id, + core, + } + } +} + +impl ReferenceCore { + pub(super) fn into_emplacer<'e>(self) -> EmplacerCore<'e, T> { + // NOTE: The clean-up duty is delegated to the emplacer + let reference = ManuallyDrop::new(self); + let manual_drop = ManualDropReferenceCore { + pointer: reference.pointer, + id: reference.id, + // SAFETY: read's invariants are upheld, and we are manually dropping the reference, + // so we won't run the drop implementation that would mess with the reference count. + core: unsafe { std::ptr::read(&reference.core) }, + }; + EmplacerCore { + inner: Some(manual_drop), + encapsulation_lifetime: std::marker::PhantomData, + } + } +} + +impl Clone for ReferenceCore { + fn clone(&self) -> Self { + let mut data = self.core.data_mut(); + let copied_data = data.for_reference(self.id).clone(); + if copied_data.reference_kind == ReferenceKind::ActiveMutable { + panic!("Cannot clone an active mutable reference"); + } + Self { + pointer: self.pointer, + id: data.new_reference(copied_data), + core: Rc::clone(&self.core), + } + } +} + +impl Drop for ReferenceCore { + fn drop(&mut self) { + self.core.data_mut().drop_reference(self.id); + } +} + +/// Essentially the same as a [ReferenceCore], but doesn't have the drop implementation. +/// Must be a temporary, whose lifetime exists only as long as the pointer is valid in the given context. +pub(super) struct ManualDropReferenceCore { + pub(super) pointer: NonNull, + pub(super) id: LocalReferenceId, + pub(super) core: Rc>, +} + +pub(super) struct EmplacerCore<'e, T: ?Sized> { + inner: Option>, + encapsulation_lifetime: std::marker::PhantomData<&'e ()>, +} + +impl<'e, T: ?Sized> EmplacerCore<'e, T> { + pub(crate) fn revert(&mut self) -> ReferenceCore { + let core = self.take(); + ReferenceCore { + pointer: core.pointer, + id: core.id, + core: core.core, + } + } + + /// SAFETY: + /// - The caller must ensure that the pointer is derived from the original content, + /// or more specifically, it lives at least as long as that content + /// - The caller must ensure that the PathExtension is correct + pub(crate) unsafe fn emplace_unchecked( + &mut self, + pointer: NonNull, + path_extension: PathExtension, + new_span: Option, + ) -> ReferenceCore { + let emplacer_core = self.take(); + let id = emplacer_core.id; + let core = emplacer_core.core; + core.data_mut() + .derive_reference(id, path_extension, new_span); + ReferenceCore { pointer, id, core } + } + + fn take(&mut self) -> ManualDropReferenceCore { + self.inner + .take() + .expect("You may only emplace or revert once from an emplacer") + } +} + +impl<'e, T: ?Sized> Drop for EmplacerCore<'e, T> { + fn drop(&mut self) { + if let Some(core) = self.inner.take() { + // If the emplacer is still around, we need to manually drop it + core.core.data_mut().drop_reference(core.id); + }; + } +} diff --git a/src/misc/dynamic_references/referenceable.rs b/src/misc/dynamic_references/referenceable.rs new file mode 100644 index 00000000..d22b4f0c --- /dev/null +++ b/src/misc/dynamic_references/referenceable.rs @@ -0,0 +1,546 @@ +use super::*; + +#[derive(Clone)] +pub(crate) struct Referenceable { + core: Rc>, +} + +impl Referenceable { + pub(crate) fn new(root: AnyValue, root_name: Option, root_span: SpanRange) -> Self { + Self { + core: Rc::new(ReferenceableCore::new(root, root_name, root_span)), + } + } + + pub(crate) fn new_inactive_shared(&self) -> InactiveShared { + InactiveShared(ReferenceCore::new_root( + self.core.clone(), + ReferenceKind::InactiveShared, + )) + } + + pub(crate) fn new_inactive_mutable(&self) -> InactiveMutable { + InactiveMutable(ReferenceCore::new_root( + self.core.clone(), + ReferenceKind::InactiveMutable, + )) + } + + pub(crate) fn new_active_shared(&self, span: SpanRange) -> FunctionResult> { + self.new_inactive_shared().activate(span) + } + + pub(crate) fn new_active_mutable(&self, span: SpanRange) -> FunctionResult> { + self.new_inactive_mutable().activate(span) + } + + /// Attempts to unwrap the inner value if this is the sole owner (no outstanding references). + pub(crate) fn try_into_inner(self) -> Result { + match Rc::try_unwrap(self.core) { + Ok(core) => Ok(core.into_inner()), + Err(core) => Err(Self { core }), + } + } +} + +pub(super) struct ReferenceableCore { + // Guaranteed not-null + root: UnsafeCell, + data: RefCell, +} + +impl ReferenceableCore { + pub(super) fn new(root: T, root_name: Option, root_span: SpanRange) -> Self { + Self { + root: UnsafeCell::new(root), + data: RefCell::new(ReferenceableData { + root_name, + root_span, + arena: SlotMap::with_key(), + }), + } + } + + pub(super) fn root(&self) -> NonNull { + unsafe { + // SAFETY: This is guaranteed non-null + NonNull::new_unchecked(self.root.get()) + } + } + + pub(super) fn data_mut(&self) -> RefMut<'_, ReferenceableData> { + self.data.borrow_mut() + } + + pub(super) fn into_inner(self) -> T { + self.root.into_inner() + } +} + +new_key_type! { + pub(crate) struct LocalReferenceId; +} + +pub(super) struct ReferenceableData { + // Could be in Referencable Core, but having it here makes the error message API easier + root_name: Option, + // Could be in Referencable Core, but having it here makes the error message API easier + root_span: SpanRange, + arena: SlotMap, +} + +impl ReferenceableData { + pub(super) fn root_span(&self) -> SpanRange { + self.root_span + } + + pub(super) fn new_reference(&mut self, data: TrackedReference) -> LocalReferenceId { + self.arena.insert(data) + } + + pub(super) fn for_reference(&self, id: LocalReferenceId) -> &TrackedReference { + self.arena.get(id).expect("reference id not found in map") + } + + fn for_reference_mut(&mut self, id: LocalReferenceId) -> &mut TrackedReference { + self.arena + .get_mut(id) + .expect("reference id not found in map") + } + + pub(super) fn drop_reference(&mut self, id: LocalReferenceId) { + self.arena.remove(id); + } + + pub(super) fn deactivate_reference(&mut self, id: LocalReferenceId) { + let data = self.for_reference_mut(id); + data.reference_kind = match data.reference_kind { + ReferenceKind::ActiveMutable => ReferenceKind::InactiveMutable, + ReferenceKind::ActiveShared => ReferenceKind::InactiveShared, + _ => panic!("cannot deactivate an inactive reference"), + } + } + + pub(super) fn make_shared(&mut self, id: LocalReferenceId) { + let data = self.for_reference_mut(id); + data.reference_kind = match data.reference_kind { + ReferenceKind::InactiveMutable | ReferenceKind::InactiveShared => { + ReferenceKind::InactiveShared + } + ReferenceKind::ActiveMutable | ReferenceKind::ActiveShared => { + ReferenceKind::ActiveShared + } + } + } + + pub(super) fn activate_mutable_reference( + &mut self, + id: LocalReferenceId, + span: SpanRange, + ) -> FunctionResult<()> { + self.for_reference_mut(id).creation_span = span; + let data = self.for_reference(id); + // Perform checks as per the module doc on `dynamic_references` + for (other_id, other_data) in self.arena.iter() { + if other_id != id { + let error_reason = data + .path + .compare(&other_data.path) + .error_comparing_mutable_with_other(other_data.reference_kind.is_active()); + let error_reason = match error_reason { + Some(reason) => reason, + None => continue, + }; + let mut error_message = String::new(); + let _ = write!(error_message, "Cannot create a mutable reference because it clashes with an existing reference:"); + let _ = write!(error_message, "\nThis reference : "); + let _ = + self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveMutable)); + let _ = write!(error_message, "\nOther reference: "); + let _ = self.display_path(&mut error_message, other_id, None); + let _ = write!(error_message, "\nReason : {}", error_reason); + return data.creation_span.ownership_err(error_message); + } + } + + let data = self.for_reference_mut(id); + data.reference_kind = match data.reference_kind { + ReferenceKind::InactiveMutable => ReferenceKind::ActiveMutable, + ReferenceKind::InactiveShared => { + panic!("cannot mut-activate an inactive shared reference") + } + _ => panic!("cannot mut-activate an active reference"), + }; + + Ok(()) + } + + pub(super) fn activate_shared_reference( + &mut self, + id: LocalReferenceId, + span: SpanRange, + ) -> FunctionResult<()> { + self.for_reference_mut(id).creation_span = span; + let data = self.for_reference(id); + // Perform checks as per the module doc on `dynamic_references` + for (other_id, other_data) in self.arena.iter() { + if other_id != id && other_data.reference_kind == ReferenceKind::ActiveMutable { + let error_reason = other_data + .path + .compare(&data.path) + .error_comparing_mutable_with_other(true); + let error_reason = match error_reason { + Some(reason) => reason, + None => continue, + }; + let mut error_message = String::new(); + let _ = write!(error_message, "Cannot create a shared reference because it clashes with an existing mutable reference:"); + let _ = write!(error_message, "\nThis reference : "); + let _ = + self.display_path(&mut error_message, id, Some(ReferenceKind::ActiveShared)); + let _ = write!(error_message, "\nOther reference: "); + let _ = self.display_path(&mut error_message, other_id, None); + let _ = write!(error_message, "\nReason : {}", error_reason); + return data.creation_span.ownership_err(error_message); + } + } + let data = self.for_reference_mut(id); + data.reference_kind = match data.reference_kind { + ReferenceKind::InactiveShared | ReferenceKind::InactiveMutable => { + ReferenceKind::ActiveShared + } + _ => panic!("cannot shared-activate an active reference"), + }; + Ok(()) + } + + pub(super) fn derive_reference( + &mut self, + id: LocalReferenceId, + path_extension: PathExtension, + new_span: Option, + ) { + let data = self.for_reference_mut(id); + if let Some(span) = new_span { + data.creation_span = span; + } + let last_path_part = data.path.parts.last_mut().expect("path is non-empty"); + match (last_path_part, path_extension) { + (last_path_part, PathExtension::Child(specifier, child_bound_as)) => { + let parent_type = specifier.parent_type_kind(); + match last_path_part { + PathPart::Value { bound_as } => { + if !bound_as.is_narrowing_to(&parent_type) { + panic!( + "Invalid path extension: cannot derive {} from {}", + parent_type.source_name(), + bound_as.source_name() + ); + } + } + PathPart::Child(_) => { + panic!("Invalid path extension: paths are expected to end in a value") + } + } + *last_path_part = PathPart::Child(specifier); + data.path.parts.push(PathPart::Value { + bound_as: child_bound_as, + }); + } + (PathPart::Value { bound_as }, PathExtension::TypeNarrowing(new_bound_as)) => { + if bound_as.is_narrowing_to(&new_bound_as) { + *bound_as = new_bound_as; + } else { + panic!( + "Invalid path extension: cannot narrow to {} from {}", + new_bound_as.source_name(), + bound_as.source_name() + ); + } + } + (PathPart::Child(_), _) => { + panic!("Invalid path extension: paths are expected to end in a value"); + } + } + } + + fn display_path( + &self, + mut f: impl std::fmt::Write, + id: LocalReferenceId, + override_kind: Option, + ) -> std::fmt::Result { + let data = self.for_reference(id); + let kind = override_kind.unwrap_or(data.reference_kind); + match kind { + // These are the same width to allow alignment in the error message + ReferenceKind::InactiveShared => f.write_str("[inactive] &")?, + ReferenceKind::InactiveMutable => f.write_str("[inactive] &mut ")?, + ReferenceKind::ActiveShared => f.write_str("[*active*] &")?, + ReferenceKind::ActiveMutable => f.write_str("[*active*] &mut ")?, + } + match self.root_name.as_ref() { + Some(name) => f.write_str(name)?, + None => f.write_str("[root]")?, + } + for part in data.path.parts.iter() { + match part { + PathPart::Value { bound_as } => { + f.write_str(&format!(" (of type {})", bound_as.source_name()))?; + } + PathPart::Child(child) => match child { + ChildSpecifier::ArrayChild(i) => { + f.write_char('[')?; + write!(f, "{}", i)?; + f.write_char(']')?; + } + ChildSpecifier::ObjectChild(key) => { + if syn::parse_str::(key).is_ok() { + f.write_char('.')?; + f.write_str(key)?; + } else { + f.write_char('[')?; + write!(f, "{:?}", key)?; + f.write_char(']')?; + } + } + }, + } + } + Ok(()) + } +} + +#[derive(Clone)] +pub(super) struct TrackedReference { + pub(super) path: ReferencePath, + pub(super) reference_kind: ReferenceKind, + /// Storing this span isn't strictly necessary for now... + /// But it's added for a future world where the diagnostic API is stabilized and we can add a diagnostic onto the clashing reference. + pub(super) creation_span: SpanRange, +} + +#[derive(PartialEq, Eq, Copy, Clone)] +pub(super) enum ReferenceKind { + InactiveShared, + InactiveMutable, + ActiveShared, + ActiveMutable, +} + +impl ReferenceKind { + fn is_active(&self) -> bool { + match self { + ReferenceKind::InactiveShared | ReferenceKind::InactiveMutable => false, + ReferenceKind::ActiveShared | ReferenceKind::ActiveMutable => true, + } + } +} + +/// ## Partial Order +/// +/// Has a partial order defined which says all of the following: +/// - P1 and P2 are incomparable if they can exist as distinct mutable reference +/// - P1 < P2 if P2 is "deeper" than P1, i.e. that: +/// - A mutation of P1 could invalidate a reference to P2 so must be banned +/// - P1 could observe a mutation of P2, but would not invalidate it +/// - P1 == P2 if they are equivalent to the same reference, i.e. that: +/// - A mutation of P1 is possible without invalidating a reference to P2 +/// - P1 could observe a mutation of P2 (and vice versa) +/// +/// If any of these assumptions are wrong, we'll need to revisit this. +#[derive(PartialEq, Eq, Clone)] +pub(super) struct ReferencePath { + parts: Vec, +} + +enum PathComparison { + /// The paths have diverged in a manner which permits mutual mutability + /// e.g. left = x.a and right = x.b + Divergent, + /// The paths parts overlap in a manner which forbids mutual mutability, + /// but they are not identical or in an ancestor/descendant relationship. + /// e.g. left = x[0..10], right = x[5..15] + Overlapping, + /// The right path is a descendant of the left path. + /// e.g. right = left.x or right = left[0]["key"] + RightIsDescendant, + /// The left path is a descendant of the right path. + /// e.g. left = right.x or left = right[0]["key"] + LeftIsDescendant, + /// The left and right path refer to the same leaf value. + /// But they may be in different forms. e.g. left = &any and right = &integer + ReferencesEqual(TypeBindingComparison), + /// Represents an impossible comparison which indicates a broken invariant + /// (e.g. left: &integer and right: &string) + Incompatible, +} + +impl PathComparison { + fn error_comparing_mutable_with_other(self, other_is_active: bool) -> Option<&'static str> { + // We are looking to start actively mutating a reference (the "Left" reference), + // with a pre-existing other reference around (the "Right" reference). + // The method protects against the various ways this may break invariants of the Right: + // - If the other reference is active, it must not overlap with any mutable reference + // (i.e. the aliasing rules must be respected) + // - If the other reference is inactive, observable mutation may happen, but + // Right must remain pointing at valid memory for its type + Some(match self { + PathComparison::Divergent => return None, + PathComparison::Overlapping => { + "mutation may invalidate the other overlapping reference" + } + PathComparison::RightIsDescendant => { + "mutation may invalidate the other descendant reference" + } + PathComparison::ReferencesEqual(TypeBindingComparison::RightIsSubtypeOfLeft) + | PathComparison::ReferencesEqual( + TypeBindingComparison::RightDerivesFromLeftButIsNotSubtype, + ) + | PathComparison::ReferencesEqual( + TypeBindingComparison::LeftDerivesFromRightButIsNotSubtype, + ) + | PathComparison::ReferencesEqual(TypeBindingComparison::Incomparable) => { + "mutation may invalidate the other reference by setting it to an incompatible type" + } + // Mutable reference is a descendant of the other reference + PathComparison::ReferencesEqual(TypeBindingComparison::Equal) + | PathComparison::ReferencesEqual(TypeBindingComparison::LeftIsSubtypeOfRight) + | PathComparison::LeftIsDescendant => { + if other_is_active { + "mutation may be observed from the other active reference, which breaks aliasing rules" + } else { + return None; + } + } + PathComparison::ReferencesEqual(TypeBindingComparison::Incompatible) => { + panic!("Unexpected incompatible type comparison. This indicates a bug in preinterpret.") + } + PathComparison::Incompatible => { + panic!("Unexpected incompatible reference comparison. This indicates a bug in preinterpret.") + } + }) + } +} + +impl ReferencePath { + pub(super) fn leaf(bound_as: TypeKind) -> Self { + Self { + parts: vec![PathPart::Value { bound_as }], + } + } + + fn compare(&self, other: &Self) -> PathComparison { + for (own, other) in self.parts.iter().zip(&other.parts) { + return match own.compare(other) { + PathPartComparison::Divergent => PathComparison::Divergent, + PathPartComparison::IdenticalChildReference => continue, + PathPartComparison::OverlappingChildReference => PathComparison::Overlapping, + PathPartComparison::RightIsDescendant => PathComparison::RightIsDescendant, + PathPartComparison::LeftIsDescendant => PathComparison::LeftIsDescendant, + PathPartComparison::ReferencesEqual(inner) => { + PathComparison::ReferencesEqual(inner) + } + PathPartComparison::Incompatible => PathComparison::Incompatible, + }; + } + unreachable!("BUG: PathParts should be [Child* Value] and so can't end with a comparison of PathPartComparison::IdenticalDeeperReference") + } +} + +pub(crate) enum PathExtension { + /// Extends the path with a child reference (e.g. .x or [0]) + Child(ChildSpecifier, TypeKind), + /// Extends the path with a value of a certain type + TypeNarrowing(TypeKind), +} + +#[derive(PartialEq, Eq, Clone)] +enum PathPart { + Value { bound_as: TypeKind }, + Child(ChildSpecifier), +} + +#[derive(PartialEq, Eq, Clone)] +pub(crate) enum ChildSpecifier { + ArrayChild(usize), + ObjectChild(String), +} + +impl ChildSpecifier { + fn parent_type_kind(&self) -> TypeKind { + match self { + ChildSpecifier::ArrayChild(_) => ArrayType::type_kind(), + ChildSpecifier::ObjectChild(_) => ObjectType::type_kind(), + } + } +} + +enum PathPartComparison { + /// The paths have diverged in a manner which permits mutual mutability + /// e.g. left = x.a and right = x.b + Divergent, + /// The path parts match. + /// e.g. left = root.x.?? and right = root.x.?? + IdenticalChildReference, + /// The paths parts overlap in a manner which forbids mutual mutability, + /// but they are not identical or in an ancestor/descendant relationship. + /// e.g. left = x[0..10], right = x[5..15] + #[allow(unused)] // Kept for future, and to ensure we have the correct abstraction + OverlappingChildReference, + /// The right path is a descendant of the left path. + /// e.g. right = left.x or right = left[0]["key"] + RightIsDescendant, + /// The left path is a descendant of the right path. + /// e.g. left = right.x or left = right[0]["key"] + LeftIsDescendant, + /// The left and right path refer to the same leaf value. + /// But they may be in different forms. e.g. left = &any and right = &integer + ReferencesEqual(TypeBindingComparison), + /// Represents an impossible comparison which indicates a broken invariant + /// (e.g. left: &integer and right: &string) + Incompatible, +} + +impl PathPart { + fn compare(&self, other: &Self) -> PathPartComparison { + match (self, other) { + (PathPart::Child(a), PathPart::Child(b)) => match (a, b) { + (ChildSpecifier::ArrayChild(i), ChildSpecifier::ArrayChild(j)) if i == j => { + PathPartComparison::IdenticalChildReference + } + (ChildSpecifier::ArrayChild(_), ChildSpecifier::ArrayChild(_)) => { + PathPartComparison::Divergent + } + (ChildSpecifier::ArrayChild(_), _) => PathPartComparison::Incompatible, + (ChildSpecifier::ObjectChild(a), ChildSpecifier::ObjectChild(b)) if a == b => { + PathPartComparison::IdenticalChildReference + } + (ChildSpecifier::ObjectChild(_), ChildSpecifier::ObjectChild(_)) => { + PathPartComparison::Divergent + } + (ChildSpecifier::ObjectChild(_), _) => PathPartComparison::Incompatible, + }, + (PathPart::Child(a), PathPart::Value { bound_as }) => { + if bound_as.is_narrowing_to(&a.parent_type_kind()) { + PathPartComparison::LeftIsDescendant + } else { + PathPartComparison::Incompatible + } + } + (PathPart::Value { bound_as }, PathPart::Child(b)) => { + if bound_as.is_narrowing_to(&b.parent_type_kind()) { + PathPartComparison::RightIsDescendant + } else { + PathPartComparison::Incompatible + } + } + ( + PathPart::Value { bound_as }, + PathPart::Value { + bound_as: other_bound_as, + }, + ) => PathPartComparison::ReferencesEqual(bound_as.compare_bindings(other_bound_as)), + } + } +} diff --git a/src/misc/dynamic_references/shared_reference.rs b/src/misc/dynamic_references/shared_reference.rs new file mode 100644 index 00000000..fc1d842e --- /dev/null +++ b/src/misc/dynamic_references/shared_reference.rs @@ -0,0 +1,187 @@ +use std::mem::transmute; + +use super::*; + +/// Represents an active `&T` reference, to something derived from a Referenceable root. +pub(crate) struct Shared(pub(super) ReferenceCore); + +impl Clone for Shared { + fn clone(&self) -> Self { + Shared(self.0.clone()) + } +} + +impl Shared { + pub(crate) fn deactivate(self) -> InactiveShared { + self.0.core.data_mut().deactivate_reference(self.0.id); + InactiveShared(self.0) + } + + /// A powerful map method which lets you place the resultant mapped reference inside + /// a structure arbitrarily. + pub(crate) fn emplace_map( + self, + f: impl for<'a> FnOnce(&'a T, &mut SharedEmplacer<'a, T>) -> O, + ) -> O { + // SAFETY: The validity + safety invariants are upheld by `ReferenceableCore` + // ... assuming this id is marked as a shared reference for the duration. + // The emplacer ensures that this is upheld whilst it is alive; via either: + // - Delegating to the created SharedReference if it is emplaced + // - Surviving until Drop at the end of this method if it is not emplaced + let copied_ref = unsafe { self.0.pointer.as_ref() }; + let mut emplacer = SharedEmplacer(self.0.into_emplacer()); + f(copied_ref, &mut emplacer) + } + + /// Maps this shared reference using a closure that returns a [`MappedRef`]. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedRef::new`] constructor, + /// making this method itself safe. + pub(crate) fn map( + self, + f: impl for<'a> FnOnce(&'a T) -> MappedRef<'a, V>, + ) -> Shared { + self.emplace_map(move |input, emplacer| emplacer.emplace(f(input))) + } + + /// Fallible version of [`map`](Self::map) that returns the original reference on error. + /// + /// The unsafe PathExtension assertion is confined to the [`MappedRef::new`] constructor. + pub(crate) fn try_map( + self, + f: impl for<'a> FnOnce(&'a T) -> Result, E>, + ) -> Result, (E, Shared)> { + self.emplace_map(|input, emplacer| match f(input) { + Ok(mapped) => Ok(emplacer.emplace(mapped)), + Err(e) => Err((e, emplacer.revert())), + }) + } +} + +impl Shared { + /// Creates a new SharedReference from an owned value, wrapping it in a Referenceable. + /// Uses a placeholder name and span for the Referenceable root. + pub(crate) fn new_from_owned( + value: AnyValue, + root_name: Option, + span_range: SpanRange, + ) -> Self { + let referenceable = Referenceable::new(value, root_name, span_range); + referenceable + .new_active_shared(span_range) + .expect("Freshly created referenceable must be borrowable as shared") + } + + /// Clones the inner value. This is infallible because AnyValue always supports clone. + pub(crate) fn infallible_clone(&self) -> AnyValue { + (**self).clone() + } +} + +impl Deref for Shared { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: See the rustdoc on dynamic_references/mod.rs for full details. + // To summarize: + // - Provided by the safety invariants of `ReferenceCore` and `ReferenceableCore`. + // - The pointer is guaranteed to be valid and properly aligned for the duration of the reference's lifetime. + // - The reference kind checks ensure that it is not mutably aliased while active. + unsafe { self.0.pointer.as_ref() } + } +} + +impl AsRef for Shared { + fn as_ref(&self) -> &T { + self + } +} + +/// Represents an inactive but *valid* shared reference. +/// +/// It can't currently deref into a `&T`, but can after being activated, when +/// the aliasing rules get checked and enforced. +pub(crate) struct InactiveShared(pub(super) ReferenceCore); + +impl Clone for InactiveShared { + fn clone(&self) -> Self { + InactiveShared(self.0.clone()) + } +} + +impl InactiveShared { + pub(crate) fn activate(self, span: SpanRange) -> FunctionResult> { + self.0 + .core + .data_mut() + .activate_shared_reference(self.0.id, span)?; + Ok(Shared(self.0)) + } +} + +/// A mapped shared reference bundled with its [`PathExtension`] and span. +/// +/// Constructing this is unsafe because the caller must ensure the +/// [`PathExtension`] correctly describes the relationship between the +/// source and mapped reference. +pub(crate) struct MappedRef<'a, V: ?Sized> { + value: &'a V, + path_extension: PathExtension, + span: Option, +} + +impl<'a, V: ?Sized> MappedRef<'a, V> { + /// SAFETY: The caller must ensure that the PathExtension correctly describes + /// the navigation from the source reference to this mapped reference. + /// An overly-specific PathExtension may cause safety issues. + pub(crate) unsafe fn new(value: &'a V, path_extension: PathExtension, span: SpanRange) -> Self { + Self { + value, + path_extension, + span: Some(span), + } + } + + /// SAFETY: In addition to the safety requirements of [`new`](Self::new), the caller + /// must ensure that the reference lifetime is valid for the target lifetime `'a`. + /// This is needed when the compiler cannot prove the lifetime relationship + /// (e.g. in LeafMapper implementations where `'l` and `'e` cannot be unified). + pub(crate) unsafe fn new_unchecked<'any>( + value: &'any V, + path_extension: PathExtension, + span: Option, + ) -> Self { + Self { + value: unsafe { transmute::<&'any V, &'a V>(value) }, + path_extension, + span, + } + } + + pub(crate) fn into_parts(self) -> (&'a V, PathExtension, Option) { + (self.value, self.path_extension, self.span) + } +} + +pub(crate) struct SharedEmplacer<'a, T: ?Sized>(EmplacerCore<'a, T>); + +impl<'a, T: ?Sized> SharedEmplacer<'a, T> { + pub(crate) fn revert(&mut self) -> Shared { + Shared(self.0.revert()) + } + + /// Emplaces a mapped shared reference, consuming the emplacer's reference tracking. + /// + /// This is safe because all preconditions (correct PathExtension and valid reference + /// derivation) are validated by the [`MappedRef`] constructor. + pub(crate) fn emplace<'e: 'a, V: 'static + ?Sized>( + &mut self, + mapped: MappedRef<'e, V>, + ) -> Shared { + let (value, path_extension, span) = mapped.into_parts(); + // SAFETY: The pointer is from a valid reference (guaranteed by MappedRef constructor), + // and the PathExtension was validated by the MappedRef constructor. + let pointer = unsafe { NonNull::new_unchecked(value as *const V as *mut V) }; + unsafe { Shared(self.0.emplace_unchecked(pointer, path_extension, span)) } + } +} diff --git a/src/misc/errors.rs b/src/misc/errors.rs index 6dd464c9..2a373e8b 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 { @@ -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)), @@ -85,8 +85,8 @@ 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() + pub(crate) fn convert_to_syn_error(self) -> syn::Error { + self.0.convert_to_syn_error() } } @@ -101,13 +101,79 @@ 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) + } + + /// 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) 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 { + 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 ExecutionResultExt for ExecutionResult { +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.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 } } @@ -124,11 +190,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 +218,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,41 +234,20 @@ 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, }) } - 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(ErrorKind::Parse, error.0)) - } - - 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) + Self::new(ExecutionInterruptInner::Error(ExecutionError( + ErrorKind::Parse, + error.0, + ))) } pub(crate) fn control_flow(control_flow: ControlFlowInterrupt) -> Self { @@ -218,6 +261,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 @@ -257,11 +306,36 @@ 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 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), @@ -329,9 +403,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) } } @@ -344,27 +416,13 @@ 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" + ), } } } 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..71277a1c 100644 --- a/src/misc/iterators.rs +++ b/src/misc/iterators.rs @@ -1,21 +1,382 @@ 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) struct PreinterpretToIterator<'a, I> { + inner: I, + errored: bool, + interpreter: &'a mut Interpreter, +} + +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>; } -pub(crate) trait ClonableIterator: Iterator { - fn clone_box(&self) -> Box>; +impl BoxedIterator for T { + fn clone_box(&self) -> Box> { + Box::new(self.clone()) + } } -impl Clone for Box> { +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 +407,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 +431,12 @@ 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 +447,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 +485,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 +516,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 +525,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 +539,7 @@ impl ZipIterators { key.clone(), ObjectEntry { key_span: *key_span, - value: iter.next().unwrap(), + value: iter.do_next(interpreter)?.unwrap(), }, ); } @@ -201,12 +563,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 +589,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 +621,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 +678,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(); diff --git a/src/misc/mod.rs b/src/misc/mod.rs index 36aa4457..2ce22658 100644 --- a/src/misc/mod.rs +++ b/src/misc/mod.rs @@ -1,18 +1,18 @@ mod arena; +mod dynamic_references; mod errors; mod field_inputs; mod iterators; mod keywords; -mod mut_rc_ref_cell; mod parse_traits; pub(crate) mod string_conversion; pub(crate) use arena::*; +pub(crate) use dynamic_references::*; pub(crate) use errors::*; pub(crate) use field_inputs::*; pub(crate) use iterators::*; pub(crate) use keywords::*; -pub(crate) use mut_rc_ref_cell::*; pub(crate) use parse_traits::*; use crate::internal_prelude::*; diff --git a/src/misc/mut_rc_ref_cell.rs b/src/misc/mut_rc_ref_cell.rs deleted file mode 100644 index 777e55d0..00000000 --- a/src/misc/mut_rc_ref_cell.rs +++ /dev/null @@ -1,351 +0,0 @@ -use crate::internal_prelude::*; -use std::cell::{BorrowError, BorrowMutError}; - -/// A mutable reference to a sub-value `U` inside a [`Rc>`]. -/// Only one [`MutableSubRcRefCell`] can exist at a time for a given [`Rc>`]. -pub(crate) struct MutableSubRcRefCell { - /// This is actually a reference to the contents of the RefCell - /// but we store it using `unsafe` as `'static`, and use unsafe blocks - /// to ensure it's dropped first. - /// - /// SAFETY: This *must* appear before `pointed_at` so that it's dropped first. - ref_mut: RefMut<'static, U>, - pointed_at: Rc>, -} - -impl MutableSubRcRefCell { - pub(crate) fn new(pointed_at: Rc>) -> Result { - let ref_mut = 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, - }) - } -} - -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() - .map(|_| &*ptr) - } - } - - /// 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), - } - } - - 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> { - 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(_) => Err(error.unwrap()), - } - } - - 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 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: 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, - }) - } -} - -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> { - 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(_) => Err(error.unwrap()), - } - } - - 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) - } - - /// 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), - } - } -} - -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 - } -} - -/// 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/src/misc/parse_traits.rs b/src/misc/parse_traits.rs index 473a4517..6658102d 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>( @@ -192,24 +201,37 @@ 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(), + analyzer: StaticAnalyzer::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 + .analyzer + .enter_frame(root_frame, root_scope, FrameKind::Root); inner(parsed, &mut context)?; - context.state.finish() + context.exit_frame(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.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( @@ -218,7 +240,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( @@ -227,19 +249,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_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) { + 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( @@ -247,7 +277,7 @@ impl ControlFlowContext { id: VariableReferenceId, #[cfg(feature = "debug")] assertion: FinalUseAssertion, ) -> ParseResult<()> { - self.state.reference_variable( + self.analyzer.reference_variable( id, #[cfg(feature = "debug")] assertion, @@ -255,7 +285,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( @@ -263,31 +293,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) } } @@ -353,7 +383,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>( @@ -550,29 +580,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)?; @@ -598,6 +629,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/src/interpretation/control_flow_pass.rs b/src/static_analysis/control_flow_analyzer.rs similarity index 98% rename from src/interpretation/control_flow_pass.rs rename to src/static_analysis/control_flow_analyzer.rs index b6df240f..df70bb56 100644 --- a/src/interpretation/control_flow_pass.rs +++ b/src/static_analysis/control_flow_analyzer.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/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 51% rename from src/interpretation/source_parsing.rs rename to src/static_analysis/static_analyzer.rs index 181b8f67..bd5c2df9 100644 --- a/src/interpretation/source_parsing.rs +++ b/src/static_analysis/static_analyzer.rs @@ -1,6 +1,7 @@ #![allow(unused)] use super::*; +new_key!(pub(crate) FrameId); new_key!(pub(crate) ScopeId); new_key!(pub(crate) VariableDefinitionId); new_key!(pub(crate) VariableReferenceId); @@ -34,17 +35,16 @@ pub(crate) enum FinalUseAssertion { } #[derive(Debug)] -pub(crate) struct ScopeDefinitions { - // Scopes - pub(crate) root_scope: ScopeId, +pub(crate) struct StaticDefinitions { + 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_segment: ControlFlowSegmentId, + root_segments: Vec<(FrameId, ControlFlowSegmentId)>, #[cfg(feature = "debug")] segments: Arena, #[cfg(feature = "debug")] @@ -52,62 +52,57 @@ pub(crate) struct ScopeDefinitions { } #[allow(unused)] -pub(crate) struct FlowAnalysisState { - // SCOPE DATA - scope_id_stack: Vec, +pub(crate) struct StaticAnalyzer { + 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 always frames_stack.last() (else placeholder) + current_frame_id: FrameId, + /// This is always frames_stack.last().scopes_stack.last() (else placeholder) + current_scope_id: ScopeId, + /// This is always 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(), - }); +impl StaticAnalyzer { + 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 @@ -142,8 +137,9 @@ impl FlowAnalysisState { } } - Ok(ScopeDefinitions { - root_scope, + Ok(StaticDefinitions { + root_frame, + frames, scopes, definitions, references, @@ -157,6 +153,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) } @@ -176,30 +176,139 @@ 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, kind: FrameKind) { + *self.frames.get_mut(frame_id) = AllocatedFrame::Defined(FrameData { + 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(), + closure_definition_segment: ControlFlowSegmentId::new_placeholder(), + }, + }, + root_segment: ControlFlowSegmentId::new_placeholder(), + 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.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. + /// 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() + } + + 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"); + 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 + // ====== + + 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; + } + + /// 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.update_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(), @@ -215,58 +324,172 @@ impl FlowAnalysisState { 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(); - for scope_id in self.scope_id_stack.iter().rev() { - let scope = self.scopes.get(*scope_id).defined_ref(); + 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, closure_definition_segment) = match &frame.kind_data { + FrameKindData::Root => return None, + FrameKindData::Closure { + lexical_parent: (parent_frame_id, _), + closure_definition_segment, + .. + } => (*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 + // (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); + self.segments + .get_mut(closure_definition_segment) + .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 = 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)) - } - - /// 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"); + 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 // ======== - 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( @@ -274,7 +497,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, @@ -289,51 +512,98 @@ 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.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( &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), + 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); - 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); + if let Some(parent_id) = parent_id { + let parent = 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" @@ -349,7 +619,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 { @@ -375,7 +645,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); @@ -389,7 +659,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), @@ -408,7 +678,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); @@ -423,7 +693,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), @@ -441,7 +711,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); @@ -533,6 +803,82 @@ 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(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"), + } + } +} + +struct FrameData { + kind_data: FrameKindData, + root_segment: ControlFlowSegmentId, + 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, + closure_definition_segment: ControlFlowSegmentId, + }, +} + +#[derive(Debug)] +pub(crate) struct RuntimeFrame { + pub(crate) closed_variables: Vec<(VariableDefinitionId, VariableReferenceId)>, +} + enum AllocatedScope { Allocated, Defined(ScopeData), @@ -563,8 +909,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 { root_frame: FrameId }, + Child, + FunctionBoundary { new_frame: FrameId, span: SpanRange }, } enum AllocatedVariableDefinition { 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/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/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/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/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/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/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..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,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 +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 acbc1223..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,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 +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 9a7cfed9..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,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 +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 535cfbc8..44d50b6f 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 +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/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/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..c68050e2 --- /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 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.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..71bc3fdd --- /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 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.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..d05d5cd2 --- /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 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/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/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/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/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..fad2741c --- /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 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_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/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/swap_itself.stderr b/tests/compilation_failures/expressions/swap_itself.stderr index 29c94dab..a15b6faf 100644 --- a/tests/compilation_failures/expressions/swap_itself.stderr +++ b/tests/compilation_failures/expressions/swap_itself.stderr @@ -1,4 +1,7 @@ -error: The variable cannot be modified as it is already being modified +error: Cannot create a mutable reference because it clashes with an existing reference: + This reference : [*active*] &mut a (of type any) + Other reference: [*active*] &mut a (of type any) + Reason : mutation may be observed from the other active reference, which breaks aliasing rules --> tests/compilation_failures/expressions/swap_itself.rs:6:16 | 6 | a.swap(a); diff --git a/tests/compilation_failures/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/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/functions/abort_on_iteration_limit_exceeded.rs b/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.rs new file mode 100644 index 00000000..20724f28 --- /dev/null +++ b/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.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/abort_on_iteration_limit_exceeded.stderr b/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.stderr new file mode 100644 index 00000000..92a9cf2e --- /dev/null +++ b/tests/compilation_failures/functions/abort_on_iteration_limit_exceeded.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/abort_on_iteration_limit_exceeded.rs:6:24 + | +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/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/incorrect_type_function_name.rs b/tests/compilation_failures/functions/incorrect_type_function_name.rs new file mode 100644 index 00000000..77fe7228 --- /dev/null +++ b/tests/compilation_failures/functions/incorrect_type_function_name.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/incorrect_type_function_name.stderr b/tests/compilation_failures/functions/incorrect_type_function_name.stderr new file mode 100644 index 00000000..37e8493c --- /dev/null +++ b/tests/compilation_failures/functions/incorrect_type_function_name.stderr @@ -0,0 +1,5 @@ +error: Type 'array' has no property, function or method named 'push_me' + --> tests/compilation_failures/functions/incorrect_type_function_name.rs:6:9 + | +6 | array::push_me(arr, 4, 5); + | ^^^^^^^^^^^^^^ 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..8b72a038 --- /dev/null +++ b/tests/compilation_failures/functions/ref_argument_cannot_be_mutated.stderr @@ -0,0 +1,5 @@ +error: Cannot mutate a non-mutable reference + --> tests/compilation_failures/functions/ref_argument_cannot_be_mutated.rs:5:48 + | +5 | let my_array_push = |arr: &any, value| arr.push(value); + | ^^^ diff --git a/tests/compilation_failures/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/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/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..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 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: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.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 7178f763..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:20 - | -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..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 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: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 9b603952..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 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: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 87093ef1..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:40 + --> tests/compilation_failures/iteration/zip_different_length_streams.rs:5:9 | 5 | [["A", "B", "C"], [1, 2, 3, 4]].zip() - | ^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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"); diff --git a/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.rs b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.rs new file mode 100644 index 00000000..29e87fdc --- /dev/null +++ b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.rs @@ -0,0 +1,13 @@ +use preinterpret::*; + +fn main() { + let _ = run! { + let x = %{ a: 1 }; + // Technically x could be inactive here during the resolution + // of its index, but for now, it's an error. + x[{ + x.a += 1; + "a" + }] = 3; + }; +} \ No newline at end of file diff --git a/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.stderr b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.stderr new file mode 100644 index 00000000..2a474bd3 --- /dev/null +++ b/tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.stderr @@ -0,0 +1,8 @@ +error: Cannot create a shared reference because it clashes with an existing mutable reference: + This reference : [*active*] &x (of type any) + Other reference: [*active*] &mut x (of type any) + Reason : mutation may be observed from the other active reference, which breaks aliasing rules + --> tests/compilation_failures/references/with_active_ref_attempt_mutate_its_descendant.rs:9:13 + | +9 | x.a += 1; + | ^ diff --git a/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs new file mode 100644 index 00000000..d1131b6e --- /dev/null +++ b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs @@ -0,0 +1,8 @@ +use preinterpret::*; + +fn main() { + let _ = run!{ + let my_arr = [[]]; + my_arr[0].push(my_arr.pop()) + }; +} \ No newline at end of file diff --git a/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr new file mode 100644 index 00000000..81913905 --- /dev/null +++ b/tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.stderr @@ -0,0 +1,8 @@ +error: Cannot create a mutable reference because it clashes with an existing reference: + This reference : [*active*] &mut my_arr (of type any) + Other reference: [inactive] &mut my_arr[0] (of type any) + Reason : mutation may invalidate the other descendant reference + --> tests/compilation_failures/references/with_inactive_mut_attempt_mutate_its_ancestor.rs:6:24 + | +6 | my_arr[0].push(my_arr.pop()) + | ^^^^^^ diff --git a/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs new file mode 100644 index 00000000..c7b3b69b --- /dev/null +++ b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs @@ -0,0 +1,11 @@ +use preinterpret::*; + +fn main() { + let _ = run! { + let x = %{ a: 1 }; + let f = |x_field: &any| { + x.b = "new!!"; + }; + f(x.a); + }; +} \ No newline at end of file diff --git a/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr new file mode 100644 index 00000000..60626c9d --- /dev/null +++ b/tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.stderr @@ -0,0 +1,8 @@ +error: Cannot create a mutable reference because it clashes with an existing reference: + This reference : [*active*] &mut x (of type any) + Other reference: [inactive] &x.a (of type any) + Reason : mutation may invalidate the other descendant reference + --> tests/compilation_failures/references/with_inactive_ref_attempt_mutate_its_ancestor.rs:7:13 + | +7 | x.b = "new!!"; + | ^ diff --git a/tests/expressions.rs b/tests/expressions.rs index 750dfff9..4e2cecc1 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 +] @@ -299,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"); } } @@ -551,6 +549,67 @@ 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); + ); + // 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 = %{}; + %[_].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; + ); + 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"] + run!( + let obj = %{ ["zip"]: 123 }; + %[_].assert_eq(obj["zip"], 123); + ); + // Setting "zip" via index access also works + run!( + let obj = %{}; + obj["zip"] = 456; + %[_].assert_eq(obj["zip"], 456); + ); + // Accessing a non-existent "zip" value works, and doesn't give the method + run!( + let obj = %{}; + %[_].assert_eq(obj["zip"], None); + ); } #[test] @@ -573,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/functions.rs b/tests/functions.rs new file mode 100644 index 00000000..aa58fd3e --- /dev/null +++ b/tests/functions.rs @@ -0,0 +1,200 @@ +#![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_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]"); + } + // 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 }; + x.push(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 +} + +#[test] +fn test_simple_closures() { + run! { + 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_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! { + preinterpret::set_recursion_limit(5); + let factorial = |n, f| { + 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); + } +} + +#[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); + } +} 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"); } } 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); + }; +}