Skip to content

Commit ad08e89

Browse files
authored
Support implementing protocols through #[rune::function] (rune-rs#584)
1 parent b8087a4 commit ad08e89

File tree

2 files changed

+60
-15
lines changed

2 files changed

+60
-15
lines changed

crates/rune-macros/src/function.rs

+49-12
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ enum Path {
1111
None,
1212
Instance(syn::Ident, syn::PathSegment),
1313
Rename(syn::PathSegment),
14+
Protocol(syn::Path),
1415
}
1516

1617
#[derive(Default)]
@@ -36,6 +37,22 @@ impl FunctionAttrs {
3637
out.instance = true;
3738
} else if ident == "keep" {
3839
out.keep = true;
40+
} else if ident == "protocol" {
41+
input.parse::<Token![=]>()?;
42+
let protocol: syn::Path = input.parse()?;
43+
out.path = Path::Protocol(if let Some(protocol) = protocol.get_ident() {
44+
syn::Path {
45+
leading_colon: None,
46+
segments: ["rune", "runtime", "Protocol"]
47+
.into_iter()
48+
.map(|i| syn::Ident::new(i, protocol.span()))
49+
.chain(Some(protocol.clone()))
50+
.map(syn::PathSegment::from)
51+
.collect(),
52+
}
53+
} else {
54+
protocol
55+
})
3956
} else if ident == "path" {
4057
input.parse::<Token![=]>()?;
4158

@@ -55,12 +72,18 @@ impl FunctionAttrs {
5572
let mut it = path.segments.into_iter();
5673

5774
let Some(first) = it.next() else {
58-
return Err(syn::Error::new(input.span(), "Expected at least one path segment"));
75+
return Err(syn::Error::new(
76+
input.span(),
77+
"Expected at least one path segment",
78+
));
5979
};
6080

6181
if let Some(second) = it.next() {
6282
let syn::PathArguments::None = &first.arguments else {
63-
return Err(syn::Error::new_spanned(first.arguments, "Unsupported arguments"));
83+
return Err(syn::Error::new_spanned(
84+
first.arguments,
85+
"Unsupported arguments",
86+
));
6487
};
6588

6689
out.path = Path::Instance(first.ident, second);
@@ -208,15 +231,24 @@ impl Function {
208231
if instance {
209232
self_type = None;
210233

211-
name = syn::Expr::Lit(syn::ExprLit {
212-
attrs: Vec::new(),
213-
lit: syn::Lit::Str(match &attrs.path {
214-
Path::None => name_string.clone(),
215-
Path::Rename(last) | Path::Instance(_, last) => {
216-
syn::LitStr::new(&last.ident.to_string(), last.ident.span())
217-
}
218-
}),
219-
});
234+
name = 'out: {
235+
syn::Expr::Lit(syn::ExprLit {
236+
attrs: Vec::new(),
237+
lit: syn::Lit::Str(match &attrs.path {
238+
Path::Protocol(protocol) => {
239+
break 'out syn::Expr::Path(syn::ExprPath {
240+
attrs: Vec::new(),
241+
qself: None,
242+
path: protocol.clone(),
243+
})
244+
}
245+
Path::None => name_string.clone(),
246+
Path::Rename(last) | Path::Instance(_, last) => {
247+
syn::LitStr::new(&last.ident.to_string(), last.ident.span())
248+
}
249+
}),
250+
})
251+
};
220252
} else {
221253
self_type = match &attrs.path {
222254
Path::Instance(self_type, _) => Some(self_type.clone()),
@@ -226,6 +258,11 @@ impl Function {
226258
name = match &attrs.path {
227259
Path::None => expr_lit(&self.sig.ident),
228260
Path::Rename(last) | Path::Instance(_, last) => expr_lit(&last.ident),
261+
Path::Protocol(protocol) => syn::Expr::Path(syn::ExprPath {
262+
attrs: Vec::new(),
263+
qself: None,
264+
path: protocol.clone(),
265+
}),
229266
};
230267

231268
if !matches!(attrs.path, Path::Instance(..)) {
@@ -241,7 +278,7 @@ impl Function {
241278
};
242279

243280
let arguments = match &attrs.path {
244-
Path::None => Punctuated::default(),
281+
Path::None | Path::Protocol(_) => Punctuated::default(),
245282
Path::Rename(last) | Path::Instance(_, last) => match &last.arguments {
246283
syn::PathArguments::AngleBracketed(arguments) => arguments.args.clone(),
247284
syn::PathArguments::None => Punctuated::default(),

crates/rune/src/lib.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,10 @@ pub(crate) use rune_macros::__internal_impl_any;
279279
/// generated Rune documentation.
280280
/// * The name of arguments is captured to improve documentation generation.
281281
/// * If an instance function is annotated this is detected (if the function
282-
/// receives `self`). This behavior can be forced using `#[rune(instance)]` if
282+
/// receives `self`). This behavior can be forced using `#[rune::function(instance)]` if
283283
/// the function doesn't take `self`.
284+
/// * The name of the function can be set using the `#[rune::function(path = ...)]`.
285+
/// * Instance functions can be made a protocol function `#[rune::function(protocol = STRING_DISPLAY)]`.
284286
///
285287
/// # Examples
286288
///
@@ -327,10 +329,11 @@ pub(crate) use rune_macros::__internal_impl_any;
327329
/// }
328330
/// ```
329331
///
330-
/// A regular instance function:
332+
/// Regular instance functions and protocol functions:
331333
///
332334
/// ```
333335
/// use rune::{Any, Module, ContextError};
336+
/// use std::fmt::{self, Write};
334337
///
335338
/// #[derive(Any)]
336339
/// struct String {
@@ -360,6 +363,11 @@ pub(crate) use rune_macros::__internal_impl_any;
360363
/// inner: self.inner.to_uppercase()
361364
/// }
362365
/// }
366+
///
367+
/// #[rune::function(protocol = STRING_DISPLAY)]
368+
/// fn display(&self, buffer: &mut std::string::String) -> fmt::Result {
369+
/// write!(buffer, "{}", self.inner)
370+
/// }
363371
/// }
364372
///
365373
/// /// Construct a new empty string.
@@ -399,10 +407,10 @@ pub(crate) use rune_macros::__internal_impl_any;
399407
/// m.function_meta(empty)?;
400408
/// m.function_meta(String::to_uppercase)?;
401409
/// m.function_meta(to_lowercase)?;
410+
/// m.function_meta(String::display)?;
402411
/// Ok(m)
403412
/// }
404413
/// ```
405-
#[doc(hidden)]
406414
pub use rune_macros::function;
407415

408416
/// Macro used to annotate native functions which can be loaded as macros in

0 commit comments

Comments
 (0)