diff --git a/crates/rune-macros/src/any.rs b/crates/rune-macros/src/any.rs index f56dabba8..9a3509785 100644 --- a/crates/rune-macros/src/any.rs +++ b/crates/rune-macros/src/any.rs @@ -79,7 +79,9 @@ impl Derive { let generics = &self.input.generics; let mut installers = Vec::new(); - let Ok(()) = expand_install_with(&cx, &self.input, &tokens, &attr, generics, &mut installers) else { + let Ok(()) = + expand_install_with(&cx, &self.input, &tokens, &attr, generics, &mut installers) + else { return Err(cx.errors.into_inner()); }; @@ -152,6 +154,13 @@ pub(crate) fn expand_install_with( } } + installers.extend(attr.protocols.iter().map(|protocol| protocol.expand())); + installers.extend(attr.functions.iter().map(|function| { + quote_spanned! {function.span()=> + module.function_meta(#function)?; + } + })); + if let Some(install_with) = &attr.install_with { installers.push(quote_spanned! { input.span() => #install_with(module)?; diff --git a/crates/rune-macros/src/context.rs b/crates/rune-macros/src/context.rs index 9b7a55c7c..248525a7c 100644 --- a/crates/rune-macros/src/context.rs +++ b/crates/rune-macros/src/context.rs @@ -3,8 +3,8 @@ use std::cell::RefCell; use crate::internals::*; use proc_macro2::Span; use proc_macro2::TokenStream; -use quote::quote_spanned; -use quote::{quote, ToTokens}; +use quote::{quote, quote_spanned, ToTokens}; +use syn::parse::Parse; use syn::parse::ParseStream; use syn::punctuated::Punctuated; use syn::spanned::Spanned as _; @@ -74,6 +74,10 @@ pub(crate) struct TypeAttr { pub(crate) item: Option, /// `#[rune(constructor)]`. pub(crate) constructor: bool, + /// Protocols to "derive" + pub(crate) protocols: Vec, + /// Assosiated functions + pub(crate) functions: Vec, /// Parsed documentation. pub(crate) docs: Vec, } @@ -113,6 +117,64 @@ pub(crate) struct FieldProtocol { custom: Option, } +pub(crate) struct TypeProtocol { + protocol: syn::Ident, + handler: Option, +} + +impl TypeProtocol { + pub fn expand(&self) -> TokenStream { + if let Some(handler) = &self.handler { + let protocol = &self.protocol; + return quote_spanned! {protocol.span()=> + module.associated_function(rune::runtime::Protocol::#protocol, #handler)?; + }; + } + match self.protocol.to_string().as_str() { + "ADD" => quote_spanned! {self.protocol.span()=> + module.associated_function(rune::runtime::Protocol::ADD, |this: Self, other: Self| this + other)?; + }, + "STRING_DISPLAY" => quote_spanned! {self.protocol.span()=> + module.associated_function(rune::runtime::Protocol::STRING_DISPLAY, |this: &Self, buf: &mut ::std::string::String| { + use ::core::fmt::Write as _; + ::core::write!(buf, "{this}") + })?; + }, + "STRING_DEBUG" => quote_spanned! {self.protocol.span()=> + module.associated_function(rune::runtime::Protocol::STRING_DEBUG, |this: &Self, buf: &mut ::std::string::String| { + use ::core::fmt::Write as _; + ::core::write!(buf, "{this:?}") + })?; + }, + _ => unreachable!("`parse()` ensures only supported protocols"), + } + } +} + +impl Parse for TypeProtocol { + fn parse(input: ParseStream) -> syn::Result { + let it = Self { + protocol: input.parse()?, + handler: if input.parse::().is_ok() { + Some(input.parse()?) + } else { + None + }, + }; + + if it.handler.is_some() + || ["ADD", "STRING_DISPLAY", "STRING_DEBUG"].contains(&it.protocol.to_string().as_str()) + { + Ok(it) + } else { + Err(syn::Error::new_spanned( + &it.protocol, + format!("Rune protocol `{}` cannot be derived", it.protocol), + )) + } + } +} + #[derive(Default)] pub(crate) struct Context { pub(crate) errors: RefCell>, @@ -397,7 +459,7 @@ impl Context { let mut error = false; let mut attr = TypeAttr::default(); - for a in input { + 'attrs: for a in input { if a.path().is_ident("doc") { if let syn::Meta::NameValue(meta) = &a.meta { attr.docs.push(meta.value.clone()); @@ -406,60 +468,86 @@ impl Context { continue; } - if a.path() == RUNE { - let result = a.parse_nested_meta(|meta| { - if meta.path == PARSE { - // Parse `#[rune(parse = "..")]` - meta.input.parse::()?; - let s: syn::LitStr = meta.input.parse()?; - - match s.value().as_str() { - "meta_only" => { - attr.parse = ParseKind::MetaOnly; - } - other => { - return Err(syn::Error::new( - meta.input.span(), - format!( - "Unsupported `#[rune(parse = ..)]` argument `{}`", - other - ), - )); - } - }; - } else if meta.path == ITEM { - // Parse `#[rune(item = "..")]` - meta.input.parse::()?; - attr.item = Some(meta.input.parse()?); - } else if meta.path == NAME { - // Parse `#[rune(name = "..")]` - meta.input.parse::()?; - attr.name = Some(meta.input.parse()?); - } else if meta.path == MODULE { - // Parse `#[rune(module = )]` - meta.input.parse::()?; - attr.module = Some(parse_path_compat(meta.input)?); - } else if meta.path == INSTALL_WITH { - // Parse `#[rune(install_with = )]` - meta.input.parse::()?; - attr.install_with = Some(parse_path_compat(meta.input)?); - } else if meta.path == CONSTRUCTOR { - attr.constructor = true; - } else { - return Err(syn::Error::new_spanned( - &meta.path, - "Unsupported type attribute", - )); - } + let err = 'error: { + if a.path() == RUNE { + let result = a.parse_nested_meta(|meta| { + if meta.path == PARSE { + // Parse `#[rune(parse = "..")]` + meta.input.parse::()?; + let s: syn::LitStr = meta.input.parse()?; + + match s.value().as_str() { + "meta_only" => { + attr.parse = ParseKind::MetaOnly; + } + other => { + return Err(syn::Error::new( + meta.input.span(), + format!( + "Unsupported `#[rune(parse = ..)]` argument `{}`", + other + ), + )); + } + }; + } else if meta.path == ITEM { + // Parse `#[rune(item = "..")]` + meta.input.parse::()?; + attr.item = Some(meta.input.parse()?); + } else if meta.path == NAME { + // Parse `#[rune(name = "..")]` + meta.input.parse::()?; + attr.name = Some(meta.input.parse()?); + } else if meta.path == MODULE { + // Parse `#[rune(module = )]` + meta.input.parse::()?; + attr.module = Some(parse_path_compat(meta.input)?); + } else if meta.path == INSTALL_WITH { + // Parse `#[rune(install_with = )]` + meta.input.parse::()?; + attr.install_with = Some(parse_path_compat(meta.input)?); + } else if meta.path == CONSTRUCTOR { + attr.constructor = true; + } else { + return Err(syn::Error::new_spanned( + &meta.path, + "Unsupported type attribute", + )); + } - Ok(()) - }); + Ok(()) + }); - if let Err(e) = result { - error = true; - self.error(e); - }; - } + if let Err(e) = result { + break 'error e; + }; + } + + if a.path() == RUNE_DERIVE { + attr.protocols.extend( + match a.parse_args_with(Punctuated::<_, Token![,]>::parse_terminated) { + Ok(it) => it, + Err(err) => { + break 'error err; + } + }, + ); + } + + if a.path() == RUNE_FUNCTIONS { + attr.functions.extend( + match a.parse_args_with(Punctuated::<_, Token![,]>::parse_terminated) { + Ok(it) => it, + Err(err) => { + break 'error err; + } + }, + ); + } + continue 'attrs; + }; + error = true; + self.error(err); } if error { diff --git a/crates/rune-macros/src/internals.rs b/crates/rune-macros/src/internals.rs index 96575846a..99fae387d 100644 --- a/crates/rune-macros/src/internals.rs +++ b/crates/rune-macros/src/internals.rs @@ -6,6 +6,8 @@ use std::fmt; pub struct Symbol(&'static str); pub const RUNE: Symbol = Symbol("rune"); +pub const RUNE_DERIVE: Symbol = Symbol("rune_derive"); +pub const RUNE_FUNCTIONS: Symbol = Symbol("rune_functions"); pub const ID: Symbol = Symbol("id"); pub const SKIP: Symbol = Symbol("skip"); pub const ITER: Symbol = Symbol("iter"); diff --git a/crates/rune-macros/src/lib.rs b/crates/rune-macros/src/lib.rs index daf934386..165f7e123 100644 --- a/crates/rune-macros/src/lib.rs +++ b/crates/rune-macros/src/lib.rs @@ -155,7 +155,7 @@ pub fn to_value(input: proc_macro::TokenStream) -> proc_macro::TokenStream { .into() } -#[proc_macro_derive(Any, attributes(rune))] +#[proc_macro_derive(Any, attributes(rune, rune_derive, rune_functions))] pub fn any(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let derive = syn::parse_macro_input!(input as any::Derive); derive.expand().unwrap_or_else(to_compile_errors).into() diff --git a/crates/rune/src/any.rs b/crates/rune/src/any.rs index 3ec26a4cc..34ca9e914 100644 --- a/crates/rune/src/any.rs +++ b/crates/rune/src/any.rs @@ -54,6 +54,51 @@ use crate::hash::Hash; /// Ok(module) /// } /// ``` +/// +/// ## `#[rune_derive(PROTOCOL, PROTOCOL = handler, ...)]` attribute +/// +/// Can directly implement supported protocols via the rust Trait, i.e., +/// `#[rune_derive(STRING_DEBUG)]` requires the type to implement +/// [`Debug`](core::fmt::Debug) and adds a handler for +/// [`Protocol::STRING_DEBUG`](crate::runtime::Protocol::STRING_DEBUG). +/// +/// For unsupported protocols, or to deviate from the trait implementation, +/// a custom handler can be specified, this can either be a closure or a path +/// to a function. +/// +/// ``` +/// use rune::Any; +/// +/// #[derive(Any, Debug)] +/// #[rune_derive(STRING_DEBUG)] +/// #[rune_derive(INDEX_GET = |it: Self, i: usize| it.0[i])] +/// struct Struct(Vec); +/// ``` +/// +/// ## `#[rune_functions(some_function, ...)]` attribute +/// +/// Allows specifying functions that will be registered together with the type, +/// these need to be annotated with [`#[rune::function]`](crate::function). +/// +/// ``` +/// use rune::Any; +/// +/// #[derive(Any)] +/// #[rune_functions(Self::first, second)] +/// struct Struct(bool, usize); +/// +/// impl Struct { +/// #[rune::function] +/// fn first(self) -> bool { +/// self.0 +/// } +/// } +/// +/// #[rune::function] +/// fn second(it: Struct) -> usize { +/// it.1 +/// } +/// ``` pub use rune_macros::Any; /// A trait which can be stored inside of an [AnyObj](crate::runtime::AnyObj). diff --git a/crates/rune/src/tests.rs b/crates/rune/src/tests.rs index a2e8d4eac..b46362b74 100644 --- a/crates/rune/src/tests.rs +++ b/crates/rune/src/tests.rs @@ -398,6 +398,7 @@ mod quote; mod range; mod reference_error; mod result; +mod rune_derive; mod stmt_reordering; mod tuple; mod type_name_native; diff --git a/crates/rune/src/tests/rune_derive.rs b/crates/rune/src/tests/rune_derive.rs new file mode 100644 index 000000000..abc6f2e62 --- /dev/null +++ b/crates/rune/src/tests/rune_derive.rs @@ -0,0 +1,76 @@ +use core::fmt::{self, Display}; +use core::ops::Add; + +use super::prelude::*; +use crate::no_std::prelude::*; + +#[derive(Any, Debug, PartialEq)] +#[rune_derive(ADD, STRING_DEBUG, STRING_DISPLAY)] +// To test the manual handler +#[rune_derive(INDEX_GET = |it: Self, _: usize| it.0)] +#[rune_functions(Self::new)] +struct Struct(usize); + +impl Struct { + #[rune::function(path = Self::new)] + fn new(it: usize) -> Self { + Self(it) + } +} + +impl Add for Struct { + type Output = Self; + + fn add(mut self, other: Self) -> Self { + self.0 += other.0; + self + } +} + +impl Display for Struct { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +#[test] +fn rune_derive() -> Result<()> { + let mut m = Module::new(); + m.ty::()?; + assert_eq!( + rune_n! { + &m, + (), + Struct => pub fn main() {Struct::new(1) + Struct::new(2)} + }, + Struct(3) + ); + + assert_eq!( + rune_n! { + &m, + (), + String => pub fn main() {format!("{}", Struct::new(1))} + }, + "1" + ); + + assert_eq!( + rune_n! { + &m, + (), + String => pub fn main() {format!("{:?}", Struct::new(1))} + }, + "Struct(1)" + ); + + assert_eq!( + rune_n! { + &m, + (), + usize => pub fn main() {Struct::new(1)[0]} + }, + 1 + ); + Ok(()) +} diff --git a/crates/rune/tests/ui/rune_derive.rs b/crates/rune/tests/ui/rune_derive.rs new file mode 100644 index 000000000..a9074b653 --- /dev/null +++ b/crates/rune/tests/ui/rune_derive.rs @@ -0,0 +1,13 @@ +use rune::Any; + +// Generates a warning that the path should no longer be using a string literal. +#[derive(Any)] +#[rune_derive(ADD, STRING_DISPLAY, STRING_DEBUG)] +struct UnimplementedTraits; + +#[derive(Any)] +#[rune_derive(NON_EXISTENT)] +struct NonExistingProtocol; + +fn main() { +} diff --git a/crates/rune/tests/ui/rune_derive.stderr b/crates/rune/tests/ui/rune_derive.stderr new file mode 100644 index 000000000..36f7e1249 --- /dev/null +++ b/crates/rune/tests/ui/rune_derive.stderr @@ -0,0 +1,57 @@ +error: Rune protocol `NON_EXISTENT` cannot be derived + --> tests/ui/rune_derive.rs:9:15 + | +9 | #[rune_derive(NON_EXISTENT)] + | ^^^^^^^^^^^^ + +error[E0369]: cannot add `UnimplementedTraits` to `UnimplementedTraits` + --> tests/ui/rune_derive.rs:5:15 + | +5 | #[rune_derive(ADD, STRING_DISPLAY, STRING_DEBUG)] + | ^^^ + | +note: an implementation of `std::ops::Add` might be missing for `UnimplementedTraits` + --> tests/ui/rune_derive.rs:6:1 + | +6 | struct UnimplementedTraits; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ must implement `std::ops::Add` +note: the trait `std::ops::Add` must be implemented + --> $RUST/core/src/ops/arith.rs + | + | pub trait Add { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0277]: `UnimplementedTraits` doesn't implement `std::fmt::Display` + --> tests/ui/rune_derive.rs:5:20 + | +5 | #[rune_derive(ADD, STRING_DISPLAY, STRING_DEBUG)] + | ^^^^^^^^^^^^^^ `UnimplementedTraits` cannot be formatted with the default formatter + | + ::: $RUST/core/src/macros/mod.rs + | + | $dst.write_fmt($crate::format_args!($($arg)*)) + | ------------------------------ in this macro invocation + | + = help: the trait `std::fmt::Display` is not implemented for `UnimplementedTraits` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: this error originates in the macro `$crate::format_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `UnimplementedTraits` doesn't implement `std::fmt::Debug` + --> tests/ui/rune_derive.rs:5:36 + | +5 | #[rune_derive(ADD, STRING_DISPLAY, STRING_DEBUG)] + | ^^^^^^^^^^^^ `UnimplementedTraits` cannot be formatted using `{:?}` + | + ::: $RUST/core/src/macros/mod.rs + | + | $dst.write_fmt($crate::format_args!($($arg)*)) + | ------------------------------ in this macro invocation + | + = help: the trait `std::fmt::Debug` is not implemented for `UnimplementedTraits` + = note: add `#[derive(Debug)]` to `UnimplementedTraits` or manually `impl std::fmt::Debug for UnimplementedTraits` + = note: this error originates in the macro `$crate::format_args` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `UnimplementedTraits` with `#[derive(Debug)]` + | +6 + #[derive(Debug)] +7 | struct UnimplementedTraits; + |