1mod blanket;
2mod getter;
3mod setters;
4mod with;
56pub(crate) use blanket::*;
7pub(crate) use getter::*;
8pub(crate) use setters::*;
9pub(crate) use with::*;
1011use super::MemberOrigin;
12use crate::parsing::SpannedKey;
13use crate::util::prelude::*;
14use std::fmt;
1516#[derive(Debug, darling::FromAttributes)]
17#[darling(attributes(builder))]
18pub(crate) struct MemberConfig {
19/// Assign a default value to the member it it's not specified.
20 ///
21 /// An optional expression can be provided to set the value for the member,
22 /// otherwise its [`Default`] trait impl will be used.
23#[darling(with = parse_optional_expr, map = Some)]
24pub(crate) default: Option<SpannedKey<Option<syn::Expr>>>,
2526/// Make the member a private field in the builder struct.
27 /// This is useful when the user needs to add custom fields to the builder,
28 /// that they would use in the custom methods they add to the builder.
29 ///
30 /// This is similar to `skip`. The difference is that `field` is evaluated
31 /// inside of the starting function, and stored in the builder. Its initialization
32 /// expression thus has access to all `start_fn` parameters. It must be declared
33 /// strictly after `#[builder(start_fn)]` members (if any) or right at the top of
34 /// the members list.
35#[darling(with = parse_optional_expr, map = Some)]
36pub(crate) field: Option<SpannedKey<Option<syn::Expr>>>,
3738/// Make the member gettable. [`GetterConfig`] specifies the signature for
39 /// the getter.
40 ///
41 /// This takes the same attributes as the setter fns; `name`, `vis`, and `doc`
42 /// and produces a getter method that returns the value of the member.
43 /// By default, the value is returned by a shared reference (&T).
44pub(crate) getter: Option<SpannedKey<GetterConfig>>,
4546/// Accept the value for the member in the finishing function parameters.
47pub(crate) finish_fn: darling::util::Flag,
4849/// Enables an `Into` conversion for the setter method.
50pub(crate) into: darling::util::Flag,
5152/// Rename the name exposed in the builder API.
53pub(crate) name: Option<syn::Ident>,
5455/// Allows setting the value for the member repeatedly. This reduces the
56 /// number of type states and thus increases the compilation performance.
57 ///
58 /// However, this also means that unintended overwrites won't be caught
59 /// at compile time. Measure the compilation time before and after enabling
60 /// this option to see if it's worth it.
61pub(crate) overwritable: darling::util::Flag,
6263/// Disables the special handling for a member of type `Option<T>`. The
64 /// member no longer has the default of `None`. It also becomes a required
65 /// member unless a separate `#[builder(default = ...)]` attribute is
66 /// also specified.
67pub(crate) required: darling::util::Flag,
6869/// Configurations for the setter methods.
70#[darling(with = crate::parsing::parse_non_empty_paren_meta_list)]
71pub(crate) setters: Option<SettersConfig>,
7273/// Skip generating a setter method for this member.
74 ///
75 /// An optional expression can be provided to set the value for the member,
76 /// otherwise its [`Default`] trait impl will be used.
77#[darling(with = parse_optional_expr, map = Some)]
78pub(crate) skip: Option<SpannedKey<Option<syn::Expr>>>,
7980/// Accept the value for the member in the starting function parameters.
81pub(crate) start_fn: darling::util::Flag,
8283/// Customize the setter signature and body with a custom closure or a well-known
84 /// function. The closure/function must return the value of the type of the member,
85 /// or optionally a `Result<_>` type where `_` is used to mark the type of
86 /// the member. In this case the generated setters will be fallible
87 /// (they'll propagate the `Result`).
88pub(crate) with: Option<SpannedKey<WithConfig>>,
89}
9091#[derive(PartialEq, Eq, Clone, Copy)]
92enum ParamName {
93 Default,
94 Field,
95 Getter,
96 FinishFn,
97 Into,
98 Name,
99 Overwritable,
100 Required,
101 Setters,
102 Skip,
103 StartFn,
104 With,
105}
106107impl fmt::Display for ParamName {
108fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109let str = match self {
110Self::Default => "default",
111Self::Field => "field",
112Self::Getter => "getter",
113Self::FinishFn => "finish_fn",
114Self::Into => "into",
115Self::Name => "name",
116Self::Overwritable => "overwritable",
117Self::Required => "required",
118Self::Setters => "setters",
119Self::Skip => "skip",
120Self::StartFn => "start_fn",
121Self::With => "with",
122 };
123 f.write_str(str)
124 }
125}
126127impl MemberConfig {
128fn validate_mutually_exclusive(
129&self,
130 attr_name: ParamName,
131 attr_span: Span,
132 mutually_exclusive: &[ParamName],
133 ) -> Result<()> {
134self.validate_compat(attr_name, attr_span, mutually_exclusive, true)
135 }
136137fn validate_mutually_allowed(
138&self,
139 attr_name: ParamName,
140 attr_span: Span,
141 mutually_allowed: &[ParamName],
142 ) -> Result<()> {
143self.validate_compat(attr_name, attr_span, mutually_allowed, false)
144 }
145146fn validate_compat(
147&self,
148 attr_name: ParamName,
149 attr_span: Span,
150 patterns: &[ParamName],
151 mutually_exclusive: bool,
152 ) -> Result<()> {
153let conflicting: Vec<_> = self
154.specified_param_names()
155 .filter(|name| *name != attr_name && patterns.contains(name) == mutually_exclusive)
156 .collect();
157158if conflicting.is_empty() {
159return Ok(());
160 }
161162let conflicting = conflicting
163 .iter()
164 .map(|name| format!("`{name}`"))
165 .join(", ");
166167bail!(
168&attr_span,
169"`{attr_name}` attribute can't be specified together with {conflicting}",
170 );
171 }
172173fn specified_param_names(&self) -> impl Iterator<Item = ParamName> {
174let Self {
175 default,
176 field,
177 getter,
178 finish_fn,
179 into,
180 name,
181 overwritable,
182 required,
183 setters,
184 skip,
185 start_fn,
186 with,
187 } = self;
188189let attrs = [
190 (default.is_some(), ParamName::Default),
191 (field.is_some(), ParamName::Field),
192 (getter.is_some(), ParamName::Getter),
193 (finish_fn.is_present(), ParamName::FinishFn),
194 (into.is_present(), ParamName::Into),
195 (name.is_some(), ParamName::Name),
196 (overwritable.is_present(), ParamName::Overwritable),
197 (required.is_present(), ParamName::Required),
198 (setters.is_some(), ParamName::Setters),
199 (skip.is_some(), ParamName::Skip),
200 (start_fn.is_present(), ParamName::StartFn),
201 (with.is_some(), ParamName::With),
202 ];
203204 attrs
205 .into_iter()
206 .filter(|(is_present, _)| *is_present)
207 .map(|(_, name)| name)
208 }
209210pub(crate) fn validate(&self, origin: MemberOrigin) -> Result {
211if !cfg!(feature = "experimental-overwritable") && self.overwritable.is_present() {
212bail!(
213&self.overwritable.span(),
214"🔬 `overwritable` attribute is experimental and requires \
215 \"experimental-overwritable\" cargo feature to be enabled; \
216 we would be glad to make this attribute stable if you find it useful; \
217 please leave a 👍 reaction under the issue https://github.com/elastio/bon/issues/149 \
218 to help us measure the demand for this feature; it would be \
219 double-awesome if you could also describe your use case in \
220 a comment under the issue for us to understand how it's used \
221 in practice",
222 );
223 }
224225if let Some(getter) = &self.getter {
226if !cfg!(feature = "experimental-getter") {
227bail!(
228&getter.key,
229"🔬 `getter` attribute is experimental and requires \
230 \"experimental-getter\" cargo feature to be enabled; \
231 if you find the current design of this attribute already \
232 solid please leave a 👍 reaction under the issue \
233 https://github.com/elastio/bon/issues/225; if you have \
234 any feedback, then feel free to leave a comment under that issue",
235 );
236 }
237238self.validate_mutually_exclusive(
239 ParamName::Getter,
240 getter.key.span(),
241&[ParamName::Overwritable],
242 )?;
243 }
244245if self.start_fn.is_present() {
246self.validate_mutually_allowed(
247 ParamName::StartFn,
248self.start_fn.span(),
249// TODO: add support for `#[builder(getter)]` with `start_fn`
250&[ParamName::Into],
251 )?;
252 }
253254if self.finish_fn.is_present() {
255self.validate_mutually_allowed(
256 ParamName::FinishFn,
257self.finish_fn.span(),
258&[ParamName::Into],
259 )?;
260 }
261262if let Some(field) = &self.field {
263self.validate_mutually_allowed(ParamName::Field, field.key.span(), &[])?;
264 }
265266if let Some(skip) = &self.skip {
267match origin {
268 MemberOrigin::FnArg => {
269bail!(
270&skip.key.span(),
271"`skip` attribute is not supported on function arguments; \
272 use a local variable instead.",
273 );
274 }
275 MemberOrigin::StructField => {}
276 }
277278if let Some(Some(_expr)) = self.default.as_deref() {
279bail!(
280&skip.key.span(),
281"`skip` attribute can't be specified with the `default` attribute; \
282 if you wanted to specify a value for the member, then use \
283 the following syntax instead `#[builder(skip = value)]`",
284 );
285 }
286287self.validate_mutually_allowed(ParamName::Skip, skip.key.span(), &[])?;
288 }
289290if let Some(with) = &self.with {
291self.validate_mutually_exclusive(ParamName::With, with.key.span(), &[ParamName::Into])?;
292 }
293294Ok(())
295 }
296}
297298fn parse_optional_expr(meta: &syn::Meta) -> Result<SpannedKey<Option<syn::Expr>>> {
299match meta {
300 syn::Meta::Path(path) => SpannedKey::new(path, None),
301 syn::Meta::List(_) => Err(Error::unsupported_format("list").with_span(meta)),
302 syn::Meta::NameValue(meta) => SpannedKey::new(&meta.path, Some(meta.value.clone())),
303 }
304}