mirror of
https://github.com/enso-org/enso.git
synced 2024-12-18 16:51:41 +03:00
Proc macro for #[derive(CloneRef)] (https://github.com/enso-org/ide/pull/315)
Original commit: 6378d9ef11
This commit is contained in:
parent
fd269d2457
commit
f39686e152
@ -100,6 +100,42 @@ pub fn field_ident_token(field:&syn::Field, index:syn::Index) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns names of the named fields.
|
||||
pub fn field_names(fields:&syn::FieldsNamed) -> Vec<&syn::Ident> {
|
||||
fields.named.iter().map(|field| {
|
||||
field.ident.as_ref().expect("Impossible: no name on a named field.")
|
||||
}).collect()
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === Path Utils ===
|
||||
// ==================
|
||||
|
||||
/// Checks if a given `Path` consists of a single identifier same as given string.
|
||||
pub fn path_matching_ident(path:&syn::Path, str:impl Str) -> bool {
|
||||
path.get_ident().map_or(false, |ident| ident == str.as_ref())
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ======================
|
||||
// === Index Sequence ===
|
||||
// ======================
|
||||
|
||||
/// For given length, returns a sequence of Literals like `[0,1,2…]`. These are unsuffixed
|
||||
/// usize literals, so e.g. can be used to identify the tuple unnamed fields.
|
||||
pub fn index_sequence(len:usize) -> Vec<syn::Index> {
|
||||
(0..len).map(syn::Index::from).collect()
|
||||
}
|
||||
|
||||
/// For given length returns sequence of identifiers like `[field0,field1,…]`.
|
||||
pub fn identifier_sequence(len:usize) -> Vec<syn::Ident> {
|
||||
let format_field = |ix| quote::format_ident!("field{}",ix);
|
||||
(0..len).map(format_field).collect()
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =======================
|
||||
|
34
gui/src/rust/lib/shapely/impl/tests/derive_clone_ref.rs
Normal file
34
gui/src/rust/lib/shapely/impl/tests/derive_clone_ref.rs
Normal file
@ -0,0 +1,34 @@
|
||||
// This module contains dead code. Its purpose is making sure that it compiles
|
||||
#![allow(dead_code)]
|
||||
|
||||
use enso_prelude::*;
|
||||
|
||||
use shapely::*;
|
||||
|
||||
#[derive(Clone,CloneRef)] struct StructUnit;
|
||||
|
||||
#[derive(Clone,CloneRef)] struct StructUnnamedEmpty();
|
||||
|
||||
#[derive(Clone,CloneRef)] struct StructUnnamed(Rc<i32>,Rc<String>);
|
||||
|
||||
#[derive(Clone,CloneRef)] struct StructNamedEmpty{}
|
||||
|
||||
#[derive(Clone,CloneRef)] struct StructNamed{named0:Rc<i32>,named1:Rc<String>}
|
||||
|
||||
#[derive(Clone,CloneRef)] enum EnumEmpty {}
|
||||
|
||||
#[derive(Clone,CloneRef)] enum Enum {
|
||||
VariantUnit,
|
||||
VariantNamedEmpty {},
|
||||
VariantNamed {named0:Rc<i32>,named1:Rc<String>},
|
||||
VariantUnnamedEmpty(),
|
||||
VariantUnnamed(Rc<i32>,Rc<String>),
|
||||
}
|
||||
|
||||
#[derive(CloneRef,Derivative)]
|
||||
#[derivative(Clone(bound=""))]
|
||||
struct StructUnnamedUnbound<T>(Rc<T>);
|
||||
|
||||
#[derive(CloneRef,Clone)]
|
||||
#[clone_ref(bound="T:CloneRef")]
|
||||
struct StructUnnamedBound<T>(T);
|
206
gui/src/rust/lib/shapely/macros/src/derive_clone_ref.rs
Normal file
206
gui/src/rust/lib/shapely/macros/src/derive_clone_ref.rs
Normal file
@ -0,0 +1,206 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use macro_utils::field_names;
|
||||
use macro_utils::identifier_sequence;
|
||||
use macro_utils::index_sequence;
|
||||
use macro_utils::path_matching_ident;
|
||||
use syn::Attribute;
|
||||
use syn::Ident;
|
||||
use syn::Data;
|
||||
use syn::DataEnum;
|
||||
use syn::DataStruct;
|
||||
use syn::Fields;
|
||||
use syn::Lit;
|
||||
use syn::Meta;
|
||||
use syn::MetaNameValue;
|
||||
use syn::NestedMeta;
|
||||
use syn::Variant;
|
||||
use syn::WhereClause;
|
||||
use syn::WherePredicate;
|
||||
|
||||
|
||||
// ==============
|
||||
// === Consts ===
|
||||
// ==============
|
||||
|
||||
/// Name of the custom attribute allowing customizing behavior of the generated `CloneRef`
|
||||
/// implementation.
|
||||
const CLONE_REF_ATTR:&str = "clone_ref";
|
||||
|
||||
/// Name of the property within customization attribute that allows defining custom bounds for
|
||||
/// the generated `CloneRef` implementation.
|
||||
const BOUND_NAME:&str = "bound";
|
||||
|
||||
|
||||
|
||||
// ============================
|
||||
// === CloneRef for structs ===
|
||||
// ============================
|
||||
|
||||
/// `clone_ref` function body for a given `struct` definition.
|
||||
pub fn body_for_struct(ident:&Ident, data:&DataStruct) -> TokenStream {
|
||||
match data.fields {
|
||||
Fields::Unit =>
|
||||
// Foo
|
||||
quote!( #ident ),
|
||||
Fields::Unnamed(ref fields) => {
|
||||
let indices = index_sequence(fields.unnamed.len());
|
||||
// Foo(self.0.clone_ref())
|
||||
quote!(
|
||||
#ident(#(self.#indices.clone_ref()),*)
|
||||
)
|
||||
}
|
||||
Fields::Named(ref fields) => {
|
||||
let names = field_names(fields);
|
||||
// Foo { field0 : self.field0.clone_ref() }
|
||||
quote!(
|
||||
#ident {
|
||||
#(#names : self.#names.clone_ref()),*
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==========================
|
||||
// === CloneRef for enums ===
|
||||
// ==========================
|
||||
|
||||
/// Prepares a match arm for a single variant that `clone_ref`s such value.
|
||||
pub fn arm_for_variant(data_ident:&Ident,variant:&Variant) -> TokenStream {
|
||||
let fields = &variant.fields;
|
||||
let variant_ident = &variant.ident;
|
||||
match fields {
|
||||
Fields::Unit => {
|
||||
// Enum::Var => Enum::Var
|
||||
quote!(
|
||||
#data_ident::#variant_ident => #data_ident::#variant_ident
|
||||
)
|
||||
}
|
||||
Fields::Named(fields) => {
|
||||
let names = field_names(fields);
|
||||
// Enum::Var {field0} => Enum::Var {field0 : field0.clone_ref()}
|
||||
quote!(
|
||||
#data_ident::#variant_ident { #(#names),* } =>
|
||||
#data_ident::#variant_ident {
|
||||
#( #names : #names.clone_ref() ),*
|
||||
}
|
||||
)
|
||||
}
|
||||
Fields::Unnamed(fields) => {
|
||||
let names = identifier_sequence(fields.unnamed.len());
|
||||
// Enum::Var(field0) => Enum::Var(field0.clone_ref())
|
||||
quote!(
|
||||
#data_ident::#variant_ident(#(#names),*) =>
|
||||
#data_ident::#variant_ident(
|
||||
#( #names.clone_ref() ),*
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `clone_ref` function body for a given `enum` definition.
|
||||
pub fn body_for_enum(ident:&Ident, data:&DataEnum) -> TokenStream {
|
||||
if data.variants.is_empty() {
|
||||
quote!(panic!("There cannot exist value of empty enum, so its clone_ref must not be called."))
|
||||
} else {
|
||||
let make_arm = |variant| arm_for_variant(ident,variant);
|
||||
let arms = data.variants.iter().map(make_arm);
|
||||
quote!(
|
||||
match self { #(#arms),* }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================
|
||||
// === Bounds customization ===
|
||||
// ============================
|
||||
|
||||
/// Checks if the given attribute is our customization attribute.
|
||||
pub fn is_clone_ref_customization(attr:&Attribute) -> bool {
|
||||
path_matching_ident(&attr.path,CLONE_REF_ATTR)
|
||||
}
|
||||
|
||||
/// Checks if the given Meta name-val pair defines user-provided bounds.
|
||||
pub fn is_custom_bound(name_val:&MetaNameValue) -> bool {
|
||||
path_matching_ident(&name_val.path,BOUND_NAME)
|
||||
}
|
||||
|
||||
/// If this is our customization attribute, we retrieve user-provided bounds for the generated
|
||||
/// `CloneRef` implementation.
|
||||
///
|
||||
/// Returns `None` is this is third-party attribute.
|
||||
/// Panics if this is our attribute but the syntax is not correct.
|
||||
pub fn clone_ref_bounds(attr:&Attribute) -> Option<Vec<WherePredicate>> {
|
||||
// Silently ignore foreign attributes. Be picky only about our one.
|
||||
is_clone_ref_customization(attr).then(())?;
|
||||
|
||||
let meta = attr.parse_meta().expect("Failed to parse attribute contents.");
|
||||
let list = match meta {
|
||||
Meta::List(ml) => ml.nested,
|
||||
_ => panic!("Attribute contents does not conform to meta item."),
|
||||
};
|
||||
if list.len() > 1 {
|
||||
panic!("Only a single entry within `{}` attribute is allowed.",CLONE_REF_ATTR);
|
||||
}
|
||||
let bound_value = match list.first() {
|
||||
Some(NestedMeta::Meta(Meta::NameValue(name_val))) => {
|
||||
if is_custom_bound(name_val) {
|
||||
&name_val.lit
|
||||
} else {
|
||||
panic!("`{}` attribute can define value only for `{}`.",CLONE_REF_ATTR,BOUND_NAME)
|
||||
}
|
||||
}
|
||||
Some(_) =>
|
||||
panic!("`{}` attribute must contain a single name=value assignment.",CLONE_REF_ATTR),
|
||||
None =>
|
||||
panic!("`{}` attribute must not be empty.",CLONE_REF_ATTR),
|
||||
};
|
||||
let bound_str = if let Lit::Str(lit_str) = bound_value {
|
||||
lit_str
|
||||
} else {
|
||||
panic!("`{}` value must be a string literal describing `where` predicates.",BOUND_NAME)
|
||||
};
|
||||
let bounds_text = format!("where {}", bound_str.value());
|
||||
let bounds = syn::parse_str::<WhereClause>(&bounds_text);
|
||||
let bounds = bounds.unwrap_or_else(|_| {
|
||||
panic!("Failed to parse user-provided where clause: `{}`.",bounds_text)
|
||||
});
|
||||
let ret = bounds.predicates.into_iter().collect();
|
||||
Some(ret)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === Entry Point ===
|
||||
// ===================
|
||||
|
||||
/// Derives `CloneRef` implementation, refer to `crate::derive_clone_ref` for details.
|
||||
pub fn derive
|
||||
(input:proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let decl = syn::parse_macro_input!(input as syn::DeriveInput);
|
||||
let params = &decl.generics.params.iter().collect::<Vec<_>>();
|
||||
let ident = &decl.ident;
|
||||
let body = match &decl.data {
|
||||
Data::Struct(data_struct) => body_for_struct(ident,data_struct),
|
||||
Data::Enum(data_enum) => body_for_enum(ident,data_enum),
|
||||
Data::Union(_) =>
|
||||
panic!("CloneRef cannot be derived for an untagged union input."),
|
||||
};
|
||||
let bounds = decl.attrs.iter().filter_map(clone_ref_bounds).flatten();
|
||||
let output = quote!{
|
||||
impl <#(#params)*> CloneRef for #ident<#(#params)*>
|
||||
where #(#bounds),* {
|
||||
fn clone_ref(&self) -> Self {
|
||||
#body
|
||||
}
|
||||
}
|
||||
};
|
||||
output.into()
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
//! directly, but only through `shapely` crate, as it provides utilities
|
||||
//! necessary for the generated code to compile.
|
||||
|
||||
#![feature(bool_to_option)]
|
||||
#![feature(exact_size_is_empty)]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(trivial_casts)]
|
||||
#![warn(trivial_numeric_casts)]
|
||||
@ -13,6 +15,7 @@
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
mod derive_clone_ref;
|
||||
mod derive_iterator;
|
||||
mod overlappable;
|
||||
|
||||
@ -68,6 +71,21 @@ pub fn derive_iterator_mut
|
||||
derive_iterator::derive(input,IsMut::Mutable)
|
||||
}
|
||||
|
||||
/// Derives `CloneRef` implementation for given type. It performs `clone_ref` on every member
|
||||
/// field. The input type must implement `Clone` and its every field must implement `CloneRef`.
|
||||
///
|
||||
/// For generic types no bounds are introduced in the generated implementation. To customize this
|
||||
/// behavior user might add `#[clone_ref(bound="…")]` attribute. Then the generated implementation
|
||||
/// will use the provided bounds.
|
||||
///
|
||||
/// Supported inputs are structs (unit, named, unnamed), enums (with unit, named, unnamed and no
|
||||
/// variants at all). Unions are currently not supported.
|
||||
#[proc_macro_derive(CloneRef, attributes(clone_ref))]
|
||||
pub fn derive_clone_ref
|
||||
(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
derive_clone_ref::derive(input)
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[proc_macro_attribute]
|
||||
pub fn overlappable
|
||||
@ -76,4 +94,3 @@ pub fn overlappable
|
||||
) -> proc_macro::TokenStream {
|
||||
overlappable::overlappable(attrs,input)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user