Skip to content

Commit

Permalink
Support implementing protocols through #[rune::function] (#584)
Browse files Browse the repository at this point in the history
  • Loading branch information
ModProg authored Jul 21, 2023
1 parent b8087a4 commit ad08e89
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 15 deletions.
61 changes: 49 additions & 12 deletions crates/rune-macros/src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ enum Path {
None,
Instance(syn::Ident, syn::PathSegment),
Rename(syn::PathSegment),
Protocol(syn::Path),
}

#[derive(Default)]
Expand All @@ -36,6 +37,22 @@ impl FunctionAttrs {
out.instance = true;
} else if ident == "keep" {
out.keep = true;
} else if ident == "protocol" {
input.parse::<Token![=]>()?;
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::<Token![=]>()?;

Expand All @@ -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);
Expand Down Expand Up @@ -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()),
Expand All @@ -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(..)) {
Expand All @@ -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(),
Expand Down
14 changes: 11 additions & 3 deletions crates/rune/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit ad08e89

Please sign in to comment.