improved api

This commit is contained in:
Aiden McClelland
2023-03-02 18:24:57 -07:00
parent 6c89300099
commit a68c49f2fd
5 changed files with 523 additions and 208 deletions

View File

@@ -8,8 +8,9 @@ use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::{Comma, Paren, Pub};
use syn::{
Attribute, Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Ident, Lit, LitInt, LitStr,
MetaNameValue, Path, Type, VisRestricted, Visibility,
Attribute, Data, DataEnum, DataStruct, DeriveInput, Error, Fields, GenericArgument, Ident, Lit,
LitInt, LitStr, Meta, MetaNameValue, Path, PathArguments, Type, TypePath, VisRestricted,
Visibility,
};
pub fn build_model(item: &DeriveInput) -> TokenStream {
@@ -126,12 +127,17 @@ impl ChildInfo {
}
struct Fns {
from_parts: TokenStream,
impl_fns: TokenStream,
impl_ref_fns: TokenStream,
impl_mut_fns: TokenStream,
}
fn impl_fns(children: &[ChildInfo]) -> Fns {
fn impl_fns(model_ty: &Type, children: &[ChildInfo]) -> Fns {
let mut parts_args = TokenStream::new();
let mut parts_assignments = TokenStream::new();
let mut impl_fns = TokenStream::new();
let mut impl_ref_fns = TokenStream::new();
let mut impl_mut_fns = TokenStream::new();
for ChildInfo {
vis,
@@ -141,6 +147,7 @@ fn impl_fns(children: &[ChildInfo]) -> Fns {
} in children
{
let name_owned = Ident::new(&format!("into_{name}"), name.span());
let name_ref = Ident::new(&format!("as_{name}"), name.span());
let name_mut = Ident::new(&format!("as_{name}_mut"), name.span());
let vis = match vis {
Visibility::Inherited => Visibility::Restricted(VisRestricted {
@@ -181,6 +188,17 @@ fn impl_fns(children: &[ChildInfo]) -> Fns {
} else {
quote! { v }
};
let accessor_ref = if let Some(accessor) = accessor {
quote! {
{
#[allow(unused_imports)]
use patch_db::value::index::Index;
#accessor.index_into(v).unwrap_or(&patch_db::value::NULL)
}
}
} else {
quote! { v }
};
let accessor_mut = if let Some(accessor) = accessor {
quote! {
{
@@ -192,24 +210,128 @@ fn impl_fns(children: &[ChildInfo]) -> Fns {
} else {
quote! { v }
};
let child_model_ty = replace_self(model_ty.clone(), &ty);
parts_args.extend(quote_spanned! { name.span() =>
#name: #child_model_ty,
});
parts_assignments.extend(quote_spanned! { name.span() =>
*#accessor_mut = #name.into_value();
});
impl_fns.extend(quote_spanned! { name.span() =>
#vis fn #name_owned (self) -> patch_db::Model::<#ty> {
#vis fn #name_owned (self) -> #child_model_ty {
use patch_db::ModelExt;
self.transmute(|v| #accessor_owned)
}
});
impl_ref_fns.extend(quote_spanned! { name.span() =>
#vis fn #name_ref (&self) -> &#child_model_ty {
use patch_db::ModelExt;
self.transmute_ref(|v| #accessor_ref)
}
});
impl_mut_fns.extend(quote_spanned! { name.span() =>
#vis fn #name_mut (&mut self) -> &mut patch_db::Model::<#ty> {
#vis fn #name_mut (&mut self) -> &mut #child_model_ty {
use patch_db::ModelExt;
self.transmute_mut(|v| #accessor_mut)
}
});
}
Fns {
from_parts: quote! {
pub fn from_parts(#parts_args) -> Self {
use patch_db::ModelExt;
let mut res = patch_db::value::json!({});
let v = &mut res;
#parts_assignments
Self::from_value(res)
}
},
impl_fns,
impl_ref_fns,
impl_mut_fns,
}
}
fn replace_self(ty: Type, replace: &Type) -> Type {
match ty {
Type::Path(mut a) => Type::Path({
a.path.segments = a
.path
.segments
.into_iter()
.map(|mut s| {
s.arguments = match s.arguments {
PathArguments::AngleBracketed(mut a) => PathArguments::AngleBracketed({
a.args = a
.args
.into_iter()
.map(|a| match a {
GenericArgument::Type(Type::Path(a)) => {
GenericArgument::Type({
if a.path.is_ident("Self") {
replace.clone()
} else {
Type::Path(a)
}
})
}
a => a,
})
.collect();
a
}),
a => a,
};
s
})
.collect();
a
}),
a => a,
}
}
fn build_model_struct(base: &DeriveInput, ast: &DataStruct) -> TokenStream {
let model_ty = match base
.attrs
.iter()
.filter(|attr| attr.path.is_ident("model"))
.filter_map(|attr| attr.parse_meta().ok())
.find_map(|meta| {
if let Meta::NameValue(a) = meta {
Some(a)
} else {
None
}
})
.ok_or_else(|| {
Error::new(
base.ident.span(),
"could not determine model type\n#[model = \"...\"] is required",
)
})
.and_then(|meta| {
if let Lit::Str(a) = meta.lit {
Ok(a)
} else {
Err(Error::new(
meta.lit.span(),
"syntax error: expected string literal",
))
}
})
.and_then(|s| syn::parse_str::<Type>(&s.value()))
{
Ok(a) => a,
Err(e) => return e.into_compile_error(),
};
let model_ty_name = replace_self(
model_ty.clone(),
&Type::Path(TypePath {
qself: None,
path: base.ident.clone().into(),
}),
);
let serde_rename_all = base
.attrs
.iter()
@@ -223,26 +345,73 @@ fn build_model_struct(base: &DeriveInput, ast: &DataStruct) -> TokenStream {
let children = ChildInfo::from_fields(&serde_rename_all, &ast.fields);
let name = &base.ident;
let Fns {
from_parts,
impl_fns,
impl_ref_fns,
impl_mut_fns,
} = impl_fns(&children);
} = impl_fns(&model_ty, &children);
quote! {
impl patch_db::HasModel for #name {}
impl patch_db::Model<#name> {
impl patch_db::HasModel for #name {
type Model = #model_ty;
}
impl #model_ty_name {
#from_parts
#impl_fns
#impl_ref_fns
#impl_mut_fns
}
}
}
fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
let model_ty = match base
.attrs
.iter()
.filter(|attr| attr.path.is_ident("model"))
.filter_map(|attr| attr.parse_meta().ok())
.find_map(|meta| {
if let Meta::NameValue(a) = meta {
Some(a)
} else {
None
}
})
.ok_or_else(|| {
Error::new(
base.ident.span(),
"could not determine model type\n#[model = \"...\"] is required",
)
})
.and_then(|meta| {
if let Lit::Str(a) = meta.lit {
Ok(a)
} else {
Err(Error::new(
meta.lit.span(),
"syntax error: expected string literal",
))
}
})
.and_then(|s| syn::parse_str::<Type>(&s.value()))
{
Ok(a) => a,
Err(e) => return e.into_compile_error(),
};
let model_ty_name = replace_self(
model_ty.clone(),
&Type::Path(TypePath {
qself: None,
path: base.ident.clone().into(),
}),
);
let mut match_name = None;
let mut match_name_ref = None;
let mut match_name_mut = None;
for arg in base
.attrs
.iter()
.filter(|attr| attr.path.is_ident("model"))
.map(|attr| attr.parse_args::<MetaNameValue>().unwrap())
.filter_map(|attr| attr.parse_args::<MetaNameValue>().ok())
{
match arg {
MetaNameValue {
@@ -250,6 +419,11 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
lit: Lit::Str(s),
..
} if path.is_ident("match") => match_name = Some(s.parse().unwrap()),
MetaNameValue {
path,
lit: Lit::Str(s),
..
} if path.is_ident("match_ref") => match_name_ref = Some(s.parse().unwrap()),
MetaNameValue {
path,
lit: Lit::Str(s),
@@ -260,6 +434,8 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
}
let match_name = match_name
.unwrap_or_else(|| Ident::new(&format!("{}MatchModel", base.ident), Span::call_site()));
let match_name_ref = match_name_ref
.unwrap_or_else(|| Ident::new(&format!("{}MatchModelRef", base.ident), Span::call_site()));
let match_name_mut = match_name_mut
.unwrap_or_else(|| Ident::new(&format!("{}MatchModelMut", base.ident), Span::call_site()));
let serde_rename_all = base
@@ -304,10 +480,15 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
})
.collect();
let mut model_variants = TokenStream::new();
let mut ref_model_variants = TokenStream::new();
let mut mut_model_variants = TokenStream::new();
let (impl_new, impl_new_mut) = if let Some(Lit::Str(tag)) = serde_tag.remove("tag") {
let (impl_new, impl_unmatch, impl_new_ref, impl_new_mut) = if let Some(Lit::Str(tag)) =
serde_tag.remove("tag")
{
if let Some(Lit::Str(content)) = serde_tag.remove("content") {
let mut tag_variants = TokenStream::new();
let mut tag_variants_unmatch = TokenStream::new();
let mut tag_variants_ref = TokenStream::new();
let mut tag_variants_mut = TokenStream::new();
for variant in &ast.variants {
let variant_name = &variant.ident;
@@ -320,8 +501,22 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
#content.index_into_owned(v).unwrap_or_default()
})),
});
tag_variants_unmatch.extend(quote_spanned! { variant_name.span() =>
#match_name::#variant_name(v) => Self::from_value({
let mut a = patch_db::value::json!({ #tag: #variant_accessor });
a[#content] = v.into_value();
a
}),
});
tag_variants_ref.extend(quote_spanned! { variant_name.span() =>
Some(#variant_accessor) => #match_name_ref::#variant_name(self.transmute_ref(|v| {
#[allow(unused_imports)]
use patch_db::value::index::Index;
#content.index_into(v).unwrap_or(&patch_db::value::NULL)
})),
});
tag_variants_mut.extend(quote_spanned! { variant_name.span() =>
Some(#variant_accessor) => #match_name_mut::#variant_name(self.transmute(|v| {
Some(#variant_accessor) => #match_name_mut::#variant_name(self.transmute_mut(|v| {
#[allow(unused_imports)]
use patch_db::value::index::Index;
#content.index_or_insert(v)
@@ -330,13 +525,29 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
}
(
quote! {
match self[#tag].as_str() {
use patch_db::ModelExt;
match self.as_value()[#tag].as_str() {
#tag_variants
_ => #match_name::Error(self.into_inner()),
}
},
quote! {
match self[#tag].as_str() {
use patch_db::ModelExt;
match value {
#tag_variants_unmatch
#match_name::Error(v) => Self::from_value(v),
}
},
quote! {
use patch_db::ModelExt;
match self.as_value()[#tag].as_str() {
#tag_variants_ref
_ => #match_name_ref::Error(&*self),
}
},
quote! {
use patch_db::ModelExt;
match self.as_value()[#tag].as_str() {
#tag_variants_mut
_ => #match_name_mut::Error(&mut *self),
}
@@ -344,6 +555,8 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
)
} else {
let mut tag_variants = TokenStream::new();
let mut tag_variants_unmatch = TokenStream::new();
let mut tag_variants_ref = TokenStream::new();
let mut tag_variants_mut = TokenStream::new();
for variant in &ast.variants {
let variant_name = &variant.ident;
@@ -356,8 +569,22 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
#variant_accessor.index_into_owned(v).unwrap_or_default()
})),
});
tag_variants_unmatch.extend(quote_spanned! { variant_name.span() =>
#match_name::#variant_name(v) => Self::from_value({
let mut a = v.into_value();
a[#tag] = patch_db::Value::String(std::sync::Arc::new(#variant_accessor.to_owned()));
a
}),
});
tag_variants_ref.extend(quote_spanned! { variant_name.span() =>
Some(#variant_accessor) => #match_name_ref::#variant_name(self.transmute_ref(|v| {
#[allow(unused_imports)]
use patch_db::value::index::Index;
#variant_accessor.index_into(v).unwrap_or(&patch_db::value::NULL)
})),
});
tag_variants_mut.extend(quote_spanned! { variant_name.span() =>
Some(#variant_accessor) => #match_name_mut::#variant_name(self.transmute(|v| {
Some(#variant_accessor) => #match_name_mut::#variant_name(self.transmute_mut(|v| {
#[allow(unused_imports)]
use patch_db::value::index::Index;
#variant_accessor.index_or_insert(v)
@@ -366,21 +593,39 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
}
(
quote! {
match self[#tag].as_str() {
use patch_db::ModelExt;
match self.as_value()[#tag].as_str() {
#tag_variants
_ => #match_name::Error(self.into_inner()),
_ => #match_name::Error(self.into_value()),
}
},
quote! {
match self[#tag].as_str() {
use patch_db::ModelExt;
match value {
#tag_variants_unmatch
#match_name::Error(v) => Self::from_value(v),
}
},
quote! {
use patch_db::ModelExt;
match self.as_value()[#tag].as_str() {
#tag_variants_ref
_ => #match_name_ref::Error(self.as_value()),
}
},
quote! {
use patch_db::ModelExt;
match self.as_value()[#tag].as_str() {
#tag_variants_mut
_ => #match_name_mut::Error(&mut *self),
_ => #match_name_mut::Error(self.as_value_mut()),
}
},
)
}
} else {
let mut tag_variants = TokenStream::new();
let mut tag_variants_unmatch = TokenStream::new();
let mut tag_variants_ref = TokenStream::new();
let mut tag_variants_mut = TokenStream::new();
for variant in &ast.variants {
let variant_name = &variant.ident;
@@ -394,9 +639,25 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
}))
} else
});
tag_variants_unmatch.extend(quote_spanned! { variant_name.span() =>
#match_name::#variant_name(v) => Self::from_value({
let mut a = patch_db::value::json!({});
a[#variant_accessor] = v.into_value();
a
}),
});
tag_variants_ref.extend(quote_spanned! { variant_name.span() =>
if value.as_object().map(|o| o.contains_key(#variant_accessor)).unwrap_or(false) {
#match_name_ref::#variant_name(self.transmute_ref(|v| {
#[allow(unused_imports)]
use patch_db::value::index::Index;
#variant_accessor.index_into(v).unwrap_or(&patch_db::value::NULL)
}))
} else
});
tag_variants_mut.extend(quote_spanned! { variant_name.span() =>
if value.as_object().map(|o| o.contains_key(#variant_accessor)).unwrap_or(false) {
#match_name_mut::#variant_name(self.transmute(|v| {
#match_name_mut::#variant_name(self.transmute_mut(|v| {
#[allow(unused_imports)]
use patch_db::value::index::Index;
#variant_accessor.index_or_insert(v)
@@ -406,11 +667,26 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
}
(
quote! {
use patch_db::ModelExt;
#tag_variants {
#match_name::Error(self.into_inner()),
}
},
quote! {
use patch_db::ModelExt;
match value {
#tag_variants_unmatch
#match_name::Error(v) => Self::from_value(v),
}
},
quote! {
use patch_db::ModelExt;
#tag_variants_ref {
#match_name_ref::Error(&*self),
}
},
quote! {
use patch_db::ModelExt;
#tag_variants_mut {
#match_name_mut::Error(&mut *self),
}
@@ -429,22 +705,31 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
.into_compile_error()
}
};
let model_variant_ty = replace_self(model_ty.clone(), &ty);
model_variants.extend(quote_spanned! { name.span() =>
#name(patch_db::Model<#ty>),
#name(#model_variant_ty),
});
ref_model_variants.extend(quote_spanned! { name.span() =>
#name(&'a #model_variant_ty),
});
mut_model_variants.extend(quote_spanned! { name.span() =>
#name(&'a mut patch_db::Model<#ty>),
#name(&'a mut #model_variant_ty),
});
}
let name = &base.ident;
let vis = &base.vis;
quote! {
impl patch_db::HasModel for #name {}
impl patch_db::HasModel for #name {
type Model = #model_ty;
}
impl patch_db::Model<#name> {
#vis fn matchable(&self) -> #match_name {
impl #model_ty_name {
#vis fn into_match(self) -> #match_name {
#impl_new
}
#vis fn from_match(value: #match_name) -> Self {
#impl_unmatch
}
}
#vis enum #match_name {
@@ -452,8 +737,19 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
Error(patch_db::Value),
}
impl patch_db::Model<#name> {
#vis fn matchable<'a>(&'a self) -> #match_name_mut<'a> {
impl #model_ty_name {
#vis fn as_match<'a>(&'a self) -> #match_name_ref<'a> {
#impl_new_ref
}
}
#vis enum #match_name_ref<'a> {
#ref_model_variants
Error(&'a patch_db::Value),
}
impl #model_ty_name {
#vis fn as_match_mut<'a>(&'a mut self) -> #match_name_mut<'a> {
#impl_new_mut
}
}