use crate::prelude::*;
use darling::{Error as DarlingError, FromMeta, ast::NestedMeta};
use std::{collections::BTreeSet, str::FromStr};
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TraitKind {
Clone,
Copy,
Debug,
Default,
Deserialize,
Deref,
DerefMut,
Display,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Add,
AddAssign,
Div,
DivAssign,
Mul,
MulAssign,
Neg,
Product,
Rem,
RemAssign,
Sub,
SubAssign,
Sum,
From,
FromIterator,
Inner,
IntoIterator,
NumericValue,
Path,
NormalizeAuto,
NormalizeCustom,
ValidateAuto,
ValidateCustom,
Visitable,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ApplicationTypeKind {
Entity,
Enum,
List,
Map,
Newtype,
Record,
Set,
Tuple,
}
impl ApplicationTypeKind {
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::Entity => "entity",
Self::Enum => "enum",
Self::List => "list",
Self::Map => "map",
Self::Newtype => "newtype",
Self::Record => "record",
Self::Set => "set",
Self::Tuple => "tuple",
}
}
fn configurable_trait_policy(
self,
trait_kind: TraitKind,
baseline: &TraitSet,
) -> Option<ConfigurableTraitPolicy> {
match trait_kind {
TraitKind::Copy | TraitKind::Hash | TraitKind::Ord | TraitKind::PartialOrd => Some(
ConfigurableTraitPolicy::from_baseline(baseline.contains(trait_kind)),
),
TraitKind::Default => Some(if matches!(self, Self::List | Self::Map | Self::Set) {
ConfigurableTraitPolicy::GeneratedOverrideable
} else {
ConfigurableTraitPolicy::OptIn
}),
TraitKind::Deref | TraitKind::DerefMut => {
Some(if matches!(self, Self::List | Self::Map | Self::Set) {
ConfigurableTraitPolicy::GeneratedOverrideable
} else if matches!(self, Self::Newtype) {
ConfigurableTraitPolicy::OptIn
} else {
ConfigurableTraitPolicy::Unsupported
})
}
TraitKind::Display => Some(if matches!(self, Self::Newtype) {
ConfigurableTraitPolicy::OptIn
} else {
ConfigurableTraitPolicy::Unsupported
}),
TraitKind::Add
| TraitKind::AddAssign
| TraitKind::Div
| TraitKind::DivAssign
| TraitKind::Mul
| TraitKind::MulAssign
| TraitKind::Neg
| TraitKind::Product
| TraitKind::Rem
| TraitKind::RemAssign
| TraitKind::Sub
| TraitKind::SubAssign
| TraitKind::Sum => Some(if matches!(self, Self::Newtype) {
ConfigurableTraitPolicy::from_baseline(baseline.contains(trait_kind))
} else {
ConfigurableTraitPolicy::Unsupported
}),
TraitKind::From => Some(
if matches!(self, Self::List | Self::Map | Self::Newtype | Self::Set) {
ConfigurableTraitPolicy::GeneratedOverrideable
} else {
ConfigurableTraitPolicy::Unsupported
},
),
TraitKind::FromIterator | TraitKind::IntoIterator => {
Some(if matches!(self, Self::List | Self::Map | Self::Set) {
ConfigurableTraitPolicy::GeneratedOverrideable
} else {
ConfigurableTraitPolicy::Unsupported
})
}
TraitKind::Inner => Some(if matches!(self, Self::Newtype) {
ConfigurableTraitPolicy::GeneratedOverrideable
} else {
ConfigurableTraitPolicy::Unsupported
}),
TraitKind::NormalizeCustom | TraitKind::ValidateCustom => {
Some(ConfigurableTraitPolicy::GeneratedOverrideable)
}
TraitKind::NumericValue => Some(if !matches!(self, Self::Newtype) {
ConfigurableTraitPolicy::Unsupported
} else if baseline.contains(TraitKind::NumericValue) {
ConfigurableTraitPolicy::GeneratedOverrideable
} else {
ConfigurableTraitPolicy::OptIn
}),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ConfigurableTraitPolicy {
GeneratedOverrideable,
OptIn,
Unsupported,
}
impl ConfigurableTraitPolicy {
const fn from_baseline(generated: bool) -> Self {
if generated {
Self::GeneratedOverrideable
} else {
Self::OptIn
}
}
}
impl FromStr for TraitKind {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Clone" => Ok(Self::Clone),
"Copy" => Ok(Self::Copy),
"Debug" => Ok(Self::Debug),
"Default" => Ok(Self::Default),
"Deserialize" => Ok(Self::Deserialize),
"Serialize" => Ok(Self::Serialize),
"Deref" => Ok(Self::Deref),
"DerefMut" => Ok(Self::DerefMut),
"Display" => Ok(Self::Display),
"Eq" => Ok(Self::Eq),
"Hash" => Ok(Self::Hash),
"Ord" => Ok(Self::Ord),
"PartialEq" => Ok(Self::PartialEq),
"PartialOrd" => Ok(Self::PartialOrd),
"Add" => Ok(Self::Add),
"AddAssign" => Ok(Self::AddAssign),
"Div" => Ok(Self::Div),
"DivAssign" => Ok(Self::DivAssign),
"Mul" => Ok(Self::Mul),
"MulAssign" => Ok(Self::MulAssign),
"Neg" => Ok(Self::Neg),
"Product" => Ok(Self::Product),
"Rem" => Ok(Self::Rem),
"RemAssign" => Ok(Self::RemAssign),
"Sub" => Ok(Self::Sub),
"SubAssign" => Ok(Self::SubAssign),
"Sum" => Ok(Self::Sum),
"From" => Ok(Self::From),
"FromIterator" => Ok(Self::FromIterator),
"Inner" => Ok(Self::Inner),
"IntoIterator" => Ok(Self::IntoIterator),
"NumericValue" => Ok(Self::NumericValue),
"Path" => Ok(Self::Path),
"NormalizeAuto" => Ok(Self::NormalizeAuto),
"NormalizeCustom" => Ok(Self::NormalizeCustom),
"ValidateAuto" => Ok(Self::ValidateAuto),
"ValidateCustom" => Ok(Self::ValidateCustom),
"Visitable" => Ok(Self::Visitable),
_ => Err("unknown TraitKind"),
}
}
}
const GENERATED_NODE_TRAITS: &[TraitKind] = &[TraitKind::Clone, TraitKind::Debug, TraitKind::Path];
const DEFAULT_CONFIGURABLE_TYPE_TRAITS: &[TraitKind] =
&[TraitKind::NormalizeCustom, TraitKind::ValidateCustom];
const REQUIRED_TYPE_TRAITS: &[TraitKind] = &[
TraitKind::Serialize,
TraitKind::Clone,
TraitKind::Debug,
TraitKind::Deserialize,
TraitKind::Eq,
TraitKind::PartialEq,
TraitKind::Path,
TraitKind::NormalizeAuto,
TraitKind::ValidateAuto,
TraitKind::Visitable,
];
const CONFIGURABLE_TYPE_TRAITS: &[TraitKind] = &[
TraitKind::Copy,
TraitKind::Default,
TraitKind::Deref,
TraitKind::DerefMut,
TraitKind::Display,
TraitKind::Hash,
TraitKind::Ord,
TraitKind::PartialOrd,
TraitKind::Add,
TraitKind::AddAssign,
TraitKind::Div,
TraitKind::DivAssign,
TraitKind::Mul,
TraitKind::MulAssign,
TraitKind::Neg,
TraitKind::Product,
TraitKind::Rem,
TraitKind::RemAssign,
TraitKind::Sub,
TraitKind::SubAssign,
TraitKind::Sum,
TraitKind::From,
TraitKind::FromIterator,
TraitKind::Inner,
TraitKind::IntoIterator,
TraitKind::NumericValue,
TraitKind::NormalizeCustom,
TraitKind::ValidateCustom,
];
#[must_use]
fn path_to_string(path: &syn::Path) -> String {
path.to_token_stream()
.to_string()
.replace(' ', "")
.trim_matches(':')
.to_string()
}
impl TraitKind {
#[must_use]
#[remain::check]
pub(crate) fn derive_path(self, paths: &CratePaths) -> Option<TokenStream> {
let model = &paths.model;
#[remain::sorted]
match self {
Self::Add => Some(quote!(#model::__reexports::icydb_model_macros::Add)),
Self::AddAssign => Some(quote!(
#model::__reexports::icydb_model_macros::AddAssign
)),
Self::Clone => Some(quote!(::std::clone::Clone)),
Self::Copy => Some(quote!(::std::marker::Copy)),
Self::Debug => Some(quote!(::std::fmt::Debug)),
Self::Default => Some(quote!(::std::default::Default)),
Self::Deref => Some(quote!(
#model::__reexports::icydb_model_macros::Deref
)),
Self::DerefMut => Some(quote!(
#model::__reexports::icydb_model_macros::DerefMut
)),
Self::Deserialize => Some(quote!(#model::__reexports::serde::Deserialize)),
Self::Display => Some(quote!(
#model::__reexports::icydb_model_macros::Display
)),
Self::Div => Some(quote!(#model::__reexports::icydb_model_macros::Div)),
Self::DivAssign => Some(quote!(
#model::__reexports::icydb_model_macros::DivAssign
)),
Self::Eq => Some(quote!(::std::cmp::Eq)),
Self::Hash => Some(quote!(::std::hash::Hash)),
Self::Inner => Some(quote!(
#model::__reexports::icydb_model_macros::Inner
)),
Self::Mul => Some(quote!(#model::__reexports::icydb_model_macros::Mul)),
Self::MulAssign => Some(quote!(
#model::__reexports::icydb_model_macros::MulAssign
)),
Self::Neg => Some(quote!(#model::__reexports::icydb_model_macros::Neg)),
Self::Ord => Some(quote!(::std::cmp::Ord)),
Self::PartialEq => Some(quote!(::std::cmp::PartialEq)),
Self::PartialOrd => Some(quote!(::std::cmp::PartialOrd)),
Self::Product => Some(quote!(
#model::__reexports::icydb_model_macros::Product
)),
Self::Rem => Some(quote!(#model::__reexports::icydb_model_macros::Rem)),
Self::RemAssign => Some(quote!(
#model::__reexports::icydb_model_macros::RemAssign
)),
Self::Serialize => Some(quote!(#model::__reexports::serde::Serialize)),
Self::Sub => Some(quote!(#model::__reexports::icydb_model_macros::Sub)),
Self::SubAssign => Some(quote!(
#model::__reexports::icydb_model_macros::SubAssign
)),
Self::Sum => Some(quote!(#model::__reexports::icydb_model_macros::Sum)),
_ => {
None
}
}
}
}
impl FromMeta for TraitKind {
fn from_nested_meta(item: &NestedMeta) -> Result<Self, DarlingError> {
match item {
NestedMeta::Meta(syn::Meta::Path(path)) => {
let path_str = path_to_string(path);
Self::from_str(&path_str).map_err(DarlingError::custom)
}
_ => Err(DarlingError::custom(format!(
"expected Meta Path, got {item:?}"
))),
}
}
}
impl TraitKind {
pub(crate) fn generated_tokens(self, paths: &CratePaths) -> TokenStream {
let model = &paths.model;
let mut tokens = TokenStream::new();
match self {
Self::Clone => quote!(::std::clone::Clone).to_tokens(&mut tokens),
Self::Copy => quote!(::std::marker::Copy).to_tokens(&mut tokens),
Self::Debug => quote!(::std::fmt::Debug).to_tokens(&mut tokens),
Self::Default => quote!(::std::default::Default).to_tokens(&mut tokens),
Self::Eq => quote!(::std::cmp::Eq).to_tokens(&mut tokens),
Self::Ord => quote!(::std::cmp::Ord).to_tokens(&mut tokens),
Self::PartialEq => quote!(::std::cmp::PartialEq).to_tokens(&mut tokens),
Self::PartialOrd => quote!(::std::cmp::PartialOrd).to_tokens(&mut tokens),
Self::Add
| Self::AddAssign
| Self::Deref
| Self::DerefMut
| Self::Div
| Self::DivAssign
| Self::Mul
| Self::MulAssign
| Self::Neg
| Self::Rem
| Self::RemAssign
| Self::Sub
| Self::SubAssign => {
let trait_name = format_ident!("{self:?}");
quote!(::std::ops::#trait_name).to_tokens(&mut tokens);
}
Self::Display => quote!(::std::fmt::Display).to_tokens(&mut tokens),
Self::From => quote!(::std::convert::From).to_tokens(&mut tokens),
Self::FromIterator => quote!(::std::iter::FromIterator).to_tokens(&mut tokens),
Self::Hash => quote!(::std::hash::Hash).to_tokens(&mut tokens),
Self::Inner | Self::Path => {
let trait_name = format_ident!("{self:?}");
quote!(#model::#trait_name).to_tokens(&mut tokens);
}
Self::IntoIterator => quote!(::std::iter::IntoIterator).to_tokens(&mut tokens),
Self::NumericValue => {
quote!(#model::schema::NumericValue).to_tokens(&mut tokens);
}
Self::Product => quote!(::std::iter::Product).to_tokens(&mut tokens),
Self::Sum => quote!(::std::iter::Sum).to_tokens(&mut tokens),
Self::Deserialize => {
quote!(#model::__reexports::serde::Deserialize).to_tokens(&mut tokens);
}
Self::Serialize => {
quote!(#model::__reexports::serde::Serialize).to_tokens(&mut tokens);
}
Self::NormalizeAuto
| Self::NormalizeCustom
| Self::ValidateAuto
| Self::ValidateCustom
| Self::Visitable => {
let trait_name = format_ident!("{self:?}");
quote!(#model::visitor::#trait_name).to_tokens(&mut tokens);
}
}
tokens
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct TraitSet(BTreeSet<TraitKind>);
impl TraitSet {
pub(crate) fn add(&mut self, tr: TraitKind) {
self.insert(tr);
}
pub(crate) fn insert(&mut self, tr: TraitKind) -> bool {
self.0.insert(tr)
}
pub(crate) fn remove(&mut self, tr: TraitKind) -> bool {
self.0.remove(&tr)
}
pub(crate) fn contains(&self, tr: TraitKind) -> bool {
self.0.contains(&tr)
}
pub(crate) fn extend<I: IntoIterator<Item = TraitKind>>(&mut self, traits: I) {
self.0.extend(traits);
}
pub(crate) fn into_vec(self) -> Vec<TraitKind> {
self.0.into_iter().collect()
}
}
impl FromIterator<TraitKind> for TraitSet {
fn from_iter<I: IntoIterator<Item = TraitKind>>(iter: I) -> Self {
Self(iter.into_iter().collect())
}
}
#[derive(Clone, Debug, Default, FromMeta)]
pub struct TraitBuilder {
#[darling(default)]
pub(crate) add: TraitListMeta,
#[darling(default)]
pub(crate) remove: TraitListMeta,
}
impl TraitBuilder {
pub(crate) fn explicitly_adds(&self, tr: TraitKind) -> bool {
self.add.iter().any(|candidate| *candidate == tr)
}
pub(crate) fn validate_for_type(
&self,
node_kind: ApplicationTypeKind,
baseline: TraitSet,
) -> Result<(), DarlingError> {
for tr in self.add.iter() {
if REQUIRED_TYPE_TRAITS.contains(tr) {
return Err(DarlingError::custom(format!(
"required trait '{tr:?}' is generated automatically and cannot be added explicitly"
)));
}
}
for tr in self.remove.iter() {
if REQUIRED_TYPE_TRAITS.contains(tr) {
return Err(DarlingError::custom(format!(
"required trait '{tr:?}' is generated automatically and cannot be removed"
)));
}
}
Self::validate_configurable_baseline(node_kind, &baseline)?;
self.validate_configurable_traits(node_kind, &baseline)?;
self.resolve(baseline).map(|_| ())
}
fn validate_configurable_baseline(
node_kind: ApplicationTypeKind,
baseline: &TraitSet,
) -> Result<(), DarlingError> {
for trait_kind in CONFIGURABLE_TYPE_TRAITS {
let policy = node_kind
.configurable_trait_policy(*trait_kind, baseline)
.ok_or_else(|| missing_trait_policy_error(node_kind, *trait_kind))?;
let generated = baseline.contains(*trait_kind);
let baseline_is_valid = matches!(
(policy, generated),
(ConfigurableTraitPolicy::GeneratedOverrideable, true)
| (
ConfigurableTraitPolicy::OptIn | ConfigurableTraitPolicy::Unsupported,
false
)
);
if !baseline_is_valid {
return Err(DarlingError::custom(format!(
"internal {} trait baseline disagrees with the '{trait_kind:?}' policy",
node_kind.as_str(),
)));
}
}
Ok(())
}
fn validate_configurable_traits(
&self,
node_kind: ApplicationTypeKind,
baseline: &TraitSet,
) -> Result<(), DarlingError> {
for trait_kind in self.add.iter() {
if self.remove.iter().any(|removed| removed == trait_kind) {
return Err(DarlingError::custom(format!(
"trait '{trait_kind:?}' cannot appear in both traits(add(...)) and traits(remove(...)) for {} application values",
node_kind.as_str(),
)));
}
match node_kind.configurable_trait_policy(*trait_kind, baseline) {
Some(ConfigurableTraitPolicy::GeneratedOverrideable) => {
return Err(DarlingError::custom(format!(
"trait '{trait_kind:?}' is generated automatically for {} application values and cannot be added explicitly",
node_kind.as_str(),
)));
}
Some(ConfigurableTraitPolicy::Unsupported) => {
return Err(unsupported_trait_error(node_kind, *trait_kind));
}
Some(ConfigurableTraitPolicy::OptIn) => {}
None => return Err(missing_trait_policy_error(node_kind, *trait_kind)),
}
}
for trait_kind in self.remove.iter() {
match node_kind.configurable_trait_policy(*trait_kind, baseline) {
Some(ConfigurableTraitPolicy::OptIn) => {
return Err(DarlingError::custom(format!(
"trait '{trait_kind:?}' is not generated for {} application values and cannot be removed",
node_kind.as_str(),
)));
}
Some(ConfigurableTraitPolicy::Unsupported) => {
return Err(unsupported_trait_error(node_kind, *trait_kind));
}
Some(ConfigurableTraitPolicy::GeneratedOverrideable) => {}
None => return Err(missing_trait_policy_error(node_kind, *trait_kind)),
}
}
Ok(())
}
fn resolve(&self, mut set: TraitSet) -> Result<TraitSet, DarlingError> {
for tr in self.add.iter() {
if !set.insert(*tr) {
return Err(DarlingError::custom(format!(
"adding duplicate trait '{tr:?}'"
)));
}
}
for tr in self.remove.iter() {
if !set.remove(*tr) {
return Err(DarlingError::custom(format!(
"cannot remove trait '{tr:?}' because it is not enabled"
)));
}
}
Ok(set)
}
pub(crate) fn build_for_type(&self, baseline: TraitSet) -> TraitSet {
self.resolve(baseline)
.unwrap_or_else(|_| panic!("validated application type traits must resolve"))
}
}
fn unsupported_trait_error(node_kind: ApplicationTypeKind, trait_kind: TraitKind) -> DarlingError {
DarlingError::custom(format!(
"trait '{trait_kind:?}' is not supported by {} application values",
node_kind.as_str(),
))
}
fn missing_trait_policy_error(
node_kind: ApplicationTypeKind,
trait_kind: TraitKind,
) -> DarlingError {
DarlingError::custom(format!(
"internal trait '{trait_kind:?}' has no policy for {} application values",
node_kind.as_str(),
))
}
pub fn generated_node_trait_set() -> TraitSet {
GENERATED_NODE_TRAITS.iter().copied().collect()
}
pub fn application_type_trait_set() -> TraitSet {
REQUIRED_TYPE_TRAITS
.iter()
.chain(DEFAULT_CONFIGURABLE_TYPE_TRAITS.iter())
.copied()
.collect()
}
#[derive(Clone, Debug, Default)]
pub struct TraitListMeta(Vec<TraitKind>);
impl TraitListMeta {
pub(crate) fn push(&mut self, tr: TraitKind) {
self.0.push(tr);
}
pub(crate) fn iter(&self) -> std::slice::Iter<'_, TraitKind> {
self.0.iter()
}
}
impl FromMeta for TraitListMeta {
fn from_list(items: &[NestedMeta]) -> Result<Self, DarlingError> {
let mut traits = Self::default();
for item in items {
let tr = TraitKind::from_nested_meta(item)?;
traits.push(tr);
}
Ok(traits)
}
}
#[cfg(test)]
mod tests;