Skip to content

Commit

Permalink
rune: Add new support for third-party constant values
Browse files Browse the repository at this point in the history
  • Loading branch information
udoprog committed Oct 29, 2024
1 parent 64aedd5 commit 74a144d
Show file tree
Hide file tree
Showing 32 changed files with 1,361 additions and 501 deletions.
204 changes: 204 additions & 0 deletions crates/rune-macros/src/const_value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
use core::fmt;

use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::DeriveInput;

use crate::context::{Context, Tokens};

/// An internal call to the macro.
pub(super) struct Derive {
input: DeriveInput,
}

impl Parse for Derive {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
input: input.parse()?,
})
}
}

pub(super) struct ConstBuilder<T> {
ident: T,
tokens: Tokens,
body: TokenStream,
variables: Vec<syn::Ident>,
members: Vec<syn::Member>,
from_const_fields: Vec<TokenStream>,
from_value_fields: Vec<TokenStream>,
}

impl Derive {
pub(super) fn into_builder(self, cx: &Context) -> Result<ConstBuilder<syn::Ident>, ()> {
let attr = cx.const_value_type_attrs(&self.input.attrs)?;
let tokens = cx.tokens_with_module(attr.module.as_ref());
let body;

let Tokens {
const_value,
from_const_value_t,
to_const_value_t,
type_hash_t,
from_value,
value,
..
} = &tokens;

let mut variables = Vec::new();
let mut members = Vec::new();
let mut from_const_fields = Vec::new();
let mut from_value_fields = Vec::new();

match self.input.data {
syn::Data::Struct(data) => {
let mut fields = Vec::new();

for (index, field) in data.fields.iter().enumerate() {
let attr = cx.const_value_field_attrs(&field.attrs)?;

let member = match &field.ident {
Some(ident) => syn::Member::Named(ident.clone()),
None => syn::Member::Unnamed(syn::Index::from(index)),
};

let ty = &field.ty;

let var = syn::Ident::new(&format!("v{index}"), Span::call_site());

if let Some(path) = &attr.with {
let to_const_value: syn::Path =
syn::parse_quote_spanned!(path.span() => #path::to_const_value);
let from_const_value: syn::Path =
syn::parse_quote_spanned!(path.span() => #path::from_const_value);
let from_value: syn::Path =
syn::parse_quote_spanned!(path.span() => #path::from_value);

fields.push(quote!(#to_const_value(self.#member)?));
from_const_fields.push(quote!(#from_const_value(#var)?));
from_value_fields.push(quote!(#from_value(#value::take(#var))?));
} else {
fields.push(quote! {
<#ty as #to_const_value_t>::to_const_value(self.#member)?
});

from_const_fields.push(quote! {
<#ty as #from_const_value_t>::from_const_value(#var)?
});

from_value_fields.push(quote! {
<#ty as #from_value>::from_value(#value::take(#var)).into_result()?
});
}

variables.push(var);
members.push(member);
}

body = quote! {
#const_value::for_struct(<Self as #type_hash_t>::HASH, [#(#fields),*])?
};
}
syn::Data::Enum(..) => {
cx.error(syn::Error::new(
Span::call_site(),
"ToConstValue: enums are not supported",
));
return Err(());
}
syn::Data::Union(..) => {
cx.error(syn::Error::new(
Span::call_site(),
"ToConstValue: unions are not supported",
));
return Err(());
}
}

Ok(ConstBuilder {
ident: self.input.ident,
tokens,
body,
variables,
members,
from_const_fields,
from_value_fields,
})
}
}

impl<T> ConstBuilder<T>
where
T: ToTokens + fmt::Display,
{
pub(super) fn expand(self) -> TokenStream {
let Tokens {
arc,
const_construct_t,
const_value,
option,
result,
runtime_error,
to_const_value_t,
value,
..
} = &self.tokens;

let ident = self.ident;
let construct = syn::Ident::new(&format!("{ident}Construct"), Span::call_site());
let body = self.body;
let members = &self.members;
let variables = &self.variables;
let from_const_fields = &self.from_const_fields;
let from_value_fields = &self.from_value_fields;

let expected = self.members.len();

quote! {
#[automatically_derived]
impl #to_const_value_t for #ident {
#[inline]
fn to_const_value(self) -> #result<#const_value, #runtime_error> {
#result::Ok(#body)
}

#[inline]
fn construct() -> #option<#arc<dyn #const_construct_t>> {
struct #construct;

impl #const_construct_t for #construct {
#[inline]
fn const_construct(&self, values: &[#const_value]) -> #result<#value, #runtime_error> {
let [#(#variables),*] = values else {
return #result::Err(#runtime_error::bad_argument_count(values.len(), #expected));
};

let value = #ident {
#(#members: #from_const_fields,)*
};

#result::Ok(Value::new(value)?)
}

#[inline]
fn runtime_construct(&self, values: &mut [#value]) -> #result<#value, #runtime_error> {
let [#(#variables),*] = values else {
return #result::Err(#runtime_error::bad_argument_count(values.len(), #expected));
};

let value = #ident {
#(#members: #from_value_fields,)*
};

#result::Ok(Value::new(value)?)
}
}

#option::Some(#arc::new(#construct))
}
}
}
}
}
107 changes: 107 additions & 0 deletions crates/rune-macros/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ impl FieldAttrs {
}
}

/// Parsed #[const_value(..)] field attributes.
#[derive(Default)]
pub(crate) struct ConstValueFieldAttrs {
/// Define a custom parsing method.
pub(crate) with: Option<syn::Path>,
}

/// The parsing implementations to build.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ParseKind {
Expand Down Expand Up @@ -91,6 +98,13 @@ pub(crate) struct TypeAttr {
pub(crate) from_value_params: Option<syn::punctuated::Punctuated<syn::Type, Token![,]>>,
}

/// Parsed #[const_value(..)] field attributes.
#[derive(Default)]
pub(crate) struct ConstValueTypeAttr {
/// `#[const_value(module = <path>)]`.
pub(crate) module: Option<syn::Path>,
}

/// Parsed variant attributes.
#[derive(Default)]
pub(crate) struct VariantAttrs {
Expand Down Expand Up @@ -178,6 +192,44 @@ impl Context {
Ok(ident)
}

pub(crate) fn const_value_field_attrs(
&self,
input: &[syn::Attribute],
) -> Result<ConstValueFieldAttrs, ()> {
let mut error = false;
let mut attr = ConstValueFieldAttrs::default();

for a in input {
if a.path() != CONST_VALUE {
continue;
}

let result = a.parse_nested_meta(|meta| {
if meta.path.is_ident("with") {
meta.input.parse::<Token![=]>()?;
attr.with = Some(meta.input.parse::<syn::Path>()?);
return Ok(());
}

return Err(syn::Error::new_spanned(
&meta.path,
"Unsupported field attribute",
));
});

if let Err(e) = result {
error = true;
self.error(e);
};
}

if error {
return Err(());
}

Ok(attr)
}

/// Parse field attributes.
pub(crate) fn field_attrs(&self, input: &[syn::Attribute]) -> Result<FieldAttrs, ()> {
macro_rules! generate_assign {
Expand Down Expand Up @@ -417,6 +469,49 @@ impl Context {
Ok(attr)
}

pub(crate) fn const_value_type_attrs(
&self,
input: &[syn::Attribute],
) -> Result<ConstValueTypeAttr, ()> {
let mut error = false;
let mut attr = ConstValueTypeAttr::default();

for a in input {
if a.path() != CONST_VALUE {
continue;
}

let result = a.parse_nested_meta(|meta| {
if meta.path == MODULE || meta.path == CRATE {
// Parse `#[rune(crate [= <path>])]`
if meta.input.parse::<Option<Token![=]>>()?.is_some() {
attr.module = Some(parse_path_compat(meta.input)?);
} else {
attr.module = Some(syn::parse_quote!(crate));
}

return Ok(());
}

return Err(syn::Error::new_spanned(
&meta.path,
"Unsupported type attribute",
));
});

if let Err(e) = result {
error = true;
self.error(e);
};
}

if error {
return Err(());
}

Ok(attr)
}

/// Parse field attributes.
pub(crate) fn type_attrs(&self, input: &[syn::Attribute]) -> Result<TypeAttr, ()> {
let mut error = false;
Expand Down Expand Up @@ -614,10 +709,14 @@ impl Context {
Tokens {
alloc: path(m, ["alloc"]),
any_t: path(m, ["Any"]),
arc: path(m, ["__private", "Arc"]),
box_: path(m, ["__private", "Box"]),
compile_error: path(m, ["compile", "Error"]),
const_construct_t: path(m, ["runtime", "ConstConstruct"]),
const_value: path(m, ["runtime", "ConstValue"]),
context_error: path(m, ["compile", "ContextError"]),
double_ended_iterator: path(&core, ["iter", "DoubleEndedIterator"]),
from_const_value_t: path(m, ["runtime", "FromConstValue"]),
from_value: path(m, ["runtime", "FromValue"]),
hash: path(m, ["Hash"]),
id: path(m, ["parse", "Id"]),
Expand All @@ -644,10 +743,12 @@ impl Context {
raw_value_guard: path(m, ["runtime", "RawValueGuard"]),
ref_: path(m, ["runtime", "Ref"]),
result: path(&core, ["result", "Result"]),
runtime_error: path(m, ["runtime", "RuntimeError"]),
span: path(m, ["ast", "Span"]),
spanned: path(m, ["ast", "Spanned"]),
static_type_mod: path(m, ["runtime", "static_type"]),
string: path(m, ["alloc", "String"]),
to_const_value_t: path(m, ["runtime", "ToConstValue"]),
to_tokens: path(m, ["macros", "ToTokens"]),
to_value: path(m, ["runtime", "ToValue"]),
token_stream: path(m, ["macros", "TokenStream"]),
Expand Down Expand Up @@ -705,10 +806,14 @@ fn path<const N: usize>(base: &syn::Path, path: [&'static str; N]) -> syn::Path
pub(crate) struct Tokens {
pub(crate) alloc: syn::Path,
pub(crate) any_t: syn::Path,
pub(crate) arc: syn::Path,
pub(crate) box_: syn::Path,
pub(crate) compile_error: syn::Path,
pub(crate) const_construct_t: syn::Path,
pub(crate) const_value: syn::Path,
pub(crate) context_error: syn::Path,
pub(crate) double_ended_iterator: syn::Path,
pub(crate) from_const_value_t: syn::Path,
pub(crate) from_value: syn::Path,
pub(crate) hash: syn::Path,
pub(crate) id: syn::Path,
Expand All @@ -735,10 +840,12 @@ pub(crate) struct Tokens {
pub(crate) raw_value_guard: syn::Path,
pub(crate) ref_: syn::Path,
pub(crate) result: syn::Path,
pub(crate) runtime_error: syn::Path,
pub(crate) span: syn::Path,
pub(crate) spanned: syn::Path,
pub(crate) static_type_mod: syn::Path,
pub(crate) string: syn::Path,
pub(crate) to_const_value_t: syn::Path,
pub(crate) to_tokens: syn::Path,
pub(crate) to_value: syn::Path,
pub(crate) token_stream: syn::Path,
Expand Down
1 change: 1 addition & 0 deletions crates/rune-macros/src/internals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::fmt;
pub struct Symbol(&'static str);

pub const RUNE: Symbol = Symbol("rune");
pub const CONST_VALUE: Symbol = Symbol("const_value");
pub const ID: Symbol = Symbol("id");
pub const SKIP: Symbol = Symbol("skip");
pub const ITER: Symbol = Symbol("iter");
Expand Down
Loading

0 comments on commit 74a144d

Please sign in to comment.