mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Restructure project directories (#333)
`/client/web` -> `/frontend` `/client/cli` -> *delete for now* `/client/native` -> *delete for now* `/core/editor` -> `/editor` `/core/document` -> `/graphene` `/core/renderer` -> `/charcoal` `/core/proc-macro` -> `/proc-macros` *(now plural)*
This commit is contained in:
19
proc-macros/Cargo.toml
Normal file
19
proc-macros/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "graphite-proc-macros"
|
||||
version = "0.1.0"
|
||||
authors = ["Graphite Authors <contact@graphite.design>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.26"
|
||||
syn = { version = "1.0.68", features = ["full"] }
|
||||
quote = "1.0.9"
|
||||
|
||||
[dev-dependencies.editor]
|
||||
path = "../editor"
|
||||
package = "graphite-editor"
|
||||
55
proc-macros/src/as_message.rs
Normal file
55
proc-macros/src/as_message.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use syn::{Data, DeriveInput};
|
||||
|
||||
pub fn derive_as_message_impl(input_item: TokenStream) -> syn::Result<TokenStream> {
|
||||
let input = syn::parse2::<DeriveInput>(input_item).unwrap();
|
||||
|
||||
let data = match input.data {
|
||||
Data::Enum(data) => data,
|
||||
_ => return Err(syn::Error::new(Span::call_site(), "Tried to derive AsMessage for non-enum")),
|
||||
};
|
||||
|
||||
let input_type = input.ident;
|
||||
|
||||
let (globs, names) = data
|
||||
.variants
|
||||
.iter()
|
||||
.map(|var| {
|
||||
let var_name = &var.ident;
|
||||
let var_name_s = var.ident.to_string();
|
||||
if var.attrs.iter().any(|a| a.path.is_ident("child")) {
|
||||
(
|
||||
quote::quote! {
|
||||
#input_type::#var_name(child)
|
||||
},
|
||||
quote::quote! {
|
||||
format!("{}.{}", #var_name_s, child.local_name())
|
||||
},
|
||||
)
|
||||
} else {
|
||||
(
|
||||
quote::quote! {
|
||||
#input_type::#var_name { .. }
|
||||
},
|
||||
quote::quote! {
|
||||
#var_name_s.to_string()
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
.unzip::<_, _, Vec<_>, Vec<_>>();
|
||||
|
||||
let res = quote::quote! {
|
||||
impl AsMessage for #input_type {
|
||||
fn local_name(self) -> String {
|
||||
match self {
|
||||
#(
|
||||
#globs => #names
|
||||
),*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
124
proc-macros/src/combined_message_attrs.rs
Normal file
124
proc-macros/src/combined_message_attrs.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use crate::helpers::call_site_ident;
|
||||
use proc_macro2::Ident;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::Token;
|
||||
use syn::{ItemEnum, TypePath};
|
||||
|
||||
struct MessageArgs {
|
||||
pub _top_parent: TypePath,
|
||||
pub _comma1: Token![,],
|
||||
pub parent: TypePath,
|
||||
pub _comma2: Token![,],
|
||||
pub variant: Ident,
|
||||
}
|
||||
|
||||
impl Parse for MessageArgs {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
Ok(Self {
|
||||
_top_parent: input.parse()?,
|
||||
_comma1: input.parse()?,
|
||||
parent: input.parse()?,
|
||||
_comma2: input.parse()?,
|
||||
variant: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct TopLevelMessageArgs {
|
||||
pub parent: TypePath,
|
||||
pub _comma2: Token![,],
|
||||
pub variant: Ident,
|
||||
}
|
||||
|
||||
impl Parse for TopLevelMessageArgs {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
Ok(Self {
|
||||
parent: input.parse()?,
|
||||
_comma2: input.parse()?,
|
||||
variant: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn combined_message_attrs_impl(attr: TokenStream, input_item: TokenStream) -> syn::Result<TokenStream> {
|
||||
if attr.is_empty() {
|
||||
return top_level_impl(input_item);
|
||||
}
|
||||
|
||||
let mut input = syn::parse2::<ItemEnum>(input_item)?;
|
||||
|
||||
let (parent_is_top, parent, variant) = match syn::parse2::<MessageArgs>(attr.clone()) {
|
||||
Ok(x) => (false, x.parent, x.variant),
|
||||
Err(_) => {
|
||||
let x = syn::parse2::<TopLevelMessageArgs>(attr)?;
|
||||
(true, x.parent, x.variant)
|
||||
}
|
||||
};
|
||||
|
||||
let parent_discriminant = quote::quote! {
|
||||
<#parent as ToDiscriminant>::Discriminant
|
||||
};
|
||||
|
||||
input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant, TransitiveChild)] });
|
||||
input.attrs.push(syn::parse_quote! { #[parent(#parent, #parent::#variant)] });
|
||||
if parent_is_top {
|
||||
input.attrs.push(syn::parse_quote! { #[parent_is_top] });
|
||||
}
|
||||
input
|
||||
.attrs
|
||||
.push(syn::parse_quote! { #[discriminant_attr(derive(Debug, Copy, Clone, PartialEq, Eq, Hash, AsMessage, TransitiveChild))] });
|
||||
input
|
||||
.attrs
|
||||
.push(syn::parse_quote! { #[discriminant_attr(parent(#parent_discriminant, #parent_discriminant::#variant))] });
|
||||
if parent_is_top {
|
||||
input.attrs.push(syn::parse_quote! { #[discriminant_attr(parent_is_top)] });
|
||||
}
|
||||
|
||||
for var in &mut input.variants {
|
||||
if let Some(attr) = var.attrs.iter_mut().find(|a| a.path.is_ident("child")) {
|
||||
let last_segment = attr.path.segments.last_mut().unwrap();
|
||||
last_segment.ident = call_site_ident("sub_discriminant");
|
||||
var.attrs.push(syn::parse_quote! {
|
||||
#[discriminant_attr(child)]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(input.into_token_stream())
|
||||
}
|
||||
|
||||
fn top_level_impl(input_item: TokenStream) -> syn::Result<TokenStream> {
|
||||
let mut input = syn::parse2::<ItemEnum>(input_item)?;
|
||||
|
||||
input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant)] });
|
||||
input.attrs.push(syn::parse_quote! { #[discriminant_attr(derive(Debug, Copy, Clone, PartialEq, Eq, Hash, AsMessage))] });
|
||||
|
||||
for var in &mut input.variants {
|
||||
if let Some(attr) = var.attrs.iter_mut().find(|a| a.path.is_ident("child")) {
|
||||
let last_segment = attr.path.segments.last_mut().unwrap();
|
||||
last_segment.ident = call_site_ident("sub_discriminant");
|
||||
var.attrs.push(syn::parse_quote! {
|
||||
#[discriminant_attr(child)]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let input_type = &input.ident;
|
||||
let discriminant = call_site_ident(format!("{}Discriminant", input_type));
|
||||
|
||||
Ok(quote::quote! {
|
||||
#input
|
||||
|
||||
impl TransitiveChild for #input_type {
|
||||
type TopParent = Self;
|
||||
type Parent = Self;
|
||||
}
|
||||
|
||||
impl TransitiveChild for #discriminant {
|
||||
type TopParent = Self;
|
||||
type Parent = Self;
|
||||
}
|
||||
})
|
||||
}
|
||||
139
proc-macros/src/discriminant.rs
Normal file
139
proc-macros/src/discriminant.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use crate::helper_structs::ParenthesizedTokens;
|
||||
use crate::helpers::call_site_ident;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Attribute, Data, DeriveInput, Field, Fields, ItemEnum};
|
||||
|
||||
pub fn derive_discriminant_impl(input_item: TokenStream) -> syn::Result<TokenStream> {
|
||||
let input = syn::parse2::<DeriveInput>(input_item).unwrap();
|
||||
|
||||
let mut data = match input.data {
|
||||
Data::Enum(data) => data,
|
||||
_ => return Err(syn::Error::new(Span::call_site(), "Tried to derive a discriminant for non-enum")),
|
||||
};
|
||||
|
||||
let mut is_sub_discriminant = vec![];
|
||||
let mut attr_errs = vec![];
|
||||
|
||||
for var in &mut data.variants {
|
||||
if var.attrs.iter().any(|a| a.path.is_ident("sub_discriminant")) {
|
||||
match var.fields.len() {
|
||||
1 => {
|
||||
let Field { ty, .. } = var.fields.iter_mut().next().unwrap();
|
||||
*ty = syn::parse_quote! {
|
||||
<#ty as ToDiscriminant>::Discriminant
|
||||
};
|
||||
is_sub_discriminant.push(true);
|
||||
}
|
||||
n => unimplemented!("#[sub_discriminant] on variants with {} fields is not supported (for now)", n),
|
||||
}
|
||||
} else {
|
||||
var.fields = Fields::Unit;
|
||||
is_sub_discriminant.push(false);
|
||||
}
|
||||
let mut retain = vec![];
|
||||
for (i, a) in var.attrs.iter_mut().enumerate() {
|
||||
if a.path.is_ident("discriminant_attr") {
|
||||
match syn::parse2::<ParenthesizedTokens>(a.tokens.clone()) {
|
||||
Ok(ParenthesizedTokens { tokens, .. }) => {
|
||||
let attr: Attribute = syn::parse_quote! {
|
||||
#[#tokens]
|
||||
};
|
||||
*a = attr;
|
||||
retain.push(i);
|
||||
}
|
||||
Err(e) => {
|
||||
attr_errs.push(syn::Error::new(a.span(), e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var.attrs = var.attrs.iter().enumerate().filter_map(|(i, x)| retain.contains(&i).then(|| x.clone())).collect();
|
||||
}
|
||||
|
||||
let attrs = input
|
||||
.attrs
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter_map(|a| {
|
||||
let a_span = a.span();
|
||||
a.path
|
||||
.is_ident("discriminant_attr")
|
||||
.then(|| match syn::parse2::<ParenthesizedTokens>(a.tokens) {
|
||||
Ok(ParenthesizedTokens { tokens, .. }) => {
|
||||
let attr: Attribute = syn::parse_quote! {
|
||||
#[#tokens]
|
||||
};
|
||||
Some(attr)
|
||||
}
|
||||
Err(e) => {
|
||||
attr_errs.push(syn::Error::new(a_span, e));
|
||||
None
|
||||
}
|
||||
})
|
||||
.and_then(|opt| opt)
|
||||
})
|
||||
.collect::<Vec<Attribute>>();
|
||||
|
||||
if !attr_errs.is_empty() {
|
||||
return Err(attr_errs
|
||||
.into_iter()
|
||||
.reduce(|mut l, r| {
|
||||
l.combine(r);
|
||||
l
|
||||
})
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
let discriminant = ItemEnum {
|
||||
attrs,
|
||||
vis: input.vis,
|
||||
enum_token: data.enum_token,
|
||||
ident: call_site_ident(format!("{}Discriminant", input.ident)),
|
||||
generics: input.generics,
|
||||
brace_token: data.brace_token,
|
||||
variants: data.variants,
|
||||
};
|
||||
|
||||
let input_type = &input.ident;
|
||||
let discriminant_type = &discriminant.ident;
|
||||
let variant = &discriminant.variants.iter().map(|var| &var.ident).collect::<Vec<&Ident>>();
|
||||
|
||||
let (pattern, value) = is_sub_discriminant
|
||||
.into_iter()
|
||||
.map(|b| {
|
||||
(
|
||||
if b {
|
||||
quote::quote! { (x) }
|
||||
} else {
|
||||
quote::quote! { { .. } }
|
||||
},
|
||||
b.then(|| quote::quote! { (x.to_discriminant()) }).unwrap_or_default(),
|
||||
)
|
||||
})
|
||||
.unzip::<_, _, Vec<_>, Vec<_>>();
|
||||
|
||||
let res = quote::quote! {
|
||||
#discriminant
|
||||
|
||||
impl ToDiscriminant for #input_type {
|
||||
type Discriminant = #discriminant_type;
|
||||
|
||||
fn to_discriminant(&self) -> #discriminant_type {
|
||||
match self {
|
||||
#(
|
||||
#input_type::#variant #pattern => #discriminant_type::#variant #value
|
||||
),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&#input_type> for #discriminant_type {
|
||||
fn from(x: &#input_type) -> #discriminant_type {
|
||||
x.to_discriminant()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
207
proc-macros/src/helper_structs.rs
Normal file
207
proc-macros/src/helper_structs.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use std::collections::HashMap;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::token::Paren;
|
||||
use syn::{parenthesized, LitStr, Token};
|
||||
|
||||
pub struct IdentList {
|
||||
pub parts: Punctuated<Ident, Token![,]>,
|
||||
}
|
||||
|
||||
impl Parse for IdentList {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let content;
|
||||
let _paren_token = parenthesized!(content in input);
|
||||
Ok(Self {
|
||||
parts: Punctuated::parse_terminated(&content)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses `("some text")`
|
||||
pub struct AttrInnerSingleString {
|
||||
_paren_token: Paren,
|
||||
pub content: LitStr,
|
||||
}
|
||||
|
||||
impl Parse for AttrInnerSingleString {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let content;
|
||||
let _paren_token = parenthesized!(content in input);
|
||||
Ok(Self {
|
||||
_paren_token,
|
||||
content: content.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses `key="value"`
|
||||
pub struct KeyEqString {
|
||||
key: Ident,
|
||||
_eq_token: Token![=],
|
||||
lit: LitStr,
|
||||
}
|
||||
|
||||
impl Parse for KeyEqString {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
Ok(Self {
|
||||
key: input.parse()?,
|
||||
_eq_token: input.parse()?,
|
||||
lit: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses `(key="value", key="value", …)`
|
||||
pub struct AttrInnerKeyStringMap {
|
||||
_paren_token: Paren,
|
||||
parts: Punctuated<KeyEqString, Token![,]>,
|
||||
}
|
||||
|
||||
impl Parse for AttrInnerKeyStringMap {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let content;
|
||||
let _paren_token = parenthesized!(content in input);
|
||||
Ok(Self {
|
||||
_paren_token,
|
||||
parts: Punctuated::parse_terminated(&content)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AttrInnerKeyStringMap {
|
||||
pub fn multi_into_iter(iter: impl IntoIterator<Item = Self>) -> impl Iterator<Item = (Ident, Vec<LitStr>)> {
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
let mut res = Vec::<(Ident, Vec<LitStr>)>::new();
|
||||
let mut idx = HashMap::<Ident, usize>::new();
|
||||
|
||||
for part in iter.into_iter().flat_map(|x: Self| x.parts) {
|
||||
match idx.entry(part.key) {
|
||||
Entry::Occupied(occ) => {
|
||||
res[*occ.get()].1.push(part.lit);
|
||||
}
|
||||
Entry::Vacant(vac) => {
|
||||
let ident = vac.key().clone();
|
||||
vac.insert(res.len());
|
||||
res.push((ident, vec![part.lit]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses `(left, right)`
|
||||
pub struct Pair<F, S> {
|
||||
pub paren_token: Paren,
|
||||
pub first: F,
|
||||
pub sep: Token![,],
|
||||
pub second: S,
|
||||
}
|
||||
|
||||
impl<F, S> Parse for Pair<F, S>
|
||||
where
|
||||
F: Parse,
|
||||
S: Parse,
|
||||
{
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let content;
|
||||
let paren_token = parenthesized!(content in input);
|
||||
Ok(Self {
|
||||
paren_token,
|
||||
first: content.parse()?,
|
||||
sep: content.parse()?,
|
||||
second: content.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// parses `(...)`
|
||||
pub struct ParenthesizedTokens {
|
||||
pub paren: Paren,
|
||||
pub tokens: TokenStream,
|
||||
}
|
||||
|
||||
impl Parse for ParenthesizedTokens {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let content;
|
||||
let paren = parenthesized!(content in input);
|
||||
Ok(Self { paren, tokens: content.parse()? })
|
||||
}
|
||||
}
|
||||
|
||||
/// parses a comma-delimeted list of `T`s with optional trailing comma
|
||||
pub struct SimpleCommaDelimeted<T>(pub Vec<T>);
|
||||
|
||||
impl<T: Parse> Parse for SimpleCommaDelimeted<T> {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let punct = Punctuated::<T, Token![,]>::parse_terminated(input)?;
|
||||
Ok(Self(punct.into_iter().collect()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn attr_inner_single_string() {
|
||||
let res = syn::parse2::<AttrInnerSingleString>(quote::quote! {
|
||||
("a string literal")
|
||||
});
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.ok().unwrap().content.value(), "a string literal");
|
||||
|
||||
let res = syn::parse2::<AttrInnerSingleString>(quote::quote! {
|
||||
wrong, "stuff"
|
||||
});
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_eq_string() {
|
||||
let res = syn::parse2::<KeyEqString>(quote::quote! {
|
||||
key="value"
|
||||
});
|
||||
assert!(res.is_ok());
|
||||
let res = res.ok().unwrap();
|
||||
assert_eq!(res.key, "key");
|
||||
assert_eq!(res.lit.value(), "value");
|
||||
|
||||
let res = syn::parse2::<KeyEqString>(quote::quote! {
|
||||
wrong, "stuff"
|
||||
});
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attr_inner_key_string_map() {
|
||||
let res = syn::parse2::<AttrInnerKeyStringMap>(quote::quote! {
|
||||
(key="value", key2="value2")
|
||||
});
|
||||
assert!(res.is_ok());
|
||||
let res = res.ok().unwrap();
|
||||
for (item, (k, v)) in res.parts.into_iter().zip(vec![("key", "value"), ("key2", "value2")]) {
|
||||
assert_eq!(item.key, k);
|
||||
assert_eq!(item.lit.value(), v);
|
||||
}
|
||||
|
||||
let res = syn::parse2::<AttrInnerKeyStringMap>(quote::quote! {
|
||||
(key="value", key2="value2",)
|
||||
});
|
||||
assert!(res.is_ok());
|
||||
let res = res.ok().unwrap();
|
||||
for (item, (k, v)) in res.parts.into_iter().zip(vec![("key", "value"), ("key2", "value2")]) {
|
||||
assert_eq!(item.key, k);
|
||||
assert_eq!(item.lit.value(), v);
|
||||
}
|
||||
|
||||
let res = syn::parse2::<AttrInnerKeyStringMap>(quote::quote! {
|
||||
wrong, "stuff"
|
||||
});
|
||||
assert!(res.is_err());
|
||||
}
|
||||
}
|
||||
67
proc-macros/src/helpers.rs
Normal file
67
proc-macros/src/helpers.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use proc_macro2::{Ident, Span};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{Path, PathArguments, PathSegment, Token};
|
||||
|
||||
/// Returns `Ok(Vec<T>)` if all items are `Ok(T)`, else returns a combination of every error encountered (not just the first one)
|
||||
pub fn fold_error_iter<T>(iter: impl Iterator<Item = syn::Result<T>>) -> syn::Result<Vec<T>> {
|
||||
iter.fold(Ok(vec![]), |acc, x| match acc {
|
||||
Ok(mut v) => x.map(|x| {
|
||||
v.push(x);
|
||||
v
|
||||
}),
|
||||
Err(mut e) => match x {
|
||||
Ok(_) => Err(e),
|
||||
Err(e2) => {
|
||||
e.combine(e2);
|
||||
Err(e)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates an ident at the call site
|
||||
pub fn call_site_ident<S: AsRef<str>>(s: S) -> Ident {
|
||||
Ident::new(s.as_ref(), Span::call_site())
|
||||
}
|
||||
|
||||
/// Creates the path `left::right` from the idents `left` and `right`
|
||||
pub fn two_segment_path(left_ident: Ident, right_ident: Ident) -> Path {
|
||||
let mut segments: Punctuated<PathSegment, Token![::]> = Punctuated::new();
|
||||
segments.push(PathSegment {
|
||||
ident: left_ident,
|
||||
arguments: PathArguments::None,
|
||||
});
|
||||
segments.push(PathSegment {
|
||||
ident: right_ident,
|
||||
arguments: PathArguments::None,
|
||||
});
|
||||
|
||||
Path { leading_colon: None, segments }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use quote::ToTokens;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
#[test]
|
||||
fn test_fold_error_iter() {
|
||||
let res = fold_error_iter(vec![Ok(()), Ok(())].into_iter());
|
||||
assert!(res.is_ok());
|
||||
|
||||
let _span = quote::quote! { "" }.span();
|
||||
let res = fold_error_iter(vec![Ok(()), Err(syn::Error::new(_span, "err1")), Err(syn::Error::new(_span, "err2"))].into_iter());
|
||||
assert!(res.is_err());
|
||||
let err = res.unwrap_err();
|
||||
let mut check_err = syn::Error::new(_span, "err1");
|
||||
check_err.combine(syn::Error::new(_span, "err2"));
|
||||
assert_eq!(err.to_compile_error().to_string(), check_err.to_compile_error().to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_two_path() {
|
||||
let _span = quote::quote! { "" }.span();
|
||||
assert_eq!(two_segment_path(Ident::new("a", _span), Ident::new("b", _span)).to_token_stream().to_string(), "a :: b");
|
||||
}
|
||||
}
|
||||
85
proc-macros/src/hint.rs
Normal file
85
proc-macros/src/hint.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use crate::helper_structs::AttrInnerKeyStringMap;
|
||||
use crate::helpers::{fold_error_iter, two_segment_path};
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use syn::{Attribute, Data, DeriveInput, LitStr, Variant};
|
||||
|
||||
fn parse_hint_helper_attrs(attrs: &[Attribute]) -> syn::Result<(Vec<LitStr>, Vec<LitStr>)> {
|
||||
fold_error_iter(
|
||||
attrs
|
||||
.iter()
|
||||
.filter(|a| a.path.get_ident().map_or(false, |i| i == "hint"))
|
||||
.map(|attr| syn::parse2::<AttrInnerKeyStringMap>(attr.tokens.clone())),
|
||||
)
|
||||
.and_then(|v: Vec<AttrInnerKeyStringMap>| {
|
||||
fold_error_iter(AttrInnerKeyStringMap::multi_into_iter(v).map(|(k, mut v)| match v.len() {
|
||||
0 => panic!("internal error: a key without values was somehow inserted into the hashmap"),
|
||||
1 => {
|
||||
let single_val = v.pop().unwrap();
|
||||
Ok((LitStr::new(&k.to_string(), Span::call_site()), single_val))
|
||||
}
|
||||
_ => {
|
||||
// the first value is ok, the other ones should error
|
||||
let after_first = v.into_iter().skip(1);
|
||||
// this call to fold_error_iter will always return Err with a combined error
|
||||
fold_error_iter(after_first.map(|lit| Err(syn::Error::new(lit.span(), format!("value for key {} was already given", k))))).map(|_: Vec<()>| unreachable!())
|
||||
}
|
||||
}))
|
||||
})
|
||||
.map(|v| v.into_iter().unzip())
|
||||
}
|
||||
|
||||
pub fn derive_hint_impl(input_item: TokenStream2) -> syn::Result<TokenStream2> {
|
||||
let input = syn::parse2::<DeriveInput>(input_item)?;
|
||||
|
||||
let ident = input.ident;
|
||||
|
||||
match input.data {
|
||||
Data::Enum(data) => {
|
||||
let variants = data.variants.iter().map(|var: &Variant| two_segment_path(ident.clone(), var.ident.clone())).collect::<Vec<_>>();
|
||||
|
||||
let hint_result = fold_error_iter(data.variants.into_iter().map(|var: Variant| parse_hint_helper_attrs(&var.attrs)));
|
||||
|
||||
hint_result.map(|hints: Vec<(Vec<LitStr>, Vec<LitStr>)>| {
|
||||
let (keys, values): (Vec<Vec<LitStr>>, Vec<Vec<LitStr>>) = hints.into_iter().unzip();
|
||||
let cap: Vec<usize> = keys.iter().map(|v| v.len()).collect();
|
||||
|
||||
quote::quote! {
|
||||
impl Hint for #ident {
|
||||
fn hints(&self) -> ::std::collections::HashMap<String, String> {
|
||||
match self {
|
||||
#(
|
||||
#variants { .. } => {
|
||||
let mut hm = ::std::collections::HashMap::with_capacity(#cap);
|
||||
#(
|
||||
hm.insert(#keys.to_string(), #values.to_string());
|
||||
)*
|
||||
hm
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Data::Struct(_) | Data::Union(_) => {
|
||||
let hint_result = parse_hint_helper_attrs(&input.attrs);
|
||||
|
||||
hint_result.map(|(keys, values)| {
|
||||
let cap = keys.len();
|
||||
|
||||
quote::quote! {
|
||||
impl Hint for #ident {
|
||||
fn hints(&self) -> ::std::collections::HashMap<String, String> {
|
||||
let mut hm = ::std::collections::HashMap::with_capacity(#cap);
|
||||
#(
|
||||
hm.insert(#keys.to_string(), #values.to_string());
|
||||
)*
|
||||
hm
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
389
proc-macros/src/lib.rs
Normal file
389
proc-macros/src/lib.rs
Normal file
@@ -0,0 +1,389 @@
|
||||
mod as_message;
|
||||
mod combined_message_attrs;
|
||||
mod discriminant;
|
||||
mod helper_structs;
|
||||
mod helpers;
|
||||
mod hint;
|
||||
mod transitive_child;
|
||||
|
||||
use crate::as_message::derive_as_message_impl;
|
||||
use crate::combined_message_attrs::combined_message_attrs_impl;
|
||||
use crate::discriminant::derive_discriminant_impl;
|
||||
use crate::helper_structs::AttrInnerSingleString;
|
||||
use crate::hint::derive_hint_impl;
|
||||
use crate::transitive_child::derive_transitive_child_impl;
|
||||
use proc_macro::TokenStream;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
/// Derive the `ToDiscriminant` trait and create a `<Type Name>Discriminant` enum
|
||||
///
|
||||
/// This derive macro is enum-only.
|
||||
///
|
||||
/// The discriminant enum is a copy of the input enum with all fields of every variant removed.\
|
||||
/// *) The exception to that rule is the `#[child]` attribute
|
||||
///
|
||||
/// # Helper attributes
|
||||
/// - `#[sub_discriminant]`: only usable on variants with a single field; instead of no fields, the discriminant of the single field will be included in the discriminant,
|
||||
/// acting as a sub-discriminant.
|
||||
/// - `#[discriminant_attr(…)]`: usable on the enum itself or on any variant; applies `#[…]` in its place on the discriminant.
|
||||
///
|
||||
/// # Attributes on the Discriminant
|
||||
/// All attributes on variants and the type itself are cleared when constructing the discriminant.
|
||||
/// If the discriminant is supposed to also have an attribute, you must double it with `#[discriminant_attr(…)]`
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use graphite_proc_macros::ToDiscriminant;
|
||||
/// # use editor::misc::derivable_custom_traits::ToDiscriminant;
|
||||
/// # use std::ffi::OsString;
|
||||
///
|
||||
/// #[derive(ToDiscriminant)]
|
||||
/// #[discriminant_attr(derive(Debug, Eq, PartialEq))]
|
||||
/// pub enum EnumA {
|
||||
/// A(u8),
|
||||
/// #[sub_discriminant]
|
||||
/// B(EnumB)
|
||||
/// }
|
||||
///
|
||||
/// #[derive(ToDiscriminant)]
|
||||
/// #[discriminant_attr(derive(Debug, Eq, PartialEq))]
|
||||
/// #[discriminant_attr(repr(u8))]
|
||||
/// pub enum EnumB {
|
||||
/// Foo(u8),
|
||||
/// Bar(String),
|
||||
/// #[cfg(feature = "some-feature")]
|
||||
/// #[discriminant_attr(cfg(feature = "some-feature"))]
|
||||
/// WindowsBar(OsString)
|
||||
/// }
|
||||
///
|
||||
/// let a = EnumA::A(1);
|
||||
/// assert_eq!(a.to_discriminant(), EnumADiscriminant::A);
|
||||
/// let b = EnumA::B(EnumB::Bar("bar".to_string()));
|
||||
/// assert_eq!(b.to_discriminant(), EnumADiscriminant::B(EnumBDiscriminant::Bar));
|
||||
/// ```
|
||||
#[proc_macro_derive(ToDiscriminant, attributes(sub_discriminant, discriminant_attr))]
|
||||
pub fn derive_discriminant(input_item: TokenStream) -> TokenStream {
|
||||
TokenStream::from(derive_discriminant_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error()))
|
||||
}
|
||||
|
||||
/// Derive the `TransitiveChild` trait and generate `From` impls to convert into the parent, as well as the top parent type
|
||||
///
|
||||
/// This macro cannot be invoked on the top parent (which has no parent but itself). Instead, implement `TransitiveChild` manually
|
||||
/// like in the example.
|
||||
///
|
||||
/// # Helper Attributes
|
||||
/// - `#[parent(<Type>, <Expr>)]` (**required**): declare the parent type (`<Type>`)
|
||||
/// and a function (`<Expr>`, has to evaluate to a single arg function) for converting a value of this type to the parent type
|
||||
/// - `#[parent_is_top]`: Denote that the parent type has no further parent type (this is required because otherwise the `From` impls for parent and top parent would overlap)
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use graphite_proc_macros::TransitiveChild;
|
||||
/// # use editor::misc::derivable_custom_traits::TransitiveChild;
|
||||
///
|
||||
/// #[derive(Debug, Eq, PartialEq)]
|
||||
/// struct A { u: u8, b: B };
|
||||
///
|
||||
/// impl A {
|
||||
/// pub fn from_b(b: B) -> Self {
|
||||
/// Self { u: 7, b }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl TransitiveChild for A {
|
||||
/// type Parent = Self;
|
||||
/// type TopParent = Self;
|
||||
/// }
|
||||
///
|
||||
/// #[derive(TransitiveChild, Debug, Eq, PartialEq)]
|
||||
/// #[parent(A, A::from_b)]
|
||||
/// #[parent_is_top]
|
||||
/// enum B {
|
||||
/// Foo,
|
||||
/// Bar,
|
||||
/// Child(C)
|
||||
/// }
|
||||
///
|
||||
/// #[derive(TransitiveChild, Debug, Eq, PartialEq)]
|
||||
/// #[parent(B, B::Child)]
|
||||
/// struct C(D);
|
||||
///
|
||||
/// #[derive(TransitiveChild, Debug, Eq, PartialEq)]
|
||||
/// #[parent(C, C)]
|
||||
/// struct D;
|
||||
///
|
||||
/// let d = D;
|
||||
/// assert_eq!(A::from(d), A { u: 7, b: B::Child(C(D)) });
|
||||
/// ```
|
||||
#[proc_macro_derive(TransitiveChild, attributes(parent, parent_is_top))]
|
||||
pub fn derive_transitive_child(input_item: TokenStream) -> TokenStream {
|
||||
TokenStream::from(derive_transitive_child_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error()))
|
||||
}
|
||||
|
||||
/// Derive the `AsMessage` trait
|
||||
///
|
||||
/// # Helper Attributes
|
||||
/// - `#[child]`: only on tuple variants with a single field; Denote that the message path should continue inside the variant
|
||||
///
|
||||
/// # Example
|
||||
/// See also [`TransitiveChild`]
|
||||
/// ```
|
||||
/// # use graphite_proc_macros::{TransitiveChild, AsMessage};
|
||||
/// # use editor::misc::derivable_custom_traits::TransitiveChild;
|
||||
/// # use editor::message_prelude::*;
|
||||
///
|
||||
/// #[derive(AsMessage)]
|
||||
/// pub enum TopMessage {
|
||||
/// A(u8),
|
||||
/// B(u16),
|
||||
/// #[child]
|
||||
/// C(MessageC),
|
||||
/// #[child]
|
||||
/// D(MessageD)
|
||||
/// }
|
||||
///
|
||||
/// impl TransitiveChild for TopMessage {
|
||||
/// type Parent = Self;
|
||||
/// type TopParent = Self;
|
||||
/// }
|
||||
///
|
||||
/// #[derive(TransitiveChild, AsMessage, Copy, Clone)]
|
||||
/// #[parent(TopMessage, TopMessage::C)]
|
||||
/// #[parent_is_top]
|
||||
/// pub enum MessageC {
|
||||
/// X1,
|
||||
/// X2
|
||||
/// }
|
||||
///
|
||||
/// #[derive(TransitiveChild, AsMessage, Copy, Clone)]
|
||||
/// #[parent(TopMessage, TopMessage::D)]
|
||||
/// #[parent_is_top]
|
||||
/// pub enum MessageD {
|
||||
/// Y1,
|
||||
/// #[child]
|
||||
/// Y2(MessageE)
|
||||
/// }
|
||||
///
|
||||
/// #[derive(TransitiveChild, AsMessage, Copy, Clone)]
|
||||
/// #[parent(MessageD, MessageD::Y2)]
|
||||
/// pub enum MessageE {
|
||||
/// Alpha,
|
||||
/// Beta
|
||||
/// }
|
||||
///
|
||||
/// let c = MessageC::X1;
|
||||
/// assert_eq!(c.local_name(), "X1");
|
||||
/// assert_eq!(c.global_name(), "C.X1");
|
||||
/// let d = MessageD::Y2(MessageE::Alpha);
|
||||
/// assert_eq!(d.local_name(), "Y2.Alpha");
|
||||
/// assert_eq!(d.global_name(), "D.Y2.Alpha");
|
||||
/// let e = MessageE::Beta;
|
||||
/// assert_eq!(e.local_name(), "Beta");
|
||||
/// assert_eq!(e.global_name(), "D.Y2.Beta");
|
||||
/// ```
|
||||
#[proc_macro_derive(AsMessage, attributes(child))]
|
||||
pub fn derive_message(input_item: TokenStream) -> TokenStream {
|
||||
TokenStream::from(derive_as_message_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error()))
|
||||
}
|
||||
|
||||
/// This macro is basically an abbreviation for the usual [`ToDiscriminant`], [`TransitiveChild`] and [`AsMessage`] invokations
|
||||
///
|
||||
/// This macro is enum-only.
|
||||
///
|
||||
/// Also note that all three of those derives have to be in scope.
|
||||
///
|
||||
/// # Usage
|
||||
/// There are three possible argument syntaxes you can use:
|
||||
/// 1. no arguments: this is for the top-level message enum. It derives `ToDiscriminant`, `AsMessage` on the discriminant, and implements `TransitiveChild` on both
|
||||
/// (the parent and top parent being the respective types themselves).
|
||||
/// It also derives the following `std` traits on the discriminant: `Debug, Copy, Clone, PartialEq, Eq, Hash`.
|
||||
/// 2. two arguments: this is for message enums whose direct parent is the top level message enum. The syntax is `#[impl_message(<Type>, <Ident>)]`,
|
||||
/// where `<Type>` is the parent message type and `<Ident>` is the identifier of the variant used to construct this child.
|
||||
/// It derives `ToDiscriminant`, `AsMessage` on the discriminant, and `TransitiveChild` on both (adding `#[parent_is_top]` to both).
|
||||
/// It also derives the following `std` traits on the discriminant: `Debug, Copy, Clone, PartialEq, Eq, Hash`.
|
||||
/// 3. three arguments: this is for all other message enums that are transitive children of the top level message enum. The syntax is
|
||||
/// `#[impl_message(<Type>, <Type>, <Ident>)]`, where the first `<Type>` is the top parent message type, the secont `<Type>` is the parent message type
|
||||
/// and `<Ident>` is the identifier of the variant used to construct this child.
|
||||
/// It derives `ToDiscriminant`, `AsMessage` on the discriminant, and `TransitiveChild` on both.
|
||||
/// It also derives the following `std` traits on the discriminant: `Debug, Copy, Clone, PartialEq, Eq, Hash`.
|
||||
/// **This third option will likely change in the future**
|
||||
#[proc_macro_attribute]
|
||||
pub fn impl_message(attr: TokenStream, input_item: TokenStream) -> TokenStream {
|
||||
TokenStream::from(combined_message_attrs_impl(attr.into(), input_item.into()).unwrap_or_else(|err| err.to_compile_error()))
|
||||
}
|
||||
|
||||
/// Derive the `Hint` trait
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use graphite_proc_macros::Hint;
|
||||
/// # use editor::misc::derivable_custom_traits::Hint;
|
||||
///
|
||||
/// #[derive(Hint)]
|
||||
/// pub enum StateMachine {
|
||||
/// #[hint(rmb = "foo", lmb = "bar")]
|
||||
/// Ready,
|
||||
/// #[hint(alt = "baz")]
|
||||
/// RMBDown,
|
||||
/// // no hint (also ok)
|
||||
/// LMBDown
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_derive(Hint, attributes(hint))]
|
||||
pub fn derive_hint(input_item: TokenStream) -> TokenStream {
|
||||
TokenStream::from(derive_hint_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error()))
|
||||
}
|
||||
|
||||
/// The `edge` proc macro does nothing, it is intended for use with an external tool
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// match (example_tool_state, event) {
|
||||
/// (ToolState::Ready, Event::MouseDown(mouse_state)) if *mouse_state == MouseState::Left => {
|
||||
/// #[edge("LMB Down")]
|
||||
/// ToolState::Pending
|
||||
/// }
|
||||
/// (SelectToolState::Pending, Event::MouseUp(mouse_state)) if *mouse_state == MouseState::Left => {
|
||||
/// #[edge("LMB Up: Select Object")]
|
||||
/// SelectToolState::Ready
|
||||
/// }
|
||||
/// (SelectToolState::Pending, Event::MouseMove(x,y)) => {
|
||||
/// #[edge("Mouse Move")]
|
||||
/// SelectToolState::TransformSelected
|
||||
/// }
|
||||
/// (SelectToolState::TransformSelected, Event::MouseMove(x,y)) => {
|
||||
/// #[egde("Mouse Move")]
|
||||
/// SelectToolState::TransformSelected
|
||||
/// }
|
||||
/// (SelectToolState::TransformSelected, Event::MouseUp(mouse_state)) if *mouse_state == MouseState::Left => {
|
||||
/// #[edge("LMB Up")]
|
||||
/// SelectToolState::Ready
|
||||
/// }
|
||||
/// (state, _) => {
|
||||
/// // Do nothing
|
||||
/// state
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn edge(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
// to make sure that only `#[edge("string")]` is allowed
|
||||
let _verify = parse_macro_input!(attr as AttrInnerSingleString);
|
||||
|
||||
item
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
|
||||
fn ts_assert_eq(l: TokenStream2, r: TokenStream2) {
|
||||
// not sure if this is the best way of doing things but if two TokenStreams are equal, their `to_string` is also equal
|
||||
// so there are at least no false negatives
|
||||
assert_eq!(l.to_string(), r.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derive_hint() {
|
||||
let res = derive_hint_impl(quote::quote! {
|
||||
#[hint(key1="val1",key2="val2",)]
|
||||
struct S { a: u8, b: String, c: bool }
|
||||
});
|
||||
assert!(res.is_ok());
|
||||
ts_assert_eq(
|
||||
res.unwrap(),
|
||||
quote::quote! {
|
||||
impl Hint for S {
|
||||
fn hints(&self) -> ::std::collections::HashMap<String, String> {
|
||||
let mut hm = ::std::collections::HashMap::with_capacity(2usize);
|
||||
hm.insert("key1".to_string(), "val1".to_string());
|
||||
hm.insert("key2".to_string(), "val2".to_string());
|
||||
hm
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let res = derive_hint_impl(quote::quote! {
|
||||
enum E {
|
||||
#[hint(key1="val1",key2="val2",)]
|
||||
S { a: u8, b: String, c: bool },
|
||||
#[hint(key3="val3")]
|
||||
X,
|
||||
Y
|
||||
}
|
||||
});
|
||||
assert!(res.is_ok());
|
||||
ts_assert_eq(
|
||||
res.unwrap(),
|
||||
quote::quote! {
|
||||
impl Hint for E {
|
||||
fn hints(&self) -> ::std::collections::HashMap<String, String> {
|
||||
match self {
|
||||
E::S { .. } => {
|
||||
let mut hm = ::std::collections::HashMap::with_capacity(2usize);
|
||||
hm.insert("key1".to_string(), "val1".to_string());
|
||||
hm.insert("key2".to_string(), "val2".to_string());
|
||||
hm
|
||||
}
|
||||
E::X { .. } => {
|
||||
let mut hm = ::std::collections::HashMap::with_capacity(1usize);
|
||||
hm.insert("key3".to_string(), "val3".to_string());
|
||||
hm
|
||||
}
|
||||
E::Y { .. } => {
|
||||
let mut hm = ::std::collections::HashMap::with_capacity(0usize);
|
||||
hm
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let res = derive_hint_impl(quote::quote! {
|
||||
union NoHint {}
|
||||
});
|
||||
assert!(res.is_ok());
|
||||
ts_assert_eq(
|
||||
res.unwrap(),
|
||||
quote::quote! {
|
||||
impl Hint for NoHint {
|
||||
fn hints(&self) -> ::std::collections::HashMap<String, String> {
|
||||
let mut hm = ::std::collections::HashMap::with_capacity(0usize);
|
||||
hm
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let res = derive_hint_impl(quote::quote! {
|
||||
#[hint(a="1", a="2")]
|
||||
struct S;
|
||||
});
|
||||
assert!(res.is_err());
|
||||
|
||||
let res = derive_hint_impl(quote::quote! {
|
||||
#[hint(a="1")]
|
||||
#[hint(b="2")]
|
||||
struct S;
|
||||
});
|
||||
assert!(res.is_ok());
|
||||
ts_assert_eq(
|
||||
res.unwrap(),
|
||||
quote::quote! {
|
||||
impl Hint for S {
|
||||
fn hints(&self) -> ::std::collections::HashMap<String, String> {
|
||||
let mut hm = ::std::collections::HashMap::with_capacity(2usize);
|
||||
hm.insert("a".to_string(), "1".to_string());
|
||||
hm.insert("b".to_string(), "2".to_string());
|
||||
hm
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// note: edge needs no testing since AttrInnerSingleString has testing and that's all you'd need to test with edge
|
||||
}
|
||||
54
proc-macros/src/transitive_child.rs
Normal file
54
proc-macros/src/transitive_child.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use crate::helper_structs::Pair;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use syn::{Attribute, DeriveInput, Expr, Type};
|
||||
|
||||
pub fn derive_transitive_child_impl(input_item: TokenStream) -> syn::Result<TokenStream> {
|
||||
let input = syn::parse2::<DeriveInput>(input_item).unwrap();
|
||||
|
||||
let Attribute { tokens, .. } = input
|
||||
.attrs
|
||||
.iter()
|
||||
.find(|a| a.path.is_ident("parent"))
|
||||
.ok_or_else(|| syn::Error::new(Span::call_site(), format!("tried to derive TransitiveChild without a #[parent] attribute (on {})", input.ident)))?;
|
||||
|
||||
let parent_is_top = input.attrs.iter().any(|a| a.path.is_ident("parent_is_top"));
|
||||
|
||||
let Pair {
|
||||
first: parent_type,
|
||||
second: to_parent,
|
||||
..
|
||||
} = syn::parse2::<Pair<Type, Expr>>(tokens.clone())?;
|
||||
|
||||
let top_parent_type: Type = syn::parse_quote! { <#parent_type as TransitiveChild>::TopParent };
|
||||
|
||||
let input_type = &input.ident;
|
||||
|
||||
let trait_impl = quote::quote! {
|
||||
impl TransitiveChild for #input_type {
|
||||
type Parent = #parent_type;
|
||||
type TopParent = #top_parent_type;
|
||||
}
|
||||
};
|
||||
|
||||
let from_for_parent = quote::quote! {
|
||||
impl From<#input_type> for #parent_type {
|
||||
fn from(x: #input_type) -> #parent_type {
|
||||
(#to_parent)(x)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let from_for_top = quote::quote! {
|
||||
impl From<#input_type> for #top_parent_type {
|
||||
fn from(x: #input_type) -> #top_parent_type {
|
||||
#top_parent_type::from((#to_parent)(x))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(if parent_is_top {
|
||||
quote::quote! { #trait_impl #from_for_parent }
|
||||
} else {
|
||||
quote::quote! { #trait_impl #from_for_parent #from_for_top }
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user