bon_macros/builder/builder_gen/member/config/
mod.rs1#![allow(clippy::needless_continue)]
4
5mod blanket;
6mod getter;
7mod setters;
8mod with;
9
10pub(crate) use blanket::*;
11pub(crate) use getter::*;
12pub(crate) use setters::*;
13pub(crate) use with::*;
14
15use super::MemberOrigin;
16use crate::builder::builder_gen::TopLevelConfig;
17use crate::parsing::SpannedKey;
18use crate::util::prelude::*;
19use std::fmt;
20
21#[derive(Debug, darling::FromAttributes)]
22#[darling(attributes(builder))]
23pub(crate) struct MemberConfig {
24 #[darling(with = parse_optional_expr, map = Some)]
29 pub(crate) default: Option<SpannedKey<Option<syn::Expr>>>,
30
31 #[darling(with = parse_optional_expr, map = Some)]
41 pub(crate) field: Option<SpannedKey<Option<syn::Expr>>>,
42
43 pub(crate) getter: Option<SpannedKey<GetterConfig>>,
50
51 pub(crate) finish_fn: darling::util::Flag,
53
54 pub(crate) into: darling::util::Flag,
56
57 pub(crate) name: Option<syn::Ident>,
59
60 pub(crate) overwritable: darling::util::Flag,
67
68 pub(crate) required: darling::util::Flag,
73
74 #[darling(with = crate::parsing::parse_non_empty_paren_meta_list)]
76 pub(crate) setters: Option<SettersConfig>,
77
78 #[darling(with = parse_optional_expr, map = Some)]
83 pub(crate) skip: Option<SpannedKey<Option<syn::Expr>>>,
84
85 pub(crate) start_fn: darling::util::Flag,
87
88 pub(crate) with: Option<SpannedKey<WithConfig>>,
94}
95
96#[derive(PartialEq, Eq, Clone, Copy)]
97enum ParamName {
98 Default,
99 Field,
100 Getter,
101 FinishFn,
102 Into,
103 Name,
104 Overwritable,
105 Required,
106 Setters,
107 Skip,
108 StartFn,
109 With,
110}
111
112impl fmt::Display for ParamName {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 let str = match self {
115 Self::Default => "default",
116 Self::Field => "field",
117 Self::Getter => "getter",
118 Self::FinishFn => "finish_fn",
119 Self::Into => "into",
120 Self::Name => "name",
121 Self::Overwritable => "overwritable",
122 Self::Required => "required",
123 Self::Setters => "setters",
124 Self::Skip => "skip",
125 Self::StartFn => "start_fn",
126 Self::With => "with",
127 };
128 f.write_str(str)
129 }
130}
131
132impl MemberConfig {
133 fn validate_mutually_exclusive(
134 &self,
135 attr_name: ParamName,
136 attr_span: Span,
137 mutually_exclusive: &[ParamName],
138 ) -> Result<()> {
139 self.validate_compat(attr_name, attr_span, mutually_exclusive, true)
140 }
141
142 fn validate_mutually_allowed(
143 &self,
144 attr_name: ParamName,
145 attr_span: Span,
146 mutually_allowed: &[ParamName],
147 ) -> Result<()> {
148 self.validate_compat(attr_name, attr_span, mutually_allowed, false)
149 }
150
151 fn validate_compat(
152 &self,
153 attr_name: ParamName,
154 attr_span: Span,
155 patterns: &[ParamName],
156 mutually_exclusive: bool,
157 ) -> Result<()> {
158 let conflicting: Vec<_> = self
159 .specified_param_names()
160 .filter(|name| *name != attr_name && patterns.contains(name) == mutually_exclusive)
161 .collect();
162
163 if conflicting.is_empty() {
164 return Ok(());
165 }
166
167 let conflicting = conflicting
168 .iter()
169 .map(|name| format!("`{name}`"))
170 .join(", ");
171
172 bail!(
173 &attr_span,
174 "`{attr_name}` attribute can't be specified together with {conflicting}",
175 );
176 }
177
178 fn specified_param_names(&self) -> impl Iterator<Item = ParamName> {
179 let Self {
180 default,
181 field,
182 getter,
183 finish_fn,
184 into,
185 name,
186 overwritable,
187 required,
188 setters,
189 skip,
190 start_fn,
191 with,
192 } = self;
193
194 let attrs = [
195 (default.is_some(), ParamName::Default),
196 (field.is_some(), ParamName::Field),
197 (getter.is_some(), ParamName::Getter),
198 (finish_fn.is_present(), ParamName::FinishFn),
199 (into.is_present(), ParamName::Into),
200 (name.is_some(), ParamName::Name),
201 (overwritable.is_present(), ParamName::Overwritable),
202 (required.is_present(), ParamName::Required),
203 (setters.is_some(), ParamName::Setters),
204 (skip.is_some(), ParamName::Skip),
205 (start_fn.is_present(), ParamName::StartFn),
206 (with.is_some(), ParamName::With),
207 ];
208
209 attrs
210 .into_iter()
211 .filter(|(is_present, _)| *is_present)
212 .map(|(_, name)| name)
213 }
214
215 pub(crate) fn validate(&self, top_config: &TopLevelConfig, origin: MemberOrigin) -> Result {
216 if top_config.const_.is_some() {
217 self.require_const_compat()?;
218 }
219
220 if !cfg!(feature = "experimental-overwritable") && self.overwritable.is_present() {
221 bail!(
222 &self.overwritable.span(),
223 "🔬 `overwritable` attribute is experimental and requires \
224 \"experimental-overwritable\" cargo feature to be enabled; \
225 we would be glad to make this attribute stable if you find it useful; \
226 please leave a 👍 reaction under the issue https://github.com/elastio/bon/issues/149 \
227 to help us measure the demand for this feature; it would be \
228 double-awesome if you could also describe your use case in \
229 a comment under the issue for us to understand how it's used \
230 in practice",
231 );
232 }
233
234 if let Some(getter) = &self.getter {
235 self.validate_mutually_exclusive(
236 ParamName::Getter,
237 getter.key.span(),
238 &[ParamName::Overwritable],
239 )?;
240 }
241
242 if self.start_fn.is_present() {
243 self.validate_mutually_allowed(
244 ParamName::StartFn,
245 self.start_fn.span(),
246 &[ParamName::Into],
248 )?;
249 }
250
251 if self.finish_fn.is_present() {
252 self.validate_mutually_allowed(
253 ParamName::FinishFn,
254 self.finish_fn.span(),
255 &[ParamName::Into],
256 )?;
257 }
258
259 if let Some(field) = &self.field {
260 self.validate_mutually_allowed(ParamName::Field, field.key.span(), &[])?;
261 }
262
263 if let Some(skip) = &self.skip {
264 match origin {
265 MemberOrigin::FnArg => {
266 bail!(
267 &skip.key.span(),
268 "`skip` attribute is not supported on function arguments; \
269 use a local variable instead.",
270 );
271 }
272 MemberOrigin::StructField => {}
273 }
274
275 if let Some(Some(_expr)) = self.default.as_deref() {
276 bail!(
277 &skip.key.span(),
278 "`skip` attribute can't be specified with the `default` attribute; \
279 if you wanted to specify a value for the member, then use \
280 the following syntax instead `#[builder(skip = value)]`",
281 );
282 }
283
284 self.validate_mutually_allowed(ParamName::Skip, skip.key.span(), &[])?;
285 }
286
287 if let Some(with) = &self.with {
288 self.validate_mutually_exclusive(ParamName::With, with.key.span(), &[ParamName::Into])?;
289 }
290
291 if let Some(setters) = &self.setters {
292 if let Some(default) = &setters.doc.default {
293 if self.default.is_none() {
294 bail!(
295 &default.key,
296 "`#[builder(setters(doc(default(...)))]` may only be specified \
297 when #[builder(default)] is also specified",
298 );
299 }
300 }
301 }
302
303 Ok(())
304 }
305
306 fn require_const_compat(&self) -> Result {
307 fn validate_default_trait_or_expr(attr: &Option<SpannedKey<Option<syn::Expr>>>) -> Result {
308 let attr = match attr {
309 Some(attr) => attr,
310 None => return Ok(()),
311 };
312
313 let name = attr.key.to_string();
314
315 if let Some(expr) = &attr.value {
316 return crate::parsing::require_embeddable_const_expr(expr);
317 }
318
319 bail!(
320 &attr.key,
321 "bare #[builder({name})] is incompatible with #[builder(const)] \
322 because Default::default() can not be called in const context; \
323 provide an explicit value via #[builder({name} = ...)] instead",
324 )
325 }
326
327 validate_default_trait_or_expr(&self.default)?;
328 validate_default_trait_or_expr(&self.skip)?;
329 validate_default_trait_or_expr(&self.field)?;
330
331 if self.into.is_present() {
332 bail!(
333 &self.into.span(),
334 "#[builder(into)] is incompatible with #[builder(const)] \
335 because Into::into() can not be called in const context",
336 );
337 }
338
339 if let Some(getter) = &self.getter {
340 if let Some(getter_kind) = &getter.kind {
341 match &getter_kind.value {
342 GetterKind::Copy => {}
343 GetterKind::Clone => {
344 bail!(
345 &getter_kind.key,
346 "#[builder(getter(clone))] is incompatible with #[builder(const)] \
347 because Clone::clone() can not be called in const context",
348 )
349 }
350 GetterKind::Deref(_) => {
351 bail!(
352 &getter_kind.key,
353 "#[builder(getter(deref))] is incompatible with #[builder(const)] \
354 because Deref::deref() can not be called in const context",
355 )
356 }
357 }
358 }
359 }
360
361 if let Some(with) = &self.with {
362 match &with.value {
363 WithConfig::Closure(closure) => {
364 crate::parsing::require_embeddable_const_expr(&closure.body)?;
365 }
366 WithConfig::Some(_) => {}
367 WithConfig::FromIter(from_iter) => {
368 bail!(
369 &from_iter,
370 "from_iter is incompatible with #[builder(const)] because \
371 FromIterator::from_iter() can not be called in const context",
372 )
373 }
374 }
375 }
376
377 Ok(())
378 }
379}
380
381fn parse_optional_expr(meta: &syn::Meta) -> Result<SpannedKey<Option<syn::Expr>>> {
382 match meta {
383 syn::Meta::Path(path) => SpannedKey::new(path, None),
384 syn::Meta::List(_) => Err(Error::unsupported_format("list").with_span(meta)),
385 syn::Meta::NameValue(meta) => SpannedKey::new(&meta.path, Some(meta.value.clone())),
386 }
387}