bon_macros/parsing/
mod.rs

1mod 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/// Utility for parsing with `#[darling(with = ...)]` attribute that allows to
48/// parse an arbitrary sequence of items inside of parentheses. For example
49/// `foo(a, b, c)`, where `a`, `b`, and `c` are of type `T` and `,` is represented
50/// by the token type `P`.
51#[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// Lint from nightly. `&Option<T>` is used to reduce syntax at the callsite
131#[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}