bon_macros/builder/builder_gen/member/named.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
use super::config::MemberConfig;
use super::{config, MemberOrigin};
use crate::builder::builder_gen::member::config::SettersFnsConfig;
use crate::builder::builder_gen::top_level_config::OnConfig;
use crate::normalization::SyntaxVariant;
use crate::parsing::{ItemSigConfig, SpannedKey};
use crate::util::prelude::*;
#[derive(Debug)]
pub(crate) struct MemberName {
/// Original name of the member (unchanged). It's used in the finishing
/// function of the builder to create a variable for each member.
pub(crate) orig: syn::Ident,
/// `snake_case` version of the member name. By default it's the `orig` name
/// itself with the `_` prefix stripped. Otherwise the user can override it
/// via `#[builder(name = custom_name)]`
pub(crate) snake: syn::Ident,
/// `snake_case` version of the member name as a string without the `r#` prefix
/// (if there is any in the `snake` representation). It's computed and
/// stored separately to avoid recomputing it multiple times. It's used
/// to derive names for other identifiers that are based on the `snake_case` name.
pub(crate) snake_raw_str: String,
/// `PascalCase` version of the member name. It's always computed as the
/// `snake` variant converted to `PascalCase`. The user doesn't have the
/// granular control over this name. Users can only specify the snake case
/// version of the name, and the pascal case is derived from it.
pub(crate) pascal: syn::Ident,
/// `PascalCase` version of the member name as a string. It's computed and
/// stored separately to avoid recomputing it multiple times. It's guaranteed
/// to not have the `r#` prefix because:
///
/// There are no pascal case keywords in Rust except for `Self`, which
/// is anyway not allowed even as a raw identifier:
/// <https://internals.rust-lang.org/t/raw-identifiers-dont-work-for-all-identifiers/9094>
pub(crate) pascal_str: String,
}
impl MemberName {
pub(crate) fn new(orig: syn::Ident, config: &MemberConfig) -> Self {
let snake = config.name.clone().unwrap_or_else(|| {
let orig_str = orig.to_string();
let norm = orig_str
// Remove the leading underscore from the member name since it's used
// to denote unused symbols in Rust. That doesn't mean the builder
// API should expose that knowledge to the caller.
.strip_prefix('_')
.unwrap_or(&orig_str);
// Preserve the original identifier span to make IDE's "go to definition" work correctly
// and make error messages point to the correct place.
syn::Ident::new_maybe_raw(norm, orig.span())
});
let pascal = snake.snake_to_pascal_case();
Self {
orig,
snake_raw_str: snake.raw_name(),
snake,
pascal_str: pascal.to_string(),
pascal,
}
}
}
/// Regular member for which the builder should have setter methods
#[derive(Debug)]
pub(crate) struct NamedMember {
/// Specifies what syntax the member comes from.
pub(crate) origin: MemberOrigin,
/// Index of the member relative to other named members. The index is 0-based.
pub(crate) index: syn::Index,
/// Name of the member is used to generate names for the setters, names for
/// the associated types and type aliases in the builder state, etc.
pub(crate) name: MemberName,
/// Doc comments on top of the original syntax. These are copied to the setters
/// unless there are overrides for them.
pub(crate) docs: Vec<syn::Attribute>,
/// Type of the member has to be known to generate the types for fields in
/// the builder, signatures of the setter methods, etc.
pub(crate) ty: SyntaxVariant<Box<syn::Type>>,
/// Parameters configured by the user explicitly via attributes
pub(crate) config: MemberConfig,
}
impl NamedMember {
pub(super) fn validate(&self) -> Result {
if let Some(default) = &self.config.default {
if self.is_special_option_ty() {
bail!(
&default.key,
"`Option<_>` already implies a default of `None`, \
so explicit #[builder(default)] is redundant",
);
}
}
let member_docs_not_copied = self
.config
.setters
.as_ref()
.map(|setters| {
if setters.docs.is_some() {
return true;
}
let SettersFnsConfig { some_fn, option_fn } = &setters.fns;
matches!(
(some_fn.as_deref(), option_fn.as_deref()),
(
Some(ItemSigConfig { docs: Some(_), .. }),
Some(ItemSigConfig { docs: Some(_), .. })
)
)
})
.unwrap_or(false);
if !member_docs_not_copied {
crate::parsing::reject_self_mentions_in_docs(
"builder struct's impl block",
&self.docs,
)?;
}
self.validate_setters_config()?;
if self.config.required.is_present() && !self.ty.norm.is_option() {
bail!(
&self.config.required.span(),
"`#[builder(required)]` can only be applied to members of \
type `Option<T>` to disable their special handling",
);
}
Ok(())
}
fn validate_setters_config(&self) -> Result {
let setters = match &self.config.setters {
Some(setters) => setters,
None => return Ok(()),
};
if self.is_required() {
let SettersFnsConfig { some_fn, option_fn } = &setters.fns;
let unexpected_setter = option_fn.as_ref().or(some_fn.as_ref());
if let Some(setter) = unexpected_setter {
bail!(
&setter.key,
"`{}` setter function applies only to members with `#[builder(default)]` \
or members of `Option<T>` type (if #[builder(required)] is not set)",
setter.key
);
}
}
if let SettersFnsConfig {
some_fn: Some(some_fn),
option_fn: Some(option_fn),
} = &setters.fns
{
let setter_fns = &[some_fn, option_fn];
Self::validate_unused_setters_cfg(setter_fns, &setters.name, |config| &config.name)?;
Self::validate_unused_setters_cfg(setter_fns, &setters.vis, |config| &config.vis)?;
Self::validate_unused_setters_cfg(setter_fns, &setters.docs, |config| &config.docs)?;
}
Ok(())
}
// Lint from nightly. `&Option<T>` is used to reduce syntax at the call site
#[allow(unknown_lints, clippy::ref_option)]
fn validate_unused_setters_cfg<T>(
overrides: &[&SpannedKey<ItemSigConfig>],
config: &Option<SpannedKey<T>>,
get_val: impl Fn(&ItemSigConfig) -> &Option<SpannedKey<T>>,
) -> Result {
let config = match config {
Some(config) => config,
None => return Ok(()),
};
let overrides_values = overrides
.iter()
.copied()
.map(|over| get_val(&over.value).as_ref());
if !overrides_values.clone().all(|over| over.is_some()) {
return Ok(());
}
let setters = overrides
.iter()
.map(|over| format!("`{}`", over.key))
.join(", ");
bail!(
&config.key,
"this `{name}` configuration is unused because all of the \
{setters} setters contain a `{name}` override",
name = config.key,
);
}
/// Returns `true` if this member is of `Option<_>` type, but returns `false`
/// if `#[builder(required)]` is set.
pub(crate) fn is_special_option_ty(&self) -> bool {
!self.config.required.is_present() && self.ty.norm.is_option()
}
/// Returns `false` if the member has a default value. It means this member
/// is required to be set before building can be finished.
pub(crate) fn is_required(&self) -> bool {
self.config.default.is_none() && !self.is_special_option_ty()
}
/// A stateful member is the one that has a corresponding associated type in
/// the builder's type state trait. This is used to track the fact that the
/// member was set or not. This is necessary to make sure all members without
/// default values are set before building can be finished.
pub(crate) fn is_stateful(&self) -> bool {
self.is_required() || !self.config.overwritable.is_present()
}
/// Returns the normalized type of the member stripping the `Option<_>`
/// wrapper if it's present unless `#[builder(required)]` is set.
pub(crate) fn underlying_norm_ty(&self) -> &syn::Type {
self.underlying_ty(&self.ty.norm)
}
/// Returns the original type of the member stripping the `Option<_>`
/// wrapper if it's present unless `#[builder(required)]` is set.
pub(crate) fn underlying_orig_ty(&self) -> &syn::Type {
self.underlying_ty(&self.ty.orig)
}
fn underlying_ty<'m>(&'m self, ty: &'m syn::Type) -> &'m syn::Type {
if self.config.required.is_present() || self.config.default.is_some() {
ty
} else {
ty.option_type_param().unwrap_or(ty)
}
}
pub(crate) fn is(&self, other: &Self) -> bool {
self.index == other.index
}
pub(crate) fn merge_on_config(&mut self, on: &[OnConfig]) -> Result {
// This is a temporary hack. We only allow `on(_, required)` as the
// first `on(...)` clause. Instead we should implement the extended design:
// https://github.com/elastio/bon/issues/152
if let Some(on) = on.first().filter(|on| on.required.is_present()) {
if self.is_special_option_ty() {
self.config.required = on.required;
}
}
self.merge_config_into(on)?;
// FIXME: refactor this to make it more consistent with `into`
// and allow for non-boolean flags in `OnConfig`. E.g. add support
// for `with = closure` to `on` as well.
self.config.overwritable = config::EvalBlanketFlagParam {
on,
param_name: config::BlanketParamName::Overwritable,
member_config: &self.config,
scrutinee: self.underlying_norm_ty(),
origin: self.origin,
}
.eval()?;
Ok(())
}
}