diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-06-06 21:13:59 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-06-06 22:06:16 +0200 |
| commit | fd417da04f7ca4b995de7f6510abafd3e9c31307 (patch) | |
| tree | 3675529c75ca7363701ac8ea306de2cc1d3cbcb3 /macros/src | |
| parent | 168bdf35bd773e67343c965cb473492cc5cae9e7 (diff) | |
Improve value casting infrastructure
Diffstat (limited to 'macros/src')
| -rw-r--r-- | macros/src/castable.rs | 273 | ||||
| -rw-r--r-- | macros/src/element.rs | 24 | ||||
| -rw-r--r-- | macros/src/func.rs | 178 | ||||
| -rw-r--r-- | macros/src/lib.rs | 32 | ||||
| -rw-r--r-- | macros/src/util.rs | 40 |
5 files changed, 320 insertions, 227 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)) } } diff --git a/macros/src/element.rs b/macros/src/element.rs index d5882666..6ce91fcb 100644 --- a/macros/src/element.rs +++ b/macros/src/element.rs @@ -1,8 +1,8 @@ use super::*; /// Expand the `#[element]` macro. -pub fn element(stream: TokenStream, body: syn::ItemStruct) -> Result<TokenStream> { - let element = prepare(stream, &body)?; +pub fn element(stream: TokenStream, body: &syn::ItemStruct) -> Result<TokenStream> { + let element = prepare(stream, body)?; Ok(create(&element)) } @@ -207,9 +207,9 @@ fn create(element: &Elem) -> TokenStream { #set_impl #locatable_impl - impl From<#ident> for ::typst::eval::Value { - fn from(value: #ident) -> Self { - value.0.into() + impl ::typst::eval::IntoValue for #ident { + fn into_value(self) -> ::typst::eval::Value { + ::typst::eval::Value::Content(self.0) } } } @@ -326,8 +326,8 @@ fn create_set_field_method(field: &Field) -> TokenStream { #vis fn #set_ident(#ident: #ty) -> ::typst::model::Style { ::typst::model::Style::Property(::typst::model::Property::new( <Self as ::typst::model::Element>::func(), - #name.into(), - #ident.into() + #name, + #ident, )) } } @@ -369,7 +369,9 @@ fn create_pack_impl(element: &Elem) -> TokenStream { keywords: #keywords, docs: #docs, params: ::std::vec![#(#infos),*], - returns: ::std::vec!["content"], + returns: ::typst::eval::CastInfo::Union(::std::vec![ + ::typst::eval::CastInfo::Type("content") + ]), category: #category, scope: #scope, }), @@ -426,7 +428,7 @@ fn create_param_info(field: &Field) -> TokenStream { quote! { || { let typed: #default_ty = #default; - ::typst::eval::Value::from(typed) + ::typst::eval::IntoValue::into_value(typed) } } })); @@ -439,9 +441,7 @@ fn create_param_info(field: &Field) -> TokenStream { ::typst::eval::ParamInfo { name: #name, docs: #docs, - cast: <#ty as ::typst::eval::Cast< - ::typst::syntax::Spanned<::typst::eval::Value> - >>::describe(), + cast: <#ty as ::typst::eval::Reflect>::describe(), default: #default, positional: #positional, named: #named, diff --git a/macros/src/func.rs b/macros/src/func.rs index 2e63ee75..4a68e846 100644 --- a/macros/src/func.rs +++ b/macros/src/func.rs @@ -1,11 +1,9 @@ -use quote::ToTokens; - use super::*; /// Expand the `#[func]` macro. -pub fn func(item: syn::ItemFn) -> Result<TokenStream> { - let func = prepare(&item)?; - Ok(create(&func)) +pub fn func(stream: TokenStream, item: &syn::ItemFn) -> Result<TokenStream> { + let func = prepare(stream, item)?; + Ok(create(&func, item)) } struct Func { @@ -16,9 +14,14 @@ struct Func { docs: String, vis: syn::Visibility, ident: Ident, + ident_func: Ident, + parent: Option<syn::Type>, + vm: bool, + vt: bool, + args: bool, + span: bool, params: Vec<Param>, - returns: Vec<String>, - body: syn::Block, + returns: syn::Type, scope: Option<BlockWithReturn>, } @@ -33,9 +36,15 @@ struct Param { ty: syn::Type, } -fn prepare(item: &syn::ItemFn) -> Result<Func> { +fn prepare(stream: TokenStream, item: &syn::ItemFn) -> Result<Func> { let sig = &item.sig; + let Parent(parent) = syn::parse2(stream)?; + + let mut vm = false; + let mut vt = false; + let mut args = false; + let mut span = false; let mut params = vec![]; for input in &sig.inputs { let syn::FnArg::Typed(typed) = input else { @@ -51,99 +60,148 @@ fn prepare(item: &syn::ItemFn) -> Result<Func> { bail!(typed.pat, "expected identifier"); }; - if sig.output.to_token_stream().to_string() != "-> Value" { - bail!(sig.output, "must return `Value`"); - } + match ident.to_string().as_str() { + "vm" => vm = true, + "vt" => vt = true, + "args" => args = true, + "span" => span = true, + _ => { + let mut attrs = typed.attrs.clone(); + params.push(Param { + name: kebab_case(ident), + docs: documentation(&attrs), + external: has_attr(&mut attrs, "external"), + named: has_attr(&mut attrs, "named"), + variadic: has_attr(&mut attrs, "variadic"), + default: parse_attr(&mut attrs, "default")?.map(|expr| { + expr.unwrap_or_else( + || parse_quote! { ::std::default::Default::default() }, + ) + }), + ident: ident.clone(), + ty: (*typed.ty).clone(), + }); - let mut attrs = typed.attrs.clone(); - params.push(Param { - name: kebab_case(ident), - docs: documentation(&attrs), - external: has_attr(&mut attrs, "external"), - named: has_attr(&mut attrs, "named"), - variadic: has_attr(&mut attrs, "variadic"), - default: parse_attr(&mut attrs, "default")?.map(|expr| { - expr.unwrap_or_else( - || parse_quote! { ::std::default::Default::default() }, - ) - }), - ident: ident.clone(), - ty: (*typed.ty).clone(), - }); - - validate_attrs(&attrs)?; + validate_attrs(&attrs)?; + } + } } let mut attrs = item.attrs.clone(); let docs = documentation(&attrs); let mut lines = docs.split('\n').collect(); - let returns = meta_line(&mut lines, "Returns")? - .split(" or ") - .map(Into::into) - .collect(); let keywords = meta_line(&mut lines, "Keywords").ok().map(Into::into); let category = meta_line(&mut lines, "Category")?.into(); let display = meta_line(&mut lines, "Display")?.into(); let docs = lines.join("\n").trim().into(); let func = Func { - name: sig.ident.to_string().replace('_', ""), + name: sig.ident.to_string().trim_end_matches('_').replace('_', "-"), display, category, keywords, docs, vis: item.vis.clone(), ident: sig.ident.clone(), + ident_func: Ident::new( + &format!("{}_func", sig.ident.to_string().trim_end_matches('_')), + sig.ident.span(), + ), + parent, params, - returns, - body: (*item.block).clone(), + returns: match &sig.output { + syn::ReturnType::Default => parse_quote! { () }, + syn::ReturnType::Type(_, ty) => ty.as_ref().clone(), + }, scope: parse_attr(&mut attrs, "scope")?.flatten(), + vm, + vt, + args, + span, }; - validate_attrs(&attrs)?; Ok(func) } -fn create(func: &Func) -> TokenStream { +fn create(func: &Func, item: &syn::ItemFn) -> TokenStream { let Func { name, display, - keywords, category, docs, vis, ident, - params, + ident_func, returns, - body, .. } = func; - let handlers = params.iter().filter(|param| !param.external).map(create_param_parser); - let params = params.iter().map(create_param_info); + + let handlers = func + .params + .iter() + .filter(|param| !param.external) + .map(create_param_parser); + + let args = func + .params + .iter() + .filter(|param| !param.external) + .map(|param| ¶m.ident); + + let parent = func.parent.as_ref().map(|ty| quote! { #ty:: }); + let vm_ = func.vm.then(|| quote! { vm, }); + let vt_ = func.vt.then(|| quote! { &mut vm.vt, }); + let args_ = func.args.then(|| quote! { args.take(), }); + let span_ = func.span.then(|| quote! { args.span, }); + let wrapper = quote! { + |vm, args| { + let __typst_func = #parent #ident; + #(#handlers)* + let output = __typst_func(#(#args,)* #vm_ #vt_ #args_ #span_); + ::typst::eval::IntoResult::into_result(output, args.span) + } + }; + + let mut item = item.clone(); + item.attrs.clear(); + + let inputs = item.sig.inputs.iter().cloned().filter_map(|mut input| { + if let syn::FnArg::Typed(typed) = &mut input { + if typed.attrs.iter().any(|attr| attr.path().is_ident("external")) { + return None; + } + typed.attrs.clear(); + } + Some(input) + }); + + item.sig.inputs = parse_quote! { #(#inputs),* }; + + let keywords = quote_option(&func.keywords); + let params = func.params.iter().map(create_param_info); let scope = create_scope_builder(func.scope.as_ref()); - let keywords = quote_option(keywords); + quote! { - #[doc = #docs] - #vis fn #ident() -> &'static ::typst::eval::NativeFunc { + #[doc(hidden)] + #vis fn #ident_func() -> &'static ::typst::eval::NativeFunc { static FUNC: ::typst::eval::NativeFunc = ::typst::eval::NativeFunc { - func: |vm, args| { - #(#handlers)* - #[allow(unreachable_code)] - Ok(#body) - }, + func: #wrapper, info: ::typst::eval::Lazy::new(|| typst::eval::FuncInfo { name: #name, display: #display, keywords: #keywords, + category: #category, docs: #docs, params: ::std::vec![#(#params),*], - returns: ::std::vec![#(#returns),*], - category: #category, + returns: <#returns as ::typst::eval::Reflect>::describe(), scope: #scope, }), }; &FUNC } + + #[doc = #docs] + #item } } @@ -156,7 +214,7 @@ fn create_param_info(param: &Param) -> TokenStream { quote! { || { let typed: #ty = #default; - ::typst::eval::Value::from(typed) + ::typst::eval::IntoValue::into_value(typed) } } })); @@ -169,9 +227,7 @@ fn create_param_info(param: &Param) -> TokenStream { ::typst::eval::ParamInfo { name: #name, docs: #docs, - cast: <#ty as ::typst::eval::Cast< - ::typst::syntax::Spanned<::typst::eval::Value> - >>::describe(), + cast: <#ty as ::typst::eval::Reflect>::describe(), default: #default, positional: #positional, named: #named, @@ -200,5 +256,13 @@ fn create_param_parser(param: &Param) -> TokenStream { value = quote! { #value.unwrap_or_else(|| #default) } } - quote! { let #ident: #ty = #value; } + quote! { let mut #ident: #ty = #value; } +} + +struct Parent(Option<syn::Type>); + +impl Parse for Parent { + fn parse(input: ParseStream) -> Result<Self> { + Ok(Self(if !input.is_empty() { Some(input.parse()?) } else { None })) + } } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 945bbcd0..49840ef2 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -21,46 +21,40 @@ use self::util::*; /// Turns a function into a `NativeFunc`. #[proc_macro_attribute] -pub fn func(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream { +pub fn func(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { let item = syn::parse_macro_input!(item as syn::ItemFn); - func::func(item).unwrap_or_else(|err| err.to_compile_error()).into() + func::func(stream.into(), &item) + .unwrap_or_else(|err| err.to_compile_error()) + .into() } -/// Turns a struct into an element. +/// Turns a type into an `Element`. #[proc_macro_attribute] pub fn element(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { let item = syn::parse_macro_input!(item as syn::ItemStruct); - element::element(stream.into(), item) + element::element(stream.into(), &item) .unwrap_or_else(|err| err.to_compile_error()) .into() } -/// Implement `Cast` for an enum. +/// Implements `Reflect`, `FromValue`, and `IntoValue` for an enum. #[proc_macro_derive(Cast, attributes(string))] -pub fn cast(item: BoundaryStream) -> BoundaryStream { +pub fn derive_cast(item: BoundaryStream) -> BoundaryStream { let item = syn::parse_macro_input!(item as DeriveInput); - castable::cast(item) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} - -/// Implement `Cast` and optionally `Type` for a type. -#[proc_macro] -pub fn cast_from_value(stream: BoundaryStream) -> BoundaryStream { - castable::cast_from_value(stream.into()) + castable::derive_cast(&item) .unwrap_or_else(|err| err.to_compile_error()) .into() } -/// Implement `From<T> for Value` for a type `T`. +/// Implements `Reflect`, `FromValue`, and `IntoValue` for a type. #[proc_macro] -pub fn cast_to_value(stream: BoundaryStream) -> BoundaryStream { - castable::cast_to_value(stream.into()) +pub fn cast(stream: BoundaryStream) -> BoundaryStream { + castable::cast(stream.into()) .unwrap_or_else(|err| err.to_compile_error()) .into() } -/// Define a list of symbols. +/// Defines a list of `Symbol`s. #[proc_macro] pub fn symbols(stream: BoundaryStream) -> BoundaryStream { symbols::symbols(stream.into()) diff --git a/macros/src/util.rs b/macros/src/util.rs index 2e12ef17..389fed06 100644 --- a/macros/src/util.rs +++ b/macros/src/util.rs @@ -5,16 +5,16 @@ use super::*; /// Return an error at the given item. macro_rules! bail { - (callsite, $fmt:literal $($tts:tt)*) => { + (callsite, $($tts:tt)*) => { return Err(syn::Error::new( proc_macro2::Span::call_site(), - format!(concat!("typst: ", $fmt) $($tts)*) + format!("typst: {}", format!($($tts)*)) )) }; - ($item:expr, $fmt:literal $($tts:tt)*) => { + ($item:expr, $($tts:tt)*) => { return Err(syn::Error::new_spanned( &$item, - format!(concat!("typst: ", $fmt) $($tts)*) + format!("typst: {}", format!($($tts)*)) )) }; } @@ -51,7 +51,13 @@ pub fn parse_attr<T: Parse>( target: &str, ) -> Result<Option<Option<T>>> { take_attr(attrs, target) - .map(|attr| (!attr.tokens.is_empty()).then(|| attr.parse_args()).transpose()) + .map(|attr| { + Ok(match attr.meta { + syn::Meta::Path(_) => None, + syn::Meta::List(list) => Some(list.parse_args()?), + syn::Meta::NameValue(meta) => bail!(meta, "not valid here"), + }) + }) .transpose() } @@ -62,16 +68,16 @@ pub fn take_attr( ) -> Option<syn::Attribute> { attrs .iter() - .position(|attr| attr.path.is_ident(target)) + .position(|attr| attr.path().is_ident(target)) .map(|i| attrs.remove(i)) } /// Ensure that no unrecognized attributes remain. pub fn validate_attrs(attrs: &[syn::Attribute]) -> Result<()> { for attr in attrs { - if !attr.path.is_ident("doc") { - let ident = attr.path.get_ident().unwrap(); - bail!(ident, "unrecognized attribute: {:?}", ident.to_string()); + if !attr.path().is_ident("doc") && !attr.path().is_ident("derive") { + let ident = attr.path().get_ident().unwrap(); + bail!(ident, "unrecognized attribute: {ident}"); } } Ok(()) @@ -88,13 +94,15 @@ pub fn documentation(attrs: &[syn::Attribute]) -> String { // Parse doc comments. for attr in attrs { - if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() { + if let syn::Meta::NameValue(meta) = &attr.meta { if meta.path.is_ident("doc") { - if let syn::Lit::Str(string) = &meta.lit { - let full = string.value(); - let line = full.strip_prefix(' ').unwrap_or(&full); - doc.push_str(line); - doc.push('\n'); + if let syn::Expr::Lit(lit) = &meta.value { + if let syn::Lit::Str(string) = &lit.lit { + let full = string.value(); + let line = full.strip_prefix(' ').unwrap_or(&full); + doc.push_str(line); + doc.push('\n'); + } } } } @@ -110,7 +118,7 @@ pub fn meta_line<'a>(lines: &mut Vec<&'a str>, key: &str) -> Result<&'a str> { lines.pop(); Ok(value.trim()) } - None => bail!(callsite, "missing metadata key: {}", key), + None => bail!(callsite, "missing metadata key: {key}"), } } |
