Proc macro for #[derive(CloneRef)] (https://github.com/enso-org/ide/pull/315)

Original commit: 6378d9ef11
This commit is contained in:
Michał Wawrzyniec Urbańczyk 2020-03-28 02:10:09 +01:00 committed by GitHub
parent fd269d2457
commit f39686e152
4 changed files with 294 additions and 1 deletions

View File

@ -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()
}
// =======================

View 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);

View 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()
}

View File

@ -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)
}