chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

57
vendor/educe/src/common/bound.rs vendored Normal file
View File

@@ -0,0 +1,57 @@
use syn::{punctuated::Punctuated, token::Comma, GenericParam, Meta, Path, Type, WherePredicate};
use crate::common::where_predicates_bool::{
create_where_predicates_from_all_generic_parameters,
create_where_predicates_from_generic_parameters_check_types, meta_2_where_predicates,
WherePredicates, WherePredicatesOrBool,
};
pub(crate) enum Bound {
Disabled,
Auto,
Custom(WherePredicates),
All,
}
impl Bound {
#[inline]
pub(crate) fn from_meta(meta: &Meta) -> syn::Result<Self> {
debug_assert!(meta.path().is_ident("bound"));
Ok(match meta_2_where_predicates(meta)? {
WherePredicatesOrBool::WherePredicates(where_predicates) => {
Self::Custom(where_predicates)
},
WherePredicatesOrBool::Bool(b) => {
if b {
Self::Auto
} else {
Self::Disabled
}
},
WherePredicatesOrBool::All => Self::All,
})
}
}
impl Bound {
#[inline]
pub(crate) fn into_where_predicates_by_generic_parameters_check_types(
self,
params: &Punctuated<GenericParam, Comma>,
bound_trait: &Path,
types: &[&Type],
supertraits: &[proc_macro2::TokenStream],
) -> Punctuated<WherePredicate, Comma> {
match self {
Self::Disabled => Punctuated::new(),
Self::Auto => create_where_predicates_from_generic_parameters_check_types(
bound_trait,
types,
supertraits,
),
Self::Custom(where_predicates) => where_predicates,
Self::All => create_where_predicates_from_all_generic_parameters(params, bound_trait),
}
}
}

109
vendor/educe/src/common/expr.rs vendored Normal file
View File

@@ -0,0 +1,109 @@
use quote::{quote, ToTokens};
use syn::{spanned::Spanned, Expr, Lit, Meta, Type};
use super::path::path_to_string;
const INT_TYPES: [&str; 12] =
["u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize"];
const FLOAT_TYPES: [&str; 2] = ["f32", "f64"];
#[inline]
pub(crate) fn meta_2_expr(meta: &Meta) -> syn::Result<Expr> {
match &meta {
Meta::NameValue(name_value) => Ok(name_value.value.clone()),
Meta::List(list) => list.parse_args::<Expr>(),
Meta::Path(path) => Err(syn::Error::new(
path.span(),
format!("expected `{path} = Expr` or `{path}(Expr)`", path = path_to_string(path)),
)),
}
}
#[inline]
pub(crate) fn auto_adjust_expr(expr: Expr, ty: Option<&Type>) -> Expr {
match &expr {
Expr::Lit(lit) => {
match &lit.lit {
Lit::Int(lit) => {
if let Some(Type::Path(ty)) = ty {
let ty_string = ty.into_token_stream().to_string();
if lit.suffix() == ty_string || INT_TYPES.contains(&ty_string.as_str()) {
// don't call into
return expr;
}
}
},
Lit::Float(lit) => {
if let Some(Type::Path(ty)) = ty {
let ty_string = ty.into_token_stream().to_string();
if lit.suffix() == ty_string || FLOAT_TYPES.contains(&ty_string.as_str()) {
// don't call into
return expr;
}
}
},
Lit::Str(_) => {
if let Some(Type::Reference(ty)) = ty {
let ty_string = ty.elem.clone().into_token_stream().to_string();
if ty_string == "str" {
// don't call into
return expr;
}
}
},
Lit::Bool(_) => {
if let Some(Type::Path(ty)) = ty {
let ty_string = ty.into_token_stream().to_string();
if ty_string == "bool" {
// don't call into
return expr;
}
}
},
Lit::Char(_) => {
if let Some(Type::Path(ty)) = ty {
let ty_string = ty.into_token_stream().to_string();
if ty_string == "char" {
// don't call into
return expr;
}
}
},
Lit::Byte(_) => {
if let Some(Type::Path(ty)) = ty {
let ty_string = ty.into_token_stream().to_string();
if ty_string == "u8" {
// don't call into
return expr;
}
}
},
Lit::ByteStr(_) => {
if let Some(Type::Reference(ty)) = ty {
if let Type::Array(ty) = ty.elem.as_ref() {
if let Type::Path(ty) = ty.elem.as_ref() {
let ty_string = ty.into_token_stream().to_string();
if ty_string == "u8" {
// don't call into
return expr;
}
}
}
}
},
_ => (),
}
syn::parse2(quote!(::core::convert::Into::into(#expr))).unwrap()
},
_ => expr,
}
}

161
vendor/educe/src/common/ident_bool.rs vendored Normal file
View File

@@ -0,0 +1,161 @@
use syn::{
parse::{Parse, ParseStream},
spanned::Spanned,
Expr, Ident, Lit, LitBool, LitStr, Meta, MetaNameValue,
};
use super::path::path_to_string;
#[derive(Debug)]
pub(crate) enum IdentOrBool {
Ident(Ident),
Bool(bool),
}
impl Parse for IdentOrBool {
#[inline]
fn parse(input: ParseStream) -> syn::Result<Self> {
if let Ok(lit) = input.parse::<Lit>() {
match lit {
Lit::Bool(lit) => return Ok(Self::Bool(lit.value)),
Lit::Str(lit) => {
return match lit.parse::<Ident>() {
Ok(ident) => Ok(Self::Ident(ident)),
Err(_) if lit.value().is_empty() => Ok(Self::Bool(false)),
Err(error) => Err(error),
}
},
_ => (),
}
}
Ok(Self::Ident(input.parse::<Ident>()?))
}
}
#[inline]
pub(crate) fn meta_name_value_2_ident(name_value: &MetaNameValue) -> syn::Result<Ident> {
match &name_value.value {
Expr::Lit(lit) => {
if let Lit::Str(lit) = &lit.lit {
return lit.parse();
}
},
Expr::Path(path) => {
if let Some(ident) = path.path.get_ident() {
return Ok(ident.clone());
}
},
_ => (),
}
Err(syn::Error::new(
name_value.value.span(),
format!("expected `{path} = Ident`", path = path_to_string(&name_value.path)),
))
}
#[inline]
pub(crate) fn meta_2_ident(meta: &Meta) -> syn::Result<Ident> {
match &meta {
Meta::NameValue(name_value) => meta_name_value_2_ident(name_value),
Meta::List(list) => {
if let Ok(lit) = list.parse_args::<LitStr>() {
lit.parse()
} else {
list.parse_args()
}
},
Meta::Path(path) => Err(syn::Error::new(
path.span(),
format!("expected `{path} = Ident` or `{path}(Ident)`", path = path_to_string(path)),
)),
}
}
#[inline]
pub(crate) fn meta_name_value_2_bool(name_value: &MetaNameValue) -> syn::Result<bool> {
if let Expr::Lit(lit) = &name_value.value {
if let Lit::Bool(b) = &lit.lit {
return Ok(b.value);
}
}
Err(syn::Error::new(
name_value.value.span(),
format!("expected `{path} = false`", path = path_to_string(&name_value.path)),
))
}
#[inline]
pub(crate) fn meta_2_bool(meta: &Meta) -> syn::Result<bool> {
match &meta {
Meta::NameValue(name_value) => meta_name_value_2_bool(name_value),
Meta::List(list) => Ok(list.parse_args::<LitBool>()?.value),
Meta::Path(path) => Err(syn::Error::new(
path.span(),
format!("expected `{path} = false` or `{path}(false)`", path = path_to_string(path)),
)),
}
}
#[inline]
pub(crate) fn meta_2_bool_allow_path(meta: &Meta) -> syn::Result<bool> {
match &meta {
Meta::Path(_) => Ok(true),
Meta::NameValue(name_value) => meta_name_value_2_bool(name_value),
Meta::List(list) => Ok(list.parse_args::<LitBool>()?.value),
}
}
#[inline]
pub(crate) fn meta_name_value_2_ident_and_bool(
name_value: &MetaNameValue,
) -> syn::Result<IdentOrBool> {
match &name_value.value {
Expr::Lit(lit) => match &lit.lit {
Lit::Str(lit) => match lit.parse::<Ident>() {
Ok(ident) => return Ok(IdentOrBool::Ident(ident)),
Err(_) if lit.value().is_empty() => {
return Ok(IdentOrBool::Bool(false));
},
Err(error) => {
return Err(error);
},
},
Lit::Bool(lit) => {
return Ok(IdentOrBool::Bool(lit.value));
},
_ => (),
},
Expr::Path(path) => {
if let Some(ident) = path.path.get_ident() {
return Ok(IdentOrBool::Ident(ident.clone()));
}
},
_ => (),
}
Err(syn::Error::new(
name_value.value.span(),
format!(
"expected `{path} = Ident` or `{path} = false`",
path = path_to_string(&name_value.path)
),
))
}
#[inline]
pub(crate) fn meta_2_ident_and_bool(meta: &Meta) -> syn::Result<IdentOrBool> {
match &meta {
Meta::NameValue(name_value) => meta_name_value_2_ident_and_bool(name_value),
Meta::List(list) => list.parse_args::<IdentOrBool>(),
Meta::Path(path) => Err(syn::Error::new(
path.span(),
format!(
"expected `{path} = Ident`, `{path}(Ident)`, `{path} = false`, or `{path}(false)`",
path = path_to_string(path)
),
)),
}
}

56
vendor/educe/src/common/ident_index.rs vendored Normal file
View File

@@ -0,0 +1,56 @@
use quote::ToTokens;
use syn::{Ident, Index};
pub(crate) enum IdentOrIndex {
Ident(Ident),
Index(Index),
}
impl From<Ident> for IdentOrIndex {
#[inline]
fn from(value: Ident) -> Self {
Self::Ident(value)
}
}
impl From<Index> for IdentOrIndex {
#[inline]
fn from(value: Index) -> Self {
Self::Index(value)
}
}
impl From<&Ident> for IdentOrIndex {
#[inline]
fn from(value: &Ident) -> Self {
Self::Ident(value.clone())
}
}
impl From<usize> for IdentOrIndex {
#[inline]
fn from(value: usize) -> Self {
Self::Index(Index::from(value))
}
}
impl ToTokens for IdentOrIndex {
#[inline]
fn to_tokens(&self, token_stream: &mut proc_macro2::TokenStream) {
match self {
Self::Ident(ident) => ToTokens::to_tokens(ident, token_stream),
Self::Index(index) => ToTokens::to_tokens(index, token_stream),
}
}
}
impl IdentOrIndex {
#[inline]
pub(crate) fn from_ident_with_index(ident: Option<&Ident>, index: usize) -> IdentOrIndex {
if let Some(ident) = ident {
Self::from(ident)
} else {
Self::from(index)
}
}
}

63
vendor/educe/src/common/int.rs vendored Normal file
View File

@@ -0,0 +1,63 @@
use syn::{spanned::Spanned, Expr, Lit, Meta, MetaNameValue, UnOp};
use super::path::path_to_string;
#[inline]
pub(crate) fn meta_name_value_2_isize(name_value: &MetaNameValue) -> syn::Result<isize> {
match &name_value.value {
Expr::Lit(lit) => match &lit.lit {
Lit::Str(lit) => {
return lit
.value()
.parse::<isize>()
.map_err(|error| syn::Error::new(lit.span(), error))
},
Lit::Int(lit) => return lit.base10_parse(),
_ => (),
},
Expr::Unary(unary) => {
if let UnOp::Neg(_) = unary.op {
if let Expr::Lit(lit) = unary.expr.as_ref() {
if let Lit::Int(lit) = &lit.lit {
let s = format!("-{}", lit.base10_digits());
return s
.parse::<isize>()
.map_err(|error| syn::Error::new(lit.span(), error));
}
}
}
},
_ => (),
}
Err(syn::Error::new(
name_value.value.span(),
format!("expected `{path} = integer`", path = path_to_string(&name_value.path)),
))
}
#[inline]
pub(crate) fn meta_2_isize(meta: &Meta) -> syn::Result<isize> {
match &meta {
Meta::NameValue(name_value) => meta_name_value_2_isize(name_value),
Meta::List(list) => {
let lit = list.parse_args::<Lit>()?;
match &lit {
Lit::Str(lit) => {
lit.value().parse::<isize>().map_err(|error| syn::Error::new(lit.span(), error))
},
Lit::Int(lit) => lit.base10_parse(),
_ => Err(syn::Error::new(lit.span(), "not an integer")),
}
},
Meta::Path(path) => Err(syn::Error::new(
path.span(),
format!(
"expected `{path} = integer` or `{path}(integer)`",
path = path_to_string(path)
),
)),
}
}

43
vendor/educe/src/common/mod.rs vendored Normal file
View File

@@ -0,0 +1,43 @@
#[allow(dead_code)]
pub(crate) mod bound;
#[allow(dead_code)]
pub(crate) mod path;
#[allow(dead_code)]
pub(crate) mod r#type;
#[allow(dead_code)]
pub(crate) mod where_predicates_bool;
#[cfg(feature = "Default")]
#[allow(dead_code)]
pub(crate) mod expr;
#[cfg(any(
feature = "Debug",
feature = "PartialEq",
feature = "PartialOrd",
feature = "Ord",
feature = "Hash",
feature = "Default"
))]
#[allow(dead_code)]
pub(crate) mod ident_bool;
#[cfg(any(
feature = "Debug",
feature = "PartialEq",
feature = "PartialOrd",
feature = "Ord",
feature = "Hash",
feature = "Deref",
feature = "DerefMut",
feature = "Into"
))]
#[allow(dead_code)]
pub(crate) mod ident_index;
#[cfg(any(feature = "PartialOrd", feature = "Ord"))]
#[allow(dead_code)]
pub(crate) mod int;
#[cfg(any(feature = "Debug", feature = "PartialEq", feature = "Hash"))]
#[allow(dead_code)]
pub(crate) mod unsafe_punctuated_meta;
#[cfg(any(feature = "PartialOrd", feature = "Ord", feature = "Into"))]
pub(crate) mod tools;

43
vendor/educe/src/common/path.rs vendored Normal file
View File

@@ -0,0 +1,43 @@
use quote::ToTokens;
use syn::{spanned::Spanned, Expr, Lit, LitStr, Meta, MetaNameValue, Path};
#[inline]
pub(crate) fn meta_name_value_2_path(name_value: &MetaNameValue) -> syn::Result<Path> {
match &name_value.value {
Expr::Lit(lit) => {
if let Lit::Str(lit) = &lit.lit {
return lit.parse();
}
},
Expr::Path(path) => return Ok(path.path.clone()),
_ => (),
}
Err(syn::Error::new(
name_value.value.span(),
format!("expected `{path} = Path`", path = path_to_string(&name_value.path)),
))
}
#[inline]
pub(crate) fn meta_2_path(meta: &Meta) -> syn::Result<Path> {
match &meta {
Meta::NameValue(name_value) => meta_name_value_2_path(name_value),
Meta::List(list) => {
if let Ok(lit) = list.parse_args::<LitStr>() {
lit.parse()
} else {
list.parse_args()
}
},
Meta::Path(path) => Err(syn::Error::new(
path.span(),
format!("expected `{path} = Path` or `{path}(Path)`", path = path_to_string(path)),
)),
}
}
#[inline]
pub(crate) fn path_to_string(path: &Path) -> String {
path.into_token_stream().to_string().replace(' ', "")
}

View File

@@ -0,0 +1,169 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::{ToTokens, TokenStreamExt};
use syn::{
punctuated::Punctuated, spanned::Spanned, Data, DeriveInput, Expr, Lit, Meta, Token, UnOp,
};
#[derive(Debug)]
pub(crate) enum DiscriminantType {
ISize,
I8,
I16,
I32,
I64,
I128,
USize,
U8,
U16,
U32,
U64,
U128,
}
impl DiscriminantType {
#[inline]
pub(crate) fn parse_str<S: AsRef<str>>(s: S) -> Option<Self> {
match s.as_ref() {
"i8" => Some(Self::I8),
"i16" => Some(Self::I16),
"i32" => Some(Self::I32),
"i64" => Some(Self::I64),
"i128" => Some(Self::I128),
"isize" => Some(Self::ISize),
"u8" => Some(Self::U8),
"u16" => Some(Self::U16),
"u32" => Some(Self::U32),
"u64" => Some(Self::U64),
"u128" => Some(Self::U128),
"usize" => Some(Self::USize),
_ => None,
}
}
#[inline]
pub(crate) const fn as_str(&self) -> &'static str {
match self {
Self::ISize => "isize",
Self::I8 => "i8",
Self::I16 => "i16",
Self::I32 => "i32",
Self::I64 => "i64",
Self::I128 => "i128",
Self::USize => "usize",
Self::U8 => "u8",
Self::U16 => "u16",
Self::U32 => "u32",
Self::U64 => "u64",
Self::U128 => "u128",
}
}
}
impl ToTokens for DiscriminantType {
#[inline]
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.append(Ident::new(self.as_str(), Span::call_site()));
}
}
impl DiscriminantType {
pub(crate) fn from_ast(ast: &DeriveInput) -> syn::Result<Self> {
if let Data::Enum(data) = &ast.data {
for attr in ast.attrs.iter() {
if attr.path().is_ident("repr") {
// #[repr(u8)], #[repr(u16)], ..., etc.
if let Meta::List(list) = &attr.meta {
let result =
list.parse_args_with(Punctuated::<Ident, Token![,]>::parse_terminated)?;
if let Some(value) = result.into_iter().next() {
if let Some(t) = Self::parse_str(value.to_string()) {
return Ok(t);
}
}
}
}
}
let mut min = i128::MAX;
let mut max = i128::MIN;
let mut counter = 0i128;
for variant in data.variants.iter() {
if let Some((_, exp)) = variant.discriminant.as_ref() {
match exp {
Expr::Lit(lit) => {
if let Lit::Int(lit) = &lit.lit {
counter = lit
.base10_parse()
.map_err(|error| syn::Error::new(lit.span(), error))?;
} else {
return Err(syn::Error::new(lit.span(), "not an integer"));
}
},
Expr::Unary(unary) => {
if let UnOp::Neg(_) = unary.op {
if let Expr::Lit(lit) = unary.expr.as_ref() {
if let Lit::Int(lit) = &lit.lit {
match lit.base10_parse::<i128>() {
Ok(i) => {
counter = -i;
},
Err(error) => {
// overflow
if lit.base10_digits()
== "170141183460469231731687303715884105728"
{
counter = i128::MIN;
} else {
return Err(syn::Error::new(lit.span(), error));
}
},
}
} else {
return Err(syn::Error::new(lit.span(), "not an integer"));
}
} else {
return Err(syn::Error::new(
unary.expr.span(),
"not a literal",
));
}
} else {
return Err(syn::Error::new(
unary.op.span(),
"this operation is not allow here",
));
}
},
_ => return Err(syn::Error::new(exp.span(), "not a literal")),
}
}
if min > counter {
min = counter;
}
if max < counter {
max = counter;
}
counter = counter.saturating_add(1);
}
Ok(if min >= i8::MIN as i128 && max <= i8::MAX as i128 {
Self::I8
} else if min >= i16::MIN as i128 && max <= i16::MAX as i128 {
Self::I16
} else if min >= i32::MIN as i128 && max <= i32::MAX as i128 {
Self::I32
} else if min >= i64::MIN as i128 && max <= i64::MAX as i128 {
Self::I64
} else {
Self::I128
})
} else {
Err(syn::Error::new(ast.span(), "not an enum"))
}
}
}

View File

@@ -0,0 +1,100 @@
use std::{
cmp::Ordering,
fmt::{self, Display, Formatter},
hash::{Hash, Hasher},
str::FromStr,
};
use proc_macro2::Span;
use quote::ToTokens;
use syn::{spanned::Spanned, Path, Type};
#[derive(Debug, Clone)]
pub(crate) struct HashType(String, Span);
impl PartialEq for HashType {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0)
}
}
impl Eq for HashType {}
impl PartialOrd for HashType {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for HashType {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl Hash for HashType {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
Hash::hash(&self.0, state);
}
}
impl Display for HashType {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0.replace("& '", "&'"), f)
}
}
impl From<Type> for HashType {
#[inline]
fn from(value: Type) -> Self {
Self::from(&value)
}
}
impl From<&Type> for HashType {
#[inline]
fn from(value: &Type) -> Self {
Self(value.into_token_stream().to_string(), value.span())
}
}
impl From<Path> for HashType {
#[inline]
fn from(value: Path) -> Self {
Self::from(&value)
}
}
impl From<&Path> for HashType {
#[inline]
fn from(value: &Path) -> Self {
Self(value.into_token_stream().to_string(), value.span())
}
}
#[allow(dead_code)]
impl HashType {
#[inline]
pub(crate) fn to_type(&self) -> Type {
syn::parse_str(self.0.as_str()).unwrap()
}
#[inline]
pub(crate) fn span(&self) -> Span {
self.1
}
}
impl ToTokens for HashType {
#[inline]
fn to_tokens(&self, token_stream: &mut proc_macro2::TokenStream) {
let ty = proc_macro2::TokenStream::from_str(self.0.as_str()).unwrap();
token_stream.extend(ty);
}
}

11
vendor/educe/src/common/tools/mod.rs vendored Normal file
View File

@@ -0,0 +1,11 @@
#[cfg(any(feature = "PartialOrd", feature = "Ord"))]
mod discriminant_type;
#[cfg(any(feature = "PartialOrd", feature = "Ord"))]
pub(crate) use discriminant_type::*;
#[cfg(feature = "Into")]
mod hash_type;
#[cfg(feature = "Into")]
pub(crate) use hash_type::*;

51
vendor/educe/src/common/type.rs vendored Normal file
View File

@@ -0,0 +1,51 @@
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
Meta, Token, Type,
};
pub(crate) struct TypeWithPunctuatedMeta {
pub(crate) ty: Type,
pub(crate) list: Punctuated<Meta, Token![,]>,
}
impl Parse for TypeWithPunctuatedMeta {
#[inline]
fn parse(input: ParseStream) -> syn::Result<Self> {
let ty = input.parse::<Type>()?;
if input.is_empty() {
return Ok(Self {
ty,
list: Punctuated::new(),
});
}
input.parse::<Token![,]>()?;
let list = input.parse_terminated(Meta::parse, Token![,])?;
Ok(Self {
ty,
list,
})
}
}
#[inline]
pub(crate) fn dereference(ty: &Type) -> &Type {
if let Type::Reference(ty) = ty {
dereference(ty.elem.as_ref())
} else {
ty
}
}
#[inline]
pub(crate) fn dereference_changed(ty: &Type) -> (&Type, bool) {
if let Type::Reference(ty) = ty {
(dereference(ty.elem.as_ref()), true)
} else {
(ty, false)
}
}

View File

@@ -0,0 +1,35 @@
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
Meta, Token,
};
pub(crate) struct UnsafePunctuatedMeta {
pub(crate) list: Punctuated<Meta, Token![,]>,
pub(crate) has_unsafe: bool,
}
impl Parse for UnsafePunctuatedMeta {
#[inline]
fn parse(input: ParseStream) -> syn::Result<Self> {
let has_unsafe = input.parse::<Token![unsafe]>().is_ok();
if input.is_empty() {
return Ok(Self {
list: Punctuated::new(),
has_unsafe,
});
}
if has_unsafe {
input.parse::<Token![,]>()?;
}
let list = input.parse_terminated(Meta::parse, Token![,])?;
Ok(Self {
list,
has_unsafe,
})
}
}

View File

@@ -0,0 +1,122 @@
use quote::{quote, ToTokens};
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
token::Comma,
Expr, GenericParam, Lit, Meta, MetaNameValue, Path, Token, Type, WherePredicate,
};
use super::path::path_to_string;
pub(crate) type WherePredicates = Punctuated<WherePredicate, Token![,]>;
pub(crate) enum WherePredicatesOrBool {
WherePredicates(WherePredicates),
Bool(bool),
All,
}
impl WherePredicatesOrBool {
fn from_lit(lit: &Lit) -> syn::Result<Self> {
Ok(match lit {
Lit::Bool(lit) => Self::Bool(lit.value),
Lit::Str(lit) => match lit.parse_with(WherePredicates::parse_terminated) {
Ok(where_predicates) => Self::WherePredicates(where_predicates),
Err(_) if lit.value().is_empty() => Self::Bool(false),
Err(error) => return Err(error),
},
other => {
return Err(syn::Error::new(
other.span(),
"unexpected kind of literal (only boolean or string allowed)",
))
},
})
}
}
impl Parse for WherePredicatesOrBool {
#[inline]
fn parse(input: ParseStream) -> syn::Result<Self> {
if let Ok(lit) = input.parse::<Lit>() {
return Self::from_lit(&lit);
}
if let Ok(_star) = input.parse::<Token![*]>() {
return Ok(Self::All);
}
Ok(Self::WherePredicates(input.parse_terminated(WherePredicate::parse, Token![,])?))
}
}
#[inline]
pub(crate) fn meta_name_value_2_where_predicates_bool(
name_value: &MetaNameValue,
) -> syn::Result<WherePredicatesOrBool> {
if let Expr::Lit(lit) = &name_value.value {
return WherePredicatesOrBool::from_lit(&lit.lit);
}
Err(syn::Error::new(
name_value.value.span(),
format!(
"expected `{path} = \"where_predicates\"` or `{path} = false`",
path = path_to_string(&name_value.path)
),
))
}
#[inline]
pub(crate) fn meta_2_where_predicates(meta: &Meta) -> syn::Result<WherePredicatesOrBool> {
match &meta {
Meta::NameValue(name_value) => meta_name_value_2_where_predicates_bool(name_value),
Meta::List(list) => list.parse_args::<WherePredicatesOrBool>(),
Meta::Path(path) => Err(syn::Error::new(
path.span(),
format!(
"expected `{path} = \"where_predicates\"`, `{path}(where_predicates)`, `{path} = \
false`, or `{path}(false)`",
path = path.clone().into_token_stream()
),
)),
}
}
#[inline]
pub(crate) fn create_where_predicates_from_all_generic_parameters(
params: &Punctuated<GenericParam, Comma>,
bound_trait: &Path,
) -> WherePredicates {
let mut where_predicates = Punctuated::new();
for param in params {
if let GenericParam::Type(ty) = param {
let ident = &ty.ident;
where_predicates.push(syn::parse2(quote! { #ident: #bound_trait }).unwrap());
}
}
where_predicates
}
#[inline]
pub(crate) fn create_where_predicates_from_generic_parameters_check_types(
bound_trait: &Path,
types: &[&Type],
supertraits: &[proc_macro2::TokenStream],
) -> WherePredicates {
let mut where_predicates = Punctuated::new();
for t in types {
where_predicates.push(syn::parse2(quote! { #t: #bound_trait }).unwrap());
}
for supertrait in supertraits {
where_predicates.push(syn::parse2(quote! { Self: #supertrait }).unwrap());
}
where_predicates
}

2135
vendor/educe/src/lib.rs vendored Normal file

File diff suppressed because it is too large Load Diff

130
vendor/educe/src/panic.rs vendored Normal file
View File

@@ -0,0 +1,130 @@
use core::fmt::{self, Display, Formatter};
use proc_macro2::Span;
use syn::{spanned::Spanned, Ident, Path, Variant};
use crate::{common::path::path_to_string, Trait};
struct DisplayStringSlice<'a>(&'a [&'static str]);
impl<'a> Display for DisplayStringSlice<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if !self.0.is_empty() {
f.write_str(", which should be reformatted as follows:")?;
for &s in self.0 {
f.write_str("\n ")?;
f.write_str(s)?;
}
}
Ok(())
}
}
struct DisplayTraits;
impl Display for DisplayTraits {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
for t in &Trait::VARIANTS[..Trait::VARIANTS.len() - 1] {
f.write_str("\n ")?;
f.write_fmt(format_args!("{t:?}"))?;
}
Ok(())
}
}
#[inline]
pub(crate) fn derive_attribute_not_set_up_yet() -> syn::Error {
syn::Error::new(
Span::call_site(),
"you are using `Educe` in the `derive` attribute, but it has not been set up yet",
)
}
#[inline]
pub(crate) fn attribute_incorrect_place(name: &Ident) -> syn::Error {
syn::Error::new(name.span(), format!("the `{name}` attribute cannot be placed here"))
}
#[inline]
pub(crate) fn attribute_incorrect_format_with_span(
name: &Ident,
span: Span,
correct_usage: &[&'static str],
) -> syn::Error {
if correct_usage.is_empty() {
attribute_incorrect_place(name)
} else {
syn::Error::new(
span,
format!(
"you are using an incorrect format of the `{name}` attribute{}",
DisplayStringSlice(correct_usage)
),
)
}
}
#[inline]
pub(crate) fn attribute_incorrect_format(
name: &Ident,
correct_usage: &[&'static str],
) -> syn::Error {
attribute_incorrect_format_with_span(name, name.span(), correct_usage)
}
#[inline]
pub(crate) fn parameter_reset(name: &Ident) -> syn::Error {
syn::Error::new(name.span(), format!("you are trying to reset the `{name}` parameter"))
}
#[inline]
pub(crate) fn educe_format_incorrect(name: &Ident) -> syn::Error {
attribute_incorrect_format(name, &[stringify!(#[educe(Trait1, Trait2, ..., TraitN)])])
}
#[inline]
pub(crate) fn unsupported_trait(name: &Path) -> syn::Error {
let span = name.span();
match name.get_ident() {
Some(name) => syn::Error::new(
span,
format!("unsupported trait `{name}`, available traits:{DisplayTraits}"),
),
None => {
let name = path_to_string(name);
syn::Error::new(
span,
format!("unsupported trait `{name}`, available traits:{DisplayTraits}"),
)
},
}
}
#[inline]
pub(crate) fn reuse_a_trait(name: &Ident) -> syn::Error {
syn::Error::new(name.span(), format!("the trait `{name}` is used repeatedly"))
}
#[inline]
pub(crate) fn trait_not_used(name: &Ident) -> syn::Error {
syn::Error::new(name.span(), format!("the trait `{name}` is not used"))
}
#[inline]
pub(crate) fn trait_not_support_union(name: &Ident) -> syn::Error {
syn::Error::new(name.span(), format!("the trait `{name}` does not support to a union"))
}
#[inline]
pub(crate) fn trait_not_support_unit_variant(name: &Ident, variant: &Variant) -> syn::Error {
syn::Error::new(
variant.span(),
format!("the trait `{name}` cannot be implemented for an enum which has unit variants"),
)
}

88
vendor/educe/src/supported_traits.rs vendored Normal file
View File

@@ -0,0 +1,88 @@
#[cfg(not(any(
feature = "Debug",
feature = "Clone",
feature = "Copy",
feature = "PartialEq",
feature = "Eq",
feature = "PartialOrd",
feature = "Ord",
feature = "Hash",
feature = "Default",
feature = "Deref",
feature = "DerefMut",
feature = "Into",
)))]
compile_error!("at least one of the trait features must be enabled");
use enum_ordinalize::Ordinalize;
use syn::Path;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ordinalize)]
#[ordinalize(impl_trait = false)]
#[ordinalize(variants(pub(crate) const VARIANTS))]
pub(crate) enum Trait {
#[cfg(feature = "Debug")]
Debug,
#[cfg(feature = "Clone")]
Clone,
#[cfg(feature = "Copy")]
Copy,
#[cfg(feature = "PartialEq")]
PartialEq,
#[cfg(feature = "Eq")]
Eq,
#[cfg(feature = "PartialOrd")]
PartialOrd,
#[cfg(feature = "Ord")]
Ord,
#[cfg(feature = "Hash")]
Hash,
#[cfg(feature = "Default")]
Default,
#[cfg(feature = "Deref")]
Deref,
#[cfg(feature = "DerefMut")]
DerefMut,
#[cfg(feature = "Into")]
Into,
_Nothing,
}
impl Trait {
#[inline]
pub(crate) fn from_path(path: &Path) -> Option<Self> {
let ident_string = match path.get_ident() {
Some(ident) => ident.to_string(),
None => return None,
};
match ident_string.as_str() {
#[cfg(feature = "Debug")]
"Debug" => Some(Self::Debug),
#[cfg(feature = "Clone")]
"Clone" => Some(Self::Clone),
#[cfg(feature = "Copy")]
"Copy" => Some(Self::Copy),
#[cfg(feature = "PartialEq")]
"PartialEq" => Some(Self::PartialEq),
#[cfg(feature = "Eq")]
"Eq" => Some(Self::Eq),
#[cfg(feature = "PartialOrd")]
"PartialOrd" => Some(Self::PartialOrd),
#[cfg(feature = "Ord")]
"Ord" => Some(Self::Ord),
#[cfg(feature = "Hash")]
"Hash" => Some(Self::Hash),
#[cfg(feature = "Default")]
"Default" => Some(Self::Default),
#[cfg(feature = "Deref")]
"Deref" => Some(Self::Deref),
#[cfg(feature = "DerefMut")]
"DerefMut" => Some(Self::DerefMut),
#[cfg(feature = "Into")]
"Into" => Some(Self::Into),
_ => None,
}
}
}

View File

@@ -0,0 +1,272 @@
use quote::{format_ident, quote};
use syn::{punctuated::Punctuated, Data, DeriveInput, Field, Fields, Meta, Type, Variant};
use super::models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder};
use crate::{
common::where_predicates_bool::WherePredicates, supported_traits::Trait, TraitHandler,
};
pub(crate) struct CloneEnumHandler;
impl TraitHandler for CloneEnumHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute = TypeAttributeBuilder {
enable_flag: true, enable_bound: true
}
.build_from_clone_meta(meta)?;
let mut bound: WherePredicates = Punctuated::new();
let mut clone_token_stream = proc_macro2::TokenStream::new();
let mut clone_from_token_stream = proc_macro2::TokenStream::new();
if let Data::Enum(data) = &ast.data {
type Variants<'a> = Vec<(&'a Variant, Vec<(&'a Field, FieldAttribute)>)>;
let mut variants: Variants = Vec::new();
#[cfg(feature = "Copy")]
let mut has_custom_clone_method = false;
for variant in data.variants.iter() {
let _ = TypeAttributeBuilder {
enable_flag: false, enable_bound: false
}
.build_from_attributes(&variant.attrs, traits)?;
let mut variant_fields: Vec<(&Field, FieldAttribute)> = Vec::new();
for field in variant.fields.iter() {
let field_attribute = FieldAttributeBuilder {
enable_method: true
}
.build_from_attributes(&field.attrs, traits)?;
#[cfg(feature = "Copy")]
if field_attribute.method.is_some() {
has_custom_clone_method = true;
}
variant_fields.push((field, field_attribute));
}
variants.push((variant, variant_fields));
}
#[cfg(feature = "Copy")]
let contains_copy = !has_custom_clone_method && traits.contains(&Trait::Copy);
#[cfg(not(feature = "Copy"))]
let contains_copy = false;
if contains_copy {
clone_token_stream.extend(quote!(*self));
}
let mut clone_types: Vec<&Type> = Vec::new();
if variants.is_empty() {
if !contains_copy {
clone_token_stream.extend(quote!(unreachable!()));
clone_from_token_stream.extend(quote!(let _ = source;));
}
} else {
let mut clone_variants_token_stream = proc_macro2::TokenStream::new();
let mut clone_from_variants_token_stream = proc_macro2::TokenStream::new();
for (variant, variant_fields) in variants {
let variant_ident = &variant.ident;
match &variant.fields {
Fields::Unit => {
clone_variants_token_stream.extend(quote! {
Self::#variant_ident => Self::#variant_ident,
});
clone_from_variants_token_stream.extend(quote! {
Self::#variant_ident => {
if let Self::#variant_ident = source {
// same
} else {
*self = ::core::clone::Clone::clone(source);
}
},
});
},
Fields::Named(_) => {
let mut pattern_src_token_stream = proc_macro2::TokenStream::new();
let mut pattern_dst_token_stream = proc_macro2::TokenStream::new();
let mut cl_fields_token_stream = proc_macro2::TokenStream::new();
let mut cf_body_token_stream = proc_macro2::TokenStream::new();
for (field, field_attribute) in variant_fields {
let field_name_real = field.ident.as_ref().unwrap();
let field_name_src = format_ident!("_s_{}", field_name_real);
let field_name_dst = format_ident!("_d_{}", field_name_real);
pattern_src_token_stream
.extend(quote!(#field_name_real: #field_name_src,));
pattern_dst_token_stream
.extend(quote!(#field_name_real: #field_name_dst,));
if let Some(clone) = field_attribute.method.as_ref() {
cl_fields_token_stream.extend(quote! {
#field_name_real: #clone(#field_name_src),
});
cf_body_token_stream.extend(
quote!(*#field_name_dst = #clone(#field_name_src);),
);
} else {
clone_types.push(&field.ty);
cl_fields_token_stream.extend(quote! {
#field_name_real: ::core::clone::Clone::clone(#field_name_src),
});
cf_body_token_stream.extend(
quote!( ::core::clone::Clone::clone_from(#field_name_dst, #field_name_src); ),
);
}
}
clone_variants_token_stream.extend(quote! {
Self::#variant_ident { #pattern_src_token_stream } => Self::#variant_ident { #cl_fields_token_stream },
});
clone_from_variants_token_stream.extend(quote! {
Self::#variant_ident { #pattern_dst_token_stream } => {
if let Self::#variant_ident { #pattern_src_token_stream } = source {
#cf_body_token_stream
} else {
*self = ::core::clone::Clone::clone(source);
}
},
});
},
Fields::Unnamed(_) => {
let mut pattern_token_stream = proc_macro2::TokenStream::new();
let mut pattern2_token_stream = proc_macro2::TokenStream::new();
let mut fields_token_stream = proc_macro2::TokenStream::new();
let mut body_token_stream = proc_macro2::TokenStream::new();
for (index, (field, field_attribute)) in
variant_fields.into_iter().enumerate()
{
let field_name_src = format_ident!("_{}", index);
pattern_token_stream.extend(quote!(#field_name_src,));
let field_name_dst = format_ident!("_{}", field_name_src);
pattern2_token_stream.extend(quote!(#field_name_dst,));
if let Some(clone) = field_attribute.method.as_ref() {
fields_token_stream.extend(quote! (#clone(#field_name_src),));
body_token_stream.extend(
quote!(*#field_name_src = #clone(#field_name_dst);),
);
} else {
clone_types.push(&field.ty);
fields_token_stream.extend(
quote! ( ::core::clone::Clone::clone(#field_name_src), ),
);
body_token_stream.extend(
quote!( ::core::clone::Clone::clone_from(#field_name_src, #field_name_dst); ),
);
}
}
clone_variants_token_stream.extend(quote! {
Self::#variant_ident ( #pattern_token_stream ) => Self::#variant_ident ( #fields_token_stream ),
});
clone_from_variants_token_stream.extend(quote! {
Self::#variant_ident ( #pattern_token_stream ) => {
if let Self::#variant_ident ( #pattern2_token_stream ) = source {
#body_token_stream
} else {
*self = ::core::clone::Clone::clone(source);
}
},
});
},
}
}
if !contains_copy {
clone_token_stream.extend(quote! {
match self {
#clone_variants_token_stream
}
});
clone_from_token_stream.extend(quote! {
match self {
#clone_from_variants_token_stream
}
});
}
}
bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(if contains_copy {
quote!(::core::marker::Copy)
} else {
quote!(::core::clone::Clone)
})
.unwrap(),
&clone_types,
&[],
);
}
let clone_from_fn_token_stream = if clone_from_token_stream.is_empty() {
None
} else {
Some(quote! {
#[inline]
fn clone_from(&mut self, source: &Self) {
#clone_from_token_stream
}
})
};
let ident = &ast.ident;
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::clone::Clone for #ident #ty_generics #where_clause {
#[inline]
fn clone(&self) -> Self {
#clone_token_stream
}
#clone_from_fn_token_stream
}
});
#[cfg(feature = "Copy")]
if traits.contains(&Trait::Copy) {
token_stream.extend(quote! {
impl #impl_generics ::core::marker::Copy for #ident #ty_generics #where_clause {
}
});
}
Ok(())
}
}

View File

@@ -0,0 +1,195 @@
use quote::quote;
use syn::{punctuated::Punctuated, Data, DeriveInput, Field, Fields, Index, Meta, Type};
use super::models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder};
use crate::{
common::where_predicates_bool::WherePredicates, supported_traits::Trait, TraitHandler,
};
pub(crate) struct CloneStructHandler;
impl TraitHandler for CloneStructHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute = TypeAttributeBuilder {
enable_flag: true, enable_bound: true
}
.build_from_clone_meta(meta)?;
let mut bound: WherePredicates = Punctuated::new();
let mut clone_token_stream = proc_macro2::TokenStream::new();
let mut clone_from_token_stream = proc_macro2::TokenStream::new();
if let Data::Struct(data) = &ast.data {
let mut fields: Vec<(&Field, FieldAttribute)> = Vec::new();
#[cfg(feature = "Copy")]
let contains_copy = traits.contains(&Trait::Copy);
#[cfg(not(feature = "Copy"))]
let contains_copy = false;
if contains_copy {
clone_token_stream.extend(quote!(*self));
}
for field in data.fields.iter() {
let field_attribute = FieldAttributeBuilder {
enable_method: !contains_copy
}
.build_from_attributes(&field.attrs, traits)?;
fields.push((field, field_attribute));
}
let mut clone_types: Vec<&Type> = Vec::new();
match &data.fields {
Fields::Unit => {
if !contains_copy {
clone_token_stream.extend(quote!(Self));
clone_from_token_stream.extend(quote!(let _ = source;));
}
},
Fields::Named(_) => {
let mut fields_token_stream = proc_macro2::TokenStream::new();
let mut clone_from_body_token_stream = proc_macro2::TokenStream::new();
if fields.is_empty() {
clone_from_body_token_stream.extend(quote!(let _ = source;));
} else {
for (field, field_attribute) in fields {
let field_name = field.ident.as_ref().unwrap();
if let Some(clone) = field_attribute.method.as_ref() {
fields_token_stream.extend(quote! {
#field_name: #clone(&self.#field_name),
});
clone_from_body_token_stream.extend(
quote!(self.#field_name = #clone(&source.#field_name);),
);
} else {
clone_types.push(&field.ty);
fields_token_stream.extend(quote! {
#field_name: ::core::clone::Clone::clone(&self.#field_name),
});
clone_from_body_token_stream.extend(
quote!( ::core::clone::Clone::clone_from(&mut self.#field_name, &source.#field_name); ),
);
}
}
}
if !contains_copy {
clone_token_stream.extend(quote! {
Self {
#fields_token_stream
}
});
clone_from_token_stream.extend(clone_from_body_token_stream);
}
},
Fields::Unnamed(_) => {
let mut fields_token_stream = proc_macro2::TokenStream::new();
let mut clone_from_body_token_stream = proc_macro2::TokenStream::new();
if fields.is_empty() {
clone_from_body_token_stream.extend(quote!(let _ = source;));
} else {
for (index, (field, field_attribute)) in fields.into_iter().enumerate() {
let field_name = Index::from(index);
if let Some(clone) = field_attribute.method.as_ref() {
fields_token_stream.extend(quote!(#clone(&self.#field_name),));
clone_from_body_token_stream.extend(
quote!(self.#field_name = #clone(&source.#field_name);),
);
} else {
clone_types.push(&field.ty);
fields_token_stream.extend(
quote! ( ::core::clone::Clone::clone(&self.#field_name), ),
);
clone_from_body_token_stream.extend(
quote!( ::core::clone::Clone::clone_from(&mut self.#field_name, &source.#field_name); ),
);
}
}
}
if !contains_copy {
clone_token_stream.extend(quote!(Self ( #fields_token_stream )));
clone_from_token_stream.extend(clone_from_body_token_stream);
}
},
}
bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(if contains_copy {
quote!(::core::marker::Copy)
} else {
quote!(::core::clone::Clone)
})
.unwrap(),
&clone_types,
&[],
);
}
let clone_from_fn_token_stream = if clone_from_token_stream.is_empty() {
None
} else {
Some(quote! {
#[inline]
fn clone_from(&mut self, source: &Self) {
#clone_from_token_stream
}
})
};
let ident = &ast.ident;
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::clone::Clone for #ident #ty_generics #where_clause {
#[inline]
fn clone(&self) -> Self {
#clone_token_stream
}
#clone_from_fn_token_stream
}
});
#[cfg(feature = "Copy")]
if traits.contains(&Trait::Copy) {
token_stream.extend(quote! {
impl #impl_generics ::core::marker::Copy for #ident #ty_generics #where_clause {
}
});
}
Ok(())
}
}

View File

@@ -0,0 +1,73 @@
use quote::quote;
use syn::{Data, DeriveInput, Meta};
use super::{
models::{FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandler,
};
use crate::supported_traits::Trait;
pub(crate) struct CloneUnionHandler;
impl TraitHandler for CloneUnionHandler {
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute = TypeAttributeBuilder {
enable_flag: true, enable_bound: true
}
.build_from_clone_meta(meta)?;
let mut field_types = vec![];
if let Data::Union(data) = &ast.data {
for field in data.fields.named.iter() {
field_types.push(&field.ty);
let _ = FieldAttributeBuilder {
enable_method: false
}
.build_from_attributes(&field.attrs, traits)?;
}
}
let ident = &ast.ident;
let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::marker::Copy)).unwrap(),
&field_types,
&[],
);
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::clone::Clone for #ident #ty_generics #where_clause {
#[inline]
fn clone(&self) -> Self {
*self
}
}
});
#[cfg(feature = "Copy")]
if traits.contains(&Trait::Copy) {
token_stream.extend(quote! {
impl #impl_generics ::core::marker::Copy for #ident #ty_generics #where_clause {
}
});
}
Ok(())
}
}

View File

@@ -0,0 +1,36 @@
mod clone_enum;
mod clone_struct;
mod clone_union;
mod models;
use syn::{Data, DeriveInput, Meta};
use super::TraitHandler;
use crate::Trait;
pub(crate) struct CloneHandler;
impl TraitHandler for CloneHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
match ast.data {
Data::Struct(_) => clone_struct::CloneStructHandler::trait_meta_handler(
ast,
token_stream,
traits,
meta,
),
Data::Enum(_) => {
clone_enum::CloneEnumHandler::trait_meta_handler(ast, token_stream, traits, meta)
},
Data::Union(_) => {
clone_union::CloneUnionHandler::trait_meta_handler(ast, token_stream, traits, meta)
},
}
}
}

View File

@@ -0,0 +1,125 @@
use syn::{punctuated::Punctuated, Attribute, Meta, Path, Token};
use crate::{common::path::meta_2_path, panic, supported_traits::Trait};
pub(crate) struct FieldAttribute {
pub(crate) method: Option<Path>,
}
pub(crate) struct FieldAttributeBuilder {
pub(crate) enable_method: bool,
}
impl FieldAttributeBuilder {
pub(crate) fn build_from_clone_meta(&self, meta: &Meta) -> syn::Result<FieldAttribute> {
debug_assert!(meta.path().is_ident("Clone"));
let mut method = None;
let correct_usage_for_clone_attribute = {
let mut usage = vec![];
if self.enable_method {
usage.push(stringify!(#[educe(Clone(method(path_to_method)))]));
}
usage
};
match meta {
Meta::Path(_) | Meta::NameValue(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_clone_attribute,
));
},
Meta::List(list) => {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
let mut method_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
if ident == "method" {
if !self.enable_method {
return Ok(false);
}
let v = meta_2_path(&meta)?;
if method_is_set {
return Err(panic::parameter_reset(ident));
}
method_is_set = true;
method = Some(v);
return Ok(true);
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_clone_attribute,
));
}
}
},
}
Ok(FieldAttribute {
method,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<FieldAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Clone {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_clone_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(FieldAttribute {
method: None
}))
}
}

View File

@@ -0,0 +1,5 @@
mod field_attribute;
mod type_attribute;
pub(crate) use field_attribute::*;
pub(crate) use type_attribute::*;

View File

@@ -0,0 +1,140 @@
use syn::{punctuated::Punctuated, Attribute, Meta, Token};
use crate::{common::bound::Bound, panic, Trait};
pub(crate) struct TypeAttribute {
pub(crate) bound: Bound,
}
#[derive(Debug)]
pub(crate) struct TypeAttributeBuilder {
pub(crate) enable_flag: bool,
pub(crate) enable_bound: bool,
}
impl TypeAttributeBuilder {
pub(crate) fn build_from_clone_meta(&self, meta: &Meta) -> syn::Result<TypeAttribute> {
debug_assert!(meta.path().is_ident("Clone"));
let mut bound = Bound::Auto;
let correct_usage_for_clone_attribute = {
let mut usage = vec![];
if self.enable_flag {
usage.push(stringify!(#[educe(Clone)]));
}
if self.enable_bound {
usage.push(stringify!(#[educe(Clone(bound(where_predicates)))]));
usage.push(stringify!(#[educe(Clone(bound = false))]));
}
usage
};
match meta {
Meta::Path(_) => {
if !self.enable_flag {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_clone_attribute,
));
}
},
Meta::NameValue(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_clone_attribute,
));
},
Meta::List(list) => {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
let mut bound_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
if ident == "bound" {
if !self.enable_bound {
return Ok(false);
}
let v = Bound::from_meta(&meta)?;
if bound_is_set {
return Err(panic::parameter_reset(ident));
}
bound_is_set = true;
bound = v;
return Ok(true);
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_clone_attribute,
));
}
}
},
}
Ok(TypeAttribute {
bound,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<TypeAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Clone {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_clone_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(TypeAttribute {
bound: Bound::Auto
}))
}
}

View File

@@ -0,0 +1,94 @@
mod models;
use models::{FieldAttributeBuilder, TypeAttributeBuilder};
use quote::quote;
use syn::{Data, DeriveInput, Meta};
use super::TraitHandler;
use crate::Trait;
pub(crate) struct CopyHandler;
impl TraitHandler for CopyHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
#[cfg(feature = "Clone")]
let contains_clone = traits.contains(&Trait::Clone);
#[cfg(not(feature = "Clone"))]
let contains_clone = false;
let type_attribute = TypeAttributeBuilder {
enable_flag: true,
enable_bound: !contains_clone,
}
.build_from_copy_meta(meta)?;
let mut field_types = vec![];
// if `contains_clone` is true, the implementation is handled by the `Clone` attribute, and field attributes is also handled by the `Clone` attribute
if !contains_clone {
match &ast.data {
Data::Struct(data) => {
for field in data.fields.iter() {
field_types.push(&field.ty);
let _ =
FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?;
}
},
Data::Enum(data) => {
for variant in data.variants.iter() {
let _ = TypeAttributeBuilder {
enable_flag: false, enable_bound: false
}
.build_from_attributes(&variant.attrs, traits)?;
for field in variant.fields.iter() {
field_types.push(&field.ty);
let _ = FieldAttributeBuilder
.build_from_attributes(&field.attrs, traits)?;
}
}
},
Data::Union(data) => {
for field in data.fields.named.iter() {
field_types.push(&field.ty);
let _ =
FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?;
}
},
}
let ident = &ast.ident;
let bound =
type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::marker::Copy)).unwrap(),
&field_types,
&[quote! {::core::clone::Clone}],
);
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::marker::Copy for #ident #ty_generics #where_clause {
}
});
}
Ok(())
}
}

View File

@@ -0,0 +1,57 @@
use syn::{punctuated::Punctuated, Attribute, Meta, Token};
use crate::{panic, supported_traits::Trait};
pub(crate) struct FieldAttribute;
pub(crate) struct FieldAttributeBuilder;
impl FieldAttributeBuilder {
pub(crate) fn build_from_copy_meta(&self, meta: &Meta) -> syn::Result<FieldAttribute> {
debug_assert!(meta.path().is_ident("Copy"));
return Err(panic::attribute_incorrect_place(meta.path().get_ident().unwrap()));
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<FieldAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Copy {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_copy_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(FieldAttribute))
}
}

View File

@@ -0,0 +1,5 @@
mod field_attribute;
mod type_attribute;
pub(crate) use field_attribute::*;
pub(crate) use type_attribute::*;

View File

@@ -0,0 +1,140 @@
use syn::{punctuated::Punctuated, Attribute, Meta, Token};
use crate::{common::bound::Bound, panic, Trait};
pub(crate) struct TypeAttribute {
pub(crate) bound: Bound,
}
#[derive(Debug)]
pub(crate) struct TypeAttributeBuilder {
pub(crate) enable_flag: bool,
pub(crate) enable_bound: bool,
}
impl TypeAttributeBuilder {
pub(crate) fn build_from_copy_meta(&self, meta: &Meta) -> syn::Result<TypeAttribute> {
debug_assert!(meta.path().is_ident("Copy"));
let mut bound = Bound::Auto;
let correct_usage_for_copy_attribute = {
let mut usage = vec![];
if self.enable_flag {
usage.push(stringify!(#[educe(Copy)]));
}
if self.enable_bound {
usage.push(stringify!(#[educe(Copy(bound(where_predicates)))]));
usage.push(stringify!(#[educe(Copy(bound = false))]));
}
usage
};
match meta {
Meta::Path(_) => {
if !self.enable_flag {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_copy_attribute,
));
}
},
Meta::NameValue(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_copy_attribute,
));
},
Meta::List(list) => {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
let mut bound_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
if ident == "bound" {
if !self.enable_bound {
return Ok(false);
}
let v = Bound::from_meta(&meta)?;
if bound_is_set {
return Err(panic::parameter_reset(ident));
}
bound_is_set = true;
bound = v;
return Ok(true);
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_copy_attribute,
));
}
}
},
}
Ok(TypeAttribute {
bound,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<TypeAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Copy {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_copy_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(TypeAttribute {
bound: Bound::Auto
}))
}
}

View File

@@ -0,0 +1,55 @@
use quote::quote;
use syn::{DeriveInput, Path, Type};
#[inline]
pub(crate) fn create_debug_map_builder() -> proc_macro2::TokenStream {
quote!(
#[allow(non_camel_case_types)] // We're using __ to help avoid clashes.
struct Educe__RawString(&'static str);
impl ::core::fmt::Debug for Educe__RawString {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.write_str(self.0)
}
}
let mut builder = f.debug_map();
)
}
#[inline]
pub(crate) fn create_format_arg(
ast: &DeriveInput,
field_ty: &Type,
format_method: &Path,
field_expr: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let ty_ident = &ast.ident;
// We use the complete original generics, not filtered by field,
// and include a PhantomData<Self> in our wrapper struct to use the generics.
//
// This avoids having to try to calculate the right *subset* of the generics
// relevant for this field, which is nontrivial and maybe impossible.
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
quote!(
let arg = {
#[allow(non_camel_case_types)] // We're using __ to help avoid clashes.
struct Educe__DebugField<V, M>(V, ::core::marker::PhantomData<M>);
impl #impl_generics ::core::fmt::Debug
for Educe__DebugField<&#field_ty, #ty_ident #ty_generics>
#where_clause
{
#[inline]
fn fmt(&self, educe__f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
#format_method(self.0, educe__f)
}
}
Educe__DebugField(#field_expr, ::core::marker::PhantomData::<Self>)
};
)
}

View File

@@ -0,0 +1,371 @@
use quote::{format_ident, quote, ToTokens};
use syn::{Data, DeriveInput, Fields, Meta, Type};
use super::models::{FieldAttributeBuilder, FieldName, TypeAttributeBuilder, TypeName};
use crate::{common::path::path_to_string, supported_traits::Trait, trait_handlers::TraitHandler};
pub(crate) struct DebugEnumHandler;
impl TraitHandler for DebugEnumHandler {
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute = TypeAttributeBuilder {
enable_flag: true,
enable_unsafe: false,
enable_name: true,
enable_named_field: false,
enable_bound: true,
name: TypeName::Disable,
named_field: false,
}
.build_from_debug_meta(meta)?;
let name = type_attribute.name.to_ident_by_ident(&ast.ident);
let mut debug_types: Vec<&Type> = Vec::new();
let mut builder_token_stream = proc_macro2::TokenStream::new();
let mut arms_token_stream = proc_macro2::TokenStream::new();
if let Data::Enum(data) = &ast.data {
for variant in data.variants.iter() {
let type_attribute = TypeAttributeBuilder {
enable_flag: false,
enable_unsafe: false,
enable_name: true,
enable_named_field: true,
enable_bound: false,
name: TypeName::Default,
named_field: matches!(&variant.fields, Fields::Named(_)),
}
.build_from_attributes(&variant.attrs, traits)?;
let variant_ident = &variant.ident;
let variant_name = type_attribute.name.to_ident_by_ident(variant_ident);
let named_field = type_attribute.named_field;
let name_string = if let Some(name) = name {
if let Some(variant_name) = variant_name {
Some(path_to_string(&syn::parse2(quote!(#name::#variant_name)).unwrap()))
} else {
Some(name.into_token_stream().to_string())
}
} else {
variant_name.map(|variant_name| variant_name.into_token_stream().to_string())
};
match &variant.fields {
Fields::Unit => {
if name_string.is_none() {
return Err(super::panic::unit_variant_need_name(variant));
}
arms_token_stream
.extend(quote!( Self::#variant_ident => f.write_str(#name_string), ));
},
Fields::Named(fields) => {
let mut has_fields = false;
let mut pattern_token_stream = proc_macro2::TokenStream::new();
let mut block_token_stream = proc_macro2::TokenStream::new();
if named_field {
block_token_stream
.extend(create_named_field_builder(name_string.as_deref()));
for field in fields.named.iter() {
let field_attribute = FieldAttributeBuilder {
enable_name: true,
enable_ignore: true,
enable_method: true,
name: FieldName::Default,
}
.build_from_attributes(&field.attrs, traits)?;
let field_name_real = field.ident.as_ref().unwrap();
let field_name_var = format_ident!("_{}", field_name_real);
if field_attribute.ignore {
pattern_token_stream.extend(quote!(#field_name_real: _,));
continue;
}
let key = match field_attribute.name {
FieldName::Custom(name) => name,
FieldName::Default => field_name_real.clone(),
};
pattern_token_stream
.extend(quote!(#field_name_real: #field_name_var,));
let ty = &field.ty;
if let Some(method) = field_attribute.method {
block_token_stream.extend(super::common::create_format_arg(
ast,
ty,
&method,
quote!(#field_name_var),
));
block_token_stream.extend(if name_string.is_some() {
quote! (builder.field(stringify!(#key), &arg);)
} else {
quote! (builder.entry(&Educe__RawString(stringify!(#key)), &arg);)
});
} else {
debug_types.push(ty);
block_token_stream.extend(if name_string.is_some() {
quote! (builder.field(stringify!(#key), #field_name_var);)
} else {
quote! (builder.entry(&Educe__RawString(stringify!(#key)), #field_name_var);)
});
}
has_fields = true;
}
} else {
block_token_stream
.extend(quote!(let mut builder = f.debug_tuple(#name_string);));
for field in fields.named.iter() {
let field_attribute = FieldAttributeBuilder {
enable_name: false,
enable_ignore: true,
enable_method: true,
name: FieldName::Default,
}
.build_from_attributes(&field.attrs, traits)?;
let field_name_real = field.ident.as_ref().unwrap();
let field_name_var = format_ident!("_{}", field_name_real);
if field_attribute.ignore {
pattern_token_stream.extend(quote!(#field_name_real: _,));
continue;
}
pattern_token_stream
.extend(quote!(#field_name_real: #field_name_var,));
let ty = &field.ty;
if let Some(method) = field_attribute.method {
block_token_stream.extend(super::common::create_format_arg(
ast,
ty,
&method,
quote!(#field_name_var),
));
block_token_stream.extend(quote! (builder.field(&arg);));
} else {
debug_types.push(ty);
block_token_stream
.extend(quote! (builder.field(#field_name_var);));
}
has_fields = true;
}
}
if !has_fields && name_string.is_none() {
return Err(super::panic::unit_struct_need_name(variant_ident));
}
arms_token_stream.extend(quote! {
Self::#variant_ident { #pattern_token_stream } => {
#block_token_stream
builder.finish()
},
});
},
Fields::Unnamed(fields) => {
let mut has_fields = false;
let mut pattern_token_stream = proc_macro2::TokenStream::new();
let mut block_token_stream = proc_macro2::TokenStream::new();
if named_field {
block_token_stream
.extend(create_named_field_builder(name_string.as_deref()));
for (index, field) in fields.unnamed.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_name: true,
enable_ignore: true,
enable_method: true,
name: FieldName::Default,
}
.build_from_attributes(&field.attrs, traits)?;
if field_attribute.ignore {
pattern_token_stream.extend(quote!(_,));
continue;
}
let field_name_var = format_ident!("_{}", index);
let key = match field_attribute.name {
FieldName::Custom(name) => name,
FieldName::Default => field_name_var.clone(),
};
pattern_token_stream.extend(quote!(#field_name_var,));
let ty = &field.ty;
if let Some(method) = field_attribute.method {
block_token_stream.extend(super::common::create_format_arg(
ast,
ty,
&method,
quote!(#field_name_var),
));
block_token_stream.extend(if name_string.is_some() {
quote! (builder.field(stringify!(#key), &arg);)
} else {
quote! (builder.entry(&Educe__RawString(stringify!(#key)), &arg);)
});
} else {
debug_types.push(ty);
block_token_stream.extend(if name_string.is_some() {
quote! (builder.field(stringify!(#key), #field_name_var);)
} else {
quote! (builder.entry(&Educe__RawString(stringify!(#key)), #field_name_var);)
});
}
has_fields = true;
}
} else {
block_token_stream
.extend(quote!(let mut builder = f.debug_tuple(#name_string);));
for (index, field) in fields.unnamed.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_name: false,
enable_ignore: true,
enable_method: true,
name: FieldName::Default,
}
.build_from_attributes(&field.attrs, traits)?;
if field_attribute.ignore {
pattern_token_stream.extend(quote!(_,));
continue;
}
let field_name_var = format_ident!("_{}", index);
pattern_token_stream.extend(quote!(#field_name_var,));
let ty = &field.ty;
if let Some(method) = field_attribute.method {
block_token_stream.extend(super::common::create_format_arg(
ast,
ty,
&method,
quote!(#field_name_var),
));
block_token_stream.extend(quote! (builder.field(&arg);));
} else {
debug_types.push(ty);
block_token_stream
.extend(quote! (builder.field(#field_name_var);));
}
has_fields = true;
}
}
if !has_fields && name_string.is_none() {
return Err(super::panic::unit_struct_need_name(variant_ident));
}
arms_token_stream.extend(quote! {
Self::#variant_ident ( #pattern_token_stream ) => {
#block_token_stream
builder.finish()
},
});
},
}
}
}
let ident = &ast.ident;
if arms_token_stream.is_empty() {
if let Some(ident) = name {
builder_token_stream.extend(quote! {
f.write_str(stringify!(#ident))
});
} else {
return Err(super::panic::unit_enum_need_name(ident));
}
} else {
builder_token_stream.extend(quote! {
match self {
#arms_token_stream
}
});
}
let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::fmt::Debug)).unwrap(),
&debug_types,
&[],
);
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::fmt::Debug for #ident #ty_generics #where_clause {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
#builder_token_stream
}
}
});
Ok(())
}
}
#[inline]
fn create_named_field_builder(name_string: Option<&str>) -> proc_macro2::TokenStream {
if let Some(name_string) = name_string {
quote!(let mut builder = f.debug_struct(#name_string);)
} else {
super::common::create_debug_map_builder()
}
}

View File

@@ -0,0 +1,185 @@
use quote::{format_ident, quote};
use syn::{Data, DeriveInput, Fields, Meta, Type};
use super::{
models::{FieldAttributeBuilder, FieldName, TypeAttributeBuilder, TypeName},
TraitHandler,
};
use crate::{common::ident_index::IdentOrIndex, Trait};
pub struct DebugStructHandler;
impl TraitHandler for DebugStructHandler {
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let is_tuple = {
if let Data::Struct(data) = &ast.data {
matches!(data.fields, Fields::Unnamed(_))
} else {
true
}
};
let type_attribute = TypeAttributeBuilder {
enable_flag: true,
enable_unsafe: false,
enable_name: true,
enable_named_field: true,
enable_bound: true,
name: TypeName::Default,
named_field: !is_tuple,
}
.build_from_debug_meta(meta)?;
let name = type_attribute.name.to_ident_by_ident(&ast.ident);
let mut debug_types: Vec<&Type> = Vec::new();
let mut builder_token_stream = proc_macro2::TokenStream::new();
let mut has_fields = false;
if type_attribute.named_field {
builder_token_stream.extend(if let Some(name) = name {
quote!(let mut builder = f.debug_struct(stringify!(#name));)
} else {
super::common::create_debug_map_builder()
});
if let Data::Struct(data) = &ast.data {
for (index, field) in data.fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_name: true,
enable_ignore: true,
enable_method: true,
name: FieldName::Default,
}
.build_from_attributes(&field.attrs, traits)?;
if field_attribute.ignore {
continue;
}
let (key, field_name) = match field_attribute.name {
FieldName::Custom(name) => {
(name, IdentOrIndex::from_ident_with_index(field.ident.as_ref(), index))
},
FieldName::Default => {
if let Some(ident) = field.ident.as_ref() {
(ident.clone(), IdentOrIndex::from(ident))
} else {
(format_ident!("_{}", index), IdentOrIndex::from(index))
}
},
};
let ty = &field.ty;
if let Some(method) = field_attribute.method {
builder_token_stream.extend(super::common::create_format_arg(
ast,
ty,
&method,
quote!(&self.#field_name),
));
builder_token_stream.extend(if name.is_some() {
quote! (builder.field(stringify!(#key), &arg);)
} else {
quote! (builder.entry(&Educe__RawString(stringify!(#key)), &arg);)
});
} else {
debug_types.push(ty);
builder_token_stream.extend(if name.is_some() {
quote! (builder.field(stringify!(#key), &self.#field_name);)
} else {
quote! (builder.entry(&Educe__RawString(stringify!(#key)), &self.#field_name);)
});
}
has_fields = true;
}
}
} else {
builder_token_stream
.extend(quote!(let mut builder = f.debug_tuple(stringify!(#name));));
if let Data::Struct(data) = &ast.data {
for (index, field) in data.fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_name: false,
enable_ignore: true,
enable_method: true,
name: FieldName::Default,
}
.build_from_attributes(&field.attrs, traits)?;
if field_attribute.ignore {
continue;
}
let field_name =
IdentOrIndex::from_ident_with_index(field.ident.as_ref(), index);
let ty = &field.ty;
if let Some(method) = field_attribute.method {
builder_token_stream.extend(super::common::create_format_arg(
ast,
ty,
&method,
quote!(&self.#field_name),
));
builder_token_stream.extend(quote! (builder.field(&arg);));
} else {
debug_types.push(ty);
builder_token_stream.extend(quote! (builder.field(&self.#field_name);));
}
has_fields = true;
}
}
}
let ident = &ast.ident;
if !has_fields && name.is_none() {
return Err(super::panic::unit_struct_need_name(ident));
}
let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::fmt::Debug)).unwrap(),
&debug_types,
&[],
);
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::fmt::Debug for #ident #ty_generics #where_clause {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
#builder_token_stream
builder.finish()
}
}
});
Ok(())
}
}

View File

@@ -0,0 +1,86 @@
use quote::quote;
use syn::{Data, DeriveInput, Meta};
use super::{
models::{FieldAttributeBuilder, FieldName, TypeAttributeBuilder, TypeName},
TraitHandler,
};
use crate::supported_traits::Trait;
pub(crate) struct DebugUnionHandler;
impl TraitHandler for DebugUnionHandler {
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute = TypeAttributeBuilder {
enable_flag: true,
enable_unsafe: true,
enable_name: true,
enable_named_field: false,
enable_bound: false,
name: TypeName::Default,
named_field: false,
}
.build_from_debug_meta(meta)?;
if !type_attribute.has_unsafe {
return Err(super::panic::union_without_unsafe(meta));
}
let name = type_attribute.name.to_ident_by_ident(&ast.ident);
let mut builder_token_stream = proc_macro2::TokenStream::new();
if let Data::Union(data) = &ast.data {
for field in data.fields.named.iter() {
let _ = FieldAttributeBuilder {
enable_name: false,
enable_ignore: false,
enable_method: false,
name: FieldName::Default,
}
.build_from_attributes(&field.attrs, traits)?;
}
if let Some(name) = name {
builder_token_stream.extend(quote!(
let mut builder = f.debug_tuple(stringify!(#name));
let size = ::core::mem::size_of::<Self>();
let data = unsafe { ::core::slice::from_raw_parts(self as *const Self as *const u8, size) };
builder.field(&data);
builder.finish()
));
} else {
builder_token_stream.extend(quote!(
let size = ::core::mem::size_of::<Self>();
let data = unsafe { ::core::slice::from_raw_parts(self as *const Self as *const u8, size) };
::core::fmt::Debug::fmt(data, f)
));
}
}
let ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::fmt::Debug for #ident #ty_generics #where_clause {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
#builder_token_stream
}
}
});
Ok(())
}
}

View File

@@ -0,0 +1,38 @@
mod common;
mod debug_enum;
mod debug_struct;
mod debug_union;
mod models;
mod panic;
use syn::{Data, DeriveInput, Meta};
use super::TraitHandler;
use crate::Trait;
pub(crate) struct DebugHandler;
impl TraitHandler for DebugHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
match ast.data {
Data::Struct(_) => debug_struct::DebugStructHandler::trait_meta_handler(
ast,
token_stream,
traits,
meta,
),
Data::Enum(_) => {
debug_enum::DebugEnumHandler::trait_meta_handler(ast, token_stream, traits, meta)
},
Data::Union(_) => {
debug_union::DebugUnionHandler::trait_meta_handler(ast, token_stream, traits, meta)
},
}
}
}

View File

@@ -0,0 +1,224 @@
use syn::{punctuated::Punctuated, Attribute, Ident, Meta, Path, Token};
use crate::{
common::{
ident_bool::{
meta_2_bool_allow_path, meta_2_ident, meta_name_value_2_bool, meta_name_value_2_ident,
meta_name_value_2_ident_and_bool, IdentOrBool,
},
path::meta_2_path,
},
panic,
supported_traits::Trait,
};
#[derive(Debug, Clone)]
pub(crate) enum FieldName {
Default,
Custom(Ident),
}
pub(crate) struct FieldAttribute {
pub(crate) name: FieldName,
pub(crate) ignore: bool,
pub(crate) method: Option<Path>,
}
pub(crate) struct FieldAttributeBuilder {
pub(crate) enable_name: bool,
pub(crate) enable_ignore: bool,
pub(crate) enable_method: bool,
pub(crate) name: FieldName,
}
impl FieldAttributeBuilder {
pub(crate) fn build_from_debug_meta(&self, meta: &Meta) -> syn::Result<FieldAttribute> {
debug_assert!(meta.path().is_ident("Debug"));
let mut name = self.name.clone();
let mut ignore = false;
let mut method = None;
let correct_usage_for_debug_attribute = {
let mut usage = vec![];
if self.enable_name {
usage.push(stringify!(#[educe(Debug = NewName)]));
usage.push(stringify!(#[educe(Debug(name(NewName)))]));
}
if self.enable_ignore {
usage.push(stringify!(#[educe(Debug = false)]));
usage.push(stringify!(#[educe(Debug(ignore))]));
}
if self.enable_method {
usage.push(stringify!(#[educe(Debug(method(path_to_method)))]));
}
usage
};
match meta {
Meta::Path(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_debug_attribute,
));
},
Meta::NameValue(name_value) => {
if self.enable_name {
if self.enable_ignore {
match meta_name_value_2_ident_and_bool(name_value)? {
IdentOrBool::Ident(ident) => {
name = FieldName::Custom(ident);
},
IdentOrBool::Bool(b) => {
ignore = !b;
},
}
} else {
name = FieldName::Custom(meta_name_value_2_ident(name_value)?);
}
} else if self.enable_ignore {
ignore = !meta_name_value_2_bool(name_value)?;
} else {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_debug_attribute,
));
}
},
Meta::List(list) => {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
let mut name_is_set = false;
let mut ignore_is_set = false;
let mut method_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
match ident.to_string().as_str() {
"name" | "rename" => {
if !self.enable_name {
return Ok(false);
}
let v = meta_2_ident(&meta)?;
if name_is_set {
return Err(panic::parameter_reset(ident));
}
name_is_set = true;
name = FieldName::Custom(v);
return Ok(true);
},
"ignore" => {
if !self.enable_ignore {
return Ok(false);
}
let v = meta_2_bool_allow_path(&meta)?;
if ignore_is_set {
return Err(panic::parameter_reset(ident));
}
ignore_is_set = true;
ignore = v;
return Ok(true);
},
"method" => {
if !self.enable_method {
return Ok(false);
}
let v = meta_2_path(&meta)?;
if method_is_set {
return Err(panic::parameter_reset(ident));
}
method_is_set = true;
method = Some(v);
return Ok(true);
},
_ => (),
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_debug_attribute,
));
}
}
},
}
Ok(FieldAttribute {
name,
ignore,
method,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<FieldAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Debug {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_debug_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(FieldAttribute {
name: self.name.clone(),
ignore: false,
method: None,
}))
}
}

View File

@@ -0,0 +1,5 @@
mod field_attribute;
mod type_attribute;
pub(crate) use field_attribute::*;
pub(crate) use type_attribute::*;

View File

@@ -0,0 +1,264 @@
use proc_macro2::Ident;
use syn::{punctuated::Punctuated, Attribute, Meta, Token};
use crate::{
common::{
bound::Bound,
ident_bool::{meta_2_bool, meta_2_ident_and_bool, meta_name_value_2_ident, IdentOrBool},
unsafe_punctuated_meta::UnsafePunctuatedMeta,
},
panic, Trait,
};
#[derive(Debug, Clone)]
pub(crate) enum TypeName {
Disable,
Default,
Custom(Ident),
}
impl TypeName {
#[inline]
pub(crate) fn to_ident_by_ident<'a, 'b: 'a>(&'a self, ident: &'b Ident) -> Option<&'a Ident> {
match self {
Self::Disable => None,
Self::Default => Some(ident),
Self::Custom(ident) => Some(ident),
}
}
}
pub(crate) struct TypeAttribute {
pub(crate) has_unsafe: bool,
pub(crate) name: TypeName,
pub(crate) named_field: bool,
pub(crate) bound: Bound,
}
#[derive(Debug)]
pub(crate) struct TypeAttributeBuilder {
pub(crate) enable_flag: bool,
pub(crate) enable_unsafe: bool,
pub(crate) enable_name: bool,
pub(crate) enable_named_field: bool,
pub(crate) enable_bound: bool,
pub(crate) name: TypeName,
pub(crate) named_field: bool,
}
impl TypeAttributeBuilder {
pub(crate) fn build_from_debug_meta(&self, meta: &Meta) -> syn::Result<TypeAttribute> {
debug_assert!(meta.path().is_ident("Debug"));
let mut has_unsafe = false;
let mut name = self.name.clone();
let mut named_field = self.named_field;
let mut bound = Bound::Auto;
let correct_usage_for_debug_attribute = {
let mut usage = vec![];
if self.enable_flag {
usage.push(stringify!(#[educe(Debug)]));
}
if self.enable_name {
if !self.enable_unsafe {
usage.push(stringify!(#[educe(Debug = NewName)]));
}
usage.push(stringify!(#[educe(Debug(name(NewName)))]));
if let TypeName::Disable = &name {
usage.push(stringify!(#[educe(Debug(name = true))]));
} else {
usage.push(stringify!(#[educe(Debug(name = false))]));
}
}
if self.enable_named_field {
if !self.named_field {
usage.push(stringify!(#[educe(Debug(named_field = true))]));
} else {
usage.push(stringify!(#[educe(Debug(named_field = false))]));
}
}
if self.enable_bound {
usage.push(stringify!(#[educe(Debug(bound(where_predicates)))]));
usage.push(stringify!(#[educe(Debug(bound = false))]));
}
usage
};
match meta {
Meta::Path(_) => {
if !self.enable_flag {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_debug_attribute,
));
}
},
Meta::NameValue(name_value) => {
if !self.enable_name {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_debug_attribute,
));
}
name = TypeName::Custom(meta_name_value_2_ident(name_value)?);
},
Meta::List(list) => {
let result = if self.enable_unsafe {
let result: UnsafePunctuatedMeta = list.parse_args()?;
has_unsafe = result.has_unsafe;
result.list
} else {
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?
};
let mut name_is_set = false;
let mut named_field_is_set = false;
let mut bound_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
match ident.to_string().as_str() {
"name" | "rename" => {
if !self.enable_name {
return Ok(false);
}
let v = meta_2_ident_and_bool(&meta)?;
if name_is_set {
return Err(panic::parameter_reset(ident));
}
name_is_set = true;
name = match v {
IdentOrBool::Ident(ident) => TypeName::Custom(ident),
IdentOrBool::Bool(b) => {
if b {
TypeName::Default
} else {
TypeName::Disable
}
},
};
return Ok(true);
},
"named_field" => {
if !self.enable_named_field {
return Ok(false);
}
let v = meta_2_bool(&meta)?;
if named_field_is_set {
return Err(panic::parameter_reset(ident));
}
named_field_is_set = true;
named_field = v;
return Ok(true);
},
"bound" => {
if !self.enable_bound {
return Ok(false);
}
let v = Bound::from_meta(&meta)?;
if bound_is_set {
return Err(panic::parameter_reset(ident));
}
bound_is_set = true;
bound = v;
return Ok(true);
},
_ => (),
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_debug_attribute,
));
}
}
},
}
Ok(TypeAttribute {
has_unsafe,
name,
named_field,
bound,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<TypeAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Debug {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_debug_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(TypeAttribute {
has_unsafe: false,
name: self.name.clone(),
named_field: self.named_field,
bound: Bound::Auto,
}))
}
}

View File

@@ -0,0 +1,43 @@
use quote::ToTokens;
use syn::{spanned::Spanned, Ident, Meta, Variant};
#[inline]
pub(crate) fn unit_struct_need_name(name: &Ident) -> syn::Error {
syn::Error::new(name.span(), "a unit struct needs to have a name")
}
#[inline]
pub(crate) fn unit_variant_need_name(variant: &Variant) -> syn::Error {
syn::Error::new(
variant.span(),
"a unit variant which doesn't use an enum name needs to have a name",
)
}
#[inline]
pub(crate) fn unit_enum_need_name(name: &Ident) -> syn::Error {
syn::Error::new(name.span(), "a unit enum needs to have a name")
}
#[inline]
pub(crate) fn union_without_unsafe(meta: &Meta) -> syn::Error {
let mut s = meta.into_token_stream().to_string().replace(" , ", ", ");
match s.len() {
5 => s.push_str("(unsafe)"),
7 => s.insert_str(6, "unsafe"),
_ => s.insert_str(6, "unsafe, "),
}
syn::Error::new(
meta.span(),
format!(
"a union's `Debug` implementation may expose uninitialized memory\n* It is \
recommended that, for a union where `Debug` is implemented, types that allow \
uninitialized memory should not be used in it.\n* If you can ensure that the union \
uses no such types, use `#[educe({s})]` to implement the `Debug` trait for it.\n* \
The `unsafe` keyword should be placed as the first parameter of the `Debug` \
attribute."
),
)
}

View File

@@ -0,0 +1,230 @@
use quote::quote;
use syn::{spanned::Spanned, Data, DeriveInput, Fields, Meta, Type, Variant};
use super::{
models::{FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandler,
};
use crate::Trait;
pub(crate) struct DefaultEnumHandler;
impl TraitHandler for DefaultEnumHandler {
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute = TypeAttributeBuilder {
enable_flag: true,
enable_new: true,
enable_expression: true,
enable_bound: true,
}
.build_from_default_meta(meta)?;
let mut default_types: Vec<&Type> = Vec::new();
let mut default_token_stream = proc_macro2::TokenStream::new();
if let Data::Enum(data) = &ast.data {
if let Some(expression) = type_attribute.expression {
for variant in data.variants.iter() {
let _ = TypeAttributeBuilder {
enable_flag: false,
enable_new: false,
enable_expression: false,
enable_bound: false,
}
.build_from_attributes(&variant.attrs, traits)?;
ensure_fields_no_attribute(&variant.fields, traits)?;
}
default_token_stream.extend(quote!(#expression));
} else {
let variant = {
let variants = &data.variants;
if variants.len() == 1 {
let variant = &variants[0];
let _ = TypeAttributeBuilder {
enable_flag: true,
enable_new: false,
enable_expression: false,
enable_bound: false,
}
.build_from_attributes(&variant.attrs, traits)?;
variant
} else {
let mut default_variant: Option<&Variant> = None;
for variant in variants {
let type_attribute = TypeAttributeBuilder {
enable_flag: true,
enable_new: false,
enable_expression: false,
enable_bound: false,
}
.build_from_attributes(&variant.attrs, traits)?;
if type_attribute.flag {
if default_variant.is_some() {
return Err(super::panic::multiple_default_variants(
type_attribute.span,
));
}
default_variant = Some(variant);
} else {
ensure_fields_no_attribute(&variant.fields, traits)?;
}
}
if let Some(default_variant) = default_variant {
default_variant
} else {
return Err(super::panic::no_default_variant(meta.span()));
}
}
};
let variant_ident = &variant.ident;
match &variant.fields {
Fields::Unit => {
default_token_stream.extend(quote!(Self::#variant_ident));
},
Fields::Named(_) => {
let mut fields_token_stream = proc_macro2::TokenStream::new();
for field in variant.fields.iter() {
let field_attribute = FieldAttributeBuilder {
enable_flag: false,
enable_expression: true,
}
.build_from_attributes(&field.attrs, traits, &field.ty)?;
let field_name = field.ident.as_ref().unwrap();
if let Some(expression) = field_attribute.expression {
fields_token_stream.extend(quote! {
#field_name: #expression,
});
} else {
let ty = &field.ty;
default_types.push(ty);
fields_token_stream.extend(quote! {
#field_name: <#ty as ::core::default::Default>::default(),
});
}
}
default_token_stream.extend(quote! {
Self::#variant_ident {
#fields_token_stream
}
});
},
Fields::Unnamed(_) => {
let mut fields_token_stream = proc_macro2::TokenStream::new();
for field in variant.fields.iter() {
let field_attribute = FieldAttributeBuilder {
enable_flag: false,
enable_expression: true,
}
.build_from_attributes(&field.attrs, traits, &field.ty)?;
if let Some(expression) = field_attribute.expression {
fields_token_stream.extend(quote!(#expression,));
} else {
let ty = &field.ty;
default_types.push(ty);
fields_token_stream
.extend(quote!(<#ty as ::core::default::Default>::default(),));
}
}
default_token_stream
.extend(quote!(Self::#variant_ident ( #fields_token_stream )));
},
}
}
}
let ident = &ast.ident;
let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::default::Default)).unwrap(),
&default_types,
&[],
);
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::default::Default for #ident #ty_generics #where_clause {
#[inline]
fn default() -> Self {
#default_token_stream
}
}
});
if type_attribute.new {
token_stream.extend(quote! {
impl #impl_generics #ident #ty_generics #where_clause {
/// Returns the "default value" for a type.
#[inline]
pub fn new() -> Self {
<Self as ::core::default::Default>::default()
}
}
});
}
Ok(())
}
}
fn ensure_fields_no_attribute(fields: &Fields, traits: &[Trait]) -> syn::Result<()> {
match fields {
Fields::Unit => (),
Fields::Named(fields) => {
for field in fields.named.iter() {
let _ = FieldAttributeBuilder {
enable_flag: false,
enable_expression: false,
}
.build_from_attributes(&field.attrs, traits, &field.ty)?;
}
},
Fields::Unnamed(fields) => {
for field in fields.unnamed.iter() {
let _ = FieldAttributeBuilder {
enable_flag: false,
enable_expression: false,
}
.build_from_attributes(&field.attrs, traits, &field.ty)?;
}
},
}
Ok(())
}

View File

@@ -0,0 +1,149 @@
use quote::quote;
use syn::{Data, DeriveInput, Fields, Meta, Type};
use super::{
models::{FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandler,
};
use crate::Trait;
pub(crate) struct DefaultStructHandler;
impl TraitHandler for DefaultStructHandler {
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute = TypeAttributeBuilder {
enable_flag: true,
enable_new: true,
enable_expression: true,
enable_bound: true,
}
.build_from_default_meta(meta)?;
let mut default_types: Vec<&Type> = Vec::new();
let mut default_token_stream = proc_macro2::TokenStream::new();
if let Data::Struct(data) = &ast.data {
if let Some(expression) = type_attribute.expression {
for field in data.fields.iter() {
let _ = FieldAttributeBuilder {
enable_flag: false,
enable_expression: false,
}
.build_from_attributes(&field.attrs, traits, &field.ty)?;
}
default_token_stream.extend(quote!(#expression));
} else {
match &data.fields {
Fields::Unit => {
default_token_stream.extend(quote!(Self));
},
Fields::Named(_) => {
let mut fields_token_stream = proc_macro2::TokenStream::new();
for field in data.fields.iter() {
let field_attribute = FieldAttributeBuilder {
enable_flag: false,
enable_expression: true,
}
.build_from_attributes(&field.attrs, traits, &field.ty)?;
let field_name = field.ident.as_ref().unwrap();
if let Some(expression) = field_attribute.expression {
fields_token_stream.extend(quote! {
#field_name: #expression,
});
} else {
let ty = &field.ty;
default_types.push(ty);
fields_token_stream.extend(quote! {
#field_name: <#ty as ::core::default::Default>::default(),
});
}
}
default_token_stream.extend(quote! {
Self {
#fields_token_stream
}
});
},
Fields::Unnamed(_) => {
let mut fields_token_stream = proc_macro2::TokenStream::new();
for field in data.fields.iter() {
let field_attribute = FieldAttributeBuilder {
enable_flag: false,
enable_expression: true,
}
.build_from_attributes(&field.attrs, traits, &field.ty)?;
if let Some(expression) = field_attribute.expression {
fields_token_stream.extend(quote!(#expression,));
} else {
let ty = &field.ty;
default_types.push(ty);
fields_token_stream
.extend(quote!(<#ty as ::core::default::Default>::default(),));
}
}
default_token_stream.extend(quote!(Self ( #fields_token_stream )));
},
}
}
}
let ident = &ast.ident;
let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::default::Default)).unwrap(),
&default_types,
&[],
);
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::default::Default for #ident #ty_generics #where_clause {
#[inline]
fn default() -> Self {
#default_token_stream
}
}
});
if type_attribute.new {
token_stream.extend(quote! {
impl #impl_generics #ident #ty_generics #where_clause {
/// Returns the "default value" for a type.
#[inline]
pub fn new() -> Self {
<Self as ::core::default::Default>::default()
}
}
});
}
Ok(())
}
}

View File

@@ -0,0 +1,153 @@
use quote::quote;
use syn::{spanned::Spanned, Data, DeriveInput, Field, Meta, Type};
use super::{
models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandler,
};
use crate::Trait;
pub(crate) struct DefaultUnionHandler;
impl TraitHandler for DefaultUnionHandler {
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute = TypeAttributeBuilder {
enable_flag: true,
enable_new: true,
enable_expression: true,
enable_bound: true,
}
.build_from_default_meta(meta)?;
let mut default_types: Vec<&Type> = Vec::new();
let mut default_token_stream = proc_macro2::TokenStream::new();
if let Data::Union(data) = &ast.data {
if let Some(expression) = type_attribute.expression {
for field in data.fields.named.iter() {
let _ = FieldAttributeBuilder {
enable_flag: false,
enable_expression: false,
}
.build_from_attributes(&field.attrs, traits, &field.ty)?;
}
default_token_stream.extend(quote!(#expression));
} else {
let (field, field_attribute) =
{
let fields = &data.fields.named;
if fields.len() == 1 {
let field = &fields[0];
let field_attribute = FieldAttributeBuilder {
enable_flag: true,
enable_expression: true,
}
.build_from_attributes(&field.attrs, traits, &field.ty)?;
(field, field_attribute)
} else {
let mut default_field: Option<(&Field, FieldAttribute)> = None;
for field in fields {
let field_attribute = FieldAttributeBuilder {
enable_flag: true,
enable_expression: true,
}
.build_from_attributes(&field.attrs, traits, &field.ty)?;
if field_attribute.flag || field_attribute.expression.is_some() {
if default_field.is_some() {
return Err(super::panic::multiple_default_fields(
field_attribute.span,
));
}
default_field = Some((field, field_attribute));
}
}
if let Some(default_field) = default_field {
default_field
} else {
return Err(super::panic::no_default_field(meta.span()));
}
}
};
let mut fields_token_stream = proc_macro2::TokenStream::new();
let field_name = field.ident.as_ref().unwrap();
if let Some(expression) = field_attribute.expression {
fields_token_stream.extend(quote! {
#field_name: #expression,
});
} else {
let ty = &field.ty;
default_types.push(ty);
fields_token_stream.extend(quote! {
#field_name: <#ty as ::core::default::Default>::default(),
});
}
default_token_stream.extend(quote! {
Self {
#fields_token_stream
}
});
}
}
let ident = &ast.ident;
let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::default::Default)).unwrap(),
&default_types,
&[],
);
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::default::Default for #ident #ty_generics #where_clause {
#[inline]
fn default() -> Self {
#default_token_stream
}
}
});
if type_attribute.new {
token_stream.extend(quote! {
impl #impl_generics #ident #ty_generics #where_clause {
/// Returns the "default value" for a type.
#[inline]
pub fn new() -> Self {
<Self as ::core::default::Default>::default()
}
}
});
}
Ok(())
}
}

View File

@@ -0,0 +1,43 @@
mod default_enum;
mod default_struct;
mod default_union;
mod models;
mod panic;
use syn::{Data, DeriveInput, Meta};
use super::TraitHandler;
use crate::Trait;
pub(crate) struct DefaultHandler;
impl TraitHandler for DefaultHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
match ast.data {
Data::Struct(_) => default_struct::DefaultStructHandler::trait_meta_handler(
ast,
token_stream,
traits,
meta,
),
Data::Enum(_) => default_enum::DefaultEnumHandler::trait_meta_handler(
ast,
token_stream,
traits,
meta,
),
Data::Union(_) => default_union::DefaultUnionHandler::trait_meta_handler(
ast,
token_stream,
traits,
meta,
),
}
}
}

View File

@@ -0,0 +1,166 @@
use proc_macro2::Span;
use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Expr, Meta, Token, Type};
use crate::{
common::expr::{auto_adjust_expr, meta_2_expr},
panic,
supported_traits::Trait,
};
pub(crate) struct FieldAttribute {
pub(crate) flag: bool,
pub(crate) expression: Option<Expr>,
pub(crate) span: Span,
}
pub(crate) struct FieldAttributeBuilder {
pub(crate) enable_flag: bool,
pub(crate) enable_expression: bool,
}
impl FieldAttributeBuilder {
pub(crate) fn build_from_default_meta(
&self,
meta: &Meta,
ty: &Type,
) -> syn::Result<FieldAttribute> {
debug_assert!(meta.path().is_ident("Default"));
let mut flag = false;
let mut expression = None;
let correct_usage_for_default_attribute = {
let mut usage = vec![];
if self.enable_flag {
usage.push(stringify!(#[educe(Default)]));
}
if self.enable_expression {
usage.push(stringify!(#[educe(Default = expr)]));
usage.push(stringify!(#[educe(Default(expression = expr))]));
}
usage
};
match meta {
Meta::Path(_) => {
if !self.enable_flag {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_default_attribute,
));
}
flag = true;
},
Meta::NameValue(name_value) => {
if !self.enable_expression {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_default_attribute,
));
}
expression = Some(auto_adjust_expr(name_value.value.clone(), Some(ty)));
},
Meta::List(list) => {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
let mut expression_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
match ident.to_string().as_str() {
"expression" | "expr" => {
if !self.enable_expression {
return Ok(false);
}
let v = meta_2_expr(&meta)?;
if expression_is_set {
return Err(panic::parameter_reset(ident));
}
expression_is_set = true;
expression = Some(auto_adjust_expr(v, Some(ty)));
return Ok(true);
},
_ => (),
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_default_attribute,
));
}
}
},
}
Ok(FieldAttribute {
flag,
expression,
span: meta.span(),
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
ty: &Type,
) -> syn::Result<FieldAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Default {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_default_meta(&meta, ty)?);
}
}
}
}
}
Ok(output.unwrap_or(FieldAttribute {
flag: false,
expression: None,
span: Span::call_site(),
}))
}
}

View File

@@ -0,0 +1,5 @@
mod field_attribute;
mod type_attribute;
pub(crate) use field_attribute::*;
pub(crate) use type_attribute::*;

View File

@@ -0,0 +1,214 @@
use proc_macro2::Span;
use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Expr, Meta, Token};
use crate::{
common::{
bound::Bound,
expr::{auto_adjust_expr, meta_2_expr},
ident_bool::meta_2_bool_allow_path,
},
panic, Trait,
};
pub(crate) struct TypeAttribute {
pub(crate) flag: bool,
pub(crate) new: bool,
pub(crate) expression: Option<Expr>,
pub(crate) bound: Bound,
pub(crate) span: Span,
}
#[derive(Debug)]
pub(crate) struct TypeAttributeBuilder {
pub(crate) enable_flag: bool,
pub(crate) enable_new: bool,
pub(crate) enable_expression: bool,
pub(crate) enable_bound: bool,
}
impl TypeAttributeBuilder {
pub(crate) fn build_from_default_meta(&self, meta: &Meta) -> syn::Result<TypeAttribute> {
debug_assert!(meta.path().is_ident("Default"));
let mut flag = false;
let mut new = false;
let mut expression = None;
let mut bound = Bound::Auto;
let correct_usage_for_default_attribute = {
let mut usage = vec![];
if self.enable_flag {
usage.push(stringify!(#[educe(Default)]));
}
if self.enable_new {
usage.push(stringify!(#[educe(Default(new))]));
}
if self.enable_expression {
usage.push(stringify!(#[educe(Default(expression = expr))]));
}
if self.enable_bound {
usage.push(stringify!(#[educe(Default(bound(where_predicates)))]));
usage.push(stringify!(#[educe(Default(bound = false))]));
}
usage
};
match meta {
Meta::Path(_) => {
if !self.enable_flag {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_default_attribute,
));
}
flag = true;
},
Meta::NameValue(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_default_attribute,
));
},
Meta::List(list) => {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
let mut new_is_set = false;
let mut expression_is_set = false;
let mut bound_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
match ident.to_string().as_str() {
"new" => {
if !self.enable_new {
return Ok(false);
}
let v = meta_2_bool_allow_path(&meta)?;
if new_is_set {
return Err(panic::parameter_reset(ident));
}
new_is_set = true;
new = v;
return Ok(true);
},
"expression" | "expr" => {
if !self.enable_expression {
return Ok(false);
}
let v = meta_2_expr(&meta)?;
if expression_is_set {
return Err(panic::parameter_reset(ident));
}
expression_is_set = true;
expression = Some(auto_adjust_expr(v, None));
return Ok(true);
},
"bound" => {
if !self.enable_bound {
return Ok(false);
}
let v = Bound::from_meta(&meta)?;
if bound_is_set {
return Err(panic::parameter_reset(ident));
}
bound_is_set = true;
bound = v;
return Ok(true);
},
_ => (),
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_default_attribute,
));
}
}
},
}
Ok(TypeAttribute {
flag,
new,
expression,
bound,
span: meta.span(),
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<TypeAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Default {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_default_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(TypeAttribute {
flag: false,
new: false,
expression: None,
bound: Bound::Auto,
span: Span::call_site(),
}))
}
}

View File

@@ -0,0 +1,21 @@
use proc_macro2::Span;
#[inline]
pub(crate) fn multiple_default_fields(span: Span) -> syn::Error {
syn::Error::new(span, "multiple default fields are set")
}
#[inline]
pub(crate) fn no_default_field(span: Span) -> syn::Error {
syn::Error::new(span, "there is no field set as default")
}
#[inline]
pub(crate) fn multiple_default_variants(span: Span) -> syn::Error {
syn::Error::new(span, "multiple default variants are set")
}
#[inline]
pub(crate) fn no_default_variant(span: Span) -> syn::Error {
syn::Error::new(span, "there is no variant set as default")
}

View File

@@ -0,0 +1,144 @@
use quote::{format_ident, quote};
use syn::{spanned::Spanned, Data, DeriveInput, Field, Fields, Ident, Meta, Type};
use super::{
models::{FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandler,
};
use crate::{common::r#type::dereference, panic, supported_traits::Trait};
pub(crate) struct DerefEnumHandler;
impl TraitHandler for DerefEnumHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let _ = TypeAttributeBuilder {
enable_flag: true
}
.build_from_deref_meta(meta)?;
let mut target_token_stream = proc_macro2::TokenStream::new();
let mut arms_token_stream = proc_macro2::TokenStream::new();
if let Data::Enum(data) = &ast.data {
type Variants<'a> = Vec<(&'a Ident, bool, usize, Ident, &'a Type)>;
let mut variants: Variants = Vec::new();
for variant in data.variants.iter() {
let _ = TypeAttributeBuilder {
enable_flag: false
}
.build_from_attributes(&variant.attrs, traits)?;
if let Fields::Unit = &variant.fields {
return Err(panic::trait_not_support_unit_variant(
meta.path().get_ident().unwrap(),
variant,
));
}
let fields = &variant.fields;
let (index, field) = if fields.len() == 1 {
let field = fields.into_iter().next().unwrap();
let _ = FieldAttributeBuilder {
enable_flag: true
}
.build_from_attributes(&field.attrs, traits)?;
(0usize, field)
} else {
let mut deref_field: Option<(usize, &Field)> = None;
for (index, field) in variant.fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_flag: true
}
.build_from_attributes(&field.attrs, traits)?;
if field_attribute.flag {
if deref_field.is_some() {
return Err(super::panic::multiple_deref_fields_of_variant(
field_attribute.span,
variant,
));
}
deref_field = Some((index, field));
}
}
if let Some(deref_field) = deref_field {
deref_field
} else {
return Err(super::panic::no_deref_field_of_variant(meta.span(), variant));
}
};
let (field_name, is_tuple): (Ident, bool) = match field.ident.as_ref() {
Some(ident) => (ident.clone(), false),
None => (format_ident!("_{}", index), true),
};
variants.push((&variant.ident, is_tuple, index, field_name, &field.ty));
}
if variants.is_empty() {
return Err(super::panic::no_deref_field(meta.span()));
}
let ty = variants[0].4;
let dereference_ty = dereference(ty);
target_token_stream.extend(quote!(#dereference_ty));
for (variant_ident, is_tuple, index, field_name, _) in variants {
let mut pattern_token_stream = proc_macro2::TokenStream::new();
if is_tuple {
for _ in 0..index {
pattern_token_stream.extend(quote!(_,));
}
pattern_token_stream.extend(quote!( #field_name, .. ));
arms_token_stream.extend(
quote!( Self::#variant_ident ( #pattern_token_stream ) => #field_name, ),
);
} else {
pattern_token_stream.extend(quote!( #field_name, .. ));
arms_token_stream.extend(
quote!( Self::#variant_ident { #pattern_token_stream } => #field_name, ),
);
}
}
}
let ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::ops::Deref for #ident #ty_generics #where_clause {
type Target = #target_token_stream;
#[inline]
fn deref(&self) -> &Self::Target {
match self {
#arms_token_stream
}
}
}
});
Ok(())
}
}

View File

@@ -0,0 +1,103 @@
use quote::quote;
use syn::{spanned::Spanned, Data, DeriveInput, Field, Meta};
use super::{
models::{FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandler,
};
use crate::{
common::{ident_index::IdentOrIndex, r#type::dereference_changed},
Trait,
};
pub(crate) struct DerefStructHandler;
impl TraitHandler for DerefStructHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let _ = TypeAttributeBuilder {
enable_flag: true
}
.build_from_deref_meta(meta)?;
let mut target_token_stream = proc_macro2::TokenStream::new();
let mut deref_token_stream = proc_macro2::TokenStream::new();
if let Data::Struct(data) = &ast.data {
let (index, field) = {
let fields = &data.fields;
if fields.len() == 1 {
let field = fields.into_iter().next().unwrap();
let _ = FieldAttributeBuilder {
enable_flag: true
}
.build_from_attributes(&field.attrs, traits)?;
(0usize, field)
} else {
let mut deref_field: Option<(usize, &Field)> = None;
for (index, field) in fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_flag: true
}
.build_from_attributes(&field.attrs, traits)?;
if field_attribute.flag {
if deref_field.is_some() {
return Err(super::panic::multiple_deref_fields(
field_attribute.span,
));
}
deref_field = Some((index, field));
}
}
if let Some(deref_field) = deref_field {
deref_field
} else {
return Err(super::panic::no_deref_field(meta.span()));
}
}
};
let ty = &field.ty;
let (dereference_ty, is_ref) = dereference_changed(ty);
target_token_stream.extend(quote!(#dereference_ty));
let field_name = IdentOrIndex::from_ident_with_index(field.ident.as_ref(), index);
deref_token_stream.extend(if is_ref {
quote! (self.#field_name)
} else {
quote! (&self.#field_name)
});
}
let ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::ops::Deref for #ident #ty_generics #where_clause {
type Target = #target_token_stream;
#[inline]
fn deref(&self) -> &Self::Target {
#deref_token_stream
}
}
});
Ok(())
}
}

View File

@@ -0,0 +1,36 @@
mod deref_enum;
mod deref_struct;
mod models;
mod panic;
use syn::{Data, DeriveInput, Meta};
use super::TraitHandler;
use crate::Trait;
pub(crate) struct DerefHandler;
impl TraitHandler for DerefHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
match ast.data {
Data::Struct(_) => deref_struct::DerefStructHandler::trait_meta_handler(
ast,
token_stream,
traits,
meta,
),
Data::Enum(_) => {
deref_enum::DerefEnumHandler::trait_meta_handler(ast, token_stream, traits, meta)
},
Data::Union(_) => {
Err(crate::panic::trait_not_support_union(meta.path().get_ident().unwrap()))
},
}
}
}

View File

@@ -0,0 +1,94 @@
use proc_macro2::Span;
use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Meta, Token};
use crate::{panic, supported_traits::Trait};
pub(crate) struct FieldAttribute {
pub(crate) flag: bool,
pub(crate) span: Span,
}
pub(crate) struct FieldAttributeBuilder {
pub(crate) enable_flag: bool,
}
impl FieldAttributeBuilder {
pub(crate) fn build_from_deref_meta(&self, meta: &Meta) -> syn::Result<FieldAttribute> {
debug_assert!(meta.path().is_ident("Deref"));
let correct_usage_for_deref_attribute = {
let mut usage = vec![];
if self.enable_flag {
usage.push(stringify!(#[educe(Deref)]));
}
usage
};
match meta {
Meta::Path(_) => {
if !self.enable_flag {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_deref_attribute,
));
}
},
Meta::NameValue(_) | Meta::List(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_deref_attribute,
));
},
}
Ok(FieldAttribute {
flag: true, span: meta.span()
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<FieldAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Deref {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_deref_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(FieldAttribute {
flag: false, span: Span::call_site()
}))
}
}

View File

@@ -0,0 +1,5 @@
mod field_attribute;
mod type_attribute;
pub(crate) use field_attribute::*;
pub(crate) use type_attribute::*;

View File

@@ -0,0 +1,87 @@
use syn::{punctuated::Punctuated, Attribute, Meta, Token};
use crate::{panic, Trait};
pub(crate) struct TypeAttribute;
#[derive(Debug)]
pub(crate) struct TypeAttributeBuilder {
pub(crate) enable_flag: bool,
}
impl TypeAttributeBuilder {
pub(crate) fn build_from_deref_meta(&self, meta: &Meta) -> syn::Result<TypeAttribute> {
debug_assert!(meta.path().is_ident("Deref"));
let correct_usage_for_deref_attribute = {
let mut usage = vec![];
if self.enable_flag {
usage.push(stringify!(#[educe(Deref)]));
}
usage
};
match meta {
Meta::Path(_) => {
if !self.enable_flag {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_deref_attribute,
));
}
},
Meta::NameValue(_) | Meta::List(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_deref_attribute,
));
},
}
Ok(TypeAttribute)
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<TypeAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Deref {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_deref_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(TypeAttribute))
}
}

View File

@@ -0,0 +1,31 @@
use proc_macro2::Span;
use syn::Variant;
#[inline]
pub(crate) fn multiple_deref_fields(span: Span) -> syn::Error {
syn::Error::new(span, "multiple fields are set for `Deref`")
}
#[inline]
pub(crate) fn multiple_deref_fields_of_variant(span: Span, variant: &Variant) -> syn::Error {
syn::Error::new(
span,
format!("multiple fields of the `{}` variant are set for `Deref`", variant.ident),
)
}
#[inline]
pub(crate) fn no_deref_field(span: Span) -> syn::Error {
syn::Error::new(span, "there is no field which is assigned for `Deref`")
}
#[inline]
pub(crate) fn no_deref_field_of_variant(span: Span, variant: &Variant) -> syn::Error {
syn::Error::new(
span,
format!(
"there is no field for the `{}` variant which is assigned for `Deref`",
variant.ident
),
)
}

View File

@@ -0,0 +1,139 @@
use quote::{format_ident, quote};
use syn::{spanned::Spanned, Data, DeriveInput, Field, Fields, Ident, Meta};
use super::{
models::{FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandler,
};
use crate::{panic, supported_traits::Trait};
pub(crate) struct DerefMutEnumHandler;
impl TraitHandler for DerefMutEnumHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let _ = TypeAttributeBuilder {
enable_flag: true
}
.build_from_deref_mut_meta(meta)?;
let mut arms_token_stream = proc_macro2::TokenStream::new();
if let Data::Enum(data) = &ast.data {
type Variants<'a> = Vec<(&'a Ident, bool, usize, Ident)>;
let mut variants: Variants = Vec::new();
for variant in data.variants.iter() {
let _ = TypeAttributeBuilder {
enable_flag: false
}
.build_from_attributes(&variant.attrs, traits)?;
if let Fields::Unit = &variant.fields {
return Err(panic::trait_not_support_unit_variant(
meta.path().get_ident().unwrap(),
variant,
));
}
let fields = &variant.fields;
let (index, field) = if fields.len() == 1 {
let field = fields.into_iter().next().unwrap();
let _ = FieldAttributeBuilder {
enable_flag: true
}
.build_from_attributes(&field.attrs, traits)?;
(0usize, field)
} else {
let mut deref_field: Option<(usize, &Field)> = None;
for (index, field) in variant.fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_flag: true
}
.build_from_attributes(&field.attrs, traits)?;
if field_attribute.flag {
if deref_field.is_some() {
return Err(super::panic::multiple_deref_mut_fields_of_variant(
field_attribute.span,
variant,
));
}
deref_field = Some((index, field));
}
}
if let Some(deref_field) = deref_field {
deref_field
} else {
return Err(super::panic::no_deref_mut_field_of_variant(
meta.span(),
variant,
));
}
};
let (field_name, is_tuple): (Ident, bool) = match field.ident.as_ref() {
Some(ident) => (ident.clone(), false),
None => (format_ident!("_{}", index), true),
};
variants.push((&variant.ident, is_tuple, index, field_name));
}
if variants.is_empty() {
return Err(super::panic::no_deref_mut_field(meta.span()));
}
for (variant_ident, is_tuple, index, field_name) in variants {
let mut pattern_token_stream = proc_macro2::TokenStream::new();
if is_tuple {
for _ in 0..index {
pattern_token_stream.extend(quote!(_,));
}
pattern_token_stream.extend(quote!( #field_name, .. ));
arms_token_stream.extend(
quote!( Self::#variant_ident ( #pattern_token_stream ) => #field_name, ),
);
} else {
pattern_token_stream.extend(quote!( #field_name, .. ));
arms_token_stream.extend(
quote!( Self::#variant_ident { #pattern_token_stream } => #field_name, ),
);
}
}
}
let ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::ops::DerefMut for #ident #ty_generics #where_clause {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
match self {
#arms_token_stream
}
}
}
});
Ok(())
}
}

View File

@@ -0,0 +1,92 @@
use quote::quote;
use syn::{spanned::Spanned, Data, DeriveInput, Field, Meta, Type};
use super::{
models::{FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandler,
};
use crate::{common::ident_index::IdentOrIndex, Trait};
pub(crate) struct DerefMutStructHandler;
impl TraitHandler for DerefMutStructHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let _ = TypeAttributeBuilder {
enable_flag: true
}
.build_from_deref_mut_meta(meta)?;
let mut deref_mut_token_stream = proc_macro2::TokenStream::new();
if let Data::Struct(data) = &ast.data {
let (index, field) = {
let fields = &data.fields;
if fields.len() == 1 {
let field = fields.into_iter().next().unwrap();
let _ = FieldAttributeBuilder {
enable_flag: true
}
.build_from_attributes(&field.attrs, traits)?;
(0usize, field)
} else {
let mut deref_field: Option<(usize, &Field)> = None;
for (index, field) in fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_flag: true
}
.build_from_attributes(&field.attrs, traits)?;
if field_attribute.flag {
if deref_field.is_some() {
return Err(super::panic::multiple_deref_mut_fields(
field_attribute.span,
));
}
deref_field = Some((index, field));
}
}
if let Some(deref_field) = deref_field {
deref_field
} else {
return Err(super::panic::no_deref_mut_field(meta.span()));
}
}
};
let field_name = IdentOrIndex::from_ident_with_index(field.ident.as_ref(), index);
deref_mut_token_stream.extend(if let Type::Reference(_) = &field.ty {
quote! (self.#field_name)
} else {
quote! (&mut self.#field_name)
});
}
let ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::ops::DerefMut for #ident #ty_generics #where_clause {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
#deref_mut_token_stream
}
}
});
Ok(())
}
}

View File

@@ -0,0 +1,39 @@
mod deref_mut_enum;
mod deref_mut_struct;
mod models;
mod panic;
use syn::{Data, DeriveInput, Meta};
use super::TraitHandler;
use crate::Trait;
pub(crate) struct DerefMutHandler;
impl TraitHandler for DerefMutHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
match ast.data {
Data::Struct(_) => deref_mut_struct::DerefMutStructHandler::trait_meta_handler(
ast,
token_stream,
traits,
meta,
),
Data::Enum(_) => deref_mut_enum::DerefMutEnumHandler::trait_meta_handler(
ast,
token_stream,
traits,
meta,
),
Data::Union(_) => {
Err(crate::panic::trait_not_support_union(meta.path().get_ident().unwrap()))
},
}
}
}

View File

@@ -0,0 +1,94 @@
use proc_macro2::Span;
use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Meta, Token};
use crate::{panic, supported_traits::Trait};
pub(crate) struct FieldAttribute {
pub(crate) flag: bool,
pub(crate) span: Span,
}
pub(crate) struct FieldAttributeBuilder {
pub(crate) enable_flag: bool,
}
impl FieldAttributeBuilder {
pub(crate) fn build_from_deref_mut_meta(&self, meta: &Meta) -> syn::Result<FieldAttribute> {
debug_assert!(meta.path().is_ident("DerefMut"));
let correct_usage_for_deref_mut_attribute = {
let mut usage = vec![];
if self.enable_flag {
usage.push(stringify!(#[educe(DerefMut)]));
}
usage
};
match meta {
Meta::Path(_) => {
if !self.enable_flag {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_deref_mut_attribute,
));
}
},
Meta::NameValue(_) | Meta::List(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_deref_mut_attribute,
));
},
}
Ok(FieldAttribute {
flag: true, span: meta.span()
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<FieldAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::DerefMut {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_deref_mut_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(FieldAttribute {
flag: false, span: Span::call_site()
}))
}
}

View File

@@ -0,0 +1,5 @@
mod field_attribute;
mod type_attribute;
pub(crate) use field_attribute::*;
pub(crate) use type_attribute::*;

View File

@@ -0,0 +1,87 @@
use syn::{punctuated::Punctuated, Attribute, Meta, Token};
use crate::{panic, Trait};
pub(crate) struct TypeAttribute;
#[derive(Debug)]
pub(crate) struct TypeAttributeBuilder {
pub(crate) enable_flag: bool,
}
impl TypeAttributeBuilder {
pub(crate) fn build_from_deref_mut_meta(&self, meta: &Meta) -> syn::Result<TypeAttribute> {
debug_assert!(meta.path().is_ident("DerefMut"));
let correct_usage_for_deref_mut_attribute = {
let mut usage = vec![];
if self.enable_flag {
usage.push(stringify!(#[educe(DerefMut)]));
}
usage
};
match meta {
Meta::Path(_) => {
if !self.enable_flag {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_deref_mut_attribute,
));
}
},
Meta::NameValue(_) | Meta::List(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_deref_mut_attribute,
));
},
}
Ok(TypeAttribute)
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<TypeAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::DerefMut {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_deref_mut_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(TypeAttribute))
}
}

View File

@@ -0,0 +1,31 @@
use proc_macro2::Span;
use syn::Variant;
#[inline]
pub(crate) fn multiple_deref_mut_fields(span: Span) -> syn::Error {
syn::Error::new(span, "multiple fields are set for `DerefMut`")
}
#[inline]
pub(crate) fn multiple_deref_mut_fields_of_variant(span: Span, variant: &Variant) -> syn::Error {
syn::Error::new(
span,
format!("multiple fields of the `{}` variant are set for `DerefMut`", variant.ident),
)
}
#[inline]
pub(crate) fn no_deref_mut_field(span: Span) -> syn::Error {
syn::Error::new(span, "there is no field which is assigned for `DerefMut`")
}
#[inline]
pub(crate) fn no_deref_mut_field_of_variant(span: Span, variant: &Variant) -> syn::Error {
syn::Error::new(
span,
format!(
"there is no field for the `{}` variant which is assigned for `DerefMut`",
variant.ident
),
)
}

View File

@@ -0,0 +1,106 @@
mod models;
use models::{FieldAttributeBuilder, TypeAttributeBuilder};
use quote::quote;
use syn::{Data, DeriveInput, Meta};
use super::TraitHandler;
use crate::Trait;
pub(crate) struct EqHandler;
impl TraitHandler for EqHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
#[cfg(feature = "PartialEq")]
let contains_partial_eq = traits.contains(&Trait::PartialEq);
#[cfg(not(feature = "PartialEq"))]
let contains_partial_eq = false;
let type_attribute = TypeAttributeBuilder {
enable_flag: true,
enable_bound: !contains_partial_eq,
}
.build_from_eq_meta(meta)?;
let mut field_types = vec![];
// if `contains_partial_eq` is true, the implementation is handled by the `PartialEq` attribute, and field attributes is also handled by the `PartialEq` attribute
if !contains_partial_eq {
match &ast.data {
Data::Struct(data) => {
for field in data.fields.iter() {
field_types.push(&field.ty);
let _ =
FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?;
}
},
Data::Enum(data) => {
for variant in data.variants.iter() {
let _ = TypeAttributeBuilder {
enable_flag: false, enable_bound: false
}
.build_from_attributes(&variant.attrs, traits)?;
for field in variant.fields.iter() {
field_types.push(&field.ty);
let _ = FieldAttributeBuilder
.build_from_attributes(&field.attrs, traits)?;
}
}
},
Data::Union(data) => {
for field in data.fields.named.iter() {
field_types.push(&field.ty);
let _ =
FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?;
}
},
}
let ident = &ast.ident;
/*
#[derive(PartialEq)]
struct B<T> {
f1: PhantomData<T>,
}
impl<T> Eq for B<T> {
}
// The above code will throw a compile error because T have to be bound to `PartialEq`. However, it seems not to be necessary logically.
*/
let bound =
type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::cmp::PartialEq)).unwrap(),
&field_types,
&[quote! {::core::cmp::PartialEq}],
);
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::cmp::Eq for #ident #ty_generics #where_clause {
}
});
}
Ok(())
}
}

View File

@@ -0,0 +1,57 @@
use syn::{punctuated::Punctuated, Attribute, Meta, Token};
use crate::{panic, supported_traits::Trait};
pub(crate) struct FieldAttribute;
pub(crate) struct FieldAttributeBuilder;
impl FieldAttributeBuilder {
pub(crate) fn build_from_eq_meta(&self, meta: &Meta) -> syn::Result<FieldAttribute> {
debug_assert!(meta.path().is_ident("Eq"));
return Err(panic::attribute_incorrect_place(meta.path().get_ident().unwrap()));
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<FieldAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Eq {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_eq_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(FieldAttribute))
}
}

View File

@@ -0,0 +1,5 @@
mod field_attribute;
mod type_attribute;
pub(crate) use field_attribute::*;
pub(crate) use type_attribute::*;

View File

@@ -0,0 +1,140 @@
use syn::{punctuated::Punctuated, Attribute, Meta, Token};
use crate::{common::bound::Bound, panic, Trait};
pub(crate) struct TypeAttribute {
pub(crate) bound: Bound,
}
#[derive(Debug)]
pub(crate) struct TypeAttributeBuilder {
pub(crate) enable_flag: bool,
pub(crate) enable_bound: bool,
}
impl TypeAttributeBuilder {
pub(crate) fn build_from_eq_meta(&self, meta: &Meta) -> syn::Result<TypeAttribute> {
debug_assert!(meta.path().is_ident("Eq"));
let mut bound = Bound::Auto;
let correct_usage_for_copy_attribute = {
let mut usage = vec![];
if self.enable_flag {
usage.push(stringify!(#[educe(Eq)]));
}
if self.enable_bound {
usage.push(stringify!(#[educe(Eq(bound(where_predicates)))]));
usage.push(stringify!(#[educe(Eq(bound = false))]));
}
usage
};
match meta {
Meta::Path(_) => {
if !self.enable_flag {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_copy_attribute,
));
}
},
Meta::NameValue(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_copy_attribute,
));
},
Meta::List(list) => {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
let mut bound_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
if ident == "bound" {
if !self.enable_bound {
return Ok(false);
}
let v = Bound::from_meta(&meta)?;
if bound_is_set {
return Err(panic::parameter_reset(ident));
}
bound_is_set = true;
bound = v;
return Ok(true);
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_copy_attribute,
));
}
}
},
}
Ok(TypeAttribute {
bound,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<TypeAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Eq {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_eq_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(TypeAttribute {
bound: Bound::Auto
}))
}
}

View File

@@ -0,0 +1,169 @@
use quote::{format_ident, quote};
use syn::{Data, DeriveInput, Fields, Meta, Path, Type};
use super::{
models::{FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandler,
};
use crate::Trait;
pub(crate) struct HashEnumHandler;
impl TraitHandler for HashEnumHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute =
TypeAttributeBuilder {
enable_flag: true, enable_unsafe: false, enable_bound: true
}
.build_from_hash_meta(meta)?;
let mut hash_types: Vec<&Type> = Vec::new();
let mut hash_token_stream = proc_macro2::TokenStream::new();
let mut arms_token_stream = proc_macro2::TokenStream::new();
if let Data::Enum(data) = &ast.data {
for (variant_index, variant) in data.variants.iter().enumerate() {
let _ = TypeAttributeBuilder {
enable_flag: false,
enable_unsafe: false,
enable_bound: false,
}
.build_from_attributes(&variant.attrs, traits)?;
let variant_ident = &variant.ident;
let built_in_hash: Path = syn::parse2(quote!(::core::hash::Hash::hash)).unwrap();
match &variant.fields {
Fields::Unit => {
arms_token_stream.extend(quote! {
Self::#variant_ident => {
::core::hash::Hash::hash(&#variant_index, state);
}
});
},
Fields::Named(_) => {
let mut pattern_token_stream = proc_macro2::TokenStream::new();
let mut block_token_stream = proc_macro2::TokenStream::new();
for field in variant.fields.iter() {
let field_attribute = FieldAttributeBuilder {
enable_ignore: true,
enable_method: true,
}
.build_from_attributes(&field.attrs, traits)?;
let field_name_real = field.ident.as_ref().unwrap();
let field_name_var = format_ident!("v_{}", field_name_real);
if field_attribute.ignore {
pattern_token_stream.extend(quote!(#field_name_real: _,));
continue;
}
pattern_token_stream.extend(quote!(#field_name_real: #field_name_var,));
let hash = field_attribute.method.as_ref().unwrap_or_else(|| {
hash_types.push(&field.ty);
&built_in_hash
});
block_token_stream.extend(quote!( #hash(#field_name_var, state); ));
}
arms_token_stream.extend(quote! {
Self::#variant_ident { #pattern_token_stream } => {
::core::hash::Hash::hash(&#variant_index, state);
#block_token_stream
}
});
},
Fields::Unnamed(_) => {
let mut pattern_token_stream = proc_macro2::TokenStream::new();
let mut block_token_stream = proc_macro2::TokenStream::new();
for (index, field) in variant.fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_ignore: true,
enable_method: true,
}
.build_from_attributes(&field.attrs, traits)?;
let field_name_var = format_ident!("_{}", index);
if field_attribute.ignore {
pattern_token_stream.extend(quote!(_,));
continue;
}
pattern_token_stream.extend(quote!(#field_name_var,));
let hash = field_attribute.method.as_ref().unwrap_or_else(|| {
hash_types.push(&field.ty);
&built_in_hash
});
block_token_stream.extend(quote!( #hash(#field_name_var, state); ));
}
arms_token_stream.extend(quote! {
Self::#variant_ident ( #pattern_token_stream ) => {
::core::hash::Hash::hash(&#variant_index, state);
#block_token_stream
}
});
},
}
}
}
if !arms_token_stream.is_empty() {
hash_token_stream.extend(quote! {
match self {
#arms_token_stream
}
});
}
let ident = &ast.ident;
let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::hash::Hash)).unwrap(),
&hash_types,
&[],
);
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::hash::Hash for #ident #ty_generics #where_clause {
#[inline]
fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
#hash_token_stream
}
}
});
Ok(())
}
}

View File

@@ -0,0 +1,88 @@
use quote::quote;
use syn::{Data, DeriveInput, Meta, Path, Type};
use super::{
models::{FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandler,
};
use crate::{common::ident_index::IdentOrIndex, Trait};
pub(crate) struct HashStructHandler;
impl TraitHandler for HashStructHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute =
TypeAttributeBuilder {
enable_flag: true, enable_unsafe: false, enable_bound: true
}
.build_from_hash_meta(meta)?;
let mut hash_types: Vec<&Type> = Vec::new();
let mut hash_token_stream = proc_macro2::TokenStream::new();
if let Data::Struct(data) = &ast.data {
let built_in_hash: Path = syn::parse2(quote!(::core::hash::Hash::hash)).unwrap();
for (index, field) in data.fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_ignore: true,
enable_method: true,
}
.build_from_attributes(&field.attrs, traits)?;
if field_attribute.ignore {
continue;
}
let field_name = if let Some(ident) = field.ident.as_ref() {
IdentOrIndex::from(ident)
} else {
IdentOrIndex::from(index)
};
let hash = field_attribute.method.as_ref().unwrap_or_else(|| {
hash_types.push(&field.ty);
&built_in_hash
});
hash_token_stream.extend(quote!( #hash(&self.#field_name, state); ));
}
}
let ident = &ast.ident;
let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::hash::Hash)).unwrap(),
&hash_types,
&[],
);
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::hash::Hash for #ident #ty_generics #where_clause {
#[inline]
fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
#hash_token_stream
}
}
});
Ok(())
}
}

View File

@@ -0,0 +1,54 @@
use quote::quote;
use syn::{Data, DeriveInput, Meta};
use super::models::{FieldAttributeBuilder, TypeAttributeBuilder};
use crate::{supported_traits::Trait, trait_handlers::TraitHandler};
pub(crate) struct HashUnionHandler;
impl TraitHandler for HashUnionHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute =
TypeAttributeBuilder {
enable_flag: true, enable_unsafe: true, enable_bound: false
}
.build_from_hash_meta(meta)?;
if !type_attribute.has_unsafe {
return Err(super::panic::union_without_unsafe(meta));
}
if let Data::Union(data) = &ast.data {
for field in data.fields.named.iter() {
let _ = FieldAttributeBuilder {
enable_ignore: false, enable_method: false
}
.build_from_attributes(&field.attrs, traits)?;
}
}
let ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::hash::Hash for #ident #ty_generics #where_clause {
#[inline]
fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
let size = ::core::mem::size_of::<Self>();
let data = unsafe { ::core::slice::from_raw_parts(self as *const Self as *const u8, size) };
::core::hash::Hash::hash(data, state)
}
}
});
Ok(())
}
}

View File

@@ -0,0 +1,34 @@
mod hash_enum;
mod hash_struct;
mod hash_union;
mod models;
mod panic;
use syn::{Data, DeriveInput, Meta};
use super::TraitHandler;
use crate::Trait;
pub(crate) struct HashHandler;
impl TraitHandler for HashHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
match ast.data {
Data::Struct(_) => {
hash_struct::HashStructHandler::trait_meta_handler(ast, token_stream, traits, meta)
},
Data::Enum(_) => {
hash_enum::HashEnumHandler::trait_meta_handler(ast, token_stream, traits, meta)
},
Data::Union(_) => {
hash_union::HashUnionHandler::trait_meta_handler(ast, token_stream, traits, meta)
},
}
}
}

View File

@@ -0,0 +1,172 @@
use syn::{punctuated::Punctuated, Attribute, Meta, Path, Token};
use crate::{
common::{
ident_bool::{meta_2_bool_allow_path, meta_name_value_2_bool},
path::meta_2_path,
},
panic,
supported_traits::Trait,
};
pub(crate) struct FieldAttribute {
pub(crate) ignore: bool,
pub(crate) method: Option<Path>,
}
pub(crate) struct FieldAttributeBuilder {
pub(crate) enable_ignore: bool,
pub(crate) enable_method: bool,
}
impl FieldAttributeBuilder {
pub(crate) fn build_from_hash_meta(&self, meta: &Meta) -> syn::Result<FieldAttribute> {
debug_assert!(meta.path().is_ident("Hash"));
let mut ignore = false;
let mut method = None;
let correct_usage_for_partial_eq_attribute = {
let mut usage = vec![];
if self.enable_ignore {
usage.push(stringify!(#[educe(Hash = false)]));
usage.push(stringify!(#[educe(Hash(ignore))]));
}
if self.enable_method {
usage.push(stringify!(#[educe(Hash(method(path_to_method)))]));
}
usage
};
match meta {
Meta::Path(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
},
Meta::NameValue(name_value) => {
if self.enable_ignore {
ignore = !meta_name_value_2_bool(name_value)?;
} else {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
}
},
Meta::List(list) => {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
let mut ignore_is_set = false;
let mut method_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
match ident.to_string().as_str() {
"ignore" => {
if !self.enable_ignore {
return Ok(false);
}
let v = meta_2_bool_allow_path(&meta)?;
if ignore_is_set {
return Err(panic::parameter_reset(ident));
}
ignore_is_set = true;
ignore = v;
return Ok(true);
},
"method" => {
if !self.enable_method {
return Ok(false);
}
let v = meta_2_path(&meta)?;
if method_is_set {
return Err(panic::parameter_reset(ident));
}
method_is_set = true;
method = Some(v);
return Ok(true);
},
_ => (),
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
}
}
},
}
Ok(FieldAttribute {
ignore,
method,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<FieldAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Hash {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_hash_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(FieldAttribute {
ignore: false, method: None
}))
}
}

View File

@@ -0,0 +1,5 @@
mod field_attribute;
mod type_attribute;
pub(crate) use field_attribute::*;
pub(crate) use type_attribute::*;

View File

@@ -0,0 +1,154 @@
use syn::{punctuated::Punctuated, Attribute, Meta, Token};
use crate::{
common::{bound::Bound, unsafe_punctuated_meta::UnsafePunctuatedMeta},
panic, Trait,
};
pub(crate) struct TypeAttribute {
pub(crate) has_unsafe: bool,
pub(crate) bound: Bound,
}
#[derive(Debug)]
pub(crate) struct TypeAttributeBuilder {
pub(crate) enable_flag: bool,
pub(crate) enable_unsafe: bool,
pub(crate) enable_bound: bool,
}
impl TypeAttributeBuilder {
pub(crate) fn build_from_hash_meta(&self, meta: &Meta) -> syn::Result<TypeAttribute> {
debug_assert!(meta.path().is_ident("Hash"));
let mut has_unsafe = false;
let mut bound = Bound::Auto;
let correct_usage_for_hash_attribute = {
let mut usage = vec![];
if self.enable_flag {
usage.push(stringify!(#[educe(Hash)]));
}
if self.enable_bound {
usage.push(stringify!(#[educe(Hash(bound(where_predicates)))]));
usage.push(stringify!(#[educe(Hash(bound = false))]));
}
usage
};
match meta {
Meta::Path(_) => {
if !self.enable_flag {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_hash_attribute,
));
}
},
Meta::NameValue(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_hash_attribute,
));
},
Meta::List(list) => {
let result = if self.enable_unsafe {
let result: UnsafePunctuatedMeta = list.parse_args()?;
has_unsafe = result.has_unsafe;
result.list
} else {
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?
};
let mut bound_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
if ident == "bound" {
if !self.enable_bound {
return Ok(false);
}
let v = Bound::from_meta(&meta)?;
if bound_is_set {
return Err(panic::parameter_reset(ident));
}
bound_is_set = true;
bound = v;
return Ok(true);
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_hash_attribute,
));
}
}
},
}
Ok(TypeAttribute {
has_unsafe,
bound,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<TypeAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Hash {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_hash_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(TypeAttribute {
has_unsafe: false, bound: Bound::Auto
}))
}
}

View File

@@ -0,0 +1,22 @@
use quote::ToTokens;
use syn::{spanned::Spanned, Meta};
#[inline]
pub(crate) fn union_without_unsafe(meta: &Meta) -> syn::Error {
let mut s = meta.into_token_stream().to_string();
match s.len() {
4 => s.push_str("(unsafe)"),
6 => s.insert_str(10, "unsafe"),
_ => unreachable!(),
}
syn::Error::new(
meta.span(),
format!(
"a union's `Hash` implementation is not precise, because it ignores the type of \
fields\n* If your union doesn't care about that, use `#[educe({s})]` to implement \
the `Hash` trait for it."
),
)
}

View File

@@ -0,0 +1,17 @@
use quote::quote_spanned;
use syn::{spanned::Spanned, Type};
use crate::common::{r#type::dereference_changed, tools::HashType};
#[inline]
pub(crate) fn to_hash_type(ty: &Type) -> HashType {
let (ty, is_ref) = dereference_changed(ty);
let ty = if is_ref {
syn::parse2(quote_spanned!( ty.span() => &'static #ty )).unwrap()
} else {
ty.clone()
};
HashType::from(ty)
}

View File

@@ -0,0 +1,224 @@
use std::collections::HashMap;
use quote::{format_ident, quote};
use syn::{Data, DeriveInput, Field, Fields, Ident, Meta, Path, Type};
use super::{
models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandlerMultiple,
};
use crate::{panic, Trait};
pub(crate) struct IntoEnumHandler;
impl TraitHandlerMultiple for IntoEnumHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &[Meta],
) -> syn::Result<()> {
let type_attribute = TypeAttributeBuilder {
enable_types: true
}
.build_from_into_meta(meta)?;
if let Data::Enum(data) = &ast.data {
let field_attributes: Vec<HashMap<usize, FieldAttribute>> = {
let mut map = Vec::new();
for variant in data.variants.iter() {
let mut field_map = HashMap::new();
let _ = TypeAttributeBuilder {
enable_types: false
}
.build_from_attributes(&variant.attrs, traits)?;
for (index, field) in variant.fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_types: true
}
.build_from_attributes(&field.attrs, traits)?;
for ty in field_attribute.types.keys() {
if !type_attribute.types.contains_key(ty) {
return Err(super::panic::no_into_impl(ty));
}
}
field_map.insert(index, field_attribute);
}
map.push(field_map);
}
map
};
for (target_ty, bound) in type_attribute.types {
let mut into_types: Vec<&Type> = Vec::new();
let mut arms_token_stream = proc_macro2::TokenStream::new();
type Variants<'a> =
Vec<(&'a Ident, bool, usize, Ident, &'a Type, Option<&'a Path>)>;
let mut variants: Variants = Vec::new();
for (variant, field_attributes) in data.variants.iter().zip(field_attributes.iter())
{
if let Fields::Unit = &variant.fields {
return Err(panic::trait_not_support_unit_variant(
meta[0].path().get_ident().unwrap(),
variant,
));
}
let (index, field, method) = {
let fields = &variant.fields;
if fields.len() == 1 {
let field = fields.into_iter().next().unwrap();
let method = if let Some(field_attribute) = field_attributes.get(&0) {
if let Some(method) = field_attribute.types.get(&target_ty) {
method.as_ref()
} else {
None
}
} else {
None
};
(0usize, field, method)
} else {
let mut into_field: Option<(usize, &Field, Option<&Path>)> = None;
for (index, field) in fields.iter().enumerate() {
if let Some(field_attribute) = field_attributes.get(&index) {
if let Some((key, method)) =
field_attribute.types.get_key_value(&target_ty)
{
if into_field.is_some() {
return Err(super::panic::multiple_into_fields(key));
}
into_field = Some((index, field, method.as_ref()));
}
}
}
if into_field.is_none() {
// search the same type
for (index, field) in fields.iter().enumerate() {
let field_ty = super::common::to_hash_type(&field.ty);
if target_ty.eq(&field_ty) {
if into_field.is_some() {
// multiple candidates
into_field = None;
break;
}
into_field = Some((index, field, None));
}
}
}
if let Some(into_field) = into_field {
into_field
} else {
return Err(super::panic::no_into_field(&target_ty));
}
}
};
let (field_name, is_tuple): (Ident, bool) = match field.ident.as_ref() {
Some(ident) => (ident.clone(), false),
None => (format_ident!("_{}", index), true),
};
variants.push((&variant.ident, is_tuple, index, field_name, &field.ty, method));
}
if variants.is_empty() {
return Err(super::panic::no_into_field(&target_ty));
}
for (variant_ident, is_tuple, index, field_name, ty, method) in variants {
let mut pattern_token_stream = proc_macro2::TokenStream::new();
let mut body_token_stream = proc_macro2::TokenStream::new();
if let Some(method) = method {
body_token_stream.extend(quote!( #method(#field_name) ));
} else {
let field_ty = super::common::to_hash_type(ty);
if target_ty.eq(&field_ty) {
body_token_stream.extend(quote!( #field_name ));
} else {
into_types.push(ty);
body_token_stream
.extend(quote!( ::core::convert::Into::into(#field_name) ));
}
}
if is_tuple {
for _ in 0..index {
pattern_token_stream.extend(quote!(_,));
}
pattern_token_stream.extend(quote!( #field_name, .. ));
arms_token_stream.extend(
quote!( Self::#variant_ident ( #pattern_token_stream ) => #body_token_stream, ),
);
} else {
pattern_token_stream.extend(quote!( #field_name, .. ));
arms_token_stream.extend(
quote!( Self::#variant_ident { #pattern_token_stream } => #body_token_stream, ),
);
}
}
let ident = &ast.ident;
let bound = bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::convert::Into<#target_ty>)).unwrap(),
&into_types,
&[],
);
// clone generics in order to not to affect other Into<T> implementations
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, _) = ast.generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::convert::Into<#target_ty> for #ident #ty_generics #where_clause {
#[inline]
fn into(self) -> #target_ty {
match self {
#arms_token_stream
}
}
}
});
}
}
Ok(())
}
}

View File

@@ -0,0 +1,168 @@
use std::collections::HashMap;
use quote::quote;
use syn::{Data, DeriveInput, Field, Meta, Path, Type};
use super::{
models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandlerMultiple,
};
use crate::{common::ident_index::IdentOrIndex, Trait};
pub(crate) struct IntoStructHandler;
impl TraitHandlerMultiple for IntoStructHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &[Meta],
) -> syn::Result<()> {
let type_attribute = TypeAttributeBuilder {
enable_types: true
}
.build_from_into_meta(meta)?;
if let Data::Struct(data) = &ast.data {
let fields = &data.fields;
let field_attributes: HashMap<usize, FieldAttribute> = {
let mut map = HashMap::new();
for (index, field) in fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_types: true
}
.build_from_attributes(&field.attrs, traits)?;
for ty in field_attribute.types.keys() {
if !type_attribute.types.contains_key(ty) {
return Err(super::panic::no_into_impl(ty));
}
}
map.insert(index, field_attribute);
}
map
};
for (target_ty, bound) in type_attribute.types {
let mut into_types: Vec<&Type> = Vec::new();
let mut into_token_stream = proc_macro2::TokenStream::new();
let (index, field, method) = {
let fields = &data.fields;
if fields.len() == 1 {
let field = fields.into_iter().next().unwrap();
let method = if let Some(field_attribute) = field_attributes.get(&0) {
if let Some(method) = field_attribute.types.get(&target_ty) {
method.as_ref()
} else {
None
}
} else {
None
};
(0usize, field, method)
} else {
let mut into_field: Option<(usize, &Field, Option<&Path>)> = None;
for (index, field) in fields.iter().enumerate() {
if let Some(field_attribute) = field_attributes.get(&index) {
if let Some((key, method)) =
field_attribute.types.get_key_value(&target_ty)
{
if into_field.is_some() {
return Err(super::panic::multiple_into_fields(key));
}
into_field = Some((index, field, method.as_ref()));
}
}
}
if into_field.is_none() {
// search the same type
for (index, field) in fields.iter().enumerate() {
let field_ty = super::common::to_hash_type(&field.ty);
if target_ty.eq(&field_ty) {
if into_field.is_some() {
// multiple candidates
into_field = None;
break;
}
into_field = Some((index, field, None));
}
}
}
if let Some(into_field) = into_field {
into_field
} else {
return Err(super::panic::no_into_field(&target_ty));
}
}
};
let field_name = IdentOrIndex::from_ident_with_index(field.ident.as_ref(), index);
if let Some(method) = method {
into_token_stream.extend(quote!( #method(self.#field_name) ));
} else {
let ty = &field.ty;
let field_ty = super::common::to_hash_type(ty);
if target_ty.eq(&field_ty) {
into_token_stream.extend(quote!( self.#field_name ));
} else {
into_types.push(ty);
into_token_stream
.extend(quote!( ::core::convert::Into::into(self.#field_name) ));
}
}
let ident = &ast.ident;
let bound = bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::convert::Into<#target_ty>)).unwrap(),
&into_types,
&[],
);
// clone generics in order to not to affect other Into<T> implementations
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, _) = ast.generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::convert::Into<#target_ty> for #ident #ty_generics #where_clause {
#[inline]
fn into(self) -> #target_ty {
#into_token_stream
}
}
});
}
}
Ok(())
}
}

View File

@@ -0,0 +1,34 @@
mod common;
mod into_enum;
mod into_struct;
mod models;
mod panic;
use syn::{Data, DeriveInput, Meta};
use super::TraitHandlerMultiple;
use crate::Trait;
pub(crate) struct IntoHandler;
impl TraitHandlerMultiple for IntoHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &[Meta],
) -> syn::Result<()> {
match ast.data {
Data::Struct(_) => {
into_struct::IntoStructHandler::trait_meta_handler(ast, token_stream, traits, meta)
},
Data::Enum(_) => {
into_enum::IntoEnumHandler::trait_meta_handler(ast, token_stream, traits, meta)
},
Data::Union(_) => {
Err(crate::panic::trait_not_support_union(meta[0].path().get_ident().unwrap()))
},
}
}
}

View File

@@ -0,0 +1,152 @@
use std::collections::HashMap;
use syn::{punctuated::Punctuated, Attribute, Meta, Path, Token};
use crate::{
common::{path::meta_2_path, r#type::TypeWithPunctuatedMeta, tools::HashType},
panic, Trait,
};
pub(crate) struct FieldAttribute {
pub(crate) types: HashMap<HashType, Option<Path>>,
}
#[derive(Debug)]
pub(crate) struct FieldAttributeBuilder {
pub(crate) enable_types: bool,
}
impl FieldAttributeBuilder {
pub(crate) fn build_from_into_meta(&self, meta: &[Meta]) -> syn::Result<FieldAttribute> {
debug_assert!(!meta.is_empty());
let mut types = HashMap::new();
for meta in meta {
debug_assert!(meta.path().is_ident("Into"));
let correct_usage_for_into_attribute = {
let mut usage = vec![];
if self.enable_types {
usage.push(stringify!(#[educe(Into(type))]));
usage.push(stringify!(#[educe(Into(type, method(path_to_method)))]));
}
usage
};
match meta {
Meta::Path(_) | Meta::NameValue(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_into_attribute,
));
},
Meta::List(list) => {
if !self.enable_types {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_into_attribute,
));
}
let TypeWithPunctuatedMeta {
ty,
list: result,
} = list.parse_args()?;
let ty = super::super::common::to_hash_type(&ty);
let mut method = None;
let mut method_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
if ident == "method" {
let v = meta_2_path(&meta)?;
if method_is_set {
return Err(panic::parameter_reset(ident));
}
method_is_set = true;
method = Some(v);
return Ok(true);
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_into_attribute,
));
}
}
if types.contains_key(&ty) {
return Err(super::super::panic::reset_a_type(&ty));
}
types.insert(ty, method);
},
}
}
Ok(FieldAttribute {
types,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<FieldAttribute> {
let mut output: Option<FieldAttribute> = None;
let mut v_meta = Vec::new();
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Into {
v_meta.push(meta);
}
}
}
}
}
if !v_meta.is_empty() {
output = Some(self.build_from_into_meta(&v_meta)?);
}
Ok(output.unwrap_or_else(|| FieldAttribute {
types: HashMap::new()
}))
}
}

View File

@@ -0,0 +1,5 @@
mod field_attribute;
mod type_attribute;
pub(crate) use field_attribute::*;
pub(crate) use type_attribute::*;

View File

@@ -0,0 +1,153 @@
use std::collections::HashMap;
use syn::{punctuated::Punctuated, Attribute, Meta, Token};
use crate::{
common::{bound::Bound, r#type::TypeWithPunctuatedMeta, tools::HashType},
panic, Trait,
};
pub(crate) struct TypeAttribute {
pub(crate) types: HashMap<HashType, Bound>,
}
#[derive(Debug)]
pub(crate) struct TypeAttributeBuilder {
pub(crate) enable_types: bool,
}
impl TypeAttributeBuilder {
pub(crate) fn build_from_into_meta(&self, meta: &[Meta]) -> syn::Result<TypeAttribute> {
debug_assert!(!meta.is_empty());
let mut types = HashMap::new();
for meta in meta {
debug_assert!(meta.path().is_ident("Into"));
let correct_usage_for_into_attribute = {
let mut usage = vec![];
if self.enable_types {
usage.push(stringify!(#[educe(Into(type))]));
usage.push(stringify!(#[educe(Into(type, bound = false))]));
usage.push(stringify!(#[educe(Into(type, bound(where_predicates)))]));
}
usage
};
match meta {
Meta::Path(_) | Meta::NameValue(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_into_attribute,
));
},
Meta::List(list) => {
if !self.enable_types {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_into_attribute,
));
}
let TypeWithPunctuatedMeta {
ty,
list: result,
} = list.parse_args()?;
let ty = super::super::common::to_hash_type(&ty);
let mut bound = Bound::Auto;
let mut bound_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
if ident == "bound" {
let v = Bound::from_meta(&meta)?;
if bound_is_set {
return Err(panic::parameter_reset(ident));
}
bound_is_set = true;
bound = v;
return Ok(true);
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_into_attribute,
));
}
}
if types.contains_key(&ty) {
return Err(super::super::panic::reset_a_type(&ty));
}
types.insert(ty, bound);
},
}
}
Ok(TypeAttribute {
types,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<TypeAttribute> {
let mut output: Option<TypeAttribute> = None;
let mut v_meta = Vec::new();
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Into {
v_meta.push(meta);
}
}
}
}
}
if !v_meta.is_empty() {
output = Some(self.build_from_into_meta(&v_meta)?);
}
Ok(output.unwrap_or(TypeAttribute {
types: HashMap::new()
}))
}
}

View File

@@ -0,0 +1,27 @@
use crate::common::tools::HashType;
#[inline]
pub(crate) fn reset_a_type(ty: &HashType) -> syn::Error {
syn::Error::new(ty.span(), format!("the type `{ty}` is repeatedly set"))
}
#[inline]
pub(crate) fn no_into_field(ty: &HashType) -> syn::Error {
syn::Error::new(ty.span(), format!("there is no field which is assigned for `Into<{ty}>`"))
}
#[inline]
pub(crate) fn no_into_impl(ty: &HashType) -> syn::Error {
syn::Error::new(
ty.span(),
format!(
"if you want to impl `Into<{ty}>` for this type, you should write \
`#[educe(Into({ty}))]` outside"
),
)
}
#[inline]
pub(crate) fn multiple_into_fields(ty: &HashType) -> syn::Error {
syn::Error::new(ty.span(), format!("multiple fields are set for `Into<{ty}>`"))
}

48
vendor/educe/src/trait_handlers/mod.rs vendored Normal file
View File

@@ -0,0 +1,48 @@
use syn::{DeriveInput, Meta};
use crate::Trait;
#[cfg(feature = "Clone")]
pub(crate) mod clone;
#[cfg(feature = "Copy")]
pub(crate) mod copy;
#[cfg(feature = "Debug")]
pub(crate) mod debug;
#[cfg(feature = "Default")]
pub(crate) mod default;
#[cfg(feature = "Deref")]
pub(crate) mod deref;
#[cfg(feature = "DerefMut")]
pub(crate) mod deref_mut;
#[cfg(feature = "Eq")]
pub(crate) mod eq;
#[cfg(feature = "Hash")]
pub(crate) mod hash;
#[cfg(feature = "Into")]
pub(crate) mod into;
#[cfg(feature = "Ord")]
pub(crate) mod ord;
#[cfg(feature = "PartialEq")]
pub(crate) mod partial_eq;
#[cfg(feature = "PartialOrd")]
pub(crate) mod partial_ord;
pub(crate) trait TraitHandler {
#[allow(dead_code)]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()>;
}
pub(crate) trait TraitHandlerMultiple {
#[allow(dead_code)]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &[Meta],
) -> syn::Result<()>;
}

View File

@@ -0,0 +1,49 @@
mod models;
mod ord_enum;
mod ord_struct;
mod panic;
use quote::quote;
use syn::{Data, DeriveInput, Meta};
use super::TraitHandler;
use crate::Trait;
pub(crate) struct OrdHandler;
impl TraitHandler for OrdHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
match ast.data {
Data::Struct(_) => {
ord_struct::OrdStructHandler::trait_meta_handler(ast, token_stream, traits, meta)
},
Data::Enum(_) => {
ord_enum::OrdEnumHandler::trait_meta_handler(ast, token_stream, traits, meta)
},
Data::Union(_) => {
Err(crate::panic::trait_not_support_union(meta.path().get_ident().unwrap()))
},
}
}
}
fn supertraits(#[allow(unused_variables)] traits: &[Trait]) -> Vec<proc_macro2::TokenStream> {
let mut supertraits = vec![];
supertraits.push(quote! {::core::cmp::Eq});
// We mustn't add the PartialOrd bound to the educed PartialOrd impl.
// When we're educing PartialOrd we can leave it off the Ord impl too,
// since we *know* Self is going to be PartialOrd.
#[cfg(feature = "PartialOrd")]
if !traits.contains(&Trait::PartialOrd) {
supertraits.push(quote! {::core::cmp::PartialOrd});
};
supertraits
}

View File

@@ -0,0 +1,217 @@
use proc_macro2::Span;
use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Meta, Path, Token};
use crate::{
common::{
ident_bool::{meta_2_bool_allow_path, meta_name_value_2_bool},
int::meta_2_isize,
path::meta_2_path,
},
panic,
supported_traits::Trait,
};
pub(crate) struct FieldAttribute {
pub(crate) ignore: bool,
pub(crate) method: Option<Path>,
pub(crate) rank: isize,
pub(crate) rank_span: Option<Span>,
}
pub(crate) struct FieldAttributeBuilder {
pub(crate) enable_ignore: bool,
pub(crate) enable_method: bool,
pub(crate) enable_rank: bool,
pub(crate) rank: isize,
}
impl FieldAttributeBuilder {
pub(crate) fn build_from_ord_meta(&self, meta: &Meta) -> syn::Result<FieldAttribute> {
debug_assert!(meta.path().is_ident("Ord") || meta.path().is_ident("PartialOrd"));
let mut ignore = false;
let mut method = None;
let mut rank = self.rank;
let mut rank_span = None;
let correct_usage_for_partial_eq_attribute = {
let mut usage = vec![];
if self.enable_ignore {
usage.push(stringify!(#[educe(Ord = false)]));
usage.push(stringify!(#[educe(Ord(ignore))]));
}
if self.enable_method {
usage.push(stringify!(#[educe(Ord(method(path_to_method)))]));
}
if self.enable_rank {
usage.push(stringify!(#[educe(Ord(rank = integer))]));
}
usage
};
match meta {
Meta::Path(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
},
Meta::NameValue(name_value) => {
if self.enable_ignore {
ignore = !meta_name_value_2_bool(name_value)?;
} else {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
}
},
Meta::List(list) => {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
let mut ignore_is_set = false;
let mut method_is_set = false;
let mut rank_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
match ident.to_string().as_str() {
"ignore" => {
if !self.enable_ignore {
return Ok(false);
}
let v = meta_2_bool_allow_path(&meta)?;
if ignore_is_set {
return Err(panic::parameter_reset(ident));
}
ignore_is_set = true;
ignore = v;
return Ok(true);
},
"method" => {
if !self.enable_method {
return Ok(false);
}
let v = meta_2_path(&meta)?;
if method_is_set {
return Err(panic::parameter_reset(ident));
}
method_is_set = true;
method = Some(v);
return Ok(true);
},
"rank" => {
if !self.enable_rank {
return Ok(false);
}
let v = meta_2_isize(&meta)?;
if rank_is_set {
return Err(panic::parameter_reset(ident));
}
rank_is_set = true;
rank = v;
rank_span = Some(meta.span());
return Ok(true);
},
_ => (),
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
}
}
},
}
Ok(FieldAttribute {
ignore,
method,
rank,
rank_span,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<FieldAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Ord {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_ord_meta(&meta)?);
}
#[cfg(feature = "PartialOrd")]
if traits.contains(&Trait::PartialOrd) && t == Trait::PartialOrd {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_ord_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(FieldAttribute {
ignore: false,
method: None,
rank: self.rank,
rank_span: None,
}))
}
}

View File

@@ -0,0 +1,5 @@
mod field_attribute;
mod type_attribute;
pub(crate) use field_attribute::*;
pub(crate) use type_attribute::*;

View File

@@ -0,0 +1,149 @@
use syn::{punctuated::Punctuated, Attribute, Meta, Token};
use crate::{common::bound::Bound, panic, Trait};
pub(crate) struct TypeAttribute {
pub(crate) bound: Bound,
}
#[derive(Debug)]
pub(crate) struct TypeAttributeBuilder {
pub(crate) enable_flag: bool,
pub(crate) enable_bound: bool,
}
impl TypeAttributeBuilder {
pub(crate) fn build_from_ord_meta(&self, meta: &Meta) -> syn::Result<TypeAttribute> {
debug_assert!(meta.path().is_ident("Ord") || meta.path().is_ident("PartialOrd"));
let mut bound = Bound::Auto;
let correct_usage_for_partial_eq_attribute = {
let mut usage = vec![];
if self.enable_flag {
usage.push(stringify!(#[educe(Ord)]));
}
if self.enable_bound {
usage.push(stringify!(#[educe(Ord(bound(where_predicates)))]));
usage.push(stringify!(#[educe(Ord(bound = false))]));
}
usage
};
match meta {
Meta::Path(_) => {
if !self.enable_flag {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
}
},
Meta::NameValue(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
},
Meta::List(list) => {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
let mut bound_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
if ident == "bound" {
if !self.enable_bound {
return Ok(false);
}
let v = Bound::from_meta(&meta)?;
if bound_is_set {
return Err(panic::parameter_reset(ident));
}
bound_is_set = true;
bound = v;
return Ok(true);
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
}
}
},
}
Ok(TypeAttribute {
bound,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<TypeAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::Ord {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_ord_meta(&meta)?);
}
#[cfg(feature = "PartialOrd")]
if traits.contains(&Trait::PartialOrd) && t == Trait::PartialOrd {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_ord_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(TypeAttribute {
bound: Bound::Auto
}))
}
}

View File

@@ -0,0 +1,283 @@
use std::collections::BTreeMap;
use quote::{format_ident, quote};
use syn::{spanned::Spanned, Data, DeriveInput, Field, Fields, Ident, Meta, Path, Type};
use super::{
models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandler,
};
use crate::{common::tools::DiscriminantType, Trait};
pub(crate) struct OrdEnumHandler;
impl TraitHandler for OrdEnumHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute = TypeAttributeBuilder {
enable_flag: true, enable_bound: true
}
.build_from_ord_meta(meta)?;
let mut ord_types: Vec<&Type> = Vec::new();
let mut cmp_token_stream = proc_macro2::TokenStream::new();
let discriminant_type = DiscriminantType::from_ast(ast)?;
let mut arms_token_stream = proc_macro2::TokenStream::new();
let mut all_unit = true;
if let Data::Enum(data) = &ast.data {
for variant in data.variants.iter() {
let _ = TypeAttributeBuilder {
enable_flag: false, enable_bound: false
}
.build_from_attributes(&variant.attrs, traits)?;
let variant_ident = &variant.ident;
let built_in_cmp: Path = syn::parse2(quote!(::core::cmp::Ord::cmp)).unwrap();
match &variant.fields {
Fields::Unit => {
arms_token_stream.extend(quote! {
Self::#variant_ident => {
return ::core::cmp::Ordering::Equal;
}
});
},
Fields::Named(_) => {
all_unit = false;
let mut pattern_self_token_stream = proc_macro2::TokenStream::new();
let mut pattern_other_token_stream = proc_macro2::TokenStream::new();
let mut block_token_stream = proc_macro2::TokenStream::new();
let mut fields: BTreeMap<isize, (&Field, Ident, Ident, FieldAttribute)> =
BTreeMap::new();
for (index, field) in variant.fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_ignore: true,
enable_method: true,
enable_rank: true,
rank: isize::MIN + index as isize,
}
.build_from_attributes(&field.attrs, traits)?;
let field_name_real = field.ident.as_ref().unwrap();
let field_name_var_self = format_ident!("_s_{}", field_name_real);
let field_name_var_other = format_ident!("_o_{}", field_name_real);
if field_attribute.ignore {
pattern_self_token_stream.extend(quote!(#field_name_real: _,));
pattern_other_token_stream.extend(quote!(#field_name_real: _,));
continue;
}
pattern_self_token_stream
.extend(quote!(#field_name_real: #field_name_var_self,));
pattern_other_token_stream
.extend(quote!(#field_name_real: #field_name_var_other,));
let rank = field_attribute.rank;
if fields.contains_key(&rank) {
return Err(super::panic::reuse_a_rank(
field_attribute.rank_span.unwrap_or_else(|| field.span()),
rank,
));
}
fields.insert(
rank,
(field, field_name_var_self, field_name_var_other, field_attribute),
);
}
for (field, field_name_var_self, field_name_var_other, field_attribute) in
fields.values()
{
let cmp = field_attribute.method.as_ref().unwrap_or_else(|| {
ord_types.push(&field.ty);
&built_in_cmp
});
block_token_stream.extend(quote! {
match #cmp(#field_name_var_self, #field_name_var_other) {
::core::cmp::Ordering::Equal => (),
::core::cmp::Ordering::Greater => return ::core::cmp::Ordering::Greater,
::core::cmp::Ordering::Less => return ::core::cmp::Ordering::Less,
}
});
}
arms_token_stream.extend(quote! {
Self::#variant_ident { #pattern_self_token_stream } => {
if let Self::#variant_ident { #pattern_other_token_stream } = other {
#block_token_stream
}
}
});
},
Fields::Unnamed(_) => {
all_unit = false;
let mut pattern_token_stream = proc_macro2::TokenStream::new();
let mut pattern2_token_stream = proc_macro2::TokenStream::new();
let mut block_token_stream = proc_macro2::TokenStream::new();
let mut fields: BTreeMap<isize, (&Field, Ident, Ident, FieldAttribute)> =
BTreeMap::new();
for (index, field) in variant.fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_ignore: true,
enable_method: true,
enable_rank: true,
rank: isize::MIN + index as isize,
}
.build_from_attributes(&field.attrs, traits)?;
let field_name_var_self = format_ident!("_{}", index);
if field_attribute.ignore {
pattern_token_stream.extend(quote!(_,));
pattern2_token_stream.extend(quote!(_,));
continue;
}
let field_name_var_other = format_ident!("_{}", field_name_var_self);
pattern_token_stream.extend(quote!(#field_name_var_self,));
pattern2_token_stream.extend(quote!(#field_name_var_other,));
let rank = field_attribute.rank;
if fields.contains_key(&rank) {
return Err(super::panic::reuse_a_rank(
field_attribute.rank_span.unwrap_or_else(|| field.span()),
rank,
));
}
fields.insert(
rank,
(field, field_name_var_self, field_name_var_other, field_attribute),
);
}
for (field, field_name, field_name2, field_attribute) in fields.values() {
let cmp = field_attribute.method.as_ref().unwrap_or_else(|| {
ord_types.push(&field.ty);
&built_in_cmp
});
block_token_stream.extend(quote! {
match #cmp(#field_name, #field_name2) {
::core::cmp::Ordering::Equal => (),
::core::cmp::Ordering::Greater => return ::core::cmp::Ordering::Greater,
::core::cmp::Ordering::Less => return ::core::cmp::Ordering::Less,
}
});
}
arms_token_stream.extend(quote! {
Self::#variant_ident ( #pattern_token_stream ) => {
if let Self::#variant_ident ( #pattern2_token_stream ) = other {
#block_token_stream
}
}
});
},
}
}
}
if arms_token_stream.is_empty() {
cmp_token_stream.extend(quote!(::core::cmp::Ordering::Equal));
} else {
let discriminant_cmp = quote! {
unsafe {
::core::cmp::Ord::cmp(&*<*const _>::from(self).cast::<#discriminant_type>(), &*<*const _>::from(other).cast::<#discriminant_type>())
}
};
cmp_token_stream.extend(if all_unit {
quote! {
match #discriminant_cmp {
::core::cmp::Ordering::Equal => ::core::cmp::Ordering::Equal,
::core::cmp::Ordering::Greater => ::core::cmp::Ordering::Greater,
::core::cmp::Ordering::Less => ::core::cmp::Ordering::Less,
}
}
} else {
quote! {
match #discriminant_cmp {
::core::cmp::Ordering::Equal => {
match self {
#arms_token_stream
}
::core::cmp::Ordering::Equal
},
::core::cmp::Ordering::Greater => ::core::cmp::Ordering::Greater,
::core::cmp::Ordering::Less => ::core::cmp::Ordering::Less,
}
}
});
}
let ident = &ast.ident;
let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::cmp::Ord)).unwrap(),
&ord_types,
&crate::trait_handlers::ord::supertraits(traits),
);
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::cmp::Ord for #ident #ty_generics #where_clause {
#[inline]
fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
#cmp_token_stream
}
}
});
#[cfg(feature = "PartialOrd")]
if traits.contains(&Trait::PartialOrd) {
token_stream.extend(quote! {
impl #impl_generics ::core::cmp::PartialOrd for #ident #ty_generics #where_clause {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
Some(::core::cmp::Ord::cmp(self, other))
}
}
});
}
Ok(())
}
}

View File

@@ -0,0 +1,123 @@
use std::collections::BTreeMap;
use quote::quote;
use syn::{spanned::Spanned, Data, DeriveInput, Field, Meta, Path, Type};
use super::{
models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandler,
};
use crate::{common::ident_index::IdentOrIndex, Trait};
pub(crate) struct OrdStructHandler;
impl TraitHandler for OrdStructHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute = TypeAttributeBuilder {
enable_flag: true, enable_bound: true
}
.build_from_ord_meta(meta)?;
let mut ord_types: Vec<&Type> = Vec::new();
let mut cmp_token_stream = proc_macro2::TokenStream::new();
if let Data::Struct(data) = &ast.data {
let mut fields: BTreeMap<isize, (usize, &Field, FieldAttribute)> = BTreeMap::new();
for (index, field) in data.fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_ignore: true,
enable_method: true,
enable_rank: true,
rank: isize::MIN + index as isize,
}
.build_from_attributes(&field.attrs, traits)?;
if field_attribute.ignore {
continue;
}
let rank = field_attribute.rank;
if fields.contains_key(&rank) {
return Err(super::panic::reuse_a_rank(
field_attribute.rank_span.unwrap_or_else(|| field.span()),
rank,
));
}
fields.insert(rank, (index, field, field_attribute));
}
let built_in_cmp: Path = syn::parse2(quote!(::core::cmp::Ord::cmp)).unwrap();
for (index, field, field_attribute) in fields.values() {
let field_name = IdentOrIndex::from_ident_with_index(field.ident.as_ref(), *index);
let cmp = field_attribute.method.as_ref().unwrap_or_else(|| {
ord_types.push(&field.ty);
&built_in_cmp
});
cmp_token_stream.extend(quote! {
match #cmp(&self.#field_name, &other.#field_name) {
::core::cmp::Ordering::Equal => (),
::core::cmp::Ordering::Greater => return ::core::cmp::Ordering::Greater,
::core::cmp::Ordering::Less => return ::core::cmp::Ordering::Less,
}
});
}
}
let ident = &ast.ident;
let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::cmp::Ord)).unwrap(),
&ord_types,
&crate::trait_handlers::ord::supertraits(traits),
);
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::cmp::Ord for #ident #ty_generics #where_clause {
#[inline]
fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
#cmp_token_stream
::core::cmp::Ordering::Equal
}
}
});
#[cfg(feature = "PartialOrd")]
if traits.contains(&Trait::PartialOrd) {
token_stream.extend(quote! {
impl #impl_generics ::core::cmp::PartialOrd for #ident #ty_generics #where_clause {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
Some(::core::cmp::Ord::cmp(self, other))
}
}
});
}
Ok(())
}
}

View File

@@ -0,0 +1,6 @@
use proc_macro2::Span;
#[inline]
pub(crate) fn reuse_a_rank(span: Span, rank: isize) -> syn::Error {
syn::Error::new(span, format!("the rank `{rank}` is repeatedly used"))
}

View File

@@ -0,0 +1,43 @@
mod models;
mod panic;
mod partial_eq_enum;
mod partial_eq_struct;
mod partial_eq_union;
use syn::{Data, DeriveInput, Meta};
use super::TraitHandler;
use crate::Trait;
pub(crate) struct PartialEqHandler;
impl TraitHandler for PartialEqHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
match ast.data {
Data::Struct(_) => partial_eq_struct::PartialEqStructHandler::trait_meta_handler(
ast,
token_stream,
traits,
meta,
),
Data::Enum(_) => partial_eq_enum::PartialEqEnumHandler::trait_meta_handler(
ast,
token_stream,
traits,
meta,
),
Data::Union(_) => partial_eq_union::PartialEqUnionHandler::trait_meta_handler(
ast,
token_stream,
traits,
meta,
),
}
}
}

View File

@@ -0,0 +1,181 @@
use syn::{punctuated::Punctuated, Attribute, Meta, Path, Token};
use crate::{
common::{
ident_bool::{meta_2_bool_allow_path, meta_name_value_2_bool},
path::meta_2_path,
},
panic,
supported_traits::Trait,
};
pub(crate) struct FieldAttribute {
pub(crate) ignore: bool,
pub(crate) method: Option<Path>,
}
pub(crate) struct FieldAttributeBuilder {
pub(crate) enable_ignore: bool,
pub(crate) enable_method: bool,
}
impl FieldAttributeBuilder {
pub(crate) fn build_from_partial_eq_meta(&self, meta: &Meta) -> syn::Result<FieldAttribute> {
debug_assert!(meta.path().is_ident("PartialEq") || meta.path().is_ident("Eq"));
let mut ignore = false;
let mut method = None;
let correct_usage_for_partial_eq_attribute = {
let mut usage = vec![];
if self.enable_ignore {
usage.push(stringify!(#[educe(PartialEq = false)]));
usage.push(stringify!(#[educe(PartialEq(ignore))]));
}
if self.enable_method {
usage.push(stringify!(#[educe(PartialEq(method(path_to_method)))]));
}
usage
};
match meta {
Meta::Path(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
},
Meta::NameValue(name_value) => {
if self.enable_ignore {
ignore = !meta_name_value_2_bool(name_value)?;
} else {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
}
},
Meta::List(list) => {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
let mut ignore_is_set = false;
let mut method_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
match ident.to_string().as_str() {
"ignore" => {
if !self.enable_ignore {
return Ok(false);
}
let v = meta_2_bool_allow_path(&meta)?;
if ignore_is_set {
return Err(panic::parameter_reset(ident));
}
ignore_is_set = true;
ignore = v;
return Ok(true);
},
"method" => {
if !self.enable_method {
return Ok(false);
}
let v = meta_2_path(&meta)?;
if method_is_set {
return Err(panic::parameter_reset(ident));
}
method_is_set = true;
method = Some(v);
return Ok(true);
},
_ => (),
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
}
}
},
}
Ok(FieldAttribute {
ignore,
method,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<FieldAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::PartialEq {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_partial_eq_meta(&meta)?);
}
#[cfg(feature = "Eq")]
if traits.contains(&Trait::Eq) && t == Trait::Eq {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_partial_eq_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(FieldAttribute {
ignore: false, method: None
}))
}
}

View File

@@ -0,0 +1,5 @@
mod field_attribute;
mod type_attribute;
pub(crate) use field_attribute::*;
pub(crate) use type_attribute::*;

View File

@@ -0,0 +1,163 @@
use syn::{punctuated::Punctuated, Attribute, Meta, Token};
use crate::{
common::{bound::Bound, unsafe_punctuated_meta::UnsafePunctuatedMeta},
panic, Trait,
};
pub(crate) struct TypeAttribute {
pub(crate) has_unsafe: bool,
pub(crate) bound: Bound,
}
#[derive(Debug)]
pub(crate) struct TypeAttributeBuilder {
pub(crate) enable_flag: bool,
pub(crate) enable_unsafe: bool,
pub(crate) enable_bound: bool,
}
impl TypeAttributeBuilder {
pub(crate) fn build_from_partial_eq_meta(&self, meta: &Meta) -> syn::Result<TypeAttribute> {
debug_assert!(meta.path().is_ident("PartialEq") || meta.path().is_ident("Eq"));
let mut has_unsafe = false;
let mut bound = Bound::Auto;
let correct_usage_for_partial_eq_attribute = {
let mut usage = vec![];
if self.enable_flag {
usage.push(stringify!(#[educe(PartialEq)]));
}
if self.enable_bound {
usage.push(stringify!(#[educe(PartialEq(bound(where_predicates)))]));
usage.push(stringify!(#[educe(PartialEq(bound = false))]));
}
usage
};
match meta {
Meta::Path(_) => {
if !self.enable_flag {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
}
},
Meta::NameValue(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
},
Meta::List(list) => {
let result = if self.enable_unsafe {
let result: UnsafePunctuatedMeta = list.parse_args()?;
has_unsafe = result.has_unsafe;
result.list
} else {
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?
};
let mut bound_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
if ident == "bound" {
if !self.enable_bound {
return Ok(false);
}
let v = Bound::from_meta(&meta)?;
if bound_is_set {
return Err(panic::parameter_reset(ident));
}
bound_is_set = true;
bound = v;
return Ok(true);
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
}
}
},
}
Ok(TypeAttribute {
has_unsafe,
bound,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<TypeAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::PartialEq {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_partial_eq_meta(&meta)?);
}
#[cfg(feature = "Eq")]
if traits.contains(&Trait::Eq) && t == Trait::Eq {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_partial_eq_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(TypeAttribute {
has_unsafe: false, bound: Bound::Auto
}))
}
}

View File

@@ -0,0 +1,22 @@
use quote::ToTokens;
use syn::{spanned::Spanned, Meta};
#[inline]
pub(crate) fn union_without_unsafe(meta: &Meta) -> syn::Error {
let mut s = meta.into_token_stream().to_string();
match s.len() {
9 => s.push_str("(unsafe)"),
11 => s.insert_str(10, "unsafe"),
_ => unreachable!(),
}
syn::Error::new(
meta.span(),
format!(
"a union's `PartialEq` implementation is not precise, because it ignores the type of \
fields\n* If your union doesn't care about that, use `#[educe({s})]` to implement \
the `PartialEq` trait for it."
),
)
}

View File

@@ -0,0 +1,218 @@
use quote::{format_ident, quote};
use syn::{Data, DeriveInput, Fields, Meta, Type};
use super::{
models::{FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandler,
};
use crate::Trait;
pub(crate) struct PartialEqEnumHandler;
impl TraitHandler for PartialEqEnumHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute =
TypeAttributeBuilder {
enable_flag: true, enable_unsafe: false, enable_bound: true
}
.build_from_partial_eq_meta(meta)?;
let mut partial_eq_types: Vec<&Type> = Vec::new();
let mut eq_token_stream = proc_macro2::TokenStream::new();
let mut arms_token_stream = proc_macro2::TokenStream::new();
if let Data::Enum(data) = &ast.data {
for variant in data.variants.iter() {
let _ = TypeAttributeBuilder {
enable_flag: false,
enable_unsafe: false,
enable_bound: false,
}
.build_from_attributes(&variant.attrs, traits)?;
let variant_ident = &variant.ident;
match &variant.fields {
Fields::Unit => {
arms_token_stream.extend(quote! {
Self::#variant_ident => {
if let Self::#variant_ident = other {
// same
} else {
return false;
}
}
});
},
Fields::Named(_) => {
let mut pattern_self_token_stream = proc_macro2::TokenStream::new();
let mut pattern_other_token_stream = proc_macro2::TokenStream::new();
let mut block_token_stream = proc_macro2::TokenStream::new();
for field in variant.fields.iter() {
let field_attribute = FieldAttributeBuilder {
enable_ignore: true,
enable_method: true,
}
.build_from_attributes(&field.attrs, traits)?;
let field_name_real = field.ident.as_ref().unwrap();
let field_name_var_self = format_ident!("_s_{}", field_name_real);
let field_name_var_other = format_ident!("_o_{}", field_name_real);
if field_attribute.ignore {
pattern_self_token_stream.extend(quote!(#field_name_real: _,));
pattern_other_token_stream.extend(quote!(#field_name_real: _,));
continue;
}
pattern_self_token_stream
.extend(quote!(#field_name_real: #field_name_var_self,));
pattern_other_token_stream
.extend(quote!(#field_name_real: #field_name_var_other,));
if let Some(method) = field_attribute.method {
block_token_stream.extend(quote! {
if !#method(#field_name_var_self, #field_name_var_other) {
return false;
}
});
} else {
let ty = &field.ty;
partial_eq_types.push(ty);
block_token_stream.extend(quote! {
if ::core::cmp::PartialEq::ne(#field_name_var_self, #field_name_var_other) {
return false;
}
});
}
}
arms_token_stream.extend(quote! {
Self::#variant_ident { #pattern_self_token_stream } => {
if let Self::#variant_ident { #pattern_other_token_stream } = other {
#block_token_stream
} else {
return false;
}
}
});
},
Fields::Unnamed(_) => {
let mut pattern_token_stream = proc_macro2::TokenStream::new();
let mut pattern2_token_stream = proc_macro2::TokenStream::new();
let mut block_token_stream = proc_macro2::TokenStream::new();
for (index, field) in variant.fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_ignore: true,
enable_method: true,
}
.build_from_attributes(&field.attrs, traits)?;
if field_attribute.ignore {
pattern_token_stream.extend(quote!(_,));
pattern2_token_stream.extend(quote!(_,));
continue;
}
let field_name_var_self = format_ident!("_{}", index);
let field_name_var_other = format_ident!("_{}", field_name_var_self);
pattern_token_stream.extend(quote!(#field_name_var_self,));
pattern2_token_stream.extend(quote!(#field_name_var_other,));
if let Some(method) = field_attribute.method {
block_token_stream.extend(quote! {
if !#method(#field_name_var_self, #field_name_var_other) {
return false;
}
});
} else {
let ty = &field.ty;
partial_eq_types.push(ty);
block_token_stream.extend(quote! {
if ::core::cmp::PartialEq::ne(#field_name_var_self, #field_name_var_other) {
return false;
}
});
}
}
arms_token_stream.extend(quote! {
Self::#variant_ident ( #pattern_token_stream ) => {
if let Self::#variant_ident ( #pattern2_token_stream ) = other {
#block_token_stream
} else {
return false;
}
}
});
},
}
}
}
if !arms_token_stream.is_empty() {
eq_token_stream.extend(quote! {
match self {
#arms_token_stream
}
});
}
let ident = &ast.ident;
let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::cmp::PartialEq)).unwrap(),
&partial_eq_types,
&[],
);
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::cmp::PartialEq for #ident #ty_generics #where_clause {
#[inline]
fn eq(&self, other: &Self) -> bool {
#eq_token_stream
true
}
}
});
#[cfg(feature = "Eq")]
if traits.contains(&Trait::Eq) {
token_stream.extend(quote! {
impl #impl_generics ::core::cmp::Eq for #ident #ty_generics #where_clause {
}
});
}
Ok(())
}
}

View File

@@ -0,0 +1,103 @@
use quote::quote;
use syn::{Data, DeriveInput, Meta, Type};
use super::{
models::{FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandler,
};
use crate::{common::ident_index::IdentOrIndex, Trait};
pub(crate) struct PartialEqStructHandler;
impl TraitHandler for PartialEqStructHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute =
TypeAttributeBuilder {
enable_flag: true, enable_unsafe: false, enable_bound: true
}
.build_from_partial_eq_meta(meta)?;
let mut partial_eq_types: Vec<&Type> = Vec::new();
let mut eq_token_stream = proc_macro2::TokenStream::new();
if let Data::Struct(data) = &ast.data {
for (index, field) in data.fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_ignore: true,
enable_method: true,
}
.build_from_attributes(&field.attrs, traits)?;
if field_attribute.ignore {
continue;
}
let field_name = IdentOrIndex::from_ident_with_index(field.ident.as_ref(), index);
if let Some(method) = field_attribute.method {
eq_token_stream.extend(quote! {
if !#method(&self.#field_name, &other.#field_name) {
return false;
}
});
} else {
let ty = &field.ty;
partial_eq_types.push(ty);
eq_token_stream.extend(quote! {
if ::core::cmp::PartialEq::ne(&self.#field_name, &other.#field_name) {
return false;
}
});
}
}
}
let ident = &ast.ident;
let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::cmp::PartialEq)).unwrap(),
&partial_eq_types,
&[],
);
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::cmp::PartialEq for #ident #ty_generics #where_clause {
#[inline]
fn eq(&self, other: &Self) -> bool {
#eq_token_stream
true
}
}
});
#[cfg(feature = "Eq")]
if traits.contains(&Trait::Eq) {
token_stream.extend(quote! {
impl #impl_generics ::core::cmp::Eq for #ident #ty_generics #where_clause {
}
});
}
Ok(())
}
}

View File

@@ -0,0 +1,63 @@
use quote::quote;
use syn::{Data, DeriveInput, Meta};
use super::models::{FieldAttributeBuilder, TypeAttributeBuilder};
use crate::{supported_traits::Trait, trait_handlers::TraitHandler};
pub(crate) struct PartialEqUnionHandler;
impl TraitHandler for PartialEqUnionHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute =
TypeAttributeBuilder {
enable_flag: true, enable_unsafe: true, enable_bound: false
}
.build_from_partial_eq_meta(meta)?;
if !type_attribute.has_unsafe {
return Err(super::panic::union_without_unsafe(meta));
}
if let Data::Union(data) = &ast.data {
for field in data.fields.named.iter() {
let _ = FieldAttributeBuilder {
enable_ignore: false, enable_method: false
}
.build_from_attributes(&field.attrs, traits)?;
}
}
let ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::cmp::PartialEq for #ident #ty_generics #where_clause {
#[inline]
fn eq(&self, other: &Self) -> bool {
let size = ::core::mem::size_of::<Self>();
let self_data = unsafe { ::core::slice::from_raw_parts(self as *const Self as *const u8, size) };
let other_data = unsafe { ::core::slice::from_raw_parts(other as *const Self as *const u8, size) };
::core::cmp::PartialEq::eq(self_data, other_data)
}
}
});
#[cfg(feature = "Eq")]
if traits.contains(&Trait::Eq) {
token_stream.extend(quote! {
impl #impl_generics ::core::cmp::Eq for #ident #ty_generics #where_clause {
}
});
}
Ok(())
}
}

View File

@@ -0,0 +1,58 @@
mod models;
mod panic;
mod partial_ord_enum;
mod partial_ord_struct;
use models::TypeAttributeBuilder;
use syn::{Data, DeriveInput, Meta};
use super::TraitHandler;
use crate::Trait;
pub(crate) struct PartialOrdHandler;
impl TraitHandler for PartialOrdHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
#[cfg(feature = "Ord")]
let contains_ord = traits.contains(&Trait::Ord);
#[cfg(not(feature = "Ord"))]
let contains_ord = false;
// if `contains_ord` is true, the implementation is handled by the `Ord` attribute
if contains_ord {
let _ = TypeAttributeBuilder {
enable_flag: true, enable_bound: false
}
.build_from_partial_ord_meta(meta)?;
// field attributes is also handled by the `Ord` attribute
Ok(())
} else {
match ast.data {
Data::Struct(_) => partial_ord_struct::PartialOrdStructHandler::trait_meta_handler(
ast,
token_stream,
traits,
meta,
),
Data::Enum(_) => partial_ord_enum::PartialOrdEnumHandler::trait_meta_handler(
ast,
token_stream,
traits,
meta,
),
Data::Union(_) => {
Err(crate::panic::trait_not_support_union(meta.path().get_ident().unwrap()))
},
}
}
}
}

View File

@@ -0,0 +1,208 @@
use proc_macro2::Span;
use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Meta, Path, Token};
use crate::{
common::{
ident_bool::{meta_2_bool_allow_path, meta_name_value_2_bool},
int::meta_2_isize,
path::meta_2_path,
},
panic,
supported_traits::Trait,
};
pub(crate) struct FieldAttribute {
pub(crate) ignore: bool,
pub(crate) method: Option<Path>,
pub(crate) rank: isize,
pub(crate) rank_span: Option<Span>,
}
pub(crate) struct FieldAttributeBuilder {
pub(crate) enable_ignore: bool,
pub(crate) enable_method: bool,
pub(crate) enable_rank: bool,
pub(crate) rank: isize,
}
impl FieldAttributeBuilder {
pub(crate) fn build_from_partial_ord_meta(&self, meta: &Meta) -> syn::Result<FieldAttribute> {
debug_assert!(meta.path().is_ident("PartialOrd"));
let mut ignore = false;
let mut method = None;
let mut rank = self.rank;
let mut rank_span = None;
let correct_usage_for_partial_eq_attribute = {
let mut usage = vec![];
if self.enable_ignore {
usage.push(stringify!(#[educe(PartialOrd = false)]));
usage.push(stringify!(#[educe(PartialOrd(ignore))]));
}
if self.enable_method {
usage.push(stringify!(#[educe(PartialOrd(method(path_to_method)))]));
}
if self.enable_rank {
usage.push(stringify!(#[educe(PartialOrd(rank = integer))]));
}
usage
};
match meta {
Meta::Path(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
},
Meta::NameValue(name_value) => {
if self.enable_ignore {
ignore = !meta_name_value_2_bool(name_value)?;
} else {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
}
},
Meta::List(list) => {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
let mut ignore_is_set = false;
let mut method_is_set = false;
let mut rank_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
match ident.to_string().as_str() {
"ignore" => {
if !self.enable_ignore {
return Ok(false);
}
let v = meta_2_bool_allow_path(&meta)?;
if ignore_is_set {
return Err(panic::parameter_reset(ident));
}
ignore_is_set = true;
ignore = v;
return Ok(true);
},
"method" => {
if !self.enable_method {
return Ok(false);
}
let v = meta_2_path(&meta)?;
if method_is_set {
return Err(panic::parameter_reset(ident));
}
method_is_set = true;
method = Some(v);
return Ok(true);
},
"rank" => {
if !self.enable_rank {
return Ok(false);
}
let v = meta_2_isize(&meta)?;
if rank_is_set {
return Err(panic::parameter_reset(ident));
}
rank_is_set = true;
rank = v;
rank_span = Some(meta.span());
return Ok(true);
},
_ => (),
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
}
}
},
}
Ok(FieldAttribute {
ignore,
method,
rank,
rank_span,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<FieldAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::PartialOrd {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_partial_ord_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(FieldAttribute {
ignore: false,
method: None,
rank: self.rank,
rank_span: None,
}))
}
}

View File

@@ -0,0 +1,5 @@
mod field_attribute;
mod type_attribute;
pub(crate) use field_attribute::*;
pub(crate) use type_attribute::*;

View File

@@ -0,0 +1,140 @@
use syn::{punctuated::Punctuated, Attribute, Meta, Token};
use crate::{common::bound::Bound, panic, Trait};
pub(crate) struct TypeAttribute {
pub(crate) bound: Bound,
}
#[derive(Debug)]
pub(crate) struct TypeAttributeBuilder {
pub(crate) enable_flag: bool,
pub(crate) enable_bound: bool,
}
impl TypeAttributeBuilder {
pub(crate) fn build_from_partial_ord_meta(&self, meta: &Meta) -> syn::Result<TypeAttribute> {
debug_assert!(meta.path().is_ident("PartialOrd"));
let mut bound = Bound::Auto;
let correct_usage_for_partial_eq_attribute = {
let mut usage = vec![];
if self.enable_flag {
usage.push(stringify!(#[educe(PartialOrd)]));
}
if self.enable_bound {
usage.push(stringify!(#[educe(PartialOrd(bound(where_predicates)))]));
usage.push(stringify!(#[educe(PartialOrd(bound = false))]));
}
usage
};
match meta {
Meta::Path(_) => {
if !self.enable_flag {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
}
},
Meta::NameValue(_) => {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
},
Meta::List(list) => {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
let mut bound_is_set = false;
let mut handler = |meta: Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident() {
if ident == "bound" {
if !self.enable_bound {
return Ok(false);
}
let v = Bound::from_meta(&meta)?;
if bound_is_set {
return Err(panic::parameter_reset(ident));
}
bound_is_set = true;
bound = v;
return Ok(true);
}
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage_for_partial_eq_attribute,
));
}
}
},
}
Ok(TypeAttribute {
bound,
})
}
pub(crate) fn build_from_attributes(
&self,
attributes: &[Attribute],
traits: &[Trait],
) -> syn::Result<TypeAttribute> {
let mut output = None;
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe") {
if let Meta::List(list) = &attribute.meta {
let result =
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for meta in result {
let path = meta.path();
let t = match Trait::from_path(path) {
Some(t) => t,
None => return Err(panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(panic::trait_not_used(path.get_ident().unwrap()));
}
if t == Trait::PartialOrd {
if output.is_some() {
return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
}
output = Some(self.build_from_partial_ord_meta(&meta)?);
}
}
}
}
}
Ok(output.unwrap_or(TypeAttribute {
bound: Bound::Auto
}))
}
}

View File

@@ -0,0 +1,6 @@
use proc_macro2::Span;
#[inline]
pub(crate) fn reuse_a_rank(span: Span, rank: isize) -> syn::Error {
syn::Error::new(span, format!("the rank `{rank}` is repeatedly used"))
}

View File

@@ -0,0 +1,276 @@
use std::collections::BTreeMap;
use quote::{format_ident, quote};
use syn::{spanned::Spanned, Data, DeriveInput, Field, Fields, Ident, Meta, Path, Type};
use super::{
models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder},
TraitHandler,
};
use crate::{common::tools::DiscriminantType, Trait};
pub(crate) struct PartialOrdEnumHandler;
impl TraitHandler for PartialOrdEnumHandler {
#[inline]
fn trait_meta_handler(
ast: &DeriveInput,
token_stream: &mut proc_macro2::TokenStream,
traits: &[Trait],
meta: &Meta,
) -> syn::Result<()> {
let type_attribute = TypeAttributeBuilder {
enable_flag: true, enable_bound: true
}
.build_from_partial_ord_meta(meta)?;
let mut partial_ord_types: Vec<&Type> = Vec::new();
let mut partial_cmp_token_stream = proc_macro2::TokenStream::new();
let discriminant_type = DiscriminantType::from_ast(ast)?;
let mut arms_token_stream = proc_macro2::TokenStream::new();
let mut all_unit = true;
if let Data::Enum(data) = &ast.data {
for variant in data.variants.iter() {
let _ = TypeAttributeBuilder {
enable_flag: false, enable_bound: false
}
.build_from_attributes(&variant.attrs, traits)?;
let variant_ident = &variant.ident;
let built_in_partial_cmp: Path =
syn::parse2(quote!(::core::cmp::PartialOrd::partial_cmp)).unwrap();
match &variant.fields {
Fields::Unit => {
arms_token_stream.extend(quote! {
Self::#variant_ident => {
return Some(::core::cmp::Ordering::Equal);
}
});
},
Fields::Named(_) => {
all_unit = false;
let mut pattern_self_token_stream = proc_macro2::TokenStream::new();
let mut pattern_other_token_stream = proc_macro2::TokenStream::new();
let mut block_token_stream = proc_macro2::TokenStream::new();
let mut fields: BTreeMap<isize, (&Field, Ident, Ident, FieldAttribute)> =
BTreeMap::new();
for (index, field) in variant.fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_ignore: true,
enable_method: true,
enable_rank: true,
rank: isize::MIN + index as isize,
}
.build_from_attributes(&field.attrs, traits)?;
let field_name_real = field.ident.as_ref().unwrap();
let field_name_var_self = format_ident!("_s_{}", field_name_real);
let field_name_var_other = format_ident!("_o_{}", field_name_real);
if field_attribute.ignore {
pattern_self_token_stream.extend(quote!(#field_name_real: _,));
pattern_other_token_stream.extend(quote!(#field_name_real: _,));
continue;
}
pattern_self_token_stream
.extend(quote!(#field_name_real: #field_name_var_self,));
pattern_other_token_stream
.extend(quote!(#field_name_real: #field_name_var_other,));
let rank = field_attribute.rank;
if fields.contains_key(&rank) {
return Err(super::panic::reuse_a_rank(
field_attribute.rank_span.unwrap_or_else(|| field.span()),
rank,
));
}
fields.insert(
rank,
(field, field_name_var_self, field_name_var_other, field_attribute),
);
}
for (field, field_name_var_self, field_name_var_other, field_attribute) in
fields.values()
{
let partial_cmp =
field_attribute.method.as_ref().unwrap_or_else(|| {
partial_ord_types.push(&field.ty);
&built_in_partial_cmp
});
block_token_stream.extend(quote! {
match #partial_cmp(#field_name_var_self, #field_name_var_other) {
Some(::core::cmp::Ordering::Equal) => (),
Some(::core::cmp::Ordering::Greater) => return Some(::core::cmp::Ordering::Greater),
Some(::core::cmp::Ordering::Less) => return Some(::core::cmp::Ordering::Less),
None => return None,
}
});
}
arms_token_stream.extend(quote! {
Self::#variant_ident { #pattern_self_token_stream } => {
if let Self::#variant_ident { #pattern_other_token_stream } = other {
#block_token_stream
}
}
});
},
Fields::Unnamed(_) => {
all_unit = false;
let mut pattern_token_stream = proc_macro2::TokenStream::new();
let mut pattern2_token_stream = proc_macro2::TokenStream::new();
let mut block_token_stream = proc_macro2::TokenStream::new();
let mut fields: BTreeMap<isize, (&Field, Ident, Ident, FieldAttribute)> =
BTreeMap::new();
for (index, field) in variant.fields.iter().enumerate() {
let field_attribute = FieldAttributeBuilder {
enable_ignore: true,
enable_method: true,
enable_rank: true,
rank: isize::MIN + index as isize,
}
.build_from_attributes(&field.attrs, traits)?;
let field_name_var_self = format_ident!("_{}", index);
if field_attribute.ignore {
pattern_token_stream.extend(quote!(_,));
pattern2_token_stream.extend(quote!(_,));
continue;
}
let field_name_var_other = format_ident!("_{}", field_name_var_self);
pattern_token_stream.extend(quote!(#field_name_var_self,));
pattern2_token_stream.extend(quote!(#field_name_var_other,));
let rank = field_attribute.rank;
if fields.contains_key(&rank) {
return Err(super::panic::reuse_a_rank(
field_attribute.rank_span.unwrap_or_else(|| field.span()),
rank,
));
}
fields.insert(
rank,
(field, field_name_var_self, field_name_var_other, field_attribute),
);
}
for (field, field_name, field_name2, field_attribute) in fields.values() {
let partial_cmp =
field_attribute.method.as_ref().unwrap_or_else(|| {
partial_ord_types.push(&field.ty);
&built_in_partial_cmp
});
block_token_stream.extend(quote! {
match #partial_cmp(#field_name, #field_name2) {
Some(::core::cmp::Ordering::Equal) => (),
Some(::core::cmp::Ordering::Greater) => return Some(::core::cmp::Ordering::Greater),
Some(::core::cmp::Ordering::Less) => return Some(::core::cmp::Ordering::Less),
None => return None,
}
});
}
arms_token_stream.extend(quote! {
Self::#variant_ident ( #pattern_token_stream ) => {
if let Self::#variant_ident ( #pattern2_token_stream ) = other {
#block_token_stream
}
}
});
},
}
}
}
if arms_token_stream.is_empty() {
partial_cmp_token_stream.extend(quote!(Some(::core::cmp::Ordering::Equal)));
} else {
let discriminant_cmp = quote! {
unsafe {
::core::cmp::Ord::cmp(&*<*const _>::from(self).cast::<#discriminant_type>(), &*<*const _>::from(other).cast::<#discriminant_type>())
}
};
partial_cmp_token_stream.extend(if all_unit {
quote! {
match #discriminant_cmp {
::core::cmp::Ordering::Equal => Some(::core::cmp::Ordering::Equal),
::core::cmp::Ordering::Greater => Some(::core::cmp::Ordering::Greater),
::core::cmp::Ordering::Less => Some(::core::cmp::Ordering::Less),
}
}
} else {
quote! {
match #discriminant_cmp {
::core::cmp::Ordering::Equal => {
match self {
#arms_token_stream
}
Some(::core::cmp::Ordering::Equal)
},
::core::cmp::Ordering::Greater => Some(::core::cmp::Ordering::Greater),
::core::cmp::Ordering::Less => Some(::core::cmp::Ordering::Less),
}
}
});
}
let ident = &ast.ident;
let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::cmp::PartialOrd)).unwrap(),
&partial_ord_types,
&[quote! {::core::cmp::PartialEq}],
);
let mut generics = ast.generics.clone();
let where_clause = generics.make_where_clause();
for where_predicate in bound {
where_clause.predicates.push(where_predicate);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
token_stream.extend(quote! {
impl #impl_generics ::core::cmp::PartialOrd for #ident #ty_generics #where_clause {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
#partial_cmp_token_stream
}
}
});
Ok(())
}
}

Some files were not shown because too many files have changed in this diff Show More