use proc_macro::TokenStream;
use quote::quote;
use syn::spanned::Spanned;
use syn::{DeriveInput, Fields};
use crate::attribute_parsing::{
ContainerAttrs, get_field_key, parse_container_attrs, parse_field_attrs,
validate_container_attrs, validate_field_attrs,
};
pub(crate) fn derive_struct(input: &DeriveInput, data: &syn::DataStruct) -> 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();
}
let container_attrs = parse_container_attrs(&input.attrs);
if container_attrs.transparent {
return derive_transparent_struct(input, data);
}
match &data.fields {
Fields::Named(fields) => {
for f in fields.named.iter() {
if let Some(e) = validate_field_attrs(&f.attrs) {
return e.to_compile_error().into();
}
}
derive_named_struct(
ident,
&impl_generics,
&ty_generics,
where_clause,
fields,
&container_attrs,
)
}
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
derive_newtype_struct(ident, &impl_generics, &ty_generics, where_clause)
}
Fields::Unnamed(fields) => {
derive_tuple_struct(ident, &impl_generics, &ty_generics, where_clause, fields)
}
Fields::Unit => derive_unit_struct(ident, &impl_generics, &ty_generics, where_clause),
}
}
pub(crate) fn derive_transparent_struct(
input: &DeriveInput,
data: &syn::DataStruct,
) -> TokenStream {
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let inner_ty = match &data.fields {
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
&fields.unnamed[0].ty
}
_ => {
return syn::Error::new(
input.ident.span(),
"#[automorph(transparent)] requires a single-field tuple struct (newtype)",
)
.to_compile_error()
.into();
}
};
let expanded = quote! {
impl #impl_generics automorph::Automorph for #ident #ty_generics #where_clause {
type Changes = <#inner_ty as automorph::Automorph>::Changes;
type Cursor = <#inner_ty as automorph::Automorph>::Cursor;
fn save<D: automerge::transaction::Transactable + automerge::ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<()> {
self.0.save(doc, obj, prop)
}
fn load<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self> {
Ok(Self(<#inner_ty as automorph::Automorph>::load(doc, obj, prop)?))
}
fn load_at<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self> {
Ok(Self(<#inner_ty as automorph::Automorph>::load_at(doc, obj, prop, heads)?))
}
fn update<D: automerge::ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
self.0.update(doc, obj, prop)
}
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> {
self.0.update_at(doc, obj, prop, heads)
}
fn diff<D: automerge::ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
self.0.diff(doc, obj, prop)
}
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> {
self.0.diff_at(doc, obj, prop, heads)
}
}
};
expanded.into()
}
pub(crate) fn derive_named_struct(
ident: &syn::Ident,
impl_generics: &syn::ImplGenerics,
ty_generics: &syn::TypeGenerics,
where_clause: Option<&syn::WhereClause>,
fields: &syn::FieldsNamed,
container_attrs: &ContainerAttrs,
) -> TokenStream {
let changes_ident = quote::format_ident!("{}Changes", ident);
let cursor_ident = quote::format_ident!("{}Cursor", ident);
let active_fields: Vec<_> = fields
.named
.iter()
.filter(|f| !parse_field_attrs(&f.attrs).skip)
.collect();
let skipped_fields: Vec<_> = fields
.named
.iter()
.filter(|f| parse_field_attrs(&f.attrs).skip)
.collect();
let field_names: Vec<_> = active_fields
.iter()
.filter_map(|f| f.ident.as_ref())
.collect();
let skipped_field_names: Vec<_> = skipped_fields
.iter()
.filter_map(|f| f.ident.as_ref())
.collect();
let field_types: Vec<_> = active_fields.iter().map(|f| &f.ty).collect();
let field_strings: Vec<String> = active_fields
.iter()
.map(|f| get_field_key(f, container_attrs))
.collect();
{
use std::collections::HashMap;
let mut seen_keys: HashMap<&str, (&syn::Field, &str)> = HashMap::new();
for (field, key) in active_fields.iter().zip(field_strings.iter()) {
if let Some((first_field, _first_key)) = seen_keys.get(key.as_str()) {
let first_field_name = first_field
.ident
.as_ref()
.map(|i| i.to_string())
.unwrap_or_else(|| "<unnamed>".to_string());
let second_field_name = field
.ident
.as_ref()
.map(|i| i.to_string())
.unwrap_or_else(|| "<unnamed>".to_string());
return syn::Error::new(
field.span(),
format!(
"field `{}` has the same key `{}` as field `{}`. \
Multiple fields cannot have the same key \
(check for conflicting `#[automorph(rename = \"...\")]` attributes)",
second_field_name, key, first_field_name
),
)
.to_compile_error()
.into();
}
seen_keys.insert(key.as_str(), (field, key.as_str()));
}
}
let field_attrs_list: Vec<_> = active_fields
.iter()
.map(|f| parse_field_attrs(&f.attrs))
.collect();
let get_default_expr = |attrs: &crate::attribute_parsing::FieldAttrs| -> proc_macro2::TokenStream {
if let Some(ref fn_path) = attrs.default_fn {
match syn::parse_str::<syn::Path>(fn_path) {
Ok(path) => quote! { #path() },
Err(_) => {
let err_msg = format!(
"invalid default function path: '{}'. \
Expected a function path like 'my_module::default_value'",
fn_path
);
quote! { { compile_error!(#err_msg); } }
}
}
} else {
quote! { Default::default() }
}
};
let changes_fields = field_names
.iter()
.zip(field_types.iter())
.zip(field_attrs_list.iter())
.map(|((name, ty), attrs)| {
if let Some(ref with_module) = attrs.with {
match syn::parse_str::<syn::Path>(with_module) {
Ok(module_path) => {
quote! {
pub #name: Option<#module_path::Changes>
}
}
Err(_) => {
let err_msg = format!(
"invalid module path in 'with' attribute: '{}'. \
Expected a module path like 'automorph::chrono_iso8601'",
with_module
);
quote! { compile_error!(#err_msg); }
}
}
} else {
quote! {
pub #name: Option<<#ty as automorph::Automorph>::Changes>
}
}
});
let any_checks = field_names.iter().map(|name| {
quote! { self.#name.is_some() }
});
let paths_impl = field_names.iter().zip(field_strings.iter()).map(|(name, key)| {
quote! {
if let Some(ref field_changes) = self.#name {
paths.push(vec![std::borrow::Cow::Borrowed(#key)]);
for sub_path in automorph::ChangeReport::paths(field_changes) {
let mut path: Vec<std::borrow::Cow<'static, str>> = vec![std::borrow::Cow::Borrowed(#key)];
path.extend(sub_path);
paths.push(path);
}
}
}
});
let leaf_paths_impl = field_names.iter().zip(field_strings.iter()).map(|(name, key)| {
quote! {
if let Some(ref field_changes) = self.#name {
let sub_paths = automorph::ChangeReport::leaf_paths(field_changes);
if sub_paths.is_empty() {
paths.push(vec![std::borrow::Cow::Borrowed(#key)]);
} else {
for sub_path in sub_paths {
let mut path: Vec<std::borrow::Cow<'static, str>> = vec![std::borrow::Cow::Borrowed(#key)];
path.extend(sub_path);
paths.push(path);
}
}
}
}
});
let cursor_fields = field_names
.iter()
.zip(field_types.iter())
.zip(field_attrs_list.iter())
.map(|((name, ty), attrs)| {
if let Some(ref with_module) = attrs.with {
match syn::parse_str::<syn::Path>(with_module) {
Ok(module_path) => {
quote! {
pub #name: #module_path::Cursor
}
}
Err(_) => {
let err_msg = format!(
"invalid module path in 'with' attribute: '{}'. \
Expected a module path like 'automorph::chrono_iso8601'",
with_module
);
quote! { compile_error!(#err_msg); }
}
}
} else if let Some(inner_ty) = extract_option_inner_type(ty) {
quote! {
pub #name: automorph::OptionCursor<<#inner_ty as automorph::Automorph>::Cursor>
}
} else if let Some((ok_ty, err_ty)) = extract_result_inner_types(ty) {
quote! {
pub #name: automorph::ResultCursor<
<#ok_ty as automorph::Automorph>::Cursor,
<#err_ty as automorph::Automorph>::Cursor
>
}
} else {
quote! {
pub #name: <#ty as automorph::Automorph>::Cursor
}
}
});
let cursor_refresh_stmts = field_names
.iter()
.zip(field_strings.iter())
.zip(field_attrs_list.iter())
.zip(field_types.iter())
.map(|(((name, key), attrs), ty)| {
if let Some(ref with_module) = attrs.with {
match syn::parse_str::<syn::Path>(with_module) {
Ok(module_path) => {
quote! {
if let Some((_, field_obj_id)) = doc.get(obj, #key)? {
<#module_path::Cursor as automorph::FieldCursor>::refresh(&mut self.#name, doc, &field_obj_id)?;
}
}
}
Err(_) => {
quote! {}
}
}
} else if extract_option_inner_type(ty).is_some() {
quote! {
automorph::OptionCursor::refresh_at_key(&mut self.#name, doc, obj, #key)?;
}
} else if extract_result_inner_types(ty).is_some() {
quote! {
automorph::ResultCursor::refresh_at_key(&mut self.#name, doc, obj, #key)?;
}
} else {
quote! {
if let Some((_, field_obj_id)) = doc.get(obj, #key)? {
automorph::FieldCursor::refresh(&mut self.#name, doc, &field_obj_id)?;
}
}
}
});
let cursor_diff_stmts: Vec<_> = field_names
.iter()
.zip(field_strings.iter())
.zip(field_attrs_list.iter())
.zip(field_types.iter())
.map(|(((name, key), attrs), ty)| {
if attrs.flatten {
return Some(quote! {
{
let field_changes = automorph::FieldCursor::diff(&self.#name, doc, obj)?;
if automorph::ChangeReport::any(&field_changes) {
changes.#name = Some(field_changes);
}
}
});
}
if let Some(ref with_module) = attrs.with {
match syn::parse_str::<syn::Path>(with_module) {
Ok(module_path) => {
return Some(quote! {
{
if let Some((_, field_obj_id)) = doc.get(obj, #key)? {
let field_changes = <#module_path::Cursor as automorph::FieldCursor>::diff(
&self.#name, doc, &field_obj_id
)?;
if automorph::ChangeReport::any(&field_changes) {
changes.#name = Some(field_changes);
}
}
}
});
}
Err(_) => {
return Some(quote! {});
}
}
}
if extract_option_inner_type(ty).is_some() {
return Some(quote! {
{
let field_changes = automorph::OptionCursor::diff_at_key(
&self.#name, doc, obj, #key
)?;
if field_changes.any() {
changes.#name = Some(field_changes);
}
}
});
}
if extract_result_inner_types(ty).is_some() {
return Some(quote! {
{
let field_changes = automorph::ResultCursor::diff_at_key(
&self.#name, doc, obj, #key
)?;
if field_changes.any() {
changes.#name = Some(field_changes);
}
}
});
}
Some(quote! {
{
if let Some((_, field_obj_id)) = doc.get(obj, #key)? {
let field_changes = automorph::FieldCursor::diff(
&self.#name, doc, &field_obj_id
)?;
if automorph::ChangeReport::any(&field_changes) {
changes.#name = Some(field_changes);
}
}
}
})
}).collect();
let save_stmts = field_names.iter()
.zip(field_strings.iter())
.zip(field_attrs_list.iter())
.map(|((name, key), attrs)| {
if attrs.flatten {
return quote! {
automorph::Automorph::save_flat(&self.#name, doc, &map_id)?;
};
}
let save_expr = if let Some(ref with_module) = attrs.with {
match syn::parse_str::<syn::Path>(with_module) {
Ok(module_path) => {
quote! { #module_path::save(&self.#name, doc, &map_id, #key)? }
}
Err(_) => {
let err_msg = format!(
"invalid module path in 'with' attribute: '{}'. \
Expected a module path like 'automorph::chrono_iso8601'",
with_module
);
return quote! { compile_error!(#err_msg); };
}
}
} else {
quote! { automorph::Automorph::save(&self.#name, doc, &map_id, #key)? }
};
if let Some(ref predicate) = attrs.skip_saving_if {
match syn::parse_str::<syn::Path>(predicate) {
Ok(pred_path) => {
quote! {
if !#pred_path(&self.#name) {
#save_expr;
}
}
}
Err(_) => {
let err_msg = format!(
"invalid path in skip_saving_if: '{}'. \
Expected a function path like 'Option::is_none' or 'my_module::is_empty'",
predicate
);
quote! {
compile_error!(#err_msg);
}
}
}
} else {
quote! { #save_expr; }
}
}).collect::<Vec<_>>();
let load_stmts: Vec<_> = field_names.iter()
.zip(field_strings.iter())
.zip(field_attrs_list.iter())
.map(|((name, key), attrs)| {
let default_expr = get_default_expr(attrs);
if attrs.flatten {
let load_call = quote! { automorph::Automorph::load_flat(doc, &map_id) };
if attrs.default {
return quote! {
#name: match #load_call {
Ok(v) => v,
Err(e) if matches!(e.kind, automorph::ErrorKind::MissingValue) => #default_expr,
Err(e) => return Err(e),
},
};
} else {
return quote! {
#name: #load_call?,
};
}
}
let primary_load = if let Some(ref with_module) = attrs.with {
match syn::parse_str::<syn::Path>(with_module) {
Ok(module_path) => {
quote! { #module_path::load(doc, &map_id, #key) }
}
Err(_) => {
let err_msg = format!(
"invalid module path in 'with' attribute: '{}'. \
Expected a module path like 'automorph::chrono_iso8601'",
with_module
);
return quote! { #name: { compile_error!(#err_msg); #default_expr }, };
}
}
} else {
quote! { automorph::Automorph::load(doc, &map_id, #key) }
};
if let Some(ref alias) = attrs.alias {
let alias_load = if let Some(ref with_module) = attrs.with {
match syn::parse_str::<syn::Path>(with_module) {
Ok(module_path) => {
quote! { #module_path::load(doc, &map_id, #alias) }
}
Err(_) => quote! { automorph::Automorph::load(doc, &map_id, #alias) },
}
} else {
quote! { automorph::Automorph::load(doc, &map_id, #alias) }
};
if attrs.default {
quote! {
#name: match #primary_load {
Ok(v) => v,
Err(e) if matches!(e.kind, automorph::ErrorKind::MissingValue) => {
match #alias_load {
Ok(v) => v,
Err(e) if matches!(e.kind, automorph::ErrorKind::MissingValue) => #default_expr,
Err(e) => return Err(e.with_field(#key)),
}
}
Err(e) => return Err(e.with_field(#key)),
},
}
} else {
quote! {
#name: match #primary_load {
Ok(v) => v,
Err(e) if matches!(e.kind, automorph::ErrorKind::MissingValue) => {
#alias_load.map_err(|e| e.with_field(#key))?
}
Err(e) => return Err(e.with_field(#key)),
},
}
}
} else {
if attrs.default {
quote! {
#name: match #primary_load {
Ok(v) => v,
Err(e) if matches!(e.kind, automorph::ErrorKind::MissingValue) => #default_expr,
Err(e) => return Err(e.with_field(#key)),
},
}
} else {
quote! {
#name: #primary_load.map_err(|e| e.with_field(#key))?,
}
}
}
}).collect::<Vec<_>>();
let load_at_stmts: Vec<_> = field_names.iter()
.zip(field_strings.iter())
.zip(field_attrs_list.iter())
.map(|((name, key), attrs)| {
let default_expr = get_default_expr(attrs);
if attrs.flatten {
let load_at_call = quote! { automorph::Automorph::load_flat_at(doc, &map_id, heads) };
if attrs.default {
return quote! {
#name: match #load_at_call {
Ok(v) => v,
Err(e) if matches!(e.kind, automorph::ErrorKind::MissingValue) => #default_expr,
Err(e) => return Err(e),
},
};
} else {
return quote! {
#name: #load_at_call?,
};
}
}
let primary_load_at = if let Some(ref with_module) = attrs.with {
match syn::parse_str::<syn::Path>(with_module) {
Ok(module_path) => {
quote! { #module_path::load_at(doc, &map_id, #key, heads) }
}
Err(_) => {
let err_msg = format!(
"invalid module path in 'with' attribute: '{}'. \
Expected a module path like 'automorph::chrono_iso8601'",
with_module
);
return quote! { #name: { compile_error!(#err_msg); #default_expr }, };
}
}
} else {
quote! { automorph::Automorph::load_at(doc, &map_id, #key, heads) }
};
if let Some(ref alias) = attrs.alias {
let alias_load_at = if let Some(ref with_module) = attrs.with {
match syn::parse_str::<syn::Path>(with_module) {
Ok(module_path) => {
quote! { #module_path::load_at(doc, &map_id, #alias, heads) }
}
Err(_) => quote! { automorph::Automorph::load_at(doc, &map_id, #alias, heads) },
}
} else {
quote! { automorph::Automorph::load_at(doc, &map_id, #alias, heads) }
};
if attrs.default {
quote! {
#name: match #primary_load_at {
Ok(v) => v,
Err(e) if matches!(e.kind, automorph::ErrorKind::MissingValue) => {
match #alias_load_at {
Ok(v) => v,
Err(e) if matches!(e.kind, automorph::ErrorKind::MissingValue) => #default_expr,
Err(e) => return Err(e.with_field(#key)),
}
}
Err(e) => return Err(e.with_field(#key)),
},
}
} else {
quote! {
#name: match #primary_load_at {
Ok(v) => v,
Err(e) if matches!(e.kind, automorph::ErrorKind::MissingValue) => {
#alias_load_at.map_err(|e| e.with_field(#key))?
}
Err(e) => return Err(e.with_field(#key)),
},
}
}
} else {
if attrs.default {
quote! {
#name: match #primary_load_at {
Ok(v) => v,
Err(e) if matches!(e.kind, automorph::ErrorKind::MissingValue) => #default_expr,
Err(e) => return Err(e.with_field(#key)),
},
}
} else {
quote! {
#name: #primary_load_at.map_err(|e| e.with_field(#key))?,
}
}
}
}).collect::<Vec<_>>();
let skipped_field_stmts: Vec<_> = skipped_field_names.iter().map(|name| {
quote! {
#name: Default::default(),
}
}).collect();
let skipped_field_stmts_clone = skipped_field_stmts.clone();
let diff_stmts = field_names
.iter()
.zip(field_strings.iter())
.zip(field_attrs_list.iter())
.map(|((name, key), attrs)| {
if attrs.flatten {
return quote! {
{
let field_changes = automorph::Automorph::diff_flat(&self.#name, doc, &map_id)?;
if automorph::ChangeReport::any(&field_changes) {
changes.#name = Some(field_changes);
}
}
};
}
if let Some(ref with_module) = attrs.with {
match syn::parse_str::<syn::Path>(with_module) {
Ok(module_path) => {
quote! {
{
let field_changes = #module_path::diff(&self.#name, doc, &map_id, #key)?;
if automorph::ChangeReport::any(&field_changes) {
changes.#name = Some(field_changes);
}
}
}
}
Err(_) => {
quote! {}
}
}
} else {
quote! {
{
let field_changes = automorph::Automorph::diff(&self.#name, doc, &map_id, #key)?;
if automorph::ChangeReport::any(&field_changes) {
changes.#name = Some(field_changes);
}
}
}
}
}).collect::<Vec<_>>();
let diff_at_stmts: Vec<_> = field_names
.iter()
.zip(field_strings.iter())
.zip(field_attrs_list.iter())
.map(|((name, key), attrs)| {
if attrs.flatten {
return quote! {
{
let field_changes = automorph::Automorph::diff_flat_at(&self.#name, doc, &map_id, heads)?;
if automorph::ChangeReport::any(&field_changes) {
changes.#name = Some(field_changes);
}
}
};
}
if let Some(ref with_module) = attrs.with {
match syn::parse_str::<syn::Path>(with_module) {
Ok(module_path) => {
quote! {
{
let field_changes = #module_path::diff_at(&self.#name, doc, &map_id, #key, heads)?;
if automorph::ChangeReport::any(&field_changes) {
changes.#name = Some(field_changes);
}
}
}
}
Err(_) => {
quote! {}
}
}
} else {
quote! {
{
let field_changes = automorph::Automorph::diff_at(&self.#name, doc, &map_id, #key, heads)?;
if automorph::ChangeReport::any(&field_changes) {
changes.#name = Some(field_changes);
}
}
}
}
}).collect::<Vec<_>>();
let update_stmts: Vec<_> = field_names
.iter()
.zip(field_strings.iter())
.zip(field_attrs_list.iter())
.map(|((name, key), attrs)| {
if attrs.flatten {
return quote! {
{
let field_changes = automorph::Automorph::update_flat(&mut self.#name, doc, &map_id)?;
if automorph::ChangeReport::any(&field_changes) {
changes.#name = Some(field_changes);
}
}
};
}
if let Some(ref with_module) = attrs.with {
match syn::parse_str::<syn::Path>(with_module) {
Ok(module_path) => {
quote! {
{
let field_changes = #module_path::update(&mut self.#name, doc, &map_id, #key)?;
if automorph::ChangeReport::any(&field_changes) {
changes.#name = Some(field_changes);
}
}
}
}
Err(_) => {
quote! {}
}
}
} else {
quote! {
{
let field_changes = automorph::Automorph::update(&mut self.#name, doc, &map_id, #key)?;
if automorph::ChangeReport::any(&field_changes) {
changes.#name = Some(field_changes);
}
}
}
}
}).collect::<Vec<_>>();
let update_at_stmts: Vec<_> = field_names
.iter()
.zip(field_strings.iter())
.zip(field_attrs_list.iter())
.map(|((name, key), attrs)| {
if attrs.flatten {
return quote! {
{
let field_changes = automorph::Automorph::update_flat_at(&mut self.#name, doc, &map_id, heads)?;
if automorph::ChangeReport::any(&field_changes) {
changes.#name = Some(field_changes);
}
}
};
}
if let Some(ref with_module) = attrs.with {
match syn::parse_str::<syn::Path>(with_module) {
Ok(module_path) => {
quote! {
{
let field_changes = #module_path::update_at(&mut self.#name, doc, &map_id, #key, heads)?;
if automorph::ChangeReport::any(&field_changes) {
changes.#name = Some(field_changes);
}
}
}
}
Err(_) => {
quote! {}
}
}
} else {
quote! {
{
let field_changes = automorph::Automorph::update_at(&mut self.#name, doc, &map_id, #key, heads)?;
if automorph::ChangeReport::any(&field_changes) {
changes.#name = Some(field_changes);
}
}
}
}
}).collect::<Vec<_>>();
let deny_unknown_check = if container_attrs.deny_unknown_fields {
let known_keys: Vec<&str> = field_strings.iter().map(|s| s.as_str()).collect();
quote! {
for key in doc.keys(&map_id) {
let known: &[&str] = &[#(#known_keys),*];
if !known.contains(&key.as_str()) {
return Err(automorph::Error::unknown_field(key.to_string()));
}
}
}
} else {
quote! {}
};
let deny_unknown_check_at = if container_attrs.deny_unknown_fields {
let known_keys: Vec<&str> = field_strings.iter().map(|s| s.as_str()).collect();
quote! {
for key in doc.keys(&map_id) {
let known: &[&str] = &[#(#known_keys),*];
if !known.contains(&key.as_str()) {
return Err(automorph::Error::unknown_field(key.to_string()));
}
}
}
} else {
quote! {}
};
let output = quote! {
#[derive(Debug, Clone, Default)]
pub struct #changes_ident {
#(#changes_fields),*
}
impl automorph::ChangeReport for #changes_ident {
fn any(&self) -> bool {
false #(|| #any_checks)*
}
fn paths(&self) -> Vec<Vec<std::borrow::Cow<'static, str>>> {
let mut paths = Vec::new();
#(#paths_impl)*
paths
}
fn leaf_paths(&self) -> Vec<Vec<std::borrow::Cow<'static, str>>> {
let mut paths = Vec::new();
#(#leaf_paths_impl)*
paths
}
}
#[derive(Debug, Clone, Default)]
pub struct #cursor_ident {
#(#cursor_fields),*
}
impl automorph::FieldCursor for #cursor_ident {
type Changes = #changes_ident;
fn diff<D: automerge::ReadDoc>(&self, doc: &D, obj: &automerge::ObjId) -> automorph::Result<Self::Changes> {
let mut changes = #changes_ident::default();
#(#cursor_diff_stmts)*
Ok(changes)
}
fn refresh<D: automerge::ReadDoc>(&mut self, doc: &D, obj: &automerge::ObjId) -> automorph::Result<()> {
#(#cursor_refresh_stmts)*
Ok(())
}
}
impl #impl_generics automorph::Automorph for #ident #ty_generics #where_clause {
type Changes = #changes_ident;
type Cursor = #cursor_ident;
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();
let cur = doc.get(obj, prop.clone())?;
let map_id = match cur {
Some((automerge::Value::Object(automerge::ObjType::Map), id)) => id,
_ => doc.put_object(obj, prop, automerge::ObjType::Map)?,
};
#(#save_stmts)*
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)) => {
#deny_unknown_check
Ok(Self {
#(#load_stmts)*
#(#skipped_field_stmts)*
})
}
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)) => {
#deny_unknown_check_at
Ok(Self {
#(#load_at_stmts)*
#(#skipped_field_stmts_clone)*
})
}
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 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 mut changes = #changes_ident::default();
#(#diff_stmts)*
Ok(changes)
}
Some((v, _)) => Err(automorph::Error::type_mismatch(stringify!(#ident), Some(format!("{:?}", v)))),
None => Err(automorph::Error::missing_value()),
}
}
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 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 mut changes = #changes_ident::default();
#(#diff_at_stmts)*
Ok(changes)
}
Some((v, _)) => Err(automorph::Error::type_mismatch(stringify!(#ident), Some(format!("{:?}", v)))),
None => Err(automorph::Error::missing_value()),
}
}
fn update<D: automerge::ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
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 mut changes = #changes_ident::default();
#(#update_stmts)*
Ok(changes)
}
Some((v, _)) => Err(automorph::Error::type_mismatch(stringify!(#ident), Some(format!("{:?}", v)))),
None => Err(automorph::Error::missing_value()),
}
}
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 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 mut changes = #changes_ident::default();
#(#update_at_stmts)*
Ok(changes)
}
Some((v, _)) => Err(automorph::Error::type_mismatch(stringify!(#ident), Some(format!("{:?}", v)))),
None => Err(automorph::Error::missing_value()),
}
}
fn save_flat<D: automerge::transaction::Transactable + automerge::ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<automerge::ObjId>,
) -> automorph::Result<()> {
let map_id = obj.as_ref();
#(#save_stmts)*
Ok(())
}
fn load_flat<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
) -> automorph::Result<Self> {
let map_id = obj.as_ref();
#deny_unknown_check
Ok(Self {
#(#load_stmts)*
#(#skipped_field_stmts)*
})
}
fn load_flat_at<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self> {
let map_id = obj.as_ref();
#deny_unknown_check_at
Ok(Self {
#(#load_at_stmts)*
#(#skipped_field_stmts_clone)*
})
}
fn diff_flat<D: automerge::ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
) -> automorph::Result<Self::Changes> {
let map_id = obj.as_ref();
let mut changes = #changes_ident::default();
#(#diff_stmts)*
Ok(changes)
}
fn diff_flat_at<D: automerge::ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self::Changes> {
let map_id = obj.as_ref();
let mut changes = #changes_ident::default();
#(#diff_at_stmts)*
Ok(changes)
}
fn update_flat<D: automerge::ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
) -> automorph::Result<Self::Changes> {
let map_id = obj.as_ref();
let mut changes = #changes_ident::default();
#(#update_stmts)*
Ok(changes)
}
fn update_flat_at<D: automerge::ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self::Changes> {
let map_id = obj.as_ref();
let mut changes = #changes_ident::default();
#(#update_at_stmts)*
Ok(changes)
}
}
};
output.into()
}
pub(crate) fn derive_newtype_struct(
ident: &syn::Ident,
impl_generics: &syn::ImplGenerics,
ty_generics: &syn::TypeGenerics,
where_clause: Option<&syn::WhereClause>,
) -> TokenStream {
let output = quote! {
impl #impl_generics automorph::Automorph for #ident #ty_generics #where_clause {
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<()> {
self.0.save(doc, obj, prop)
}
fn load<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self> {
Ok(Self(automorph::Automorph::load(doc, obj, prop)?))
}
fn load_at<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self> {
Ok(Self(automorph::Automorph::load_at(doc, obj, prop, heads)?))
}
fn diff<D: automerge::ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
self.0.diff(doc, obj, prop)
}
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> {
self.0.diff_at(doc, obj, prop, heads)
}
fn update<D: automerge::ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
self.0.update(doc, obj, prop)
}
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> {
self.0.update_at(doc, obj, prop, heads)
}
}
};
output.into()
}
pub(crate) fn derive_tuple_struct(
ident: &syn::Ident,
impl_generics: &syn::ImplGenerics,
ty_generics: &syn::TypeGenerics,
where_clause: Option<&syn::WhereClause>,
fields: &syn::FieldsUnnamed,
) -> TokenStream {
let field_count = fields.unnamed.len();
let indices: Vec<_> = (0..field_count).collect();
let field_names: Vec<_> = (0..field_count).map(syn::Index::from).collect();
let output = quote! {
impl #impl_generics automorph::Automorph for #ident #ty_generics #where_clause {
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();
let list_id = doc.put_object(obj, prop, automerge::ObjType::List)?;
#(
self.#field_names.save(doc, &list_id, #indices)?;
)*
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::List), list_id)) => {
Ok(Self(
#(automorph::Automorph::load(doc, &list_id, #indices)?),*
))
}
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::List), list_id)) => {
Ok(Self(
#(automorph::Automorph::load_at(doc, &list_id, #indices, heads)?),*
))
}
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)?;
let changed = false #(|| {
let self_val = &self.#field_names;
let loaded_val = &loaded.#field_names;
self_val != loaded_val
})*;
Ok(automorph::PrimitiveChanged::new(changed))
}
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)?;
let changed = false #(|| {
let self_val = &self.#field_names;
let loaded_val = &loaded.#field_names;
self_val != loaded_val
})*;
Ok(automorph::PrimitiveChanged::new(changed))
}
fn update<D: automerge::ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
let prop: automerge::Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((automerge::Value::Object(automerge::ObjType::List), list_id)) => {
let mut any_changed = false;
#(
let field_changes = self.#field_names.update(doc, &list_id, #indices)?;
if automorph::ChangeReport::any(&field_changes) {
any_changed = true;
}
)*
Ok(automorph::PrimitiveChanged::new(any_changed))
}
Some((v, _)) => Err(automorph::Error::type_mismatch(stringify!(#ident), Some(format!("{:?}", v)))),
None => Err(automorph::Error::missing_value()),
}
}
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 prop: automerge::Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((automerge::Value::Object(automerge::ObjType::List), list_id)) => {
let mut any_changed = false;
#(
let field_changes = self.#field_names.update_at(doc, &list_id, #indices, heads)?;
if automorph::ChangeReport::any(&field_changes) {
any_changed = true;
}
)*
Ok(automorph::PrimitiveChanged::new(any_changed))
}
Some((v, _)) => Err(automorph::Error::type_mismatch(stringify!(#ident), Some(format!("{:?}", v)))),
None => Err(automorph::Error::missing_value()),
}
}
}
};
output.into()
}
pub(crate) fn derive_unit_struct(
ident: &syn::Ident,
impl_generics: &syn::ImplGenerics,
ty_generics: &syn::TypeGenerics,
where_clause: Option<&syn::WhereClause>,
) -> TokenStream {
let output = quote! {
impl #impl_generics automorph::Automorph for #ident #ty_generics #where_clause {
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<()> {
().save(doc, obj, prop)
}
fn load<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self> {
<()>::load(doc, obj, prop)?;
Ok(Self)
}
fn load_at<D: automerge::ReadDoc>(
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
heads: &[automerge::ChangeHash],
) -> automorph::Result<Self> {
<()>::load_at(doc, obj, prop, heads)?;
Ok(Self)
}
fn diff<D: automerge::ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
().diff(doc, obj, prop)
}
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> {
().diff_at(doc, obj, prop, heads)
}
fn update<D: automerge::ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<automerge::ObjId>,
prop: impl Into<automerge::Prop>,
) -> automorph::Result<Self::Changes> {
().update(doc, obj, prop)
}
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> {
().update_at(doc, obj, prop, heads)
}
}
};
output.into()
}
fn extract_option_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
if let syn::Type::Path(type_path) = ty {
let path = &type_path.path;
let is_option = path.segments.len() == 1 && path.segments[0].ident == "Option"
|| path.segments.len() == 3
&& path.segments[0].ident == "std"
&& path.segments[1].ident == "option"
&& path.segments[2].ident == "Option";
if is_option {
if let syn::PathArguments::AngleBracketed(args) = &path.segments.last()?.arguments {
if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
return Some(inner_ty);
}
}
}
}
None
}
fn extract_result_inner_types(ty: &syn::Type) -> Option<(&syn::Type, &syn::Type)> {
if let syn::Type::Path(type_path) = ty {
let path = &type_path.path;
let is_result = path.segments.len() == 1 && path.segments[0].ident == "Result"
|| path.segments.len() == 3
&& path.segments[0].ident == "std"
&& path.segments[1].ident == "result"
&& path.segments[2].ident == "Result";
if is_result {
if let syn::PathArguments::AngleBracketed(args) = &path.segments.last()?.arguments {
let mut types = args.args.iter().filter_map(|arg| {
if let syn::GenericArgument::Type(ty) = arg {
Some(ty)
} else {
None
}
});
let ok_ty = types.next()?;
let err_ty = types.next()?;
return Some((ok_ty, err_ty));
}
}
}
None
}