From ad08e89d92ec56fb01e1ae13699f3f6e20bd5f48 Mon Sep 17 00:00:00 2001 From: Roland Fredenhagen Date: Fri, 21 Jul 2023 05:28:00 +0200 Subject: [PATCH] Support implementing protocols through `#[rune::function]` (#584) --- crates/rune-macros/src/function.rs | 61 ++++++++++++++++++++++++------ crates/rune/src/lib.rs | 14 +++++-- 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/crates/rune-macros/src/function.rs b/crates/rune-macros/src/function.rs index dff779567..fc53d2ea3 100644 --- a/crates/rune-macros/src/function.rs +++ b/crates/rune-macros/src/function.rs @@ -11,6 +11,7 @@ enum Path { None, Instance(syn::Ident, syn::PathSegment), Rename(syn::PathSegment), + Protocol(syn::Path), } #[derive(Default)] @@ -36,6 +37,22 @@ impl FunctionAttrs { out.instance = true; } else if ident == "keep" { out.keep = true; + } else if ident == "protocol" { + input.parse::()?; + let protocol: syn::Path = input.parse()?; + out.path = Path::Protocol(if let Some(protocol) = protocol.get_ident() { + syn::Path { + leading_colon: None, + segments: ["rune", "runtime", "Protocol"] + .into_iter() + .map(|i| syn::Ident::new(i, protocol.span())) + .chain(Some(protocol.clone())) + .map(syn::PathSegment::from) + .collect(), + } + } else { + protocol + }) } else if ident == "path" { input.parse::()?; @@ -55,12 +72,18 @@ impl FunctionAttrs { let mut it = path.segments.into_iter(); let Some(first) = it.next() else { - return Err(syn::Error::new(input.span(), "Expected at least one path segment")); + return Err(syn::Error::new( + input.span(), + "Expected at least one path segment", + )); }; if let Some(second) = it.next() { let syn::PathArguments::None = &first.arguments else { - return Err(syn::Error::new_spanned(first.arguments, "Unsupported arguments")); + return Err(syn::Error::new_spanned( + first.arguments, + "Unsupported arguments", + )); }; out.path = Path::Instance(first.ident, second); @@ -208,15 +231,24 @@ impl Function { if instance { self_type = None; - name = syn::Expr::Lit(syn::ExprLit { - attrs: Vec::new(), - lit: syn::Lit::Str(match &attrs.path { - Path::None => name_string.clone(), - Path::Rename(last) | Path::Instance(_, last) => { - syn::LitStr::new(&last.ident.to_string(), last.ident.span()) - } - }), - }); + name = 'out: { + syn::Expr::Lit(syn::ExprLit { + attrs: Vec::new(), + lit: syn::Lit::Str(match &attrs.path { + Path::Protocol(protocol) => { + break 'out syn::Expr::Path(syn::ExprPath { + attrs: Vec::new(), + qself: None, + path: protocol.clone(), + }) + } + Path::None => name_string.clone(), + Path::Rename(last) | Path::Instance(_, last) => { + syn::LitStr::new(&last.ident.to_string(), last.ident.span()) + } + }), + }) + }; } else { self_type = match &attrs.path { Path::Instance(self_type, _) => Some(self_type.clone()), @@ -226,6 +258,11 @@ impl Function { name = match &attrs.path { Path::None => expr_lit(&self.sig.ident), Path::Rename(last) | Path::Instance(_, last) => expr_lit(&last.ident), + Path::Protocol(protocol) => syn::Expr::Path(syn::ExprPath { + attrs: Vec::new(), + qself: None, + path: protocol.clone(), + }), }; if !matches!(attrs.path, Path::Instance(..)) { @@ -241,7 +278,7 @@ impl Function { }; let arguments = match &attrs.path { - Path::None => Punctuated::default(), + Path::None | Path::Protocol(_) => Punctuated::default(), Path::Rename(last) | Path::Instance(_, last) => match &last.arguments { syn::PathArguments::AngleBracketed(arguments) => arguments.args.clone(), syn::PathArguments::None => Punctuated::default(), diff --git a/crates/rune/src/lib.rs b/crates/rune/src/lib.rs index 5a93f38c9..9c1150493 100644 --- a/crates/rune/src/lib.rs +++ b/crates/rune/src/lib.rs @@ -279,8 +279,10 @@ pub(crate) use rune_macros::__internal_impl_any; /// generated Rune documentation. /// * The name of arguments is captured to improve documentation generation. /// * If an instance function is annotated this is detected (if the function -/// receives `self`). This behavior can be forced using `#[rune(instance)]` if +/// receives `self`). This behavior can be forced using `#[rune::function(instance)]` if /// the function doesn't take `self`. +/// * The name of the function can be set using the `#[rune::function(path = ...)]`. +/// * Instance functions can be made a protocol function `#[rune::function(protocol = STRING_DISPLAY)]`. /// /// # Examples /// @@ -327,10 +329,11 @@ pub(crate) use rune_macros::__internal_impl_any; /// } /// ``` /// -/// A regular instance function: +/// Regular instance functions and protocol functions: /// /// ``` /// use rune::{Any, Module, ContextError}; +/// use std::fmt::{self, Write}; /// /// #[derive(Any)] /// struct String { @@ -360,6 +363,11 @@ pub(crate) use rune_macros::__internal_impl_any; /// inner: self.inner.to_uppercase() /// } /// } +/// +/// #[rune::function(protocol = STRING_DISPLAY)] +/// fn display(&self, buffer: &mut std::string::String) -> fmt::Result { +/// write!(buffer, "{}", self.inner) +/// } /// } /// /// /// Construct a new empty string. @@ -399,10 +407,10 @@ pub(crate) use rune_macros::__internal_impl_any; /// m.function_meta(empty)?; /// m.function_meta(String::to_uppercase)?; /// m.function_meta(to_lowercase)?; +/// m.function_meta(String::display)?; /// Ok(m) /// } /// ``` -#[doc(hidden)] pub use rune_macros::function; /// Macro used to annotate native functions which can be loaded as macros in