diff --git a/crates/rune-macros/src/any.rs b/crates/rune-macros/src/any.rs index c4006a53d..c75a37799 100644 --- a/crates/rune-macros/src/any.rs +++ b/crates/rune-macros/src/any.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; use rune_core::Hash; use syn::punctuated::Punctuated; @@ -51,8 +51,7 @@ impl InternalCall { }; let name = syn::LitStr::new(&name.to_string(), name.span()); - - expand_any(&self.path, type_hash, &name, &[], &tokens, &generics) + expand_any(None, &self.path, type_hash, &name, &[], &tokens, &generics) } } @@ -110,7 +109,15 @@ impl Derive { let name = syn::LitStr::new(&name.to_string(), name.span()); - expand_any(ident, type_hash, &name, &installers, &tokens, generics) + expand_any( + attr.builtin, + ident, + type_hash, + &name, + &installers, + &tokens, + generics, + ) } } @@ -334,7 +341,7 @@ fn expand_enum_install_with( enum_.variant_mut(#variant_index)?.make_named(&[#(#field_names),*])?.static_docs(&#variant_docs) }); - variants.push((None, attr)); + variants.push((None, variant_attr)); } syn::Fields::Unnamed(fields) => { let mut fields_len = 0usize; @@ -373,7 +380,7 @@ fn expand_enum_install_with( None }; - variants.push((constructor, attr)); + variants.push((constructor, variant_attr)); } syn::Fields::Unit => { variant_metas.push(quote! { @@ -386,7 +393,7 @@ fn expand_enum_install_with( None }; - variants.push((constructor, attr)); + variants.push((constructor, variant_attr)); } } } @@ -463,6 +470,7 @@ fn expand_enum_install_with( /// Expand the necessary implementation details for `Any`. pub(super) fn expand_any( + builtin: Option, ident: T, type_hash: Hash, name: &syn::LitStr, @@ -522,20 +530,6 @@ where } }; - let type_hash = type_hash.into_inner(); - - let make_hash = if !generic_names.is_empty() { - quote!(#hash::new_with_type_parameters(#type_hash, #hash::parameters([#(<#generic_names as #type_of>::type_hash()),*]))) - } else { - quote!(#hash::new(#type_hash)) - }; - - let type_parameters = if !generic_names.is_empty() { - quote!(#hash::parameters([#(<#generic_names as #type_of>::type_hash()),*])) - } else { - quote!(#hash::EMPTY) - }; - let install_with = quote! { #[automatically_derived] impl #impl_generics #install_with for #ident #type_generics #where_clause { @@ -546,92 +540,113 @@ where } }; - Ok(quote! { - #[automatically_derived] - impl #impl_generics #any for #ident #type_generics #where_clause { - fn type_hash() -> #hash { - #make_hash - } - } + let any = if builtin.is_none() { + let type_hash = type_hash.into_inner(); - #install_with - #impl_named + let make_hash = if !generic_names.is_empty() { + quote!(#hash::new_with_type_parameters(#type_hash, #hash::parameters([#(<#generic_names as #type_of>::type_hash()),*]))) + } else { + quote!(#hash::new(#type_hash)) + }; - #[automatically_derived] - impl #impl_generics #type_of for #ident #type_generics #where_clause { - #[inline] - fn type_hash() -> #hash { - ::type_hash() - } + let type_parameters = if !generic_names.is_empty() { + quote!(#hash::parameters([#(<#generic_names as #type_of>::type_hash()),*])) + } else { + quote!(#hash::EMPTY) + }; - #[inline] - fn type_parameters() -> #hash { - #type_parameters + Some(quote! { + #[automatically_derived] + impl #impl_generics #any for #ident #type_generics #where_clause { + fn type_hash() -> #hash { + #make_hash + } } - #[inline] - fn type_info() -> #type_info { - #type_info::Any(#any_type_info::__private_new( - #raw_str::from_str(core::any::type_name::()), - ::type_hash(), - )) + #[automatically_derived] + impl #impl_generics #type_of for #ident #type_generics #where_clause { + #[inline] + fn type_hash() -> #hash { + ::type_hash() + } + + #[inline] + fn type_parameters() -> #hash { + #type_parameters + } + + #[inline] + fn type_info() -> #type_info { + #type_info::Any(#any_type_info::__private_new( + #raw_str::from_str(core::any::type_name::()), + ::type_hash(), + )) + } } - } - #[automatically_derived] - impl #impl_generics #maybe_type_of for #ident #type_generics #where_clause { - #[inline] - fn maybe_type_of() -> Option<#full_type_of> { - Some(::type_of()) + #[automatically_derived] + impl #impl_generics #maybe_type_of for #ident #type_generics #where_clause { + #[inline] + fn maybe_type_of() -> Option<#full_type_of> { + Some(::type_of()) + } } - } - #[automatically_derived] - impl #impl_generics #unsafe_to_ref for #ident #type_generics #where_clause { - type Guard = #raw_into_ref; + #[automatically_derived] + impl #impl_generics #unsafe_to_ref for #ident #type_generics #where_clause { + type Guard = #raw_into_ref; - unsafe fn unsafe_to_ref<'a>(value: #value) -> #vm_result<(&'a Self, Self::Guard)> { - let (value, guard) = match value.into_any_ptr() { - #vm_result::Ok(value) => value, - #vm_result::Err(err) => return #vm_result::Err(err), - }; + unsafe fn unsafe_to_ref<'a>(value: #value) -> #vm_result<(&'a Self, Self::Guard)> { + let (value, guard) = match value.into_any_ptr() { + #vm_result::Ok(value) => value, + #vm_result::Err(err) => return #vm_result::Err(err), + }; - #vm_result::Ok((&*value, guard)) + #vm_result::Ok((&*value, guard)) + } } - } - #[automatically_derived] - impl #impl_generics #unsafe_to_mut for #ident #type_generics #where_clause { - type Guard = #raw_into_mut; + #[automatically_derived] + impl #impl_generics #unsafe_to_mut for #ident #type_generics #where_clause { + type Guard = #raw_into_mut; - unsafe fn unsafe_to_mut<'a>(value: #value) -> #vm_result<(&'a mut Self, Self::Guard)> { - let (value, guard) = match value.into_any_mut() { - #vm_result::Ok(value) => value, - #vm_result::Err(err) => return #vm_result::Err(err), - }; + unsafe fn unsafe_to_mut<'a>(value: #value) -> #vm_result<(&'a mut Self, Self::Guard)> { + let (value, guard) = match value.into_any_mut() { + #vm_result::Ok(value) => value, + #vm_result::Err(err) => return #vm_result::Err(err), + }; - #vm_result::Ok((&mut *value, guard)) + #vm_result::Ok((&mut *value, guard)) + } } - } - #[automatically_derived] - impl #impl_generics #unsafe_to_value for &#ident #type_generics #where_clause { - type Guard = #pointer_guard; + #[automatically_derived] + impl #impl_generics #unsafe_to_value for &#ident #type_generics #where_clause { + type Guard = #pointer_guard; - unsafe fn unsafe_to_value(self) -> #vm_result<(#value, Self::Guard)> { - let (shared, guard) = #shared::from_ref(self); - #vm_result::Ok((#value::from(shared), guard)) + unsafe fn unsafe_to_value(self) -> #vm_result<(#value, Self::Guard)> { + let (shared, guard) = #shared::from_ref(self); + #vm_result::Ok((#value::from(shared), guard)) + } } - } - #[automatically_derived] - impl #impl_generics #unsafe_to_value for &mut #ident #type_generics #where_clause { - type Guard = #pointer_guard; + #[automatically_derived] + impl #impl_generics #unsafe_to_value for &mut #ident #type_generics #where_clause { + type Guard = #pointer_guard; - unsafe fn unsafe_to_value(self) -> #vm_result<(#value, Self::Guard)> { - let (shared, guard) = #shared::from_mut(self); - #vm_result::Ok((#value::from(shared), guard)) + unsafe fn unsafe_to_value(self) -> #vm_result<(#value, Self::Guard)> { + let (shared, guard) = #shared::from_mut(self); + #vm_result::Ok((#value::from(shared), guard)) + } } - } + }) + } else { + None + }; + + Ok(quote! { + #install_with + #impl_named + #any }) } diff --git a/crates/rune-macros/src/context.rs b/crates/rune-macros/src/context.rs index c069558ca..48e502744 100644 --- a/crates/rune-macros/src/context.rs +++ b/crates/rune-macros/src/context.rs @@ -76,6 +76,9 @@ pub(crate) struct TypeAttr { pub(crate) constructor: bool, /// Parsed documentation. pub(crate) docs: Vec, + /// Indicates that this is a builtin type, so don't generate an `Any` + /// implementation for it. + pub(crate) builtin: Option, } /// Parsed variant attributes. @@ -450,6 +453,8 @@ impl Context { attr.install_with = Some(parse_path_compat(meta.input)?); } else if meta.path == CONSTRUCTOR { attr.constructor = true; + } else if meta.path == BUILTIN { + attr.builtin = Some(meta.path.span()); } else { return Err(syn::Error::new_spanned( &meta.path, diff --git a/crates/rune-macros/src/internals.rs b/crates/rune-macros/src/internals.rs index 2477712d7..1b3a2c3e2 100644 --- a/crates/rune-macros/src/internals.rs +++ b/crates/rune-macros/src/internals.rs @@ -21,6 +21,7 @@ pub const MODULE: Symbol = Symbol("module"); pub const INSTALL_WITH: Symbol = Symbol("install_with"); pub const CONSTRUCTOR: Symbol = Symbol("constructor"); +pub const BUILTIN: Symbol = Symbol("builtin"); pub const GET: Symbol = Symbol("get"); pub const SET: Symbol = Symbol("set"); pub const COPY: Symbol = Symbol("copy"); diff --git a/crates/rune/src/modules/core.rs b/crates/rune/src/modules/core.rs index edd62b052..907b765cf 100644 --- a/crates/rune/src/modules/core.rs +++ b/crates/rune/src/modules/core.rs @@ -79,6 +79,7 @@ fn is_readable(value: Value) -> bool { Value::RangeToInclusive(value) => value.is_readable(), Value::RangeTo(value) => value.is_readable(), Value::Range(value) => value.is_readable(), + Value::ControlFlow(value) => value.is_readable(), Value::Future(value) => value.is_readable(), Value::Stream(value) => value.is_readable(), Value::Generator(value) => value.is_readable(), @@ -132,6 +133,7 @@ fn is_writable(value: Value) -> bool { Value::RangeToInclusive(value) => value.is_writable(), Value::RangeTo(value) => value.is_writable(), Value::Range(value) => value.is_writable(), + Value::ControlFlow(value) => value.is_writable(), Value::Future(value) => value.is_writable(), Value::Stream(value) => value.is_writable(), Value::Generator(value) => value.is_writable(), diff --git a/crates/rune/src/modules/ops.rs b/crates/rune/src/modules/ops.rs index 0646371e3..a892ed9bd 100644 --- a/crates/rune/src/modules/ops.rs +++ b/crates/rune/src/modules/ops.rs @@ -4,8 +4,8 @@ use core::cmp::Ordering; use crate as rune; use crate::runtime::{ - EnvProtocolCaller, Function, Generator, GeneratorState, Iterator, Protocol, Range, RangeFrom, - RangeFull, RangeInclusive, RangeTo, RangeToInclusive, Value, Vm, VmResult, + ControlFlow, EnvProtocolCaller, Function, Generator, GeneratorState, Iterator, Protocol, Range, + RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, Value, Vm, VmResult, }; use crate::{ContextError, Module}; @@ -285,6 +285,26 @@ pub fn module() -> Result { m.function_meta(Range::cmp__meta)?; } + { + m.ty::()?.docs([ + " Used to tell an operation whether it should exit early or go on as usual.", + "", + " This acts as the basis of the [`TRY`] protocol in Rune.", + "", + " [`TRY`]: crate::Protocol::TRY", + "", + "# Examples", + "", + "```rune", + "use std::ops::ControlFlow;", + "", + "let c = ControlFlow::Continue(42);", + "assert_eq!(c.0, 42);", + "assert_eq!(c, ControlFlow::Continue(42));", + "```", + ]); + } + m.ty::()?.docs([ "The type of a function in Rune.", "", diff --git a/crates/rune/src/modules/option.rs b/crates/rune/src/modules/option.rs index 7ecaddd45..e43d73289 100644 --- a/crates/rune/src/modules/option.rs +++ b/crates/rune/src/modules/option.rs @@ -3,7 +3,7 @@ use core::fmt; use crate as rune; -use crate::runtime::{Formatter, Function, Iterator, Panic, Shared, Value, VmResult}; +use crate::runtime::{ControlFlow, Formatter, Function, Iterator, Panic, Shared, Value, VmResult}; use crate::{ContextError, Module}; /// Construct the `std::option` module. @@ -25,6 +25,7 @@ pub fn module() -> Result { module.function_meta(ok_or)?; module.function_meta(ok_or_else)?; module.function_meta(into_iter)?; + module.function_meta(option_try__meta)?; Ok(module) } @@ -397,3 +398,23 @@ fn ok_or_else(this: Option, err: Function) -> VmResult VmResult::Ok(Err(vm_try!(err.call(())))), } } + +/// Using [`Option`] with the try protocol. +/// +/// # Examples +/// +/// ```rune +/// fn maybe_add_one(value) { +/// Some(value? + 1) +/// } +/// +/// assert_eq!(maybe_add_one(Some(4)), Some(5)); +/// assert_eq!(maybe_add_one(None), None); +/// ``` +#[rune::function(keep, instance, protocol = TRY)] +pub(crate) fn option_try(this: Option) -> ControlFlow { + match this { + Some(value) => ControlFlow::Continue(value), + None => ControlFlow::Break(Value::Option(Shared::new(None))), + } +} diff --git a/crates/rune/src/modules/result.rs b/crates/rune/src/modules/result.rs index ab0733f15..9154fe5f6 100644 --- a/crates/rune/src/modules/result.rs +++ b/crates/rune/src/modules/result.rs @@ -3,7 +3,7 @@ use core::fmt; use crate as rune; -use crate::runtime::{Formatter, Function, Panic, Value, VmResult}; +use crate::runtime::{ControlFlow, Formatter, Function, Panic, Shared, Value, VmResult}; use crate::{ContextError, Module}; /// Construct the `std::result` module. @@ -31,6 +31,7 @@ pub fn module() -> Result { module.function_meta(expect)?; module.function_meta(and_then)?; module.function_meta(map)?; + module.function_meta(result_try__meta)?; Ok(module) } @@ -258,3 +259,23 @@ fn map(this: &Result, then: Function) -> VmResult VmResult::Ok(Err(e.clone())), } } + +/// Using [`Result`] with the try protocol. +/// +/// # Examples +/// +/// ```rune +/// fn maybe_add_one(value) { +/// Ok(value? + 1) +/// } +/// +/// assert_eq!(maybe_add_one(Ok(4)), Ok(5)); +/// assert_eq!(maybe_add_one(Err("not a number")), Err("not a number")); +/// ``` +#[rune::function(keep, instance, protocol = TRY)] +pub(crate) fn result_try(this: Result) -> ControlFlow { + match this { + Ok(value) => ControlFlow::Continue(value), + Err(error) => ControlFlow::Break(Value::Result(Shared::new(Err(error)))), + } +} diff --git a/crates/rune/src/runtime.rs b/crates/rune/src/runtime.rs index a1c96b28d..f44601427 100644 --- a/crates/rune/src/runtime.rs +++ b/crates/rune/src/runtime.rs @@ -179,3 +179,6 @@ pub use self::vm_halt::VmHaltInfo; mod fmt; pub use self::fmt::Formatter; + +mod control_flow; +pub use self::control_flow::ControlFlow; diff --git a/crates/rune/src/runtime/control_flow.rs b/crates/rune/src/runtime/control_flow.rs new file mode 100644 index 000000000..cf0b75b2a --- /dev/null +++ b/crates/rune/src/runtime/control_flow.rs @@ -0,0 +1,117 @@ +use core::fmt::{self, Write}; +use core::ops; + +use crate as rune; +use crate::runtime::{Formatter, FromValue, ProtocolCaller, ToValue, Value, VmResult}; +use crate::Any; + +/// Used to tell an operation whether it should exit early or go on as usual. +/// +/// This acts as the basis of the [`TRY`] protocol in Rune. +/// +/// [`TRY`]: crate::Protocol::TRY +#[derive(Debug, Clone, Any)] +#[rune(builtin)] +pub enum ControlFlow { + /// Move on to the next phase of the operation as normal. + #[rune(constructor)] + Continue(#[rune(get, set)] Value), + /// Exit the operation without running subsequent phases. + #[rune(constructor)] + Break(#[rune(get, set)] Value), +} + +impl ControlFlow { + pub(crate) fn string_debug_with( + &self, + f: &mut Formatter, + caller: &mut impl ProtocolCaller, + ) -> VmResult { + match self { + ControlFlow::Continue(value) => { + vm_write!(f, "Continue("); + + if let Err(fmt::Error) = vm_try!(Value::string_debug_with(value, f, caller)) { + return VmResult::Ok(Err(fmt::Error)); + } + + vm_write!(f, ")"); + } + ControlFlow::Break(value) => { + vm_write!(f, "Break("); + + if let Err(fmt::Error) = vm_try!(Value::string_debug_with(value, f, caller)) { + return VmResult::Ok(Err(fmt::Error)); + } + + vm_write!(f, ")"); + } + } + + VmResult::Ok(Ok(())) + } + + pub(crate) fn partial_eq_with( + &self, + other: &Self, + caller: &mut impl ProtocolCaller, + ) -> VmResult { + match (self, other) { + (ControlFlow::Continue(a), ControlFlow::Continue(b)) => { + Value::partial_eq_with(a, b, caller) + } + (ControlFlow::Break(a), ControlFlow::Break(b)) => Value::partial_eq_with(a, b, caller), + _ => VmResult::Ok(false), + } + } + + pub(crate) fn eq_with( + &self, + other: &ControlFlow, + caller: &mut impl ProtocolCaller, + ) -> VmResult { + match (self, other) { + (ControlFlow::Continue(a), ControlFlow::Continue(b)) => Value::eq_with(a, b, caller), + (ControlFlow::Break(a), ControlFlow::Break(b)) => Value::eq_with(a, b, caller), + _ => VmResult::Ok(false), + } + } +} + +from_value!(ControlFlow, into_control_flow); + +impl ToValue for ops::ControlFlow +where + B: ToValue, + C: ToValue, +{ + #[inline] + fn to_value(self) -> VmResult { + let value = match self { + ops::ControlFlow::Continue(value) => { + ControlFlow::Continue(vm_try!(ToValue::to_value(value))) + } + ops::ControlFlow::Break(value) => ControlFlow::Break(vm_try!(ToValue::to_value(value))), + }; + + VmResult::Ok(Value::from(value)) + } +} + +impl FromValue for ops::ControlFlow +where + B: FromValue, + C: FromValue, +{ + #[inline] + fn from_value(value: Value) -> VmResult { + let value = vm_try!(value.into_control_flow()); + + VmResult::Ok(match vm_try!(value.take()) { + ControlFlow::Continue(value) => { + ops::ControlFlow::Continue(vm_try!(C::from_value(value))) + } + ControlFlow::Break(value) => ops::ControlFlow::Break(vm_try!(B::from_value(value))), + }) + } +} diff --git a/crates/rune/src/runtime/static_type.rs b/crates/rune/src/runtime/static_type.rs index 12e191c1b..a2e3f7c06 100644 --- a/crates/rune/src/runtime/static_type.rs +++ b/crates/rune/src/runtime/static_type.rs @@ -1,5 +1,6 @@ use core::cmp::{Eq, Ordering, PartialEq}; use core::hash; +use core::ops::ControlFlow; use crate::no_std::collections::HashMap; use crate::no_std::prelude::*; @@ -184,6 +185,14 @@ pub(crate) static RANGE_TYPE: &StaticType = &StaticType { impl_static_type!(rt::Range => RANGE_TYPE); +pub(crate) static CONTROL_FLOW_TYPE: &StaticType = &StaticType { + name: RawStr::from_str("ControlFlow"), + hash: ::rune_macros::hash!(::std::ops::ControlFlow), +}; + +impl_static_type!(rt::ControlFlow => CONTROL_FLOW_TYPE); +impl_static_type!(impl ControlFlow => CONTROL_FLOW_TYPE); + pub(crate) static FUTURE_TYPE: &StaticType = &StaticType { name: RawStr::from_str("Future"), hash: ::rune_macros::hash!(::std::future::Future), diff --git a/crates/rune/src/runtime/value.rs b/crates/rune/src/runtime/value.rs index 2bf689aac..5d56311f2 100644 --- a/crates/rune/src/runtime/value.rs +++ b/crates/rune/src/runtime/value.rs @@ -12,11 +12,11 @@ use crate::no_std::vec; use crate::compile::ItemBuf; use crate::runtime::vm::CallResult; use crate::runtime::{ - AccessKind, AnyObj, Bytes, ConstValue, EnvProtocolCaller, Format, Formatter, FromValue, - FullTypeOf, Function, Future, Generator, GeneratorState, Iterator, MaybeTypeOf, Mut, Object, - OwnedTuple, Protocol, ProtocolCaller, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, - RangeToInclusive, RawMut, RawRef, Ref, Shared, Stream, ToValue, Type, TypeInfo, Variant, Vec, - Vm, VmError, VmErrorKind, VmIntegerRepr, VmResult, + AccessKind, AnyObj, Bytes, ConstValue, ControlFlow, EnvProtocolCaller, Format, Formatter, + FromValue, FullTypeOf, Function, Future, Generator, GeneratorState, Iterator, MaybeTypeOf, Mut, + Object, OwnedTuple, Protocol, ProtocolCaller, Range, RangeFrom, RangeFull, RangeInclusive, + RangeTo, RangeToInclusive, RawMut, RawRef, Ref, Shared, Stream, ToValue, Type, TypeInfo, + Variant, Vec, Vm, VmError, VmErrorKind, VmIntegerRepr, VmResult, }; use crate::{Any, Hash}; @@ -275,6 +275,8 @@ pub enum Value { RangeTo(Shared), /// A range `start..end`. Range(Shared), + /// A control flow indicator. + ControlFlow(Shared), /// A stored future. Future(Shared), /// A Stream. @@ -444,6 +446,10 @@ impl Value { Value::Range(value) => { write!(f, "{:?}", value) } + Value::ControlFlow(value) => { + let value = vm_try!(value.borrow_ref()); + vm_try!(ControlFlow::string_debug_with(&value, f, caller)) + } Value::Future(value) => { write!(f, "{:?}", value) } @@ -635,6 +641,7 @@ impl Value { } Self::RangeTo(value) => Self::RangeTo(Shared::new(vm_try!(value.take()))), Self::Range(value) => Self::Range(Shared::new(vm_try!(value.take()))), + Self::ControlFlow(value) => Self::ControlFlow(Shared::new(vm_try!(value.take()))), Self::Future(value) => Self::Future(Shared::new(vm_try!(value.take()))), Self::Stream(value) => Self::Stream(Shared::new(vm_try!(value.take()))), Self::Generator(value) => Self::Generator(Shared::new(vm_try!(value.take()))), @@ -981,6 +988,17 @@ impl Value { } } + /// Try to coerce value into a [`ControlFlow`]. + #[inline] + pub fn into_control_flow(self) -> VmResult> { + match self { + Self::ControlFlow(object) => VmResult::Ok(object), + actual => err(VmErrorKind::expected::(vm_try!( + actual.type_info() + ))), + } + } + /// Try to coerce value into a function pointer. #[inline] pub fn into_function(self) -> VmResult> { @@ -1094,6 +1112,7 @@ impl Value { Self::RangeToInclusive(..) => crate::runtime::static_type::RANGE_TO_INCLUSIVE_TYPE.hash, Self::RangeTo(..) => crate::runtime::static_type::RANGE_TO_TYPE.hash, Self::Range(..) => crate::runtime::static_type::RANGE_TYPE.hash, + Self::ControlFlow(..) => crate::runtime::static_type::CONTROL_FLOW_TYPE.hash, Self::Future(..) => crate::runtime::static_type::FUTURE_TYPE.hash, Self::Stream(..) => crate::runtime::static_type::STREAM_TYPE.hash, Self::Generator(..) => crate::runtime::static_type::GENERATOR_TYPE.hash, @@ -1141,6 +1160,9 @@ impl Value { } Self::RangeTo(..) => TypeInfo::StaticType(crate::runtime::static_type::RANGE_TO_TYPE), Self::Range(..) => TypeInfo::StaticType(crate::runtime::static_type::RANGE_TYPE), + Self::ControlFlow(..) => { + TypeInfo::StaticType(crate::runtime::static_type::CONTROL_FLOW_TYPE) + } Self::Future(..) => TypeInfo::StaticType(crate::runtime::static_type::FUTURE_TYPE), Self::Stream(..) => TypeInfo::StaticType(crate::runtime::static_type::STREAM_TYPE), Self::Generator(..) => { @@ -1239,6 +1261,11 @@ impl Value { let b = vm_try!(b.borrow_ref()); return Range::partial_eq_with(&a, &b, caller); } + (Self::ControlFlow(a), Self::ControlFlow(b)) => { + let a = vm_try!(a.borrow_ref()); + let b = vm_try!(b.borrow_ref()); + return ControlFlow::partial_eq_with(&a, &b, caller); + } (Self::EmptyStruct(a), Self::EmptyStruct(b)) => { if vm_try!(a.borrow_ref()).rtti.hash == vm_try!(b.borrow_ref()).rtti.hash { // NB: don't get any future ideas, this must fall through to @@ -1395,6 +1422,11 @@ impl Value { let b = vm_try!(b.borrow_ref()); return Range::eq_with(&a, &b, caller); } + (Self::ControlFlow(a), Self::ControlFlow(b)) => { + let a = vm_try!(a.borrow_ref()); + let b = vm_try!(b.borrow_ref()); + return ControlFlow::eq_with(&a, &b, caller); + } (Self::EmptyStruct(a), Self::EmptyStruct(b)) => { if vm_try!(a.borrow_ref()).rtti.hash == vm_try!(b.borrow_ref()).rtti.hash { // NB: don't get any future ideas, this must fall through to @@ -1861,6 +1893,9 @@ impl fmt::Debug for Value { Value::Range(value) => { write!(f, "{:?}", value)?; } + Value::ControlFlow(value) => { + write!(f, "{:?}", value)?; + } Value::Future(value) => { write!(f, "{:?}", value)?; } @@ -2010,6 +2045,7 @@ impl_from_wrapper! { RangeToInclusive => Shared, RangeTo => Shared, Range => Shared, + ControlFlow => Shared, Future => Shared, Stream => Shared>, Generator => Shared>, @@ -2116,6 +2152,9 @@ impl ser::Serialize for Value { } Value::RangeTo(..) => Err(ser::Error::custom("cannot serialize `..end` ranges")), Value::Range(..) => Err(ser::Error::custom("cannot serialize `start..end` ranges")), + Value::ControlFlow(..) => { + Err(ser::Error::custom("cannot serialize `start..end` ranges")) + } Value::Any(..) => Err(ser::Error::custom("cannot serialize external objects")), } } diff --git a/crates/rune/src/runtime/vm.rs b/crates/rune/src/runtime/vm.rs index 599dfb4e9..3a17b36b0 100644 --- a/crates/rune/src/runtime/vm.rs +++ b/crates/rune/src/runtime/vm.rs @@ -5,6 +5,7 @@ use core::ops; use core::slice; use crate::hash::{Hash, IntoHash, ToTypeHash}; +use crate::modules::{option, result}; use crate::no_std::prelude::*; use crate::no_std::sync::Arc; use crate::no_std::vec; @@ -12,12 +13,13 @@ use crate::runtime::budget; use crate::runtime::future::SelectFuture; use crate::runtime::unit::{UnitFn, UnitStorage}; use crate::runtime::{ - self, Args, Awaited, BorrowMut, Bytes, Call, EmptyStruct, Format, FormatSpec, Formatter, - FromValue, Function, Future, Generator, GuardedArgs, Inst, InstAddress, InstAssignOp, InstOp, - InstRange, InstTarget, InstValue, InstVariant, Object, OwnedTuple, Panic, Protocol, Range, - RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, RuntimeContext, Select, - Shared, Stack, Stream, Struct, Type, TypeCheck, TypeOf, Unit, Value, Variant, VariantData, Vec, - VmError, VmErrorKind, VmExecution, VmHalt, VmIntegerRepr, VmResult, VmSendExecution, + self, Args, Awaited, BorrowMut, Bytes, Call, ControlFlow, EmptyStruct, Format, FormatSpec, + Formatter, FromValue, Function, Future, Generator, GuardedArgs, Inst, InstAddress, + InstAssignOp, InstOp, InstRange, InstTarget, InstValue, InstVariant, Object, OwnedTuple, Panic, + Protocol, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, + RuntimeContext, Select, Shared, Stack, Stream, Struct, Type, TypeCheck, TypeOf, Unit, Value, + Variant, VariantData, Vec, VmError, VmErrorKind, VmExecution, VmHalt, VmIntegerRepr, VmResult, + VmSendExecution, }; /// Small helper function to build errors. @@ -2488,44 +2490,36 @@ impl Vm { /// Perform the try operation on the given stack location. #[cfg_attr(feature = "bench", inline(never))] fn op_try(&mut self, address: InstAddress, clean: usize, preserve: bool) -> VmResult { - let return_value = vm_try!(self.stack.address(address)); + let value = vm_try!(self.stack.address(address)); - let result = match &return_value { - Value::Result(result) => { - let ok = match &*vm_try!(result.borrow_ref()) { - Result::Ok(value) => Some(value.clone()), - Result::Err(..) => None, - }; - ok.ok_or(return_value) - } - Value::Option(option) => { - let some = (*vm_try!(option.borrow_ref())).clone(); - some.ok_or(return_value) - } - _ => { + let result = match value { + Value::Result(result) => result::result_try(vm_try!(result.take())), + Value::Option(option) => option::option_try(vm_try!(option.take())), + value => { if let CallResult::Unsupported(target) = - vm_try!(self.call_instance_fn(return_value, Protocol::TRY, ())) + vm_try!(self.call_instance_fn(value, Protocol::TRY, ())) { return err(VmErrorKind::UnsupportedTryOperand { actual: vm_try!(target.type_info()), }); } - vm_try!(>::from_value(vm_try!(self - .stack - .pop()))) + let value = vm_try!(self.stack.pop()); + vm_try!(ControlFlow::from_value(value)) } }; match result { - Ok(value) => { + ControlFlow::Continue(value) => { if preserve { self.stack.push(value); } VmResult::Ok(false) } - Err(err) => VmResult::Ok(vm_try!(self.op_return_internal(err, clean))), + ControlFlow::Break(error) => { + VmResult::Ok(vm_try!(self.op_return_internal(error, clean))) + } } } diff --git a/crates/rune/src/tests/vm_try.rs b/crates/rune/src/tests/vm_try.rs index 0fb6e17e7..68f7031b8 100644 --- a/crates/rune/src/tests/vm_try.rs +++ b/crates/rune/src/tests/vm_try.rs @@ -1,5 +1,7 @@ prelude!(); +use core::ops::ControlFlow; + #[test] fn test_unwrap() { let out: Result = rune! { @@ -47,10 +49,17 @@ fn test_unwrap() { fn custom_try() -> Result<()> { #[derive(Any)] struct CustomResult(bool); + let mut module = Module::new(); + module.ty::()?; + module.associated_function(Protocol::TRY, |r: CustomResult| { - r.0.then_some(42).ok_or(Err::<(), _>(0)) + if r.0 { + ControlFlow::Continue(42i64) + } else { + ControlFlow::Break(Err::(0i64)) + } })?; assert_eq!(