mirror of
https://github.com/enso-org/enso.git
synced 2024-12-18 19:41:32 +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
|
//! directly, but only through `shapely` crate, as it provides utilities
|
||||||
//! necessary for the generated code to compile.
|
//! necessary for the generated code to compile.
|
||||||
|
|
||||||
|
#![feature(bool_to_option)]
|
||||||
|
#![feature(exact_size_is_empty)]
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![warn(trivial_casts)]
|
#![warn(trivial_casts)]
|
||||||
#![warn(trivial_numeric_casts)]
|
#![warn(trivial_numeric_casts)]
|
||||||
@ -13,6 +15,7 @@
|
|||||||
|
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
mod derive_clone_ref;
|
||||||
mod derive_iterator;
|
mod derive_iterator;
|
||||||
mod overlappable;
|
mod overlappable;
|
||||||
|
|
||||||
@ -68,6 +71,21 @@ pub fn derive_iterator_mut
|
|||||||
derive_iterator::derive(input,IsMut::Mutable)
|
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)]
|
#[allow(missing_docs)]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn overlappable
|
pub fn overlappable
|
||||||
@ -76,4 +94,3 @@ pub fn overlappable
|
|||||||
) -> proc_macro::TokenStream {
|
) -> proc_macro::TokenStream {
|
||||||
overlappable::overlappable(attrs,input)
|
overlappable::overlappable(attrs,input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user