use proc_macro::TokenStream;
use quote::quote;
use syn::{DeriveInput, Fields, Ident};
use crate::attribute_parsing::{
ContainerAttrs, get_field_key, get_variant_key, parse_container_attrs, parse_variant_attrs,
validate_container_attrs, validate_field_attrs, validate_variant_attrs,
};
pub(crate) fn derive_enum(input: &DeriveInput, data: &syn::DataEnum) -> TokenStream {
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
if let Some(e) = validate_container_attrs(&input.attrs) {
return e.to_compile_error().into();
}
for v in data.variants.iter() {
if let Some(e) = validate_variant_attrs(&v.attrs) {
return e.to_compile_error().into();
}
for f in v.fields.iter() {
if let Some(e) = validate_field_attrs(&f.attrs) {
return e.to_compile_error().into();
}
}
}
let container_attrs = parse_container_attrs(&input.attrs);
let active_variants: Vec<_> = data
.variants
.iter()
.filter(|v| !parse_variant_attrs(&v.attrs).skip)
.collect();
if container_attrs.untagged {
derive_untagged_enum(
ident,
&impl_generics,
&ty_generics,
where_clause,
&active_variants,
&container_attrs,
)
} else if let Some(tag) = &container_attrs.tag {
if let Some(content) = &container_attrs.content {
derive_adjacent_enum(
ident,
&impl_generics,
&ty_generics,
where_clause,
&active_variants,
&container_attrs,
tag,
content,
)
} else {
derive_internal_enum(
ident,
&impl_generics,
&ty_generics,
where_clause,
&active_variants,
&container_attrs,
tag,
)
}
} else {
derive_external_enum(
ident,
&impl_generics,
&ty_generics,
where_clause,
&active_variants,
&container_attrs,
)
}
}
pub(crate) fn derive_external_enum(
ident: &Ident,
impl_generics: &syn::ImplGenerics,
ty_generics: &syn::TypeGenerics,
where_clause: Option<&syn::WhereClause>,
variants: &[&syn::Variant],
container_attrs: &ContainerAttrs,
) -> TokenStream {
let variant_keys: Vec<String> = variants
.iter()
.map(|v| get_variant_key(v, container_attrs))
.collect();
let save_arms = variants
.iter()
.zip(variant_keys.iter())
.map(|(variant, key)| {
let vident = &variant.ident;
match &variant.fields {
Fields::Unit => quote! {
Self::#vident => {
let map_id = doc.put_object(obj, prop, automerge::ObjType::Map)?;
().save(doc, &map_id, #key)?;
}
},
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => quote! {
Self::#vident(inner) => {
let map_id = doc.put_object(obj, prop, automerge::ObjType::Map)?;
inner.save(doc, &map_id, #key)?;
}
},
Fields::Unnamed(fields) => {
let field_indices: Vec<_> =
(0..fields.unnamed.len()).map(syn::Index::from).collect();
let field_names: Vec<_> = (0..fields.unnamed.len())
.map(|i| quote::format_ident!("f{}", i))
.collect();
quote! {
Self::#vident(#(#field_names),*) => {
let map_id = doc.put_object(obj, prop, automerge::ObjType::Map)?;
let inner_id = doc.put_object(&map_id, #key, automerge::ObjType::List)?;
#(
#field_names.save(doc, &inner_id, #field_indices)?;
)*
}
}
}
Fields::Named(fields) => {
let field_names: Vec<_> = fields
.named
.iter()
.filter_map(|f| f.ident.as_ref())
.collect();
let field_keys: Vec<String> = fields
.named
.iter()
.map(|f| get_field_key(f, container_attrs))
.collect();
quote! {
Self::#vident { #(#field_names),* } => {
let map_id = doc.put_object(obj, prop, automerge::ObjType::Map)?;
let inner_id = doc.put_object(&map_id, #key, automerge::ObjType::Map)?;
#(
#field_names.save(doc, &inner_id, #field_keys)?;
)*
}
}
}
}
});
let load_arms = variants.iter().zip(variant_keys.iter()).map(|(variant, key)| {
let vident = &variant.ident;
match &variant.fields {
Fields::Unit => quote! {
#key => return Ok(Self::#vident),
},
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => quote! {
#key => return Ok(Self::#vident(automorph::Automorph::load(doc, &map_id, #key)?)),
},
Fields::Unnamed(fields) => {
let field_indices: Vec<_> = (0..fields.unnamed.len())
.map(syn::Index::from)
.collect();
quote! {
#key => {
return match doc.get(&map_id, #key)? {
Some((automerge::Value::Object(automerge::ObjType::List), inner_id)) => {
Ok(Self::#vident(
#(automorph::Automorph::load(doc, &inner_id, #field_indices)?),*
))
}
_ => Err(automorph::Error::type_mismatch(stringify!(#ident), None)),
}
}
}
},
Fields::Named(fields) => {
let field_names: Vec<_> = fields.named.iter()
.filter_map(|f| f.ident.as_ref())
.collect();
let field_keys: Vec<String> = fields.named.iter()
.map(|f| get_field_key(f, container_attrs))
.collect();
quote! {
#key => {
return match doc.get(&map_id, #key)? {
Some((automerge::Value::Object(automerge::ObjType::Map), inner_id)) => {
Ok(Self::#vident {
#(#field_names: automorph::Automorph::load(doc, &inner_id, #field_keys)?),*
})
}
_ => Err(automorph::Error::type_mismatch(stringify!(#ident), None)),
}
}
}
},
}
});
let load_at_arms = variants.iter().zip(variant_keys.iter()).map(|(variant, key)| {
let vident = &variant.ident;
match &variant.fields {
Fields::Unit => quote! {
#key => return Ok(Self::#vident),
},
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => quote! {
#key => return Ok(Self::#vident(automorph::Automorph::load_at(doc, &map_id, #key, heads)?)),
},
Fields::Unnamed(fields) => {
let field_indices: Vec<_> = (0..fields.unnamed.len())
.map(syn::Index::from)
.collect();
quote! {
#key => {
return match doc.get_at(&map_id, #key, heads)? {
Some((automerge::Value::Object(automerge::ObjType::List), inner_id)) => {
Ok(Self::#vident(
#(automorph::Automorph::load_at(doc, &inner_id, #field_indices, heads)?),*
))
}
_ => Err(automorph::Error::type_mismatch(stringify!(#ident), None)),
}
}
}
},
Fields::Named(fields) => {
let field_names: Vec<_> = fields.named.iter()
.filter_map(|f| f.ident.as_ref())
.collect();
let field_keys: Vec<String> = fields.named.iter()
.map(|f| get_field_key(f, container_attrs))
.collect();
quote! {
#key => {
return match doc.get_at(&map_id, #key, heads)? {
Some((automerge::Value::Object(automerge::ObjType::Map), inner_id)) => {
Ok(Self::#vident {
#(#field_names: automorph::Automorph::load_at(doc, &inner_id, #field_keys, heads)?),*
})
}
_ => Err(automorph::Error::type_mismatch(stringify!(#ident), None)),
}
}
}
},
}
});
let variant_keys_slice = &variant_keys;
let where_clause_with_eq = if let Some(wc) = where_clause {
quote! { #wc, Self: PartialEq }
} else {
quote! { where Self: PartialEq }
};
let output = quote! {
impl #impl_generics automorph::Automorph for #ident #ty_generics #where_clause_with_eq {
type Changes = automorph::PrimitiveChanged;
type Cursor = automorph::ScalarCursor;
fn save<D: automerge::transaction::Transactable + automerge::ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<()> {
let prop: automerge::Prop = prop.into();
let obj = obj.as_ref();
match self {
#(#save_arms)*
}
Ok(())
}
fn load<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self> {
let prop: automerge::Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((automerge::Value::Object(automerge::ObjType::Map), map_id)) => {
let known_keys: &[&str] = &[#(#variant_keys_slice),*];
let present_keys: Vec<String> = doc.keys(&map_id)
.filter(|k| known_keys.contains(&k.as_str()))
.collect();
if present_keys.len() > 1 {
return Err(automorph::Error::invalid_value(format!(
"enum has multiple variant keys present: {:?}. This may be due to a merge conflict.",
present_keys
)));
}
for key in doc.keys(&map_id) {
match key.as_str() {
#(#load_arms)*
_ => continue,
}
}
Err(automorph::Error::invalid_value("no recognized variant key found"))
}
Some((v, _)) => Err(automorph::Error::type_mismatch(stringify!(#ident), Some(format!("{:?}", v)))),
None => Err(automorph::Error::missing_value()),
}
}
fn load_at<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self> {
let prop: automerge::Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((automerge::Value::Object(automerge::ObjType::Map), map_id)) => {
let known_keys: &[&str] = &[#(#variant_keys_slice),*];
let present_keys: Vec<String> = doc.keys_at(&map_id, heads)
.filter(|k| known_keys.contains(&k.as_str()))
.collect();
if present_keys.len() > 1 {
return Err(automorph::Error::invalid_value(format!(
"enum has multiple variant keys present: {:?}. This may be due to a merge conflict.",
present_keys
)));
}
for key in doc.keys_at(&map_id, heads) {
match key.as_str() {
#(#load_at_arms)*
_ => continue,
}
}
Err(automorph::Error::invalid_value("no recognized variant key found"))
}
Some((v, _)) => Err(automorph::Error::type_mismatch(stringify!(#ident), Some(format!("{:?}", v)))),
None => Err(automorph::Error::missing_value()),
}
}
fn diff<D: automerge::ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
let loaded = Self::load(doc, obj, prop)?;
Ok(automorph::PrimitiveChanged::new(self != &loaded))
}
fn diff_at<D: automerge::ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self::Changes> {
let loaded = Self::load_at(doc, obj, prop, heads)?;
Ok(automorph::PrimitiveChanged::new(self != &loaded))
}
fn update<D: automerge::ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
let loaded = Self::load(doc, obj, prop)?;
let changed = self != &loaded;
*self = loaded;
Ok(automorph::PrimitiveChanged::new(changed))
}
fn update_at<D: automerge::ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self::Changes> {
let loaded = Self::load_at(doc, obj, prop, heads)?;
let changed = self != &loaded;
*self = loaded;
Ok(automorph::PrimitiveChanged::new(changed))
}
}
};
output.into()
}
pub(crate) fn derive_internal_enum(
ident: &Ident,
impl_generics: &syn::ImplGenerics,
ty_generics: &syn::TypeGenerics,
where_clause: Option<&syn::WhereClause>,
variants: &[&syn::Variant],
container_attrs: &ContainerAttrs,
tag: &str,
) -> TokenStream {
let variant_keys: Vec<String> = variants
.iter()
.map(|v| get_variant_key(v, container_attrs))
.collect();
let save_arms = variants
.iter()
.zip(variant_keys.iter())
.map(|(variant, key)| {
let vident = &variant.ident;
match &variant.fields {
Fields::Unit => quote! {
Self::#vident => {
let map_id = doc.put_object(obj, prop, automerge::ObjType::Map)?;
#key.to_string().save(doc, &map_id, #tag)?;
}
},
Fields::Named(fields) => {
let field_names: Vec<_> = fields
.named
.iter()
.filter_map(|f| f.ident.as_ref())
.collect();
let field_keys: Vec<String> = fields
.named
.iter()
.map(|f| get_field_key(f, container_attrs))
.collect();
quote! {
Self::#vident { #(#field_names),* } => {
let map_id = doc.put_object(obj, prop, automerge::ObjType::Map)?;
#key.to_string().save(doc, &map_id, #tag)?;
#(
#field_names.save(doc, &map_id, #field_keys)?;
)*
}
}
}
Fields::Unnamed(_) => quote! {
Self::#vident(..) => {
return Err(automorph::Error::invalid_value(
"internally tagged enums do not support tuple variants"
));
}
},
}
});
let load_arms = variants.iter().zip(variant_keys.iter()).map(|(variant, key)| {
let vident = &variant.ident;
match &variant.fields {
Fields::Unit => quote! {
#key => Ok(Self::#vident),
},
Fields::Named(fields) => {
let field_names: Vec<_> = fields.named.iter()
.filter_map(|f| f.ident.as_ref())
.collect();
let field_keys: Vec<String> = fields.named.iter()
.map(|f| get_field_key(f, container_attrs))
.collect();
quote! {
#key => Ok(Self::#vident {
#(#field_names: automorph::Automorph::load(doc, &map_id, #field_keys)?),*
}),
}
},
Fields::Unnamed(_) => quote! {
#key => Err(automorph::Error::invalid_value(
"internally tagged enums do not support tuple variants"
)),
},
}
});
let load_at_arms = variants.iter().zip(variant_keys.iter()).map(|(variant, key)| {
let vident = &variant.ident;
match &variant.fields {
Fields::Unit => quote! {
#key => Ok(Self::#vident),
},
Fields::Named(fields) => {
let field_names: Vec<_> = fields.named.iter()
.filter_map(|f| f.ident.as_ref())
.collect();
let field_keys: Vec<String> = fields.named.iter()
.map(|f| get_field_key(f, container_attrs))
.collect();
quote! {
#key => Ok(Self::#vident {
#(#field_names: automorph::Automorph::load_at(doc, &map_id, #field_keys, heads)?),*
}),
}
},
Fields::Unnamed(_) => quote! {
#key => Err(automorph::Error::invalid_value(
"internally tagged enums do not support tuple variants"
)),
},
}
});
let where_clause_with_eq = if let Some(wc) = where_clause {
quote! { #wc, Self: PartialEq }
} else {
quote! { where Self: PartialEq }
};
let output = quote! {
impl #impl_generics automorph::Automorph for #ident #ty_generics #where_clause_with_eq {
type Changes = automorph::PrimitiveChanged;
type Cursor = automorph::ScalarCursor;
fn save<D: automerge::transaction::Transactable + automerge::ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<()> {
let prop: automerge::Prop = prop.into();
let obj = obj.as_ref();
match self {
#(#save_arms)*
}
Ok(())
}
fn load<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self> {
let prop: automerge::Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((automerge::Value::Object(automerge::ObjType::Map), map_id)) => {
let tag_value: String = automorph::Automorph::load(doc, &map_id, #tag)?;
match tag_value.as_str() {
#(#load_arms)*
_ => Err(automorph::Error::invalid_value(format!("unknown variant: {}", tag_value))),
}
}
Some((v, _)) => Err(automorph::Error::type_mismatch(stringify!(#ident), Some(format!("{:?}", v)))),
None => Err(automorph::Error::missing_value()),
}
}
fn load_at<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self> {
let prop: automerge::Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((automerge::Value::Object(automerge::ObjType::Map), map_id)) => {
let tag_value: String = automorph::Automorph::load_at(doc, &map_id, #tag, heads)?;
match tag_value.as_str() {
#(#load_at_arms)*
_ => Err(automorph::Error::invalid_value(format!("unknown variant: {}", tag_value))),
}
}
Some((v, _)) => Err(automorph::Error::type_mismatch(stringify!(#ident), Some(format!("{:?}", v)))),
None => Err(automorph::Error::missing_value()),
}
}
fn diff<D: automerge::ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
let loaded = Self::load(doc, obj, prop)?;
Ok(automorph::PrimitiveChanged::new(self != &loaded))
}
fn diff_at<D: automerge::ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self::Changes> {
let loaded = Self::load_at(doc, obj, prop, heads)?;
Ok(automorph::PrimitiveChanged::new(self != &loaded))
}
fn update<D: automerge::ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
let loaded = Self::load(doc, obj, prop)?;
let changed = self != &loaded;
*self = loaded;
Ok(automorph::PrimitiveChanged::new(changed))
}
fn update_at<D: automerge::ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self::Changes> {
let loaded = Self::load_at(doc, obj, prop, heads)?;
let changed = self != &loaded;
*self = loaded;
Ok(automorph::PrimitiveChanged::new(changed))
}
}
};
output.into()
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn derive_adjacent_enum(
ident: &Ident,
impl_generics: &syn::ImplGenerics,
ty_generics: &syn::TypeGenerics,
where_clause: Option<&syn::WhereClause>,
variants: &[&syn::Variant],
container_attrs: &ContainerAttrs,
tag: &str,
content: &str,
) -> TokenStream {
let variant_keys: Vec<String> = variants
.iter()
.map(|v| get_variant_key(v, container_attrs))
.collect();
let save_arms = variants.iter().zip(variant_keys.iter()).map(|(variant, key)| {
let vident = &variant.ident;
match &variant.fields {
Fields::Unit => quote! {
Self::#vident => {
let map_id = doc.put_object(obj, prop, automerge::ObjType::Map)?;
#key.to_string().save(doc, &map_id, #tag)?;
().save(doc, &map_id, #content)?;
}
},
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => quote! {
Self::#vident(inner) => {
let map_id = doc.put_object(obj, prop, automerge::ObjType::Map)?;
#key.to_string().save(doc, &map_id, #tag)?;
inner.save(doc, &map_id, #content)?;
}
},
Fields::Unnamed(fields) => {
let field_indices: Vec<_> = (0..fields.unnamed.len())
.map(syn::Index::from)
.collect();
let field_names: Vec<_> = (0..fields.unnamed.len())
.map(|i| quote::format_ident!("f{}", i))
.collect();
quote! {
Self::#vident(#(#field_names),*) => {
let map_id = doc.put_object(obj, prop, automerge::ObjType::Map)?;
#key.to_string().save(doc, &map_id, #tag)?;
let content_id = doc.put_object(&map_id, #content, automerge::ObjType::List)?;
#(
#field_names.save(doc, &content_id, #field_indices)?;
)*
}
}
},
Fields::Named(fields) => {
let field_names: Vec<_> = fields.named.iter()
.filter_map(|f| f.ident.as_ref())
.collect();
let field_keys: Vec<String> = fields.named.iter()
.map(|f| get_field_key(f, container_attrs))
.collect();
quote! {
Self::#vident { #(#field_names),* } => {
let map_id = doc.put_object(obj, prop, automerge::ObjType::Map)?;
#key.to_string().save(doc, &map_id, #tag)?;
let content_id = doc.put_object(&map_id, #content, automerge::ObjType::Map)?;
#(
#field_names.save(doc, &content_id, #field_keys)?;
)*
}
}
},
}
});
let load_arms = variants.iter().zip(variant_keys.iter()).map(|(variant, key)| {
let vident = &variant.ident;
match &variant.fields {
Fields::Unit => quote! {
#key => Ok(Self::#vident),
},
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => quote! {
#key => Ok(Self::#vident(automorph::Automorph::load(doc, &map_id, #content)?)),
},
Fields::Unnamed(fields) => {
let field_indices: Vec<_> = (0..fields.unnamed.len())
.map(syn::Index::from)
.collect();
quote! {
#key => {
match doc.get(&map_id, #content)? {
Some((automerge::Value::Object(automerge::ObjType::List), content_id)) => {
Ok(Self::#vident(
#(automorph::Automorph::load(doc, &content_id, #field_indices)?),*
))
}
_ => Err(automorph::Error::type_mismatch(stringify!(#ident), None)),
}
}
}
},
Fields::Named(fields) => {
let field_names: Vec<_> = fields.named.iter()
.filter_map(|f| f.ident.as_ref())
.collect();
let field_keys: Vec<String> = fields.named.iter()
.map(|f| get_field_key(f, container_attrs))
.collect();
quote! {
#key => {
match doc.get(&map_id, #content)? {
Some((automerge::Value::Object(automerge::ObjType::Map), content_id)) => {
Ok(Self::#vident {
#(#field_names: automorph::Automorph::load(doc, &content_id, #field_keys)?),*
})
}
_ => Err(automorph::Error::type_mismatch(stringify!(#ident), None)),
}
}
}
},
}
});
let load_at_arms = variants.iter().zip(variant_keys.iter()).map(|(variant, key)| {
let vident = &variant.ident;
match &variant.fields {
Fields::Unit => quote! {
#key => Ok(Self::#vident),
},
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => quote! {
#key => Ok(Self::#vident(automorph::Automorph::load_at(doc, &map_id, #content, heads)?)),
},
Fields::Unnamed(fields) => {
let field_indices: Vec<_> = (0..fields.unnamed.len())
.map(syn::Index::from)
.collect();
quote! {
#key => {
match doc.get_at(&map_id, #content, heads)? {
Some((automerge::Value::Object(automerge::ObjType::List), content_id)) => {
Ok(Self::#vident(
#(automorph::Automorph::load_at(doc, &content_id, #field_indices, heads)?),*
))
}
_ => Err(automorph::Error::type_mismatch(stringify!(#ident), None)),
}
}
}
},
Fields::Named(fields) => {
let field_names: Vec<_> = fields.named.iter()
.filter_map(|f| f.ident.as_ref())
.collect();
let field_keys: Vec<String> = fields.named.iter()
.map(|f| get_field_key(f, container_attrs))
.collect();
quote! {
#key => {
match doc.get_at(&map_id, #content, heads)? {
Some((automerge::Value::Object(automerge::ObjType::Map), content_id)) => {
Ok(Self::#vident {
#(#field_names: automorph::Automorph::load_at(doc, &content_id, #field_keys, heads)?),*
})
}
_ => Err(automorph::Error::type_mismatch(stringify!(#ident), None)),
}
}
}
},
}
});
let where_clause_with_eq = if let Some(wc) = where_clause {
quote! { #wc, Self: PartialEq }
} else {
quote! { where Self: PartialEq }
};
let output = quote! {
impl #impl_generics automorph::Automorph for #ident #ty_generics #where_clause_with_eq {
type Changes = automorph::PrimitiveChanged;
type Cursor = automorph::ScalarCursor;
fn save<D: automerge::transaction::Transactable + automerge::ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<()> {
let prop: automerge::Prop = prop.into();
let obj = obj.as_ref();
match self {
#(#save_arms)*
}
Ok(())
}
fn load<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self> {
let prop: automerge::Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((automerge::Value::Object(automerge::ObjType::Map), map_id)) => {
let tag_value: String = automorph::Automorph::load(doc, &map_id, #tag)?;
match tag_value.as_str() {
#(#load_arms)*
_ => Err(automorph::Error::invalid_value(format!("unknown variant: {}", tag_value))),
}
}
Some((v, _)) => Err(automorph::Error::type_mismatch(stringify!(#ident), Some(format!("{:?}", v)))),
None => Err(automorph::Error::missing_value()),
}
}
fn load_at<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self> {
let prop: automerge::Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((automerge::Value::Object(automerge::ObjType::Map), map_id)) => {
let tag_value: String = automorph::Automorph::load_at(doc, &map_id, #tag, heads)?;
match tag_value.as_str() {
#(#load_at_arms)*
_ => Err(automorph::Error::invalid_value(format!("unknown variant: {}", tag_value))),
}
}
Some((v, _)) => Err(automorph::Error::type_mismatch(stringify!(#ident), Some(format!("{:?}", v)))),
None => Err(automorph::Error::missing_value()),
}
}
fn diff<D: automerge::ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
let loaded = Self::load(doc, obj, prop)?;
Ok(automorph::PrimitiveChanged::new(self != &loaded))
}
fn diff_at<D: automerge::ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self::Changes> {
let loaded = Self::load_at(doc, obj, prop, heads)?;
Ok(automorph::PrimitiveChanged::new(self != &loaded))
}
fn update<D: automerge::ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
let loaded = Self::load(doc, obj, prop)?;
let changed = self != &loaded;
*self = loaded;
Ok(automorph::PrimitiveChanged::new(changed))
}
fn update_at<D: automerge::ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self::Changes> {
let loaded = Self::load_at(doc, obj, prop, heads)?;
let changed = self != &loaded;
*self = loaded;
Ok(automorph::PrimitiveChanged::new(changed))
}
}
};
output.into()
}
pub(crate) fn derive_untagged_enum(
ident: &Ident,
impl_generics: &syn::ImplGenerics,
ty_generics: &syn::TypeGenerics,
where_clause: Option<&syn::WhereClause>,
variants: &[&syn::Variant],
container_attrs: &ContainerAttrs,
) -> TokenStream {
let save_arms = variants.iter().map(|variant| {
let vident = &variant.ident;
match &variant.fields {
Fields::Unit => quote! {
Self::#vident => {
().save(doc, obj, prop)?;
}
},
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => quote! {
Self::#vident(inner) => {
inner.save(doc, obj, prop)?;
}
},
Fields::Unnamed(fields) => {
let field_indices: Vec<_> =
(0..fields.unnamed.len()).map(syn::Index::from).collect();
let field_names: Vec<_> = (0..fields.unnamed.len())
.map(|i| quote::format_ident!("f{}", i))
.collect();
quote! {
Self::#vident(#(#field_names),*) => {
let list_id = doc.put_object(obj, prop, automerge::ObjType::List)?;
#(
#field_names.save(doc, &list_id, #field_indices)?;
)*
}
}
}
Fields::Named(fields) => {
let field_names: Vec<_> = fields
.named
.iter()
.filter_map(|f| f.ident.as_ref())
.collect();
let field_keys: Vec<String> = fields
.named
.iter()
.map(|f| get_field_key(f, container_attrs))
.collect();
quote! {
Self::#vident { #(#field_names),* } => {
let map_id = doc.put_object(obj, prop, automerge::ObjType::Map)?;
#(
#field_names.save(doc, &map_id, #field_keys)?;
)*
}
}
}
}
});
let load_try_arms = variants.iter().map(|variant| {
let vident = &variant.ident;
match &variant.fields {
Fields::Unit => quote! {
if let Ok(()) = <()>::load(doc, obj.as_ref(), prop.clone()) {
return Ok(Self::#vident);
}
},
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
let inner_ty = &fields.unnamed.first().map(|f| &f.ty);
quote! {
if let Ok(inner) = <#inner_ty>::load(doc, obj.as_ref(), prop.clone()) {
return Ok(Self::#vident(inner));
}
}
},
Fields::Unnamed(fields) => {
let field_indices: Vec<_> = (0..fields.unnamed.len())
.map(syn::Index::from)
.collect();
let field_types: Vec<_> = fields.unnamed.iter()
.map(|f| &f.ty)
.collect();
quote! {
if let Some((automerge::Value::Object(automerge::ObjType::List), list_id)) = doc.get(obj.as_ref(), prop.clone())? {
let result: automorph::Result<Self> = (|| {
Ok(Self::#vident(
#(<#field_types>::load(doc, &list_id, #field_indices)?),*
))
})();
if let Ok(v) = result {
return Ok(v);
}
}
}
},
Fields::Named(fields) => {
let field_names: Vec<_> = fields.named.iter()
.filter_map(|f| f.ident.as_ref())
.collect();
let field_keys: Vec<String> = fields.named.iter()
.map(|f| get_field_key(f, container_attrs))
.collect();
quote! {
if let Some((automerge::Value::Object(automerge::ObjType::Map), map_id)) = doc.get(obj.as_ref(), prop.clone())? {
let result: automorph::Result<Self> = (|| {
Ok(Self::#vident {
#(#field_names: automorph::Automorph::load(doc, &map_id, #field_keys)?),*
})
})();
if let Ok(v) = result {
return Ok(v);
}
}
}
},
}
});
let load_at_try_arms = variants.iter().map(|variant| {
let vident = &variant.ident;
match &variant.fields {
Fields::Unit => quote! {
if let Ok(()) = <()>::load_at(doc, obj.as_ref(), prop.clone(), heads) {
return Ok(Self::#vident);
}
},
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
let inner_ty = &fields.unnamed.first().map(|f| &f.ty);
quote! {
if let Ok(inner) = <#inner_ty>::load_at(doc, obj.as_ref(), prop.clone(), heads) {
return Ok(Self::#vident(inner));
}
}
},
Fields::Unnamed(fields) => {
let field_indices: Vec<_> = (0..fields.unnamed.len())
.map(syn::Index::from)
.collect();
let field_types: Vec<_> = fields.unnamed.iter()
.map(|f| &f.ty)
.collect();
quote! {
if let Some((automerge::Value::Object(automerge::ObjType::List), list_id)) = doc.get_at(obj.as_ref(), prop.clone(), heads)? {
let result: automorph::Result<Self> = (|| {
Ok(Self::#vident(
#(<#field_types>::load_at(doc, &list_id, #field_indices, heads)?),*
))
})();
if let Ok(v) = result {
return Ok(v);
}
}
}
},
Fields::Named(fields) => {
let field_names: Vec<_> = fields.named.iter()
.filter_map(|f| f.ident.as_ref())
.collect();
let field_keys: Vec<String> = fields.named.iter()
.map(|f| get_field_key(f, container_attrs))
.collect();
quote! {
if let Some((automerge::Value::Object(automerge::ObjType::Map), map_id)) = doc.get_at(obj.as_ref(), prop.clone(), heads)? {
let result: automorph::Result<Self> = (|| {
Ok(Self::#vident {
#(#field_names: automorph::Automorph::load_at(doc, &map_id, #field_keys, heads)?),*
})
})();
if let Ok(v) = result {
return Ok(v);
}
}
}
},
}
});
let where_clause_with_eq = if let Some(wc) = where_clause {
quote! { #wc, Self: PartialEq }
} else {
quote! { where Self: PartialEq }
};
let output = quote! {
impl #impl_generics automorph::Automorph for #ident #ty_generics #where_clause_with_eq {
type Changes = automorph::PrimitiveChanged;
type Cursor = automorph::ScalarCursor;
fn save<D: automerge::transaction::Transactable + automerge::ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<()> {
let prop: automerge::Prop = prop.into();
let obj = obj.as_ref();
match self {
#(#save_arms)*
}
Ok(())
}
fn load<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self> {
let prop: automerge::Prop = prop.into();
#(#load_try_arms)*
Err(automorph::Error::invalid_value("no variant matched for untagged enum"))
}
fn load_at<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self> {
let prop: automerge::Prop = prop.into();
#(#load_at_try_arms)*
Err(automorph::Error::invalid_value("no variant matched for untagged enum"))
}
fn diff<D: automerge::ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
let loaded = Self::load(doc, obj, prop)?;
Ok(automorph::PrimitiveChanged::new(self != &loaded))
}
fn diff_at<D: automerge::ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self::Changes> {
let loaded = Self::load_at(doc, obj, prop, heads)?;
Ok(automorph::PrimitiveChanged::new(self != &loaded))
}
fn update<D: automerge::ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
let loaded = Self::load(doc, obj, prop)?;
let changed = self != &loaded;
*self = loaded;
Ok(automorph::PrimitiveChanged::new(changed))
}
fn update_at<D: automerge::ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self::Changes> {
let loaded = Self::load_at(doc, obj, prop, heads)?;
let changed = self != &loaded;
*self = loaded;
Ok(automorph::PrimitiveChanged::new(changed))
}
}
};
output.into()
}