diff options
Diffstat (limited to 'macros/src/castable.rs')
| -rw-r--r-- | macros/src/castable.rs | 273 |
1 files changed, 150 insertions, 123 deletions
diff --git a/macros/src/castable.rs b/macros/src/castable.rs index cd05ed2d..05c1b4d1 100644 --- a/macros/src/castable.rs +++ b/macros/src/castable.rs @@ -1,7 +1,7 @@ use super::*; /// Expand the `#[derive(Cast)]` macro. -pub fn cast(item: DeriveInput) -> Result<TokenStream> { +pub fn derive_cast(item: &DeriveInput) -> Result<TokenStream> { let ty = &item.ident; let syn::Data::Enum(data) = &item.data else { @@ -15,7 +15,7 @@ pub fn cast(item: DeriveInput) -> Result<TokenStream> { } let string = if let Some(attr) = - variant.attrs.iter().find(|attr| attr.path.is_ident("string")) + variant.attrs.iter().find(|attr| attr.path().is_ident("string")) { attr.parse_args::<syn::LitStr>()?.value() } else { @@ -43,107 +43,117 @@ pub fn cast(item: DeriveInput) -> Result<TokenStream> { }); Ok(quote! { - ::typst::eval::cast_from_value! { + ::typst::eval::cast! { #ty, - #(#strs_to_variants),* - } - - ::typst::eval::cast_to_value! { - v: #ty => ::typst::eval::Value::from(match v { + self => ::typst::eval::IntoValue::into_value(match self { #(#variants_to_strs),* - }) + }), + #(#strs_to_variants),* } }) } +/// An enum variant in a `derive(Cast)`. struct Variant { ident: Ident, string: String, docs: String, } -/// Expand the `cast_from_value!` macro. -pub fn cast_from_value(stream: TokenStream) -> Result<TokenStream> { - let castable: Castable = syn::parse2(stream)?; - let ty = &castable.ty; +/// Expand the `cast!` macro. +pub fn cast(stream: TokenStream) -> Result<TokenStream> { + let input: CastInput = syn::parse2(stream)?; + let ty = &input.ty; + let eval = quote! { ::typst::eval }; - if castable.casts.is_empty() && castable.name.is_none() { - bail!(castable.ty, "expected at least one pattern"); - } + let castable_body = create_castable_body(&input); + let describe_body = create_describe_body(&input); + let into_value_body = create_into_value_body(&input); + let from_value_body = create_from_value_body(&input); - let is_func = create_is_func(&castable); - let cast_func = create_cast_func(&castable); - let describe_func = create_describe_func(&castable); - let dynamic_impls = castable.name.as_ref().map(|name| { + let reflect = (!input.from_value.is_empty() || input.name.is_some()).then(|| { quote! { - impl ::typst::eval::Type for #ty { - const TYPE_NAME: &'static str = #name; - } + impl #eval::Reflect for #ty { + fn describe() -> #eval::CastInfo { + #describe_body + } - impl From<#ty> for ::typst::eval::Value { - fn from(v: #ty) -> Self { - ::typst::eval::Value::Dyn(::typst::eval::Dynamic::new(v)) + fn castable(value: &#eval::Value) -> bool { + #castable_body } } } }); - Ok(quote! { - impl ::typst::eval::Cast for #ty { - #is_func - #cast_func - #describe_func + let into_value = (input.into_value.is_some() || input.name.is_some()).then(|| { + quote! { + impl #eval::IntoValue for #ty { + fn into_value(self) -> #eval::Value { + #into_value_body + } + } } + }); - #dynamic_impls - }) -} - -/// Expand the `cast_to_value!` macro. -pub fn cast_to_value(stream: TokenStream) -> Result<TokenStream> { - let cast: Cast = syn::parse2(stream)?; - let Pattern::Ty(pat, ty) = &cast.pattern else { - bail!(callsite, "expected pattern"); - }; + let from_value = (!input.from_value.is_empty() || input.name.is_some()).then(|| { + quote! { + impl #eval::FromValue for #ty { + fn from_value(value: #eval::Value) -> ::typst::diag::StrResult<Self> { + #from_value_body + } + } + } + }); - let expr = &cast.expr; - Ok(quote! { - impl ::std::convert::From<#ty> for ::typst::eval::Value { - fn from(#pat: #ty) -> Self { - #expr + let ty = input.name.as_ref().map(|name| { + quote! { + impl #eval::Type for #ty { + const TYPE_NAME: &'static str = #name; } } + }); + + Ok(quote! { + #reflect + #into_value + #from_value + #ty }) } -struct Castable { +/// The input to `cast!`. +struct CastInput { ty: syn::Type, name: Option<syn::LitStr>, - casts: Punctuated<Cast, Token![,]>, -} - -struct Cast { - attrs: Vec<syn::Attribute>, - pattern: Pattern, - expr: syn::Expr, + into_value: Option<syn::Expr>, + from_value: Punctuated<Cast, Token![,]>, } -enum Pattern { - Str(syn::LitStr), - Ty(syn::Pat, syn::Type), -} - -impl Parse for Castable { +impl Parse for CastInput { fn parse(input: ParseStream) -> Result<Self> { - let ty = input.parse()?; + let ty; let mut name = None; - if input.peek(Token![:]) { + if input.peek(syn::Token![type]) { + let _: syn::Token![type] = input.parse()?; + ty = input.parse()?; let _: syn::Token![:] = input.parse()?; name = Some(input.parse()?); + } else { + ty = input.parse()?; } + let _: syn::Token![,] = input.parse()?; - let casts = Punctuated::parse_terminated(input)?; - Ok(Self { ty, name, casts }) + + let mut into_value = None; + if input.peek(syn::Token![self]) { + let _: syn::Token![self] = input.parse()?; + let _: syn::Token![=>] = input.parse()?; + into_value = Some(input.parse()?); + let _: syn::Token![,] = input.parse()?; + } + + let from_value = Punctuated::parse_terminated(input)?; + Ok(Self { ty, name, into_value, from_value }) } } @@ -162,7 +172,7 @@ impl Parse for Pattern { if input.peek(syn::LitStr) { Ok(Pattern::Str(input.parse()?)) } else { - let pat = input.parse()?; + let pat = syn::Pat::parse_single(input)?; let _: syn::Token![:] = input.parse()?; let ty = input.parse()?; Ok(Pattern::Ty(pat, ty)) @@ -170,19 +180,31 @@ impl Parse for Pattern { } } -/// Create the castable's `is` function. -fn create_is_func(castable: &Castable) -> TokenStream { - let mut string_arms = vec![]; - let mut cast_checks = vec![]; +/// A single cast, e.g. `v: i64 => Self::Int(v)`. +struct Cast { + attrs: Vec<syn::Attribute>, + pattern: Pattern, + expr: syn::Expr, +} - for cast in &castable.casts { +/// A pattern in a cast, e.g.`"ascender"` or `v: i64`. +enum Pattern { + Str(syn::LitStr), + Ty(syn::Pat, syn::Type), +} + +fn create_castable_body(input: &CastInput) -> TokenStream { + let mut strings = vec![]; + let mut casts = vec![]; + + for cast in &input.from_value { match &cast.pattern { Pattern::Str(lit) => { - string_arms.push(quote! { #lit => return true }); + strings.push(quote! { #lit => return true }); } Pattern::Ty(_, ty) => { - cast_checks.push(quote! { - if <#ty as ::typst::eval::Cast>::is(value) { + casts.push(quote! { + if <#ty as ::typst::eval::Reflect>::castable(value) { return true; } }); @@ -190,7 +212,7 @@ fn create_is_func(castable: &Castable) -> TokenStream { } } - let dynamic_check = castable.name.is_some().then(|| { + let dynamic_check = input.name.is_some().then(|| { quote! { if let ::typst::eval::Value::Dyn(dynamic) = &value { if dynamic.is::<Self>() { @@ -200,11 +222,11 @@ fn create_is_func(castable: &Castable) -> TokenStream { } }); - let str_check = (!string_arms.is_empty()).then(|| { + let str_check = (!strings.is_empty()).then(|| { quote! { if let ::typst::eval::Value::Str(string) = &value { match string.as_str() { - #(#string_arms,)* + #(#strings,)* _ => {} } } @@ -212,21 +234,57 @@ fn create_is_func(castable: &Castable) -> TokenStream { }); quote! { - fn is(value: &::typst::eval::Value) -> bool { - #dynamic_check - #str_check - #(#cast_checks)* - false - } + #dynamic_check + #str_check + #(#casts)* + false } } -/// Create the castable's `cast` function. -fn create_cast_func(castable: &Castable) -> TokenStream { +fn create_describe_body(input: &CastInput) -> TokenStream { + let mut infos = vec![]; + + for cast in &input.from_value { + let docs = documentation(&cast.attrs); + infos.push(match &cast.pattern { + Pattern::Str(lit) => { + quote! { + ::typst::eval::CastInfo::Value( + ::typst::eval::IntoValue::into_value(#lit), + #docs, + ) + } + } + Pattern::Ty(_, ty) => { + quote! { <#ty as ::typst::eval::Reflect>::describe() } + } + }); + } + + if let Some(name) = &input.name { + infos.push(quote! { + ::typst::eval::CastInfo::Type(#name) + }); + } + + quote! { + #(#infos)+* + } +} + +fn create_into_value_body(input: &CastInput) -> TokenStream { + if let Some(expr) = &input.into_value { + quote! { #expr } + } else { + quote! { ::typst::eval::Value::dynamic(self) } + } +} + +fn create_from_value_body(input: &CastInput) -> TokenStream { let mut string_arms = vec![]; let mut cast_checks = vec![]; - for cast in &castable.casts { + for cast in &input.from_value { let expr = &cast.expr; match &cast.pattern { Pattern::Str(lit) => { @@ -234,8 +292,8 @@ fn create_cast_func(castable: &Castable) -> TokenStream { } Pattern::Ty(binding, ty) => { cast_checks.push(quote! { - if <#ty as ::typst::eval::Cast>::is(&value) { - let #binding = <#ty as ::typst::eval::Cast>::cast(value)?; + if <#ty as ::typst::eval::Reflect>::castable(&value) { + let #binding = <#ty as ::typst::eval::FromValue>::from_value(value)?; return Ok(#expr); } }); @@ -243,7 +301,7 @@ fn create_cast_func(castable: &Castable) -> TokenStream { } } - let dynamic_check = castable.name.is_some().then(|| { + let dynamic_check = input.name.is_some().then(|| { quote! { if let ::typst::eval::Value::Dyn(dynamic) = &value { if let Some(concrete) = dynamic.downcast::<Self>() { @@ -265,40 +323,9 @@ fn create_cast_func(castable: &Castable) -> TokenStream { }); quote! { - fn cast(value: ::typst::eval::Value) -> ::typst::diag::StrResult<Self> { - #dynamic_check - #str_check - #(#cast_checks)* - <Self as ::typst::eval::Cast>::error(value) - } - } -} - -/// Create the castable's `describe` function. -fn create_describe_func(castable: &Castable) -> TokenStream { - let mut infos = vec![]; - - for cast in &castable.casts { - let docs = documentation(&cast.attrs); - infos.push(match &cast.pattern { - Pattern::Str(lit) => { - quote! { ::typst::eval::CastInfo::Value(#lit.into(), #docs) } - } - Pattern::Ty(_, ty) => { - quote! { <#ty as ::typst::eval::Cast>::describe() } - } - }); - } - - if let Some(name) = &castable.name { - infos.push(quote! { - ::typst::eval::CastInfo::Type(#name) - }); - } - - quote! { - fn describe() -> ::typst::eval::CastInfo { - #(#infos)+* - } + #dynamic_check + #str_check + #(#cast_checks)* + Err(<Self as ::typst::eval::Reflect>::error(&value)) } } |
