bon_macros/parsing/
mod.rs1mod docs;
2mod item_sig;
3mod simple_closure;
4mod spanned_key;
5
6pub(crate) use docs::*;
7pub(crate) use item_sig::*;
8pub(crate) use simple_closure::*;
9pub(crate) use spanned_key::*;
10
11use crate::util::prelude::*;
12use darling::FromMeta;
13use syn::parse::Parser;
14use syn::punctuated::Punctuated;
15use syn::spanned::Spanned;
16
17pub(crate) fn parse_non_empty_paren_meta_list<T: FromMeta>(meta: &syn::Meta) -> Result<T> {
18 require_non_empty_paren_meta_list_or_name_value(meta)?;
19 T::from_meta(meta)
20}
21
22pub(crate) fn require_non_empty_paren_meta_list_or_name_value(meta: &syn::Meta) -> Result {
23 match meta {
24 syn::Meta::List(meta) => {
25 meta.require_parens_delim()?;
26
27 if meta.tokens.is_empty() {
28 bail!(
29 &meta.delimiter.span().join(),
30 "expected parameters in parentheses"
31 );
32 }
33 }
34 syn::Meta::Path(path) => bail!(
35 &meta,
36 "this empty `{0}` attribute is unexpected; \
37 remove it or pass parameters in parentheses: \
38 `{0}(...)`",
39 darling::util::path_to_string(path)
40 ),
41 syn::Meta::NameValue(_) => {}
42 }
43
44 Ok(())
45}
46
47#[allow(dead_code)]
52pub(crate) fn parse_paren_meta_list_with_terminated<T, P>(
53 meta: &syn::Meta,
54) -> Result<Punctuated<T, P>>
55where
56 T: syn::parse::Parse,
57 P: syn::parse::Parse,
58{
59 let item = std::any::type_name::<T>();
60 let punct = std::any::type_name::<P>();
61
62 let name = |val: &str| {
63 format!(
64 "'{}'",
65 val.rsplit("::").next().unwrap_or(val).to_lowercase()
66 )
67 };
68
69 let meta = match meta {
70 syn::Meta::List(meta) => meta,
71 _ => bail!(
72 &meta,
73 "expected a list of {} separated by {}",
74 name(item),
75 name(punct),
76 ),
77 };
78
79 meta.require_parens_delim()?;
80
81 let punctuated = Punctuated::parse_terminated.parse2(meta.tokens.clone())?;
82
83 Ok(punctuated)
84}
85
86fn parse_path_mod_style(meta: &syn::Meta) -> Result<syn::Path> {
87 let expr = match meta {
88 syn::Meta::NameValue(meta) => &meta.value,
89 _ => bail!(meta, "expected a simple path, like `foo::bar`"),
90 };
91
92 Ok(expr.require_path_mod_style()?.clone())
93}
94
95pub(crate) fn parse_bon_crate_path(meta: &syn::Meta) -> Result<syn::Path> {
96 let path = parse_path_mod_style(meta)?;
97
98 let prefix = &path
99 .segments
100 .first()
101 .ok_or_else(|| err!(&path, "path must have at least one segment"))?
102 .ident;
103
104 let is_absolute = path.leading_colon.is_some() || prefix == "crate" || prefix == "$crate";
105
106 if is_absolute {
107 return Ok(path);
108 }
109
110 if prefix == "super" || prefix == "self" {
111 bail!(
112 &path,
113 "path must not be relative; specify the path that starts with `crate::` \
114 instead; if you want to refer to a reexport from an external crate then \
115 use a leading colon like `::crate_name::reexport::path::bon`"
116 )
117 }
118
119 let path_str = darling::util::path_to_string(&path);
120
121 bail!(
122 &path,
123 "path must be absolute; if you want to refer to a reexport from an external \
124 crate then add a leading colon like `::{path_str}`; if the path leads to a module \
125 in the current crate, then specify the absolute path with `crate` like \
126 `crate::reexport::path::bon` or `$crate::reexport::path::bon` (if within a macro)"
127 )
128}
129
130#[allow(unknown_lints, clippy::ref_option)]
132pub(crate) fn reject_syntax<T: Spanned>(name: &'static str, syntax: &Option<T>) -> Result {
133 if let Some(syntax) = syntax {
134 bail!(syntax, "{name} is not allowed here")
135 }
136
137 Ok(())
138}