summaryrefslogtreecommitdiff
path: root/macros
diff options
context:
space:
mode:
Diffstat (limited to 'macros')
-rw-r--r--macros/Cargo.toml2
-rw-r--r--macros/src/castable.rs273
-rw-r--r--macros/src/element.rs24
-rw-r--r--macros/src/func.rs178
-rw-r--r--macros/src/lib.rs32
-rw-r--r--macros/src/util.rs40
6 files changed, 321 insertions, 228 deletions
diff --git a/macros/Cargo.toml b/macros/Cargo.toml
index 179666d8..15f2b2bd 100644
--- a/macros/Cargo.toml
+++ b/macros/Cargo.toml
@@ -19,4 +19,4 @@ bench = false
heck = "0.4"
proc-macro2 = "1"
quote = "1"
-syn = { version = "1", features = ["full", "extra-traits"] }
+syn = { version = "2", features = ["full", "extra-traits"] }
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| &param.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}"),
}
}