From bdd3c59be807d8ee83b97752458d494d0a994b20 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 20 Oct 2024 21:32:34 +0000 Subject: [PATCH 01/12] Some renames and rearrangements --- .../builder/builder_gen/builder_derives.rs | 2 +- .../src/builder/builder_gen/input_fn.rs | 8 +- .../src/builder/builder_gen/input_struct.rs | 16 +- .../builder_gen/member/into_conversion.rs | 2 +- .../src/builder/builder_gen/member/mod.rs | 2 +- .../src/builder/builder_gen/member/named.rs | 12 +- .../builder_gen/member/params/blanket.rs | 2 +- .../builder_gen/member/params/setters.rs | 8 +- bon-macros/src/builder/builder_gen/mod.rs | 2 +- bon-macros/src/builder/builder_gen/models.rs | 6 +- .../src/builder/builder_gen/setters/mod.rs | 14 +- .../mod.rs | 16 +- .../on_params.rs | 0 bon-macros/src/parsing/item_params.rs | 10 +- website/.vitepress/config.mts | 21 +- website/reference/builder.md | 340 +++++++++--------- 16 files changed, 230 insertions(+), 231 deletions(-) rename bon-macros/src/builder/builder_gen/{builder_params => top_level_params}/mod.rs (82%) rename bon-macros/src/builder/builder_gen/{builder_params => top_level_params}/on_params.rs (100%) diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index 8d7d334a..e720e1c4 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -1,4 +1,4 @@ -use super::builder_params::{BuilderDerive, BuilderDerives}; +use super::top_level_params::{BuilderDerive, BuilderDerives}; use super::BuilderGenCtx; use crate::builder::builder_gen::Member; use crate::util::prelude::*; diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index 5ec08a71..b5d441d3 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -1,4 +1,4 @@ -use super::builder_params::BuilderParams; +use super::top_level_params::TopLevelParams; use super::models::FinishFnParams; use super::{ AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFnBody, Generics, Member, @@ -6,7 +6,7 @@ use super::{ }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; use crate::normalization::{GenericsNamespace, NormalizeSelfTy, SyntaxVariant}; -use crate::parsing::{ItemParams, SpannedKey}; +use crate::parsing::{SymbolParams, SpannedKey}; use crate::util::prelude::*; use darling::util::SpannedValue; use darling::FromMeta; @@ -21,7 +21,7 @@ pub(crate) struct FnInputParams { expose_positional_fn: Option>, #[darling(flatten)] - base: BuilderParams, + base: TopLevelParams, } #[derive(Debug, Default)] @@ -378,7 +378,7 @@ impl FnInputCtx<'_> { self.fn_item.norm.sig.ident.clone() }; - let ItemParams { + let SymbolParams { name: finish_fn_ident, vis: finish_fn_vis, docs: finish_fn_docs, diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 45aef731..926d2323 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -1,11 +1,11 @@ -use super::builder_params::BuilderParams; +use super::top_level_params::TopLevelParams; use super::models::FinishFnParams; use super::{ AssocMethodCtx, BuilderGenCtx, FinishFnBody, Generics, Member, MemberOrigin, RawMember, }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; use crate::normalization::{GenericsNamespace, SyntaxVariant}; -use crate::parsing::{ItemParams, ItemParamsParsing, SpannedKey}; +use crate::parsing::{SymbolParams, ItemParamsParsing, SpannedKey}; use crate::util::prelude::*; use darling::FromMeta; use std::borrow::Cow; @@ -15,13 +15,13 @@ use syn::visit_mut::VisitMut; #[derive(Debug, FromMeta)] pub(crate) struct StructInputParams { #[darling(flatten)] - base: BuilderParams, + base: TopLevelParams, #[darling(default, with = parse_start_fn)] - start_fn: ItemParams, + start_fn: SymbolParams, } -fn parse_start_fn(meta: &syn::Meta) -> Result { +fn parse_start_fn(meta: &syn::Meta) -> Result { ItemParamsParsing { meta, reject_self_mentions: None, @@ -155,7 +155,7 @@ impl StructInputCtx { struct_ident: self.struct_item.norm.ident.clone(), }; - let ItemParams { + let SymbolParams { name: start_fn_ident, vis: start_fn_vis, docs: start_fn_docs, @@ -165,7 +165,7 @@ impl StructInputCtx { .map(SpannedKey::into_value) .unwrap_or_else(|| syn::Ident::new("builder", self.struct_item.norm.ident.span())); - let ItemParams { + let SymbolParams { name: finish_fn_ident, vis: finish_fn_vis, docs: finish_fn_docs, @@ -227,7 +227,7 @@ impl StructInputCtx { .collect(); let builder_type = { - let ItemParams { name, vis, docs } = self.params.base.builder_type; + let SymbolParams { name, vis, docs } = self.params.base.builder_type; let builder_ident = name.map(SpannedKey::into_value).unwrap_or_else(|| { format_ident!("{}Builder", self.struct_item.norm.ident.raw_name()) diff --git a/bon-macros/src/builder/builder_gen/member/into_conversion.rs b/bon-macros/src/builder/builder_gen/member/into_conversion.rs index bffb259f..a4edfbbf 100644 --- a/bon-macros/src/builder/builder_gen/member/into_conversion.rs +++ b/bon-macros/src/builder/builder_gen/member/into_conversion.rs @@ -1,6 +1,6 @@ use super::params::{BlanketParamName, EvalBlanketFlagParam}; use super::{NamedMember, PositionalFnArgMember}; -use crate::builder::builder_gen::builder_params::OnParams; +use crate::builder::builder_gen::top_level_params::OnParams; use crate::util::prelude::*; impl NamedMember { diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 976fc41f..d02b10d0 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -5,7 +5,7 @@ mod params; pub(crate) use named::*; pub(crate) use params::*; -use super::builder_params::OnParams; +use super::top_level_params::OnParams; use crate::normalization::SyntaxVariant; use crate::parsing::SpannedKey; use crate::util::prelude::*; diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index 04e4a3c4..81139304 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -1,9 +1,9 @@ use super::params::MemberParams; use super::{params, MemberOrigin}; -use crate::builder::builder_gen::builder_params::OnParams; +use crate::builder::builder_gen::top_level_params::OnParams; use crate::builder::builder_gen::member::params::SettersFnParams; use crate::normalization::SyntaxVariant; -use crate::parsing::{ItemParams, SpannedKey}; +use crate::parsing::{SymbolParams, SpannedKey}; use crate::util::prelude::*; #[derive(Debug)] @@ -117,8 +117,8 @@ impl NamedMember { matches!( (some_fn.as_deref(), option_fn.as_deref()), ( - Some(ItemParams { docs: Some(_), .. }), - Some(ItemParams { docs: Some(_), .. }) + Some(SymbolParams { docs: Some(_), .. }), + Some(SymbolParams { docs: Some(_), .. }) ) ) }) @@ -183,9 +183,9 @@ impl NamedMember { // Lint from nightly. `&Option` is used to reduce syntax at the call site #[allow(unknown_lints, clippy::ref_option)] fn validate_unused_setters_cfg( - overrides: &[&SpannedKey], + overrides: &[&SpannedKey], config: &Option>, - get_val: impl Fn(&ItemParams) -> &Option>, + get_val: impl Fn(&SymbolParams) -> &Option>, ) -> Result { let config = match config { Some(config) => config, diff --git a/bon-macros/src/builder/builder_gen/member/params/blanket.rs b/bon-macros/src/builder/builder_gen/member/params/blanket.rs index e0124483..38da988f 100644 --- a/bon-macros/src/builder/builder_gen/member/params/blanket.rs +++ b/bon-macros/src/builder/builder_gen/member/params/blanket.rs @@ -1,5 +1,5 @@ use super::MemberParams; -use crate::builder::builder_gen::builder_params::OnParams; +use crate::builder::builder_gen::top_level_params::OnParams; use crate::builder::builder_gen::member::MemberOrigin; use crate::util::prelude::*; use std::fmt; diff --git a/bon-macros/src/builder/builder_gen/member/params/setters.rs b/bon-macros/src/builder/builder_gen/member/params/setters.rs index ea9a51b2..688a2af6 100644 --- a/bon-macros/src/builder/builder_gen/member/params/setters.rs +++ b/bon-macros/src/builder/builder_gen/member/params/setters.rs @@ -1,10 +1,10 @@ -use crate::parsing::{ItemParams, ItemParamsParsing, SpannedKey}; +use crate::parsing::{SymbolParams, ItemParamsParsing, SpannedKey}; use crate::util::prelude::*; use darling::FromMeta; const DOCS_CONTEXT: &str = "builder struct's impl block"; -fn parse_setter_fn(meta: &syn::Meta) -> Result> { +fn parse_setter_fn(meta: &syn::Meta) -> Result> { let params = ItemParamsParsing { meta, reject_self_mentions: Some(DOCS_CONTEXT), @@ -37,12 +37,12 @@ pub(crate) struct SettersFnParams { /// /// By default, it's named `{member}` without any prefix or suffix. #[darling(default, with = parse_setter_fn, map = Some)] - pub(crate) some_fn: Option>, + pub(crate) some_fn: Option>, /// The setter that accepts the value of type `Option` for a member of /// type `Option` or with `#[builder(default)]`. /// /// By default, it's named `maybe_{member}`. #[darling(default, with = parse_setter_fn, map = Some)] - pub(crate) option_fn: Option>, + pub(crate) option_fn: Option>, } diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 040d2bad..580e0c37 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -1,5 +1,5 @@ mod builder_derives; -mod builder_params; +mod top_level_params; mod finish_fn; mod member; mod models; diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 83f54c29..7d11f1bd 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -1,7 +1,7 @@ -use super::builder_params::{BuilderDerives, OnParams}; +use super::top_level_params::{BuilderDerives, OnParams}; use super::member::Member; use crate::normalization::GenericsNamespace; -use crate::parsing::{ItemParams, SpannedKey}; +use crate::parsing::{SymbolParams, SpannedKey}; use crate::util::prelude::*; use std::borrow::Cow; @@ -196,7 +196,7 @@ pub(super) struct BuilderGenCtxParams<'a> { pub(super) assoc_method_ctx: Option, pub(super) builder_type: BuilderTypeParams, - pub(super) state_mod: ItemParams, + pub(super) state_mod: SymbolParams, pub(super) start_fn: StartFnParams, pub(super) finish_fn: FinishFnParams, } diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index dab66e3d..c20230bc 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -1,6 +1,6 @@ use super::member::SetterClosure; use super::{BuilderGenCtx, NamedMember}; -use crate::parsing::ItemParams; +use crate::parsing::SymbolParams; use crate::util::prelude::*; use std::iter; @@ -392,14 +392,14 @@ impl SettersItems { let some_fn = params.and_then(|params| params.fns.some_fn.as_deref()); let some_fn_name = some_fn - .and_then(ItemParams::name) + .and_then(SymbolParams::name) .or(common_name) .unwrap_or(&member.name.snake) .clone(); let option_fn = params.and_then(|params| params.fns.option_fn.as_deref()); let option_fn_name = option_fn - .and_then(ItemParams::name) + .and_then(SymbolParams::name) .cloned() .unwrap_or_else(|| { let base_name = common_name.unwrap_or(&member.name.snake); @@ -434,7 +434,7 @@ impl SettersItems { // FIXME: the docs shouldn't reference the companion setter if that // setter has a lower visibility. let some_fn_docs = some_fn - .and_then(ItemParams::docs) + .and_then(SymbolParams::docs) .or(common_docs) .unwrap_or(&member.docs); @@ -445,7 +445,7 @@ impl SettersItems { }; let option_fn_docs = option_fn - .and_then(ItemParams::docs) + .and_then(SymbolParams::docs) .or(common_docs) .unwrap_or(&member.docs); @@ -462,7 +462,7 @@ impl SettersItems { let some_fn = SetterItem { name: some_fn_name, vis: some_fn - .and_then(ItemParams::vis) + .and_then(SymbolParams::vis) .or(common_vis) .unwrap_or(&builder_type.vis) .clone(), @@ -475,7 +475,7 @@ impl SettersItems { name: option_fn_name, vis: option_fn - .and_then(ItemParams::vis) + .and_then(SymbolParams::vis) .or(common_vis) .unwrap_or(&builder_type.vis) .clone(), diff --git a/bon-macros/src/builder/builder_gen/builder_params/mod.rs b/bon-macros/src/builder/builder_gen/top_level_params/mod.rs similarity index 82% rename from bon-macros/src/builder/builder_gen/builder_params/mod.rs rename to bon-macros/src/builder/builder_gen/top_level_params/mod.rs index 67e9413b..5dd249a5 100644 --- a/bon-macros/src/builder/builder_gen/builder_params/mod.rs +++ b/bon-macros/src/builder/builder_gen/top_level_params/mod.rs @@ -2,12 +2,12 @@ mod on_params; pub(crate) use on_params::OnParams; -use crate::parsing::{ItemParams, ItemParamsParsing}; +use crate::parsing::{SymbolParams, ItemParamsParsing}; use crate::util::prelude::*; use darling::FromMeta; use syn::punctuated::Punctuated; -fn parse_finish_fn(meta: &syn::Meta) -> Result { +fn parse_finish_fn(meta: &syn::Meta) -> Result { ItemParamsParsing { meta, reject_self_mentions: Some("builder struct's impl block"), @@ -15,7 +15,7 @@ fn parse_finish_fn(meta: &syn::Meta) -> Result { .parse() } -fn parse_builder_type(meta: &syn::Meta) -> Result { +fn parse_builder_type(meta: &syn::Meta) -> Result { ItemParamsParsing { meta, reject_self_mentions: Some("builder struct"), @@ -23,7 +23,7 @@ fn parse_builder_type(meta: &syn::Meta) -> Result { .parse() } -fn parse_state_mod(meta: &syn::Meta) -> Result { +fn parse_state_mod(meta: &syn::Meta) -> Result { ItemParamsParsing { meta, reject_self_mentions: Some("builder's state module"), @@ -32,15 +32,15 @@ fn parse_state_mod(meta: &syn::Meta) -> Result { } #[derive(Debug, FromMeta)] -pub(crate) struct BuilderParams { +pub(crate) struct TopLevelParams { #[darling(default, with = parse_finish_fn)] - pub(crate) finish_fn: ItemParams, + pub(crate) finish_fn: SymbolParams, #[darling(default, with = parse_builder_type)] - pub(crate) builder_type: ItemParams, + pub(crate) builder_type: SymbolParams, #[darling(default, with = parse_state_mod)] - pub(crate) state_mod: ItemParams, + pub(crate) state_mod: SymbolParams, #[darling(multiple, with = crate::parsing::parse_non_empty_paren_meta_list)] pub(crate) on: Vec, diff --git a/bon-macros/src/builder/builder_gen/builder_params/on_params.rs b/bon-macros/src/builder/builder_gen/top_level_params/on_params.rs similarity index 100% rename from bon-macros/src/builder/builder_gen/builder_params/on_params.rs rename to bon-macros/src/builder/builder_gen/top_level_params/on_params.rs diff --git a/bon-macros/src/parsing/item_params.rs b/bon-macros/src/parsing/item_params.rs index 441a5bc4..ca7694a3 100644 --- a/bon-macros/src/parsing/item_params.rs +++ b/bon-macros/src/parsing/item_params.rs @@ -3,13 +3,13 @@ use crate::util::prelude::*; use darling::FromMeta; #[derive(Debug, Clone, Default)] -pub(crate) struct ItemParams { +pub(crate) struct SymbolParams { pub(crate) name: Option>, pub(crate) vis: Option>, pub(crate) docs: Option>>, } -impl ItemParams { +impl SymbolParams { pub(crate) fn name(&self) -> Option<&syn::Ident> { self.name.as_ref().map(|name| &name.value) } @@ -29,14 +29,14 @@ pub(crate) struct ItemParamsParsing<'a> { } impl ItemParamsParsing<'_> { - pub(crate) fn parse(self) -> Result { + pub(crate) fn parse(self) -> Result { let meta = self.meta; if let syn::Meta::NameValue(meta) = meta { let val = &meta.value; let name = syn::parse2(val.to_token_stream())?; - return Ok(ItemParams { + return Ok(SymbolParams { name: Some(SpannedKey::new(&meta.path, name)?), vis: None, docs: None, @@ -60,7 +60,7 @@ impl ItemParamsParsing<'_> { } } - let params = ItemParams { + let params = SymbolParams { name: full.name, vis: full.vis, docs: full.doc, diff --git a/website/.vitepress/config.mts b/website/.vitepress/config.mts index da2446c1..21790290 100644 --- a/website/.vitepress/config.mts +++ b/website/.vitepress/config.mts @@ -156,8 +156,8 @@ export default defineConfig({ link: "/reference/builder", items: [ { - text: "Item attributes", - link: "/reference/builder#item-attributes", + text: "Top-level attributes", + link: "/reference/builder#top-level-attributes", items: [ { text: "builder_type", @@ -175,14 +175,14 @@ export default defineConfig({ text: "finish_fn", link: "/reference/builder#finish-fn", }, - { - text: "start_fn", - link: "/reference/builder#start-fn", - }, { text: "on", link: "/reference/builder#on", }, + { + text: "start_fn", + link: "/reference/builder#start-fn", + }, ], }, { @@ -193,6 +193,10 @@ export default defineConfig({ text: "default", link: "/reference/builder#default", }, + { + text: "finish_fn", + link: "/reference/builder#finish-fn-1", + }, { text: "into", link: "/reference/builder#into", @@ -209,11 +213,6 @@ export default defineConfig({ text: "start_fn", link: "/reference/builder#start-fn-1", }, - { - text: "finish_fn", - link: "/reference/builder#finish-fn-1", - }, - ], }, ], diff --git a/website/reference/builder.md b/website/reference/builder.md index 6784f223..bbbb09ca 100644 --- a/website/reference/builder.md +++ b/website/reference/builder.md @@ -6,8 +6,8 @@ outline: [2, 3] You can generate a builder using three different kinds of syntax (struct, free function, associated method). They all share two common groups of attributes. -- [Item attributes](#item-attributes) - apply to a `struct` or `fn` declaration itself. -- [Member attributes](#member-attributes) - apply to a `struct` field or `fn` argument. +- [Top-level attributes](#item-attributes) - placed on a `struct` or `fn` declaration. +- [Member attributes](#member-attributes) - placed on a `struct` field or `fn` argument. See examples. Make sure to click through the tabs: @@ -17,7 +17,7 @@ See examples. Make sure to click through the tabs: use bon::Builder; #[derive(Builder)] -#[builder(finish_fn = finish)] // <-- this is an item attribute // [!code highlight] +#[builder(finish_fn = finish)] // <-- this is a top-level attribute // [!code highlight] struct Example { #[builder(default)] // <-- this is a member attribute // [!code highlight] field: u32 @@ -27,7 +27,7 @@ struct Example { ```rust [Free function] use bon::builder; -#[builder(finish_fn = finish)] // <-- this is an item attribute // [!code highlight] +#[builder(finish_fn = finish)] // <-- this is a top-level attribute // [!code highlight] fn example( #[builder(default)] // <-- this is a member attribute // [!code highlight] arg: u32 @@ -41,7 +41,7 @@ struct Example; #[bon] impl Example { - #[builder(finish_fn = finish)] // <-- this is an item attribute // [!code highlight] + #[builder(finish_fn = finish)] // <-- this is a top-level attribute // [!code highlight] fn example( #[builder(default)] // <-- this is a member attribute // [!code highlight] arg: u32 @@ -59,7 +59,7 @@ Most of the attributes apply to all kinds of syntax. However, some of them are o ::: -## Item attributes +## Top-level attributes ### `builder_type` @@ -457,57 +457,6 @@ assert_eq!(response, "Some article with id 42"); ::: -### `start_fn` - -**Applies to:** - -Overrides the name and visibility of the associated method that starts the building process, i.e. returns the builder for the struct. - -The default name for this method is `builder`, and the default visibility is the same as the visibility of the struct itself. - -This attribute can take several forms. - -- Simple: `#[builder(start_fn = identifier)]`. Overrides only the name of the "start" method. -- Verbose: `#[builder(start_fn(name = identifier, vis = "visibility"))]`. - Allows overriding both the name and the visibility of the "start" method. - Each key is optional. The `vis` must be specified as a string literal e.g. `"pub(crate)"`, `"pub"` or `""` (empty string means private visibility). - -**Example:** - -::: code-group - -```rust [Simple form] -use bon::Builder; - -#[derive(Builder)] -#[builder(start_fn = init)] // [!code highlight] -struct User { - id: u32 -} - -User::init() // [!code highlight] - .id(42) - .build(); -``` - -```rust [Verbose form] -use bon::Builder; - -// `User::init()` method will have `pub(crate)` visibility // [!code highlight] -// Use `vis = ""` to make it fully private instead // [!code highlight] -#[derive(Builder)] -#[builder(start_fn(name = init, vis = "pub(crate)"))] // [!code highlight] -pub struct User { - id: u32 -} - -User::init() // [!code highlight] - .id(42) - .build(); -``` - -::: - ### `on` **Applies to:** @@ -646,6 +595,57 @@ Example::builder() .build(); ``` +### `start_fn` + +**Applies to:** + +Overrides the name and visibility of the associated method that starts the building process, i.e. returns the builder for the struct. + +The default name for this method is `builder`, and the default visibility is the same as the visibility of the struct itself. + +This attribute can take several forms. + +- Simple: `#[builder(start_fn = identifier)]`. Overrides only the name of the "start" method. +- Verbose: `#[builder(start_fn(name = identifier, vis = "visibility"))]`. + Allows overriding both the name and the visibility of the "start" method. + Each key is optional. The `vis` must be specified as a string literal e.g. `"pub(crate)"`, `"pub"` or `""` (empty string means private visibility). + +**Example:** + +::: code-group + +```rust [Simple form] +use bon::Builder; + +#[derive(Builder)] +#[builder(start_fn = init)] // [!code highlight] +struct User { + id: u32 +} + +User::init() // [!code highlight] + .id(42) + .build(); +``` + +```rust [Verbose form] +use bon::Builder; + +// `User::init()` method will have `pub(crate)` visibility // [!code highlight] +// Use `vis = ""` to make it fully private instead // [!code highlight] +#[derive(Builder)] +#[builder(start_fn(name = init, vis = "pub(crate)"))] // [!code highlight] +pub struct User { + id: u32 +} + +User::init() // [!code highlight] + .id(42) + .build(); +``` + +::: + ## Member attributes ### `default` @@ -859,6 +859,119 @@ The `self` parameter in associated methods is not available to the `default` exp This attribute is incompatible with members of `Option` type, since `Option` already implies the default value of `None`. +### `finish_fn` + +**Applies to:** + +Makes the member a positional argument on the finishing function that consumes the builder and returns the resulting object (for struct syntax) or performs the requested action (for function/method syntax). + +The ordering of members annotated with `#[builder(finish_fn)]` matters! They will appear in the same order relative to each other in the finishing function signature. They must also be declared at the top of the members list strictly after members annotated with [`#[builder(start_fn)]`](#start-fn-1) (if any). + +This ensures a consistent initialization order, and it makes these members available for expressions in `#[builder(default/skip = ...)]` for regular members that follow them. + +::: tip + +Don't confuse this with the top-level [`#[builder(finish_fn = ...)]`](#finish-fn) attribute that can be used to configure the name and visibility of the finishing function. You'll likely want to use it in combination with this member-level attribute to define a better name for the finishing function. + +::: + +**Example:** + +::: code-group + +```rust [Struct field] +use bon::Builder; + +#[derive(Builder)] +// Top-level attribute to give a better name for the finishing function // [!code highlight] +#[builder(finish_fn = sign)] // [!code highlight] +struct Message { + // Member-level attribute to mark the member as a parameter of `sign()` // [!code highlight] + #[builder(finish_fn)] // [!code highlight] + author_first_name: String, + + #[builder(finish_fn)] // [!code highlight] + author_last_name: String, + + payload: String, +} + +let message = Message::builder() + .payload("Bon is great! Give it a ⭐".to_owned()) + .sign("Sweetie".to_owned(), "Drops".to_owned()); + +assert_eq!(message.payload, "Bon is great! Give it a ⭐"); +assert_eq!(message.author_first_name, "Sweetie"); +assert_eq!(message.author_last_name, "Drops"); +``` + +```rust [Free function] +use bon::builder; + +// Top-level attribute to give a better name for the finishing function // [!code highlight] +#[builder(finish_fn = send)] // [!code highlight] +fn message( + // Member-level attribute to mark the member as a parameter of `sign()` // [!code highlight] + #[builder(finish_fn)] // [!code highlight] + receiver_first_name: String, + + #[builder(finish_fn)] // [!code highlight] + receiver_last_name: String, + + payload: String, +) {} + +message() + .payload("Bon is great! Give it a ⭐".to_owned()) + .send("Sweetie".to_owned(), "Drops".to_owned()); +``` + +```rust [Associated method] +use bon::bon; + +struct Chat {} + +#[bon] +impl Chat { + // Top-level attribute to give a better name for the finishing function // [!code highlight] + #[builder(finish_fn = send)] // [!code highlight] + fn message( + &self, + + // Member-level attribute to mark the member as a parameter of `sign()` // [!code highlight] + #[builder(finish_fn)] // [!code highlight] + receiver_first_name: String, + + #[builder(finish_fn)] // [!code highlight] + receiver_last_name: String, + + payload: String, + ) {} +} + +let chat = Chat {}; + +chat.message() + .payload("Bon is great! Give it a ⭐".to_owned()) + .send("Sweetie".to_owned(), "Drops".to_owned()); +``` + +::: + +You can also combine this attribute with [`#[builder(into)]`](#into) or [`#[builder(on(..., into))]`](#on) to add an into conversion for the parameter. + +Importantly, `Into` conversions for such members work slightly differently from the regular (named) members in regard to the `Option` type. The `Option` type gives no additional meaning to the member annotated with `#[builder(finish_fn)]`. Thus, it is matched by the type pattern of `on(..., into)` and wrapped with `impl Into>` as any other type. + +::: tip + +In general, it's not recommended to annotate optional members with `#[builder(finish_fn)]` because you can't omit setting them using the positional function call syntax. + +::: + +*[Member]: Struct field or a function argument +*[member]: Struct field or a function argument +*[members]: Struct fields or function arguments + ### `into` **Applies to:** @@ -1198,116 +1311,3 @@ Importantly, `Into` conversions for such members work slightly differently from In general, it's not recommended to annotate optional members with `#[builder(start_fn)]` because you can't omit setting them using the positional function call syntax. ::: - -### `finish_fn` - -**Applies to:** - -Makes the member a positional argument on the finishing function that consumes the builder and returns the resulting object (for struct syntax) or performs the requested action (for function/method syntax). - -The ordering of members annotated with `#[builder(finish_fn)]` matters! They will appear in the same order relative to each other in the finishing function signature. They must also be declared at the top of the members list strictly after members annotated with [`#[builder(start_fn)]`](#start-fn-1) (if any). - -This ensures a consistent initialization order, and it makes these members available for expressions in `#[builder(default/skip = ...)]` for regular members that follow them. - -::: tip - -Don't confuse this with the top-level [`#[builder(finish_fn = ...)]`](#finish-fn) attribute that can be used to configure the name and visibility of the finishing function. You'll likely want to use it in combination with this member-level attribute to define a better name for the finishing function. - -::: - -**Example:** - -::: code-group - -```rust [Struct field] -use bon::Builder; - -#[derive(Builder)] -// Top-level attribute to give a better name for the finishing function // [!code highlight] -#[builder(finish_fn = sign)] // [!code highlight] -struct Message { - // Member-level attribute to mark the member as a parameter of `sign()` // [!code highlight] - #[builder(finish_fn)] // [!code highlight] - author_first_name: String, - - #[builder(finish_fn)] // [!code highlight] - author_last_name: String, - - payload: String, -} - -let message = Message::builder() - .payload("Bon is great! Give it a ⭐".to_owned()) - .sign("Sweetie".to_owned(), "Drops".to_owned()); - -assert_eq!(message.payload, "Bon is great! Give it a ⭐"); -assert_eq!(message.author_first_name, "Sweetie"); -assert_eq!(message.author_last_name, "Drops"); -``` - -```rust [Free function] -use bon::builder; - -// Top-level attribute to give a better name for the finishing function // [!code highlight] -#[builder(finish_fn = send)] // [!code highlight] -fn message( - // Member-level attribute to mark the member as a parameter of `sign()` // [!code highlight] - #[builder(finish_fn)] // [!code highlight] - receiver_first_name: String, - - #[builder(finish_fn)] // [!code highlight] - receiver_last_name: String, - - payload: String, -) {} - -message() - .payload("Bon is great! Give it a ⭐".to_owned()) - .send("Sweetie".to_owned(), "Drops".to_owned()); -``` - -```rust [Associated method] -use bon::bon; - -struct Chat {} - -#[bon] -impl Chat { - // Top-level attribute to give a better name for the finishing function // [!code highlight] - #[builder(finish_fn = send)] // [!code highlight] - fn message( - &self, - - // Member-level attribute to mark the member as a parameter of `sign()` // [!code highlight] - #[builder(finish_fn)] // [!code highlight] - receiver_first_name: String, - - #[builder(finish_fn)] // [!code highlight] - receiver_last_name: String, - - payload: String, - ) {} -} - -let chat = Chat {}; - -chat.message() - .payload("Bon is great! Give it a ⭐".to_owned()) - .send("Sweetie".to_owned(), "Drops".to_owned()); -``` - -::: - -You can also combine this attribute with [`#[builder(into)]`](#into) or [`#[builder(on(..., into))]`](#on) to add an into conversion for the parameter. - -Importantly, `Into` conversions for such members work slightly differently from the regular (named) members in regard to the `Option` type. The `Option` type gives no additional meaning to the member annotated with `#[builder(finish_fn)]`. Thus, it is matched by the type pattern of `on(..., into)` and wrapped with `impl Into>` as any other type. - -::: tip - -In general, it's not recommended to annotate optional members with `#[builder(finish_fn)]` because you can't omit setting them using the positional function call syntax. - -::: - -*[Member]: Struct field or a function argument -*[member]: Struct field or a function argument -*[members]: Struct fields or function arguments From 01ae5de93196baea8786e3673a35149f8e57efe8 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 24 Oct 2024 22:25:45 +0000 Subject: [PATCH 02/12] Implement --- Cargo.lock | 1 + benchmarks/runtime/src/args_10.rs | 4 +- benchmarks/runtime/src/args_10_alloc.rs | 4 +- benchmarks/runtime/src/args_10_structs.rs | 4 +- benchmarks/runtime/src/args_20.rs | 4 +- benchmarks/runtime/src/args_3.rs | 4 +- benchmarks/runtime/src/args_5.rs | 4 +- bon-macros/Cargo.toml | 1 + bon-macros/src/bon.rs | 30 +- .../builder/builder_gen/builder_derives.rs | 20 +- .../src/builder/builder_gen/finish_fn.rs | 4 +- .../src/builder/builder_gen/input_fn.rs | 354 ++++++++---------- .../src/builder/builder_gen/input_struct.rs | 102 +++-- .../member/{params => config}/blanket.rs | 32 +- .../member/{params => config}/mod.rs | 12 +- .../{params => config}/setter_closure.rs | 0 .../member/{params => config}/setters.rs | 12 +- .../builder_gen/member/into_conversion.rs | 26 +- .../src/builder/builder_gen/member/mod.rs | 32 +- .../src/builder/builder_gen/member/named.rs | 50 +-- bon-macros/src/builder/builder_gen/mod.rs | 12 +- bon-macros/src/builder/builder_gen/models.rs | 32 +- .../src/builder/builder_gen/setters/mod.rs | 34 +- .../src/builder/builder_gen/state_mod.rs | 5 +- .../builder_gen/top_level_config/mod.rs | 132 +++++++ .../on_params.rs => top_level_config/on.rs} | 6 +- .../builder_gen/top_level_params/mod.rs | 87 ----- bon-macros/src/builder/item_fn.rs | 12 +- bon-macros/src/builder/item_impl.rs | 36 +- bon-macros/src/builder/mod.rs | 29 +- bon-macros/src/{error.rs => error/mod.rs} | 22 +- bon-macros/src/error/panic_context.rs | 208 ++++++++++ bon-macros/src/normalization/cfg/mod.rs | 62 +-- .../parsing/{item_params.rs => item_sig.rs} | 17 +- bon-macros/src/parsing/mod.rs | 36 +- bon-macros/src/parsing/simple_closure.rs | 12 +- bon-macros/src/parsing/spanned_key.rs | 4 + bon-macros/src/util/attrs.rs | 4 +- bon-macros/src/util/ide.rs | 2 +- bon-macros/src/util/path.rs | 17 + bon/src/{private => __private}/cfg_eval.rs | 0 bon/src/{private => __private}/derives.rs | 0 bon/src/{private => __private}/ide.rs | 8 + bon/src/{private => __private}/mod.rs | 0 bon/src/builder_state.rs | 4 +- bon/src/collections.rs | 6 +- bon/src/lib.rs | 4 +- .../builder/attr_expose_positional_fn.rs | 52 --- .../builder/attr_top_level_start_fn.rs | 90 +++++ bon/tests/integration/builder/mod.rs | 4 +- .../ui/compile_fail/attr_start_finish_fn.rs | 11 - .../compile_fail/attr_start_finish_fn.stderr | 11 - .../attr_top_level_start_finish_fn.rs | 53 +++ 53 files changed, 1039 insertions(+), 673 deletions(-) rename bon-macros/src/builder/builder_gen/member/{params => config}/blanket.rs (68%) rename bon-macros/src/builder/builder_gen/member/{params => config}/mod.rs (96%) rename bon-macros/src/builder/builder_gen/member/{params => config}/setter_closure.rs (100%) rename bon-macros/src/builder/builder_gen/member/{params => config}/setters.rs (79%) create mode 100644 bon-macros/src/builder/builder_gen/top_level_config/mod.rs rename bon-macros/src/builder/builder_gen/{top_level_params/on_params.rs => top_level_config/on.rs} (97%) delete mode 100644 bon-macros/src/builder/builder_gen/top_level_params/mod.rs rename bon-macros/src/{error.rs => error/mod.rs} (90%) create mode 100644 bon-macros/src/error/panic_context.rs rename bon-macros/src/parsing/{item_params.rs => item_sig.rs} (78%) rename bon/src/{private => __private}/cfg_eval.rs (100%) rename bon/src/{private => __private}/derives.rs (100%) rename bon/src/{private => __private}/ide.rs (90%) rename bon/src/{private => __private}/mod.rs (100%) delete mode 100644 bon/tests/integration/builder/attr_expose_positional_fn.rs create mode 100644 bon/tests/integration/builder/attr_top_level_start_fn.rs delete mode 100644 bon/tests/integration/ui/compile_fail/attr_start_finish_fn.rs delete mode 100644 bon/tests/integration/ui/compile_fail/attr_start_finish_fn.stderr create mode 100644 bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs diff --git a/Cargo.lock b/Cargo.lock index 8774fd16..ebcf9cc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,6 +105,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", + "rustversion", "syn", ] diff --git a/benchmarks/runtime/src/args_10.rs b/benchmarks/runtime/src/args_10.rs index 6d6f268e..d7188771 100644 --- a/benchmarks/runtime/src/args_10.rs +++ b/benchmarks/runtime/src/args_10.rs @@ -46,8 +46,8 @@ pub fn builder_bench() -> u32 { .call() } -#[builder(expose_positional_fn = regular)] -fn builder( +#[builder(start_fn = builder)] +fn regular( arg1: &str, arg2: u32, arg3: bool, diff --git a/benchmarks/runtime/src/args_10_alloc.rs b/benchmarks/runtime/src/args_10_alloc.rs index 06dc976d..c43d91c6 100644 --- a/benchmarks/runtime/src/args_10_alloc.rs +++ b/benchmarks/runtime/src/args_10_alloc.rs @@ -46,8 +46,8 @@ pub fn builder_bench() -> u32 { .call() } -#[builder(expose_positional_fn = regular)] -fn builder( +#[builder(start_fn = builder)] +fn regular( arg1: String, arg2: u32, arg3: bool, diff --git a/benchmarks/runtime/src/args_10_structs.rs b/benchmarks/runtime/src/args_10_structs.rs index 037ccff3..a98b3f64 100644 --- a/benchmarks/runtime/src/args_10_structs.rs +++ b/benchmarks/runtime/src/args_10_structs.rs @@ -87,8 +87,8 @@ pub fn builder_bench() -> u32 { .call() } -#[builder(expose_positional_fn = regular)] -fn builder( +#[builder(start_fn = builder)] +fn regular( arg1: Point3D, arg2: u32, arg3: bool, diff --git a/benchmarks/runtime/src/args_20.rs b/benchmarks/runtime/src/args_20.rs index dc87323f..d87c6d29 100644 --- a/benchmarks/runtime/src/args_20.rs +++ b/benchmarks/runtime/src/args_20.rs @@ -121,8 +121,8 @@ pub fn builder_bench() -> u32 { .call() } -#[builder(expose_positional_fn = regular)] -fn builder( +#[builder(start_fn = builder)] +fn regular( arg1: &str, arg2: u32, arg3: bool, diff --git a/benchmarks/runtime/src/args_3.rs b/benchmarks/runtime/src/args_3.rs index cfee7dac..da24e90e 100644 --- a/benchmarks/runtime/src/args_3.rs +++ b/benchmarks/runtime/src/args_3.rs @@ -13,8 +13,8 @@ pub fn builder_bench() -> u32 { builder().arg1(arg1).arg2(arg2).maybe_arg3(arg3).call() } -#[builder(expose_positional_fn = regular)] -fn builder(arg1: &str, arg2: u32, arg3: Option<&str>) -> u32 { +#[builder(start_fn = builder)] +fn regular(arg1: &str, arg2: u32, arg3: Option<&str>) -> u32 { let x = arg1.parse::().unwrap() + arg2; let x = x + arg3.map(|x| x.parse::().unwrap()).unwrap_or(0); x diff --git a/benchmarks/runtime/src/args_5.rs b/benchmarks/runtime/src/args_5.rs index eda0741d..d7af533d 100644 --- a/benchmarks/runtime/src/args_5.rs +++ b/benchmarks/runtime/src/args_5.rs @@ -19,8 +19,8 @@ pub fn builder_bench() -> u32 { .call() } -#[builder(expose_positional_fn = regular)] -fn builder(arg1: &str, arg2: u32, arg3: bool, arg4: Option<&str>, arg5: Option) -> u32 { +#[builder(start_fn = builder)] +fn regular(arg1: &str, arg2: u32, arg3: bool, arg4: Option<&str>, arg5: Option) -> u32 { let x = arg1.parse::().unwrap() + arg2; let x = x + u32::from(arg3); let x = x + arg4.map(|x| x.parse::().unwrap()).unwrap_or(0); diff --git a/bon-macros/Cargo.toml b/bon-macros/Cargo.toml index 9ea686bd..d21db423 100644 --- a/bon-macros/Cargo.toml +++ b/bon-macros/Cargo.toml @@ -58,6 +58,7 @@ quote = "1" syn = { version = "2.0.56", features = ["full", "visit-mut", "visit"] } prettyplease = "0.2" +rustversion = "1.0" [features] default = [] diff --git a/bon-macros/src/bon.rs b/bon-macros/src/bon.rs index 51b115f7..7157e9d5 100644 --- a/bon-macros/src/bon.rs +++ b/bon-macros/src/bon.rs @@ -1,39 +1,35 @@ use crate::builder; -use crate::normalization::{ExpandCfg, ExpansionOutput}; +use crate::normalization::{ExpandCfg, Expansion}; use crate::util::prelude::*; +use darling::ast::NestedMeta; +use darling::FromMeta; pub(crate) fn generate(params: TokenStream, item: TokenStream) -> TokenStream { - crate::error::with_fallback(item.clone(), || try_generate(params, item)) + crate::error::handle_errors(item.clone(), || try_generate(params, item)) .unwrap_or_else(std::convert::identity) } pub(crate) fn try_generate(params: TokenStream, item: TokenStream) -> Result { let item: syn::Item = syn::parse2(item)?; - let macro_path = syn::parse_quote!(::bon::bon); let ctx = ExpandCfg { - macro_path, + current_macro: format_ident!("bon"), params, item, }; - let (params, item) = match ctx.expand_cfg()? { - ExpansionOutput::Expanded { params, item } => (params, item), - ExpansionOutput::Recurse(output) => return Ok(output), + let input = match ctx.expand_cfg()? { + Expansion::Expanded(input) => input, + Expansion::Recurse(output) => return Ok(output), }; - if !params.is_empty() { - bail!( - ¶ms, - "`#[bon]` attribute does not accept any parameters yet, \ - but it will in future releases" - ); - } + let params = NestedMeta::parse_meta_list(input.params)?; + let params = FromMeta::from_list(¶ms)?; - match item { - syn::Item::Impl(item_impl) => builder::item_impl::generate(item_impl), + match input.item { + syn::Item::Impl(item_impl) => builder::item_impl::generate(params, item_impl), _ => bail!( - &item, + &input.item, "`#[bon]` attribute is expected to be placed on an `impl` block \ but it was placed on other syntax instead" ), diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index e720e1c4..faa3651a 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -1,4 +1,4 @@ -use super::top_level_params::{BuilderDerive, BuilderDerives}; +use super::top_level_config::{DeriveConfig, DerivesConfig}; use super::BuilderGenCtx; use crate::builder::builder_gen::Member; use crate::util::prelude::*; @@ -6,7 +6,7 @@ use darling::ast::GenericParamExt; impl BuilderGenCtx { pub(crate) fn builder_derives(&self) -> TokenStream { - let BuilderDerives { clone, debug } = &self.builder_type.derives; + let DerivesConfig { clone, debug } = &self.builder_type.derives; let mut tokens = TokenStream::new(); @@ -31,7 +31,7 @@ impl BuilderGenCtx { fn where_clause_for_derive( &self, target_trait_bounds: &TokenStream, - derive: &BuilderDerive, + derive: &DeriveConfig, ) -> TokenStream { let derive_specific_predicates = derive .bounds @@ -64,7 +64,8 @@ impl BuilderGenCtx { } } - fn derive_clone(&self, derive: &BuilderDerive) -> TokenStream { + fn derive_clone(&self, derive: &DeriveConfig) -> TokenStream { + let bon = &self.bon; let generics_decl = &self.generics.decl_without_defaults; let generic_args = &self.generics.args; let builder_ident = &self.builder_type.ident; @@ -113,7 +114,7 @@ impl BuilderGenCtx { let ty = member.underlying_norm_ty(); quote! { - ::bon::private::derives::clone_member::<#ty>( + #bon::__private::derives::clone_member::<#ty>( &self.#named_members_field.#member_index ) } @@ -153,10 +154,11 @@ impl BuilderGenCtx { } } - fn derive_debug(&self, derive: &BuilderDerive) -> TokenStream { + fn derive_debug(&self, derive: &DeriveConfig) -> TokenStream { let receiver_field = &self.ident_pool.receiver; let start_fn_args_field = &self.ident_pool.start_fn_args; let named_members_field = &self.ident_pool.named_members; + let bon = &self.bon; let format_members = self.members.iter().filter_map(|member| { match member { @@ -168,7 +170,7 @@ impl BuilderGenCtx { if let Some(value) = &self.#named_members_field.#member_index { output.field( #member_ident_str, - ::bon::private::derives::as_dyn_debug::<#member_ty>(value) + #bon::__private::derives::as_dyn_debug::<#member_ty>(value) ); } }) @@ -180,7 +182,7 @@ impl BuilderGenCtx { Some(quote! { output.field( #member_ident_str, - ::bon::private::derives::as_dyn_debug::<#member_ty>( + #bon::__private::derives::as_dyn_debug::<#member_ty>( &self.#start_fn_args_field.#member_index ) ); @@ -199,7 +201,7 @@ impl BuilderGenCtx { quote! { output.field( "self", - ::bon::private::derives::as_dyn_debug::<#ty>( + #bon::__private::derives::as_dyn_debug::<#ty>( &self.#receiver_field ) ); diff --git a/bon-macros/src/builder/builder_gen/finish_fn.rs b/bon-macros/src/builder/builder_gen/finish_fn.rs index 4a274853..1f06d704 100644 --- a/bon-macros/src/builder/builder_gen/finish_fn.rs +++ b/bon-macros/src/builder/builder_gen/finish_fn.rs @@ -32,14 +32,14 @@ impl super::BuilderGenCtx { }; let param_default = member - .params + .config .default .as_ref() .map(|default| default.value.as_ref()); match param_default { Some(Some(default)) => { - let has_into = member.params.into.is_present(); + let has_into = member.config.into.is_present(); let default = if has_into { quote! { Into::into((|| #default)()) } } else { diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index b5d441d3..3593d94c 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -1,72 +1,34 @@ -use super::top_level_params::TopLevelParams; use super::models::FinishFnParams; +use super::top_level_config::TopLevelConfig; use super::{ AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFnBody, Generics, Member, MemberOrigin, RawMember, }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; use crate::normalization::{GenericsNamespace, NormalizeSelfTy, SyntaxVariant}; -use crate::parsing::{SymbolParams, SpannedKey}; +use crate::parsing::{ItemSigConfig, SpannedKey}; use crate::util::prelude::*; -use darling::util::SpannedValue; -use darling::FromMeta; use std::borrow::Cow; use std::rc::Rc; use syn::punctuated::Punctuated; use syn::visit::Visit; use syn::visit_mut::VisitMut; -#[derive(Debug, FromMeta)] -pub(crate) struct FnInputParams { - expose_positional_fn: Option>, - - #[darling(flatten)] - base: TopLevelParams, -} - -#[derive(Debug, Default)] -struct ExposePositionalFnParams { - name: Option, - vis: Option, -} - -impl FromMeta for ExposePositionalFnParams { - fn from_meta(meta: &syn::Meta) -> Result { - match meta { - syn::Meta::Path(_) => { - return Ok(Self::default()); - } - syn::Meta::NameValue(meta) => { - let val = &meta.value; - let name = syn::parse2(quote!(#val))?; - - return Ok(Self { name, vis: None }); - } - syn::Meta::List(_) => {} - } - - #[derive(Debug, FromMeta)] - struct Full { - name: Option, - vis: Option, - } - - let full = Full::from_meta(meta)?; - - let me = Self { - name: full.name, - vis: full.vis, - }; +pub(crate) struct FnInputCtx<'a> { + namespace: &'a GenericsNamespace, + fn_item: SyntaxVariant, + impl_ctx: Option>, + config: TopLevelConfig, - Ok(me) - } + start_fn: StartFnParams, + self_ty_prefix: Option, } -pub(crate) struct FnInputCtx<'a> { +pub(crate) struct FnInputCtxParams<'a> { pub(crate) namespace: &'a GenericsNamespace, pub(crate) fn_item: SyntaxVariant, pub(crate) impl_ctx: Option>, - pub(crate) params: FnInputParams, + pub(crate) config: TopLevelConfig, } pub(crate) struct ImplCtx { @@ -79,20 +41,85 @@ pub(crate) struct ImplCtx { pub(crate) allow_attrs: Vec, } -impl FnInputCtx<'_> { - fn self_ty_prefix(&self) -> Option { - let prefix = self - .impl_ctx - .as_deref()? - .self_ty - .as_path()? - .path - .segments - .last()? - .ident - .to_string(); - - Some(prefix) +impl<'a> FnInputCtx<'a> { + pub(crate) fn new(params: FnInputCtxParams<'a>) -> Self { + let start_fn = params.config.start_fn.clone(); + + let start_fn_ident = start_fn + .name + .map(SpannedKey::into_value) + .unwrap_or_else(|| { + let fn_ident = ¶ms.fn_item.norm.sig.ident; + + // Special case for the method named `new`. We rename it to `builder` + // since this is the name that is conventionally used by starting + // function in the builder pattern. We also want to make + // the `#[builder]` attribute on the method `new` fully compatible + // with deriving a builder from a struct. + if params.impl_ctx.is_some() && fn_ident == "new" { + syn::Ident::new("builder", fn_ident.span()) + } else { + fn_ident.clone() + } + }); + + let start_fn = StartFnParams { + ident: start_fn_ident, + + vis: start_fn.vis.map(SpannedKey::into_value), + + docs: start_fn + .docs + .map(SpannedKey::into_value) + .unwrap_or_else(|| { + params + .fn_item + .norm + .attrs + .iter() + .filter(|attr| attr.is_doc_expr()) + .cloned() + .collect() + }), + + // Override on the start fn to use the the generics from the + // target function itself. We must not duplicate the generics + // from the impl block here + generics: Some(Generics::new( + params + .fn_item + .norm + .sig + .generics + .params + .iter() + .cloned() + .collect(), + params.fn_item.norm.sig.generics.where_clause.clone(), + )), + }; + + let self_ty_prefix = params.impl_ctx.as_deref().and_then(|impl_ctx| { + let prefix = impl_ctx + .self_ty + .as_path()? + .path + .segments + .last()? + .ident + .to_string(); + + Some(prefix) + }); + + Self { + namespace: params.namespace, + fn_item: params.fn_item, + impl_ctx: params.impl_ctx, + config: params.config, + self_ty_prefix, + start_fn, + } } fn assoc_method_ctx(&self) -> Result> { @@ -163,79 +190,21 @@ impl FnInputCtx<'_> { Generics::new(params, where_clause) } - fn builder_ident(&self) -> syn::Ident { - let user_override = self.params.base.builder_type.name.as_deref(); - - if let Some(user_override) = user_override { - return user_override.clone(); - } - - if self.is_method_new() { - return format_ident!("{}Builder", self.self_ty_prefix().unwrap_or_default()); - } - - let pascal_case_fn = self.fn_item.norm.sig.ident.snake_to_pascal_case(); - - format_ident!( - "{}{pascal_case_fn}Builder", - self.self_ty_prefix().unwrap_or_default(), - ) - } - pub(crate) fn adapted_fn(&self) -> Result { let mut orig = self.fn_item.orig.clone(); - let params = self.params.expose_positional_fn.as_ref(); - - orig.vis = params - .map(|params| { - params - .vis - .clone() - // If exposing of positional fn is enabled without an explicit - // visibility, then just use the visibility of the original function. - .unwrap_or_else(|| self.fn_item.norm.vis.clone()) - }) - // By default we change the positional function's visibility to private - // to avoid exposing it to the surrounding code. The surrounding code is - // supposed to use this function through the builder only. - // - // Not that this doesn't guarantee that adjacent code in this module can't - // access the function, therefore we rename it below. - .unwrap_or(syn::Visibility::Inherited); - - let orig_ident = orig.sig.ident.clone(); - - if let Some(params) = params { - let has_no_value = matches!( - params.as_ref(), - ExposePositionalFnParams { - name: None, - vis: None, - } - ); - - if has_no_value && !self.is_method_new() { + if let Some(name) = self.config.start_fn.name.as_deref() { + if *name == orig.sig.ident { bail!( - ¶ms.span(), - "Positional function identifier is required. It must be \ - specified with `#[builder(expose_positional_fn = function_name_here)]`" + &name, + "the starting function name must be different from the name \ + of the positional function under the #[builder] attribute" ) } - } + } else { + // By default the original positional function becomes hidden. + orig.vis = syn::Visibility::Inherited; - orig.sig.ident = params - .and_then(|params| { - params - .name - .clone() - // We treat `new` method specially. In this case we already know the best - // default name for the positional function, which is `new` itself. - .or_else(|| self.is_method_new().then(|| orig.sig.ident)) - }) - // By default we don't want to expose the positional function, so we - // hide it under a generated name to avoid name conflicts. - // // We don't use a random name here because the name of this function // can be used by other macros that may need a stable identifier. // For example, if `#[tracing::instrument]` is placed on the function, @@ -243,7 +212,22 @@ impl FnInputCtx<'_> { // may be indexed in some logs database (e.g. Grafana Loki). If the name // of the span changes the DB index may grow and also log queries won't // be stable. - .unwrap_or_else(|| format_ident!("__orig_{}", orig_ident.raw_name())); + orig.sig.ident = format_ident!("__orig_{}", orig.sig.ident.raw_name()); + + // Remove all doc comments from the function itself to avoid docs duplication + // which may lead to duplicating doc tests, which in turn implies repeated doc + // tests execution, which means worse tests performance. + // + // We don't do this for the case when the positional function is exposed + // alongside the builder which implies that the docs should be visible + // as the function itself is visible. + orig.attrs.retain(|attr| !attr.is_doc_expr()); + + orig.attrs.extend([syn::parse_quote!(#[doc(hidden)])]); + } + + // Also remove any `#[builder]` attributes that were meant for this proc macro. + orig.attrs.retain(|attr| !attr.path().is_ident("builder")); // Remove all doc comments attributes from function arguments, because they are // not valid in that position in regular Rust code. The cool trick is that they @@ -258,31 +242,6 @@ impl FnInputCtx<'_> { .retain(|attr| !attr.is_doc_expr() && !attr.path().is_ident("builder")); } - // Remove all doc comments from the function itself to avoid docs duplication - // which may lead to duplicating doc tests, which in turn implies repeated doc - // tests execution, which means worse tests performance. - // - // Also remove any `#[builder]` attributes that were meant for this proc macro. - orig.attrs - .retain(|attr| !attr.is_doc_expr() && !attr.path().is_ident("builder")); - - let prefix = self - .self_ty_prefix() - .map(|self_ty_prefix| format!("{self_ty_prefix}::")) - .unwrap_or_default(); - - let builder_entry_fn_link = format!("{prefix}{orig_ident}",); - - let doc = format!( - "Positional function equivalent of [`{builder_entry_fn_link}()`].\n\ - See its docs for details.", - ); - - orig.attrs.push(syn::parse_quote!(#[doc = #doc])); - - if self.params.expose_positional_fn.is_none() { - orig.attrs.extend([syn::parse_quote!(#[doc(hidden)])]); - } orig.attrs.push(syn::parse_quote!(#[allow( // It's fine if there are too many positional arguments in the function // because the whole purpose of this macro is to fight with this problem @@ -299,10 +258,6 @@ impl FnInputCtx<'_> { Ok(orig) } - fn is_method_new(&self) -> bool { - self.impl_ctx.is_some() && self.fn_item.norm.sig.ident == "new" - } - pub(crate) fn into_builder_gen_ctx(self) -> Result { let assoc_method_ctx = self.assoc_method_ctx()?; @@ -310,7 +265,7 @@ impl FnInputCtx<'_> { let explanation = "\ but #[bon] attribute is absent on top of the impl block; this \ additional #[bon] attribute on the impl block is required for \ - the macro to see the type of `Self` and properly generate + the macro to see the type of `Self` and properly generate \ the builder struct definition adjacently to the impl block."; if let Some(receiver) = &self.fn_item.orig.sig.receiver() { @@ -330,8 +285,6 @@ impl FnInputCtx<'_> { } } - let builder_ident = self.builder_ident(); - let members = self .fn_item .apply_ref(|fn_item| fn_item.sig.inputs.iter().filter_map(syn::FnArg::as_typed)) @@ -359,7 +312,7 @@ impl FnInputCtx<'_> { }) .collect::>>()?; - let members = Member::from_raw(&self.params.base.on, MemberOrigin::FnArg, members)?; + let members = Member::from_raw(&self.config.on, MemberOrigin::FnArg, members)?; let generics = self.generics(); @@ -368,27 +321,17 @@ impl FnInputCtx<'_> { impl_ctx: self.impl_ctx.clone(), }; - let is_method_new = self.is_method_new(); - - // Special case for `new` methods. We rename them to `builder` - // since this is the name that is used in the builder pattern - let start_fn_ident = if is_method_new { - format_ident!("builder") - } else { - self.fn_item.norm.sig.ident.clone() - }; - - let SymbolParams { + let ItemSigConfig { name: finish_fn_ident, vis: finish_fn_vis, docs: finish_fn_docs, - } = self.params.base.finish_fn; + } = self.config.finish_fn; let finish_fn_ident = finish_fn_ident .map(SpannedKey::into_value) .unwrap_or_else(|| { - // For `new` methods the `build` finisher is more conventional - if is_method_new { + // For `builder` methods the `build` finisher is more conventional + if self.impl_ctx.is_some() && self.start_fn.ident == "builder" { format_ident!("build") } else { format_ident!("call") @@ -429,53 +372,56 @@ impl FnInputCtx<'_> { .chain(fn_allows) .collect(); - let start_fn = StartFnParams { - ident: start_fn_ident, + let builder_ident = || { + let user_override = self.config.builder_type.name.map(SpannedKey::into_value); - // No override for visibility for the start fn is provided here. - // It's supposed to be the same as the original function's visibility. - vis: None, + if let Some(user_override) = user_override { + return user_override; + } - attrs: self - .fn_item - .norm - .attrs - .into_iter() - .filter(<_>::is_doc_expr) - .collect(), + let ty_prefix = self.self_ty_prefix.unwrap_or_default(); - // Override on the start fn to use the the generics from the - // target function itself. We don't need to duplicate the generics - // from the impl block here. - generics: Some(Generics::new( - Vec::from_iter(self.fn_item.norm.sig.generics.params), - self.fn_item.norm.sig.generics.where_clause, - )), + // A special case for the starting function named `builder`. + // We don't insert the `Builder` suffix in this case because + // we this special case should be compatible with deriving + // a builder from a struct. + // + // We can arrive inside of this if only if the function under + // the macro is called `new` or `builder` without `start_fn` + // name override, or if the `start_fn = builder/start_fn(name = builder)` + // is specified in the macro invocation explicitly. + if self.impl_ctx.is_some() && self.start_fn.ident == "builder" { + return format_ident!("{ty_prefix}Builder"); + } + + let pascal_case_fn = self.fn_item.norm.sig.ident.snake_to_pascal_case(); + + format_ident!("{ty_prefix}{pascal_case_fn}Builder") }; - let builder_type = self.params.base.builder_type; let builder_type = BuilderTypeParams { - ident: builder_ident, - derives: self.params.base.derive, - docs: builder_type.docs.map(SpannedKey::into_value), - vis: builder_type.vis.map(SpannedKey::into_value), + ident: builder_ident(), + derives: self.config.derive, + docs: self.config.builder_type.docs.map(SpannedKey::into_value), + vis: self.config.builder_type.vis.map(SpannedKey::into_value), }; BuilderGenCtx::new(BuilderGenCtxParams { + bon: self.config.bon, namespace: Cow::Borrowed(self.namespace), members, allow_attrs, - on_params: self.params.base.on, + on: self.config.on, assoc_method_ctx, generics, orig_item_vis: self.fn_item.norm.vis, builder_type, - state_mod: self.params.base.state_mod, - start_fn, + state_mod: self.config.state_mod, + start_fn: self.start_fn, finish_fn, }) } diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 926d2323..9ffb8838 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -1,76 +1,57 @@ -use super::top_level_params::TopLevelParams; use super::models::FinishFnParams; +use super::top_level_config::TopLevelConfig; use super::{ AssocMethodCtx, BuilderGenCtx, FinishFnBody, Generics, Member, MemberOrigin, RawMember, }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; use crate::normalization::{GenericsNamespace, SyntaxVariant}; -use crate::parsing::{SymbolParams, ItemParamsParsing, SpannedKey}; +use crate::parsing::{SpannedKey, ItemSigConfig}; use crate::util::prelude::*; use darling::FromMeta; use std::borrow::Cow; use syn::visit::Visit; use syn::visit_mut::VisitMut; -#[derive(Debug, FromMeta)] -pub(crate) struct StructInputParams { - #[darling(flatten)] - base: TopLevelParams, - - #[darling(default, with = parse_start_fn)] - start_fn: SymbolParams, -} - -fn parse_start_fn(meta: &syn::Meta) -> Result { - ItemParamsParsing { - meta, - reject_self_mentions: None, - } - .parse() -} - -impl StructInputParams { - fn parse(item_struct: &syn::ItemStruct) -> Result { - let meta = item_struct - .attrs - .iter() - .filter(|attr| attr.path().is_ident("builder")) - .map(|attr| { - let meta = match &attr.meta { - syn::Meta::List(meta) => meta, - syn::Meta::Path(_) => bail!( - &attr.meta, - "this empty `#[builder]` attribute is redundant; remove it" - ), - syn::Meta::NameValue(_) => bail!( - &attr.meta, - "`#[builder = ...]` syntax is unsupported; use `#[builder(...)]` instead" - ), - }; - - crate::parsing::require_non_empty_paren_meta_list_or_name_value(&attr.meta)?; - - let meta = darling::ast::NestedMeta::parse_meta_list(meta.tokens.clone())?; - - Ok(meta) - }) - .collect::>>()? - .into_iter() - .concat(); +fn parse_top_level_params(item_struct: &syn::ItemStruct) -> Result { + let meta = item_struct + .attrs + .iter() + .filter(|attr| attr.path().is_ident("builder")) + .map(|attr| { + let meta = match &attr.meta { + syn::Meta::List(meta) => meta, + syn::Meta::Path(_) => bail!( + &attr.meta, + "this empty `#[builder]` attribute is redundant; remove it" + ), + syn::Meta::NameValue(_) => bail!( + &attr.meta, + "`#[builder = ...]` syntax is unsupported; use `#[builder(...)]` instead" + ), + }; + + crate::parsing::require_non_empty_paren_meta_list_or_name_value(&attr.meta)?; + + let meta = darling::ast::NestedMeta::parse_meta_list(meta.tokens.clone())?; + + Ok(meta) + }) + .collect::>>()? + .into_iter() + .concat(); - Self::from_list(&meta) - } + TopLevelConfig::from_list(&meta) } pub(crate) struct StructInputCtx { struct_item: SyntaxVariant, - params: StructInputParams, + params: TopLevelConfig, struct_ty: syn::Type, } impl StructInputCtx { pub(crate) fn new(orig_struct: syn::ItemStruct) -> Result { - let params = StructInputParams::parse(&orig_struct)?; + let params = parse_top_level_params(&orig_struct)?; let generic_args = orig_struct .generics @@ -138,7 +119,7 @@ impl StructInputCtx { }) .collect::>>()?; - let members = Member::from_raw(&self.params.base.on, MemberOrigin::StructField, members)?; + let members = Member::from_raw(&self.params.on, MemberOrigin::StructField, members)?; let generics = Generics::new( self.struct_item @@ -155,7 +136,7 @@ impl StructInputCtx { struct_ident: self.struct_item.norm.ident.clone(), }; - let SymbolParams { + let ItemSigConfig { name: start_fn_ident, vis: start_fn_vis, docs: start_fn_docs, @@ -165,11 +146,11 @@ impl StructInputCtx { .map(SpannedKey::into_value) .unwrap_or_else(|| syn::Ident::new("builder", self.struct_item.norm.ident.span())); - let SymbolParams { + let ItemSigConfig { name: finish_fn_ident, vis: finish_fn_vis, docs: finish_fn_docs, - } = self.params.base.finish_fn; + } = self.params.finish_fn; let finish_fn_ident = finish_fn_ident .map(SpannedKey::into_value) @@ -209,7 +190,7 @@ impl StructInputCtx { let start_fn = StartFnParams { ident: start_fn_ident, vis: start_fn_vis.map(SpannedKey::into_value), - attrs: start_fn_docs, + docs: start_fn_docs, generics: None, }; @@ -227,14 +208,14 @@ impl StructInputCtx { .collect(); let builder_type = { - let SymbolParams { name, vis, docs } = self.params.base.builder_type; + let ItemSigConfig { name, vis, docs } = self.params.builder_type; let builder_ident = name.map(SpannedKey::into_value).unwrap_or_else(|| { format_ident!("{}Builder", self.struct_item.norm.ident.raw_name()) }); BuilderTypeParams { - derives: self.params.base.derive, + derives: self.params.derive, ident: builder_ident, docs: docs.map(SpannedKey::into_value), vis: vis.map(SpannedKey::into_value), @@ -245,19 +226,20 @@ impl StructInputCtx { namespace.visit_item_struct(&self.struct_item.orig); BuilderGenCtx::new(BuilderGenCtxParams { + bon: self.params.bon, namespace: Cow::Owned(namespace), members, allow_attrs, - on_params: self.params.base.on, + on: self.params.on, assoc_method_ctx, generics, orig_item_vis: self.struct_item.norm.vis, builder_type, - state_mod: self.params.base.state_mod, + state_mod: self.params.state_mod, start_fn, finish_fn, }) diff --git a/bon-macros/src/builder/builder_gen/member/params/blanket.rs b/bon-macros/src/builder/builder_gen/member/config/blanket.rs similarity index 68% rename from bon-macros/src/builder/builder_gen/member/params/blanket.rs rename to bon-macros/src/builder/builder_gen/member/config/blanket.rs index 38da988f..3d1e4b4d 100644 --- a/bon-macros/src/builder/builder_gen/member/params/blanket.rs +++ b/bon-macros/src/builder/builder_gen/member/config/blanket.rs @@ -1,6 +1,6 @@ -use super::MemberParams; -use crate::builder::builder_gen::top_level_params::OnParams; +use super::MemberConfig; use crate::builder::builder_gen::member::MemberOrigin; +use crate::builder::builder_gen::top_level_config::OnConfig; use crate::util::prelude::*; use std::fmt; @@ -19,25 +19,25 @@ impl fmt::Display for BlanketParamName { } impl BlanketParamName { - fn value_in_on_params(&self, on_params: &OnParams) -> darling::util::Flag { + fn value_in_on_config(&self, cfg: &OnConfig) -> darling::util::Flag { match self { - Self::Into => on_params.into, - Self::Overwritable => on_params.overwritable, + Self::Into => cfg.into, + Self::Overwritable => cfg.overwritable, } } - fn value_in_member_params(&self, member_params: &MemberParams) -> darling::util::Flag { + fn value_in_member_config(&self, cfg: &MemberConfig) -> darling::util::Flag { match self { - Self::Into => member_params.into, - Self::Overwritable => member_params.overwritable, + Self::Into => cfg.into, + Self::Overwritable => cfg.overwritable, } } } pub(crate) struct EvalBlanketFlagParam<'a> { - pub(crate) on_params: &'a [OnParams], + pub(crate) on: &'a [OnConfig], pub(crate) param_name: BlanketParamName, - pub(crate) member_params: &'a MemberParams, + pub(crate) member_config: &'a MemberConfig, pub(crate) scrutinee: &'a syn::Type, pub(crate) origin: MemberOrigin, } @@ -45,24 +45,24 @@ pub(crate) struct EvalBlanketFlagParam<'a> { impl EvalBlanketFlagParam<'_> { pub(crate) fn eval(self) -> Result { let Self { - on_params, + on, param_name, - member_params, + member_config, scrutinee, origin, } = self; - let verdict_from_on_params = on_params + let verdict_from_on = on .iter() .map(|params| Ok((params, scrutinee.matches(¶ms.type_pattern)?))) .collect::>>()? .into_iter() .filter(|(_, matched)| *matched) - .map(|(params, _)| param_name.value_in_on_params(params)) + .map(|(params, _)| param_name.value_in_on_config(params)) .find(darling::util::Flag::is_present); - let value_in_member = param_name.value_in_member_params(member_params); - let flag = match (verdict_from_on_params, value_in_member.is_present()) { + let value_in_member = param_name.value_in_member_config(member_config); + let flag = match (verdict_from_on, value_in_member.is_present()) { (Some(_), true) => { bail!( &value_in_member.span(), diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/config/mod.rs similarity index 96% rename from bon-macros/src/builder/builder_gen/member/params/mod.rs rename to bon-macros/src/builder/builder_gen/member/config/mod.rs index 3beb71c2..e4fab79a 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/config/mod.rs @@ -13,7 +13,7 @@ use std::fmt; #[derive(Debug, darling::FromAttributes)] #[darling(attributes(builder))] -pub(crate) struct MemberParams { +pub(crate) struct MemberConfig { /// Enables an `Into` conversion for the setter method. pub(crate) into: darling::util::Flag, @@ -36,7 +36,7 @@ pub(crate) struct MemberParams { /// Configurations for the setter methods. #[darling(with = crate::parsing::parse_non_empty_paren_meta_list)] - pub(crate) setters: Option, + pub(crate) setters: Option, /// Where to place the member in the generated builder methods API. /// By default the member is treated like a named parameter that @@ -97,7 +97,7 @@ impl fmt::Display for ParamName { } } -impl MemberParams { +impl MemberConfig { fn validate_mutually_exclusive( &self, attr_name: ParamName, @@ -218,7 +218,11 @@ impl MemberParams { } if let Some(with) = &self.with { - self.validate_mutually_exclusive(ParamName::With, with.key.span(), &[ParamName::Into])?; + self.validate_mutually_exclusive( + ParamName::With, + with.key.span(), + &[ParamName::Into], + )?; } Ok(()) diff --git a/bon-macros/src/builder/builder_gen/member/params/setter_closure.rs b/bon-macros/src/builder/builder_gen/member/config/setter_closure.rs similarity index 100% rename from bon-macros/src/builder/builder_gen/member/params/setter_closure.rs rename to bon-macros/src/builder/builder_gen/member/config/setter_closure.rs diff --git a/bon-macros/src/builder/builder_gen/member/params/setters.rs b/bon-macros/src/builder/builder_gen/member/config/setters.rs similarity index 79% rename from bon-macros/src/builder/builder_gen/member/params/setters.rs rename to bon-macros/src/builder/builder_gen/member/config/setters.rs index 688a2af6..59d94636 100644 --- a/bon-macros/src/builder/builder_gen/member/params/setters.rs +++ b/bon-macros/src/builder/builder_gen/member/config/setters.rs @@ -1,11 +1,11 @@ -use crate::parsing::{SymbolParams, ItemParamsParsing, SpannedKey}; +use crate::parsing::{ItemSigConfig, ItemSigConfigParsing, SpannedKey}; use crate::util::prelude::*; use darling::FromMeta; const DOCS_CONTEXT: &str = "builder struct's impl block"; -fn parse_setter_fn(meta: &syn::Meta) -> Result> { - let params = ItemParamsParsing { +fn parse_setter_fn(meta: &syn::Meta) -> Result> { + let params = ItemSigConfigParsing { meta, reject_self_mentions: Some(DOCS_CONTEXT), } @@ -19,7 +19,7 @@ fn parse_docs(meta: &syn::Meta) -> Result>> { } #[derive(Debug, FromMeta)] -pub(crate) struct SettersParams { +pub(crate) struct SettersMeta { pub(crate) name: Option>, pub(crate) vis: Option>, @@ -37,12 +37,12 @@ pub(crate) struct SettersFnParams { /// /// By default, it's named `{member}` without any prefix or suffix. #[darling(default, with = parse_setter_fn, map = Some)] - pub(crate) some_fn: Option>, + pub(crate) some_fn: Option>, /// The setter that accepts the value of type `Option` for a member of /// type `Option` or with `#[builder(default)]`. /// /// By default, it's named `maybe_{member}`. #[darling(default, with = parse_setter_fn, map = Some)] - pub(crate) option_fn: Option>, + pub(crate) option_fn: Option>, } diff --git a/bon-macros/src/builder/builder_gen/member/into_conversion.rs b/bon-macros/src/builder/builder_gen/member/into_conversion.rs index a4edfbbf..0661e380 100644 --- a/bon-macros/src/builder/builder_gen/member/into_conversion.rs +++ b/bon-macros/src/builder/builder_gen/member/into_conversion.rs @@ -1,13 +1,13 @@ -use super::params::{BlanketParamName, EvalBlanketFlagParam}; +use super::config::{BlanketParamName, EvalBlanketFlagParam}; use super::{NamedMember, PositionalFnArgMember}; -use crate::builder::builder_gen::top_level_params::OnParams; +use crate::builder::builder_gen::top_level_config::OnConfig; use crate::util::prelude::*; impl NamedMember { - pub(super) fn merge_param_into(&mut self, on_params: &[OnParams]) -> Result { + pub(super) fn merge_param_into(&mut self, on: &[OnConfig]) -> Result { // `with` is mutually exclusive with `into`. So there is nothing to merge here // if `with` is present. - if self.params.with.is_some() { + if self.config.with.is_some() { return Ok(()); } @@ -17,10 +17,10 @@ impl NamedMember { // if `Option` is used or the member of type `T` has `#[builder(default)]` on it. let scrutinee = self.underlying_orig_ty(); - self.params.into = EvalBlanketFlagParam { - on_params, + self.config.into = EvalBlanketFlagParam { + on, param_name: BlanketParamName::Into, - member_params: &self.params, + member_config: &self.config, scrutinee, origin: self.origin, } @@ -31,16 +31,16 @@ impl NamedMember { } impl PositionalFnArgMember { - pub(crate) fn merge_param_into(&mut self, on_params: &[OnParams]) -> Result { + pub(crate) fn merge_param_into(&mut self, on: &[OnConfig]) -> Result { // Positional members are never optional. Users must always specify them, so there // is no need for us to look into the `Option` generic parameter, because the // `Option` itself is the target of the into conversion, not the `T` inside it. let scrutinee = self.ty.orig.as_ref(); - self.params.into = EvalBlanketFlagParam { - on_params, + self.meta.into = EvalBlanketFlagParam { + on, param_name: BlanketParamName::Into, - member_params: &self.params, + member_config: &self.meta, scrutinee, origin: self.origin, } @@ -53,7 +53,7 @@ impl PositionalFnArgMember { let ty = &self.ty.norm; let ident = &self.ident; - if self.params.into.is_present() { + if self.meta.into.is_present() { quote! { #ident: impl Into<#ty> } } else { quote! { #ident: #ty } @@ -63,7 +63,7 @@ impl PositionalFnArgMember { pub(crate) fn init_expr(&self) -> TokenStream { let ident = &self.ident; - if self.params.into.is_present() { + if self.meta.into.is_present() { quote! { Into::into(#ident) } diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index d02b10d0..af6d789e 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -1,16 +1,16 @@ mod into_conversion; +mod config; mod named; -mod params; +pub(crate) use config::*; pub(crate) use named::*; -pub(crate) use params::*; -use super::top_level_params::OnParams; +use super::top_level_config::OnConfig; use crate::normalization::SyntaxVariant; use crate::parsing::SpannedKey; use crate::util::prelude::*; use darling::FromAttributes; -use params::MemberParams; +use config::MemberConfig; use std::fmt; #[derive(Debug, Clone, Copy)] @@ -71,7 +71,7 @@ pub(crate) struct PositionalFnArgMember { pub(crate) ty: SyntaxVariant>, /// Parameters configured by the user explicitly via attributes - pub(crate) params: MemberParams, + pub(crate) meta: MemberConfig, } /// Member that was skipped by the user with `#[builder(skip)]` @@ -99,7 +99,7 @@ impl Member { // (there is an other lint that checks for this). #[allow(single_use_lifetimes)] pub(crate) fn from_raw<'a>( - on_params: &[OnParams], + on: &[OnConfig], origin: MemberOrigin, members: impl IntoIterator>, ) -> Result> { @@ -114,7 +114,7 @@ impl Member { } } - let params = MemberParams::from_attributes(member.attrs)?; + let params = MemberConfig::from_attributes(member.attrs)?; params.validate(origin)?; Ok((member, params)) }) @@ -125,12 +125,12 @@ impl Member { let mut output = vec![]; for index in 0.. { - let next = members.next_if(|(_, params)| params.start_fn.is_present()); - let (member, params) = match next { + let next = members.next_if(|(_, meta)| meta.start_fn.is_present()); + let (member, meta) = match next { Some(item) => item, None => break, }; - let base = PositionalFnArgMember::new(origin, member, on_params, params)?; + let base = PositionalFnArgMember::new(origin, member, on, meta)?; output.push(Self::StartFnArg(StartFnArgMember { base, index: index.into(), @@ -140,7 +140,7 @@ impl Member { while let Some((member, params)) = members.next_if(|(_, params)| params.finish_fn.is_present()) { - let member = PositionalFnArgMember::new(origin, member, on_params, params)?; + let member = PositionalFnArgMember::new(origin, member, on, params)?; output.push(Self::FinishFnArg(member)); } @@ -191,11 +191,11 @@ impl Member { origin, name: MemberName::new(ident, ¶ms), ty, - params, + config: params, docs, }; - member.merge_on_params(on_params)?; + member.merge_on_params(on)?; member.validate()?; output.push(Self::Named(member)); @@ -251,8 +251,8 @@ impl PositionalFnArgMember { fn new( origin: MemberOrigin, member: RawMember<'_>, - on_params: &[OnParams], - params: MemberParams, + on_params: &[OnConfig], + params: MemberConfig, ) -> Result { let RawMember { attrs: _, @@ -264,7 +264,7 @@ impl PositionalFnArgMember { origin, ident, ty, - params, + meta: params, }; me.merge_param_into(on_params)?; diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index 81139304..3dde1575 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -1,9 +1,9 @@ -use super::params::MemberParams; -use super::{params, MemberOrigin}; -use crate::builder::builder_gen::top_level_params::OnParams; -use crate::builder::builder_gen::member::params::SettersFnParams; +use super::config::MemberConfig; +use super::{config, MemberOrigin}; +use crate::builder::builder_gen::top_level_config::OnConfig; +use crate::builder::builder_gen::member::config::SettersFnParams; use crate::normalization::SyntaxVariant; -use crate::parsing::{SymbolParams, SpannedKey}; +use crate::parsing::{ItemSigConfig, SpannedKey}; use crate::util::prelude::*; #[derive(Debug)] @@ -40,7 +40,7 @@ pub(crate) struct MemberName { } impl MemberName { - pub(crate) fn new(orig: syn::Ident, params: &MemberParams) -> Self { + pub(crate) fn new(orig: syn::Ident, params: &MemberConfig) -> Self { let snake = params.name.clone().unwrap_or_else(|| { let orig_str = orig.to_string(); let norm = orig_str @@ -89,12 +89,12 @@ pub(crate) struct NamedMember { pub(crate) ty: SyntaxVariant>, /// Parameters configured by the user explicitly via attributes - pub(crate) params: MemberParams, + pub(crate) config: MemberConfig, } impl NamedMember { pub(super) fn validate(&self) -> Result { - if let Some(default) = &self.params.default { + if let Some(default) = &self.config.default { if self.is_special_option_ty() { bail!( &default.key, @@ -105,7 +105,7 @@ impl NamedMember { } let member_docs_not_copied = self - .params + .config .setters .as_ref() .map(|setters| { @@ -117,8 +117,8 @@ impl NamedMember { matches!( (some_fn.as_deref(), option_fn.as_deref()), ( - Some(SymbolParams { docs: Some(_), .. }), - Some(SymbolParams { docs: Some(_), .. }) + Some(ItemSigConfig { docs: Some(_), .. }), + Some(ItemSigConfig { docs: Some(_), .. }) ) ) }) @@ -133,9 +133,9 @@ impl NamedMember { self.validate_setters_params()?; - if self.params.transparent.is_present() && !self.ty.norm.is_option() { + if self.config.transparent.is_present() && !self.ty.norm.is_option() { bail!( - &self.params.transparent.span(), + &self.config.transparent.span(), "`#[builder(transparent)]` can only be applied to members of \ type `Option` to disable their special handling", ); @@ -145,7 +145,7 @@ impl NamedMember { } fn validate_setters_params(&self) -> Result { - let setters = match &self.params.setters { + let setters = match &self.config.setters { Some(setters) => setters, None => return Ok(()), }; @@ -183,9 +183,9 @@ impl NamedMember { // Lint from nightly. `&Option` is used to reduce syntax at the call site #[allow(unknown_lints, clippy::ref_option)] fn validate_unused_setters_cfg( - overrides: &[&SpannedKey], + overrides: &[&SpannedKey], config: &Option>, - get_val: impl Fn(&SymbolParams) -> &Option>, + get_val: impl Fn(&ItemSigConfig) -> &Option>, ) -> Result { let config = match config { Some(config) => config, @@ -217,13 +217,13 @@ impl NamedMember { /// Returns `true` if this member is of `Option<_>` type, but returns `false` /// if `#[builder(transparent)]` is set. pub(crate) fn is_special_option_ty(&self) -> bool { - !self.params.transparent.is_present() && self.ty.norm.is_option() + !self.config.transparent.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.params.default.is_none() && !self.is_special_option_ty() + self.config.default.is_none() && !self.is_special_option_ty() } /// A stateful member is the one that has a corresponding associated type in @@ -231,7 +231,7 @@ impl NamedMember { /// 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.params.overwritable.is_present() + self.is_required() || !self.config.overwritable.is_present() } /// Returns the normalized type of the member stripping the `Option<_>` @@ -247,7 +247,7 @@ impl NamedMember { } fn underlying_ty<'m>(&'m self, ty: &'m syn::Type) -> &'m syn::Type { - if self.params.transparent.is_present() || self.params.default.is_some() { + if self.config.transparent.is_present() || self.config.default.is_some() { ty } else { ty.option_type_param().unwrap_or(ty) @@ -258,16 +258,16 @@ impl NamedMember { self.index == other.index } - pub(crate) fn merge_on_params(&mut self, on_params: &[OnParams]) -> Result { + pub(crate) fn merge_on_params(&mut self, on_params: &[OnConfig]) -> Result { self.merge_param_into(on_params)?; // FIXME: refactor this to make it more consistent with `into` // and allow for non-boolean flags in `OnParams`. E.g. add support // for `with = closure` to `on` as well. - self.params.overwritable = params::EvalBlanketFlagParam { - on_params, - param_name: params::BlanketParamName::Overwritable, - member_params: &self.params, + self.config.overwritable = config::EvalBlanketFlagParam { + on: on_params, + param_name: config::BlanketParamName::Overwritable, + member_config: &self.config, scrutinee: self.underlying_norm_ty(), origin: self.origin, } diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 580e0c37..e8cfdbca 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -1,5 +1,5 @@ mod builder_derives; -mod top_level_params; +mod top_level_config; mod finish_fn; mod member; mod models; @@ -9,6 +9,8 @@ mod state_mod; pub(crate) mod input_fn; pub(crate) mod input_struct; +pub(crate) use top_level_config::TopLevelConfig; + use crate::util::prelude::*; use member::{Member, MemberOrigin, NamedMember, RawMember, StartFnArgMember}; use models::{AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFnBody, Generics}; @@ -44,7 +46,7 @@ impl BuilderGenCtx { let builder_derives = self.builder_derives(); let default_allows = syn::parse_quote!(#[allow( - // We have a `deprecated` lint on all `bon::private` items which we + // We have a `deprecated` lint on all `bon::__private` items which we // use in the generated code extensively deprecated )]); @@ -131,7 +133,7 @@ impl BuilderGenCtx { /// hints. fn ide_hints(&self) -> TokenStream { let type_patterns = self - .on_params + .on .iter() .map(|params| ¶ms.type_pattern) .collect::>(); @@ -163,7 +165,7 @@ impl BuilderGenCtx { fn start_fn(&self) -> syn::ItemFn { let builder_ident = &self.builder_type.ident; - let attrs = &self.start_fn.attrs; + let docs = &self.start_fn.docs; let vis = &self.start_fn.vis; let start_fn_ident = &self.start_fn.ident; @@ -231,7 +233,7 @@ impl BuilderGenCtx { }; syn::parse_quote! { - #(#attrs)* + #(#docs)* #[inline(always)] #[allow( // This is intentional. We want the builder syntax to compile away diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 7d11f1bd..d03ab6b4 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -1,7 +1,7 @@ -use super::top_level_params::{BuilderDerives, OnParams}; use super::member::Member; +use super::top_level_config::{DerivesConfig, OnConfig}; use crate::normalization::GenericsNamespace; -use crate::parsing::{SymbolParams, SpannedKey}; +use crate::parsing::{SpannedKey, ItemSigConfig}; use crate::util::prelude::*; use std::borrow::Cow; @@ -62,8 +62,7 @@ pub(super) struct StartFn { pub(super) ident: syn::Ident, pub(super) vis: syn::Visibility, - /// Additional attributes to apply to the item - pub(super) attrs: Vec, + pub(super) docs: Vec, /// Overrides the default generics pub(super) generics: Option, @@ -75,8 +74,7 @@ pub(super) struct StartFnParams { /// If present overrides the default visibility derived from the builder's type. pub(super) vis: Option, - /// Additional attributes to apply to the item - pub(super) attrs: Vec, + pub(super) docs: Vec, /// Overrides the default generics pub(super) generics: Option, @@ -88,14 +86,14 @@ pub(super) struct BuilderType { /// Visibility of the builder module itself. pub(super) vis: syn::Visibility, - pub(super) derives: BuilderDerives, + pub(super) derives: DerivesConfig, pub(super) docs: Vec, } pub(super) struct BuilderTypeParams { pub(super) ident: syn::Ident, pub(super) vis: Option, - pub(super) derives: BuilderDerives, + pub(super) derives: DerivesConfig, pub(super) docs: Option>, } @@ -134,6 +132,9 @@ pub(super) struct Generics { } pub(crate) struct BuilderGenCtx { + /// Path to the `bon` crate. + pub(super) bon: syn::Path, + /// Private identifiers that are used in the builder implementation. /// They are intentionally randomized to prevent users from accessing them. pub(super) ident_pool: PrivateIdentsPool, @@ -147,7 +148,7 @@ pub(crate) struct BuilderGenCtx { /// generated by the macro. If the original syntax used `#[expect(...)]`, /// then it must be represented as `#[allow(...)]` here. pub(super) allow_attrs: Vec, - pub(super) on_params: Vec, + pub(super) on: Vec, pub(super) generics: Generics, @@ -176,11 +177,12 @@ pub(super) struct PrivateIdentsPool { } pub(super) struct BuilderGenCtxParams<'a> { + pub(crate) bon: Option, pub(super) namespace: Cow<'a, GenericsNamespace>, pub(super) members: Vec, pub(super) allow_attrs: Vec, - pub(super) on_params: Vec, + pub(super) on: Vec, /// This is the visibility of the original item that the builder is generated for. /// For example, the `struct` or `fn` item visibility that the `#[builder]` or @@ -196,7 +198,7 @@ pub(super) struct BuilderGenCtxParams<'a> { pub(super) assoc_method_ctx: Option, pub(super) builder_type: BuilderTypeParams, - pub(super) state_mod: SymbolParams, + pub(super) state_mod: ItemSigConfig, pub(super) start_fn: StartFnParams, pub(super) finish_fn: FinishFnParams, } @@ -204,10 +206,11 @@ pub(super) struct BuilderGenCtxParams<'a> { impl BuilderGenCtx { pub(super) fn new(params: BuilderGenCtxParams<'_>) -> Result { let BuilderGenCtxParams { + bon, namespace, members, allow_attrs, - on_params, + on, generics, orig_item_vis, assoc_method_ctx, @@ -300,7 +303,7 @@ impl BuilderGenCtx { let start_fn = StartFn { ident: start_fn.ident, vis: start_fn.vis.unwrap_or_else(|| builder_type.vis.clone()), - attrs: start_fn.attrs, + docs: start_fn.docs, generics: start_fn.generics, }; @@ -325,11 +328,12 @@ impl BuilderGenCtx { }; Ok(Self { + bon: bon.unwrap_or_else(|| syn::parse_quote!(::bon)), state_var, ident_pool: PrivateIdentsPool::new(), members, allow_attrs, - on_params, + on, generics, assoc_method_ctx, builder_type, diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index c20230bc..e69528bf 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -1,6 +1,6 @@ use super::member::SetterClosure; use super::{BuilderGenCtx, NamedMember}; -use crate::parsing::SymbolParams; +use crate::parsing::ItemSigConfig; use crate::util::prelude::*; use std::iter; @@ -27,10 +27,10 @@ impl<'a> SettersCtx<'a> { let member_type = self.member.ty.norm.as_ref(); - if let Some(closure) = &self.member.params.with { + if let Some(closure) = &self.member.config.with { input = Self::underlying_input_from_closure(closure); expr = self.member_expr_from_closure(closure); - } else if self.member.params.into.is_present() { + } else if self.member.config.into.is_present() { input = quote!(value: impl Into<#member_type>); expr = quote!(Into::into(value)); } else { @@ -49,12 +49,12 @@ impl<'a> SettersCtx<'a> { } fn setters_for_optional_member(&self, items: OptionalSettersItems) -> TokenStream { - if let Some(closure) = &self.member.params.with { + if let Some(closure) = &self.member.config.with { return self.setters_for_optional_member_with_closure(closure, items); } let underlying_ty = self.member.underlying_norm_ty(); - let underlying_ty = if self.member.params.into.is_present() { + let underlying_ty = if self.member.config.into.is_present() { quote!(impl Into<#underlying_ty>) } else { quote!(#underlying_ty) @@ -80,7 +80,7 @@ impl<'a> SettersCtx<'a> { imp: SetterImpl { input: quote!(value: Option<#underlying_ty>), body: SetterBody::SetMember { - expr: if self.member.params.into.is_present() { + expr: if self.member.config.into.is_present() { quote! { Option::map(value, Into::into) } @@ -257,7 +257,7 @@ impl<'a> SettersCtx<'a> { let result_output = self .member - .params + .config .with .as_ref() .and_then(|closure| closure.output.as_ref()); @@ -290,11 +290,11 @@ impl<'a> SettersCtx<'a> { } }; - if let Some(closure) = &self.member.params.with { + if let Some(closure) = &self.member.config.with { return_type = Self::maybe_wrap_in_result(closure, return_type); } - let where_clause = (!self.member.params.overwritable.is_present()).then(|| { + let where_clause = (!self.member.config.overwritable.is_present()).then(|| { let state_var = &self.base.state_var; let member_pascal = &self.member.name.pascal; quote! { @@ -366,7 +366,7 @@ impl SettersItems { let SettersCtx { member, base } = ctx; let builder_type = &base.builder_type; - let params = member.params.setters.as_ref(); + let params = member.config.setters.as_ref(); let common_name = params.and_then(|params| params.name.as_deref()); let common_vis = params.and_then(|params| params.vis.as_deref()); @@ -392,14 +392,14 @@ impl SettersItems { let some_fn = params.and_then(|params| params.fns.some_fn.as_deref()); let some_fn_name = some_fn - .and_then(SymbolParams::name) + .and_then(ItemSigConfig::name) .or(common_name) .unwrap_or(&member.name.snake) .clone(); let option_fn = params.and_then(|params| params.fns.option_fn.as_deref()); let option_fn_name = option_fn - .and_then(SymbolParams::name) + .and_then(ItemSigConfig::name) .cloned() .unwrap_or_else(|| { let base_name = common_name.unwrap_or(&member.name.snake); @@ -410,7 +410,7 @@ impl SettersItems { syn::Ident::new(&format!("maybe_{base_name}"), base_name.span()) }); - let default = member.params.default.as_deref().and_then(|default| { + let default = member.config.default.as_deref().and_then(|default| { let default = default .clone() .or_else(|| well_known_default(&member.ty.norm)) @@ -434,7 +434,7 @@ impl SettersItems { // FIXME: the docs shouldn't reference the companion setter if that // setter has a lower visibility. let some_fn_docs = some_fn - .and_then(SymbolParams::docs) + .and_then(ItemSigConfig::docs) .or(common_docs) .unwrap_or(&member.docs); @@ -445,7 +445,7 @@ impl SettersItems { }; let option_fn_docs = option_fn - .and_then(SymbolParams::docs) + .and_then(ItemSigConfig::docs) .or(common_docs) .unwrap_or(&member.docs); @@ -462,7 +462,7 @@ impl SettersItems { let some_fn = SetterItem { name: some_fn_name, vis: some_fn - .and_then(SymbolParams::vis) + .and_then(ItemSigConfig::vis) .or(common_vis) .unwrap_or(&builder_type.vis) .clone(), @@ -475,7 +475,7 @@ impl SettersItems { name: option_fn_name, vis: option_fn - .and_then(SymbolParams::vis) + .and_then(ItemSigConfig::vis) .or(common_vis) .unwrap_or(&builder_type.vis) .clone(), diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index ca093d26..5208903e 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -38,6 +38,7 @@ impl<'a> StateModGenCtx<'a> { } pub(super) fn state_mod(&self) -> TokenStream { + let bon = &self.base.bon; let vis = &self.base.state_mod.vis; let vis_child = &self.base.state_mod.vis_child; let vis_child_child = &self.base.state_mod.vis_child_child; @@ -68,8 +69,8 @@ impl<'a> StateModGenCtx<'a> { #( #state_mod_docs )* #vis mod #state_mod_ident { #[doc(inline)] - #vis_child use ::bon::private::{IsSet, IsUnset}; - use ::bon::private::{Set, Unset}; + #vis_child use #bon::__private::{IsSet, IsUnset}; + use #bon::__private::{Set, Unset}; mod sealed { #vis_child_child struct Sealed; diff --git a/bon-macros/src/builder/builder_gen/top_level_config/mod.rs b/bon-macros/src/builder/builder_gen/top_level_config/mod.rs new file mode 100644 index 00000000..3166ed79 --- /dev/null +++ b/bon-macros/src/builder/builder_gen/top_level_config/mod.rs @@ -0,0 +1,132 @@ +mod on; + +pub(crate) use on::OnConfig; + +use crate::parsing::{ItemSigConfig, ItemSigConfigParsing, SpannedKey}; +use crate::util::prelude::*; +use darling::FromMeta; +use syn::punctuated::Punctuated; + +fn parse_finish_fn(meta: &syn::Meta) -> Result { + ItemSigConfigParsing { + meta, + reject_self_mentions: Some("builder struct's impl block"), + } + .parse() +} + +fn parse_builder_type(meta: &syn::Meta) -> Result { + ItemSigConfigParsing { + meta, + reject_self_mentions: Some("builder struct"), + } + .parse() +} + +fn parse_state_mod(meta: &syn::Meta) -> Result { + ItemSigConfigParsing { + meta, + reject_self_mentions: Some("builder's state module"), + } + .parse() +} + +fn parse_start_fn(meta: &syn::Meta) -> Result { + ItemSigConfigParsing { + meta, + reject_self_mentions: None, + } + .parse() +} + +#[derive(Debug, FromMeta)] +pub(crate) struct TopLevelConfig { + #[darling(rename = "crate", default, map = Some, with = crate::parsing::parse_path_mod_style)] + pub(crate) bon: Option, + + #[darling(default, with = parse_start_fn)] + pub(crate) start_fn: ItemSigConfig, + + #[darling(default, with = parse_finish_fn)] + pub(crate) finish_fn: ItemSigConfig, + + #[darling(default, with = parse_builder_type)] + pub(crate) builder_type: ItemSigConfig, + + #[darling(default, with = parse_state_mod)] + pub(crate) state_mod: ItemSigConfig, + + #[darling(multiple, with = crate::parsing::parse_non_empty_paren_meta_list)] + pub(crate) on: Vec, + + /// Specifies the derives to apply to the builder. + #[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list)] + pub(crate) derive: DerivesConfig, +} + +impl TopLevelConfig { + pub(crate) fn parse_for_fn(meta_list: &[darling::ast::NestedMeta]) -> Result { + let me = Self::from_list(meta_list)?; + + if me.start_fn.name.is_none() { + let ItemSigConfig { name: _, vis, docs } = &me.start_fn; + + let unexpected_param = None + .or_else(|| vis.as_ref().map(SpannedKey::key)) + .or_else(|| docs.as_ref().map(SpannedKey::key)); + + if let Some(unexpected_param) = unexpected_param { + bail!( + unexpected_param, + "#[builder(start_fn({unexpected_param}))] requires that you \ + also specify #[builder(start_fn(name))] which makes the starting \ + function not to replace the positional function under the #[builder] \ + attribute; by default (without the explicit #[builder(start_fn(name))]) \ + the name, visibility and documentation of the positional \ + function are all copied to the starting function, and the positional \ + function under the #[builder] attribute becomes private with \ + #[doc(hidden)] and it's renamed (the name is not guaranteed \ + to be stable) to make it inaccessible even within the current module", + ); + } + } + + Ok(me) + } +} + +#[derive(Debug, Clone, Default, FromMeta)] +pub(crate) struct DerivesConfig { + #[darling(rename = "Clone")] + pub(crate) clone: Option, + + #[darling(rename = "Debug")] + pub(crate) debug: Option, +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct DeriveConfig { + pub(crate) bounds: Option>, +} + +impl FromMeta for DeriveConfig { + fn from_meta(meta: &syn::Meta) -> Result { + if let syn::Meta::Path(_) = meta { + return Ok(Self { bounds: None }); + } + + meta.require_list()?.require_parens_delim()?; + + #[derive(FromMeta)] + struct Parsed { + #[darling(with = crate::parsing::parse_paren_meta_list_with_terminated)] + bounds: Punctuated, + } + + let Parsed { bounds } = Parsed::from_meta(meta)?; + + Ok(Self { + bounds: Some(bounds), + }) + } +} diff --git a/bon-macros/src/builder/builder_gen/top_level_params/on_params.rs b/bon-macros/src/builder/builder_gen/top_level_config/on.rs similarity index 97% rename from bon-macros/src/builder/builder_gen/top_level_params/on_params.rs rename to bon-macros/src/builder/builder_gen/top_level_config/on.rs index 1acf1d23..6a105dd1 100644 --- a/bon-macros/src/builder/builder_gen/top_level_params/on_params.rs +++ b/bon-macros/src/builder/builder_gen/top_level_config/on.rs @@ -5,13 +5,13 @@ use syn::spanned::Spanned; use syn::visit::Visit; #[derive(Debug)] -pub(crate) struct OnParams { +pub(crate) struct OnConfig { pub(crate) type_pattern: syn::Type, pub(crate) into: darling::util::Flag, pub(crate) overwritable: darling::util::Flag, } -impl Parse for OnParams { +impl Parse for OnConfig { fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { let type_pattern = input.parse()?; @@ -86,7 +86,7 @@ impl Parse for OnParams { } } -impl FromMeta for OnParams { +impl FromMeta for OnConfig { fn from_meta(meta: &syn::Meta) -> Result { let meta = match meta { syn::Meta::List(meta) => meta, diff --git a/bon-macros/src/builder/builder_gen/top_level_params/mod.rs b/bon-macros/src/builder/builder_gen/top_level_params/mod.rs deleted file mode 100644 index 5dd249a5..00000000 --- a/bon-macros/src/builder/builder_gen/top_level_params/mod.rs +++ /dev/null @@ -1,87 +0,0 @@ -mod on_params; - -pub(crate) use on_params::OnParams; - -use crate::parsing::{SymbolParams, ItemParamsParsing}; -use crate::util::prelude::*; -use darling::FromMeta; -use syn::punctuated::Punctuated; - -fn parse_finish_fn(meta: &syn::Meta) -> Result { - ItemParamsParsing { - meta, - reject_self_mentions: Some("builder struct's impl block"), - } - .parse() -} - -fn parse_builder_type(meta: &syn::Meta) -> Result { - ItemParamsParsing { - meta, - reject_self_mentions: Some("builder struct"), - } - .parse() -} - -fn parse_state_mod(meta: &syn::Meta) -> Result { - ItemParamsParsing { - meta, - reject_self_mentions: Some("builder's state module"), - } - .parse() -} - -#[derive(Debug, FromMeta)] -pub(crate) struct TopLevelParams { - #[darling(default, with = parse_finish_fn)] - pub(crate) finish_fn: SymbolParams, - - #[darling(default, with = parse_builder_type)] - pub(crate) builder_type: SymbolParams, - - #[darling(default, with = parse_state_mod)] - pub(crate) state_mod: SymbolParams, - - #[darling(multiple, with = crate::parsing::parse_non_empty_paren_meta_list)] - pub(crate) on: Vec, - - /// Specifies the derives to apply to the builder. - #[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list)] - pub(crate) derive: BuilderDerives, -} - -#[derive(Debug, Clone, Default, FromMeta)] -pub(crate) struct BuilderDerives { - #[darling(rename = "Clone")] - pub(crate) clone: Option, - - #[darling(rename = "Debug")] - pub(crate) debug: Option, -} - -#[derive(Debug, Clone, Default)] -pub(crate) struct BuilderDerive { - pub(crate) bounds: Option>, -} - -impl FromMeta for BuilderDerive { - fn from_meta(meta: &syn::Meta) -> Result { - if let syn::Meta::Path(_) = meta { - return Ok(Self { bounds: None }); - } - - meta.require_list()?.require_parens_delim()?; - - #[derive(FromMeta)] - struct Parsed { - #[darling(with = crate::parsing::parse_paren_meta_list_with_terminated)] - bounds: Punctuated, - } - - let Parsed { bounds } = Parsed::from_meta(meta)?; - - Ok(Self { - bounds: Some(bounds), - }) - } -} diff --git a/bon-macros/src/builder/item_fn.rs b/bon-macros/src/builder/item_fn.rs index 7f6735aa..dc49b557 100644 --- a/bon-macros/src/builder/item_fn.rs +++ b/bon-macros/src/builder/item_fn.rs @@ -1,11 +1,11 @@ -use super::builder_gen::input_fn::{FnInputCtx, FnInputParams}; -use super::builder_gen::MacroOutput; +use super::builder_gen::input_fn::{FnInputCtx, FnInputCtxParams}; +use super::builder_gen::{MacroOutput, TopLevelConfig}; use crate::normalization::SyntaxVariant; use crate::util::prelude::*; use syn::visit_mut::VisitMut; pub(crate) fn generate( - params: FnInputParams, + config: TopLevelConfig, orig_fn: syn::ItemFn, namespace: &crate::normalization::GenericsNamespace, ) -> Result { @@ -19,12 +19,12 @@ pub(crate) fn generate( norm: norm_fn, }; - let ctx = FnInputCtx { + let ctx = FnInputCtx::new(FnInputCtxParams { namespace, fn_item, impl_ctx: None, - params, - }; + config, + }); let adapted_fn = ctx.adapted_fn()?; diff --git a/bon-macros/src/builder/item_impl.rs b/bon-macros/src/builder/item_impl.rs index 09dd3da5..624044a3 100644 --- a/bon-macros/src/builder/item_impl.rs +++ b/bon-macros/src/builder/item_impl.rs @@ -1,4 +1,5 @@ -use super::builder_gen::input_fn::{FnInputCtx, FnInputParams, ImplCtx}; +use super::builder_gen::input_fn::{FnInputCtx, FnInputCtxParams, ImplCtx}; +use super::builder_gen::TopLevelConfig; use crate::normalization::{GenericsNamespace, SyntaxVariant}; use crate::util::prelude::*; use darling::ast::NestedMeta; @@ -7,7 +8,19 @@ use std::rc::Rc; use syn::visit::Visit; use syn::visit_mut::VisitMut; -pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result { +#[derive(FromMeta)] +pub(crate) struct ImplInputParams { + /// Overrides the path to the `bon` crate. + #[darling(rename = "crate", default, map = Some, with = crate::parsing::parse_path_mod_style)] + bon: Option, +} + +// ImplInputParams will evolve in the future where we'll probably want to move from it +#[allow(clippy::needless_pass_by_value)] +pub(crate) fn generate( + impl_params: ImplInputParams, + mut orig_impl_block: syn::ItemImpl, +) -> Result { let mut namespace = GenericsNamespace::default(); namespace.visit_item_impl(&orig_impl_block); @@ -114,19 +127,30 @@ pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result>(); - let params = FnInputParams::from_list(&meta)?; + let mut config = TopLevelConfig::parse_for_fn(&meta)?; + + if let Some(bon) = config.bon { + bail!( + &bon, + "`crate` parameter should be specified via `#[bon(crate = path::to::bon)]` \ + when impl block syntax is used; no need to specify it in the method's \ + `#[builder]` attribute" + ); + } + + config.bon.clone_from(&impl_params.bon); let fn_item = SyntaxVariant { orig: orig_fn, norm: norm_fn, }; - let ctx = FnInputCtx { + let ctx = FnInputCtx::new(FnInputCtxParams { namespace: &namespace, fn_item, impl_ctx: Some(impl_ctx.clone()), - params, - }; + config, + }); Result::<_>::Ok((ctx.adapted_fn()?, ctx.into_builder_gen_ctx()?.output()?)) }) diff --git a/bon-macros/src/builder/mod.rs b/bon-macros/src/builder/mod.rs index 1108fde6..4a8e6c08 100644 --- a/bon-macros/src/builder/mod.rs +++ b/bon-macros/src/builder/mod.rs @@ -5,10 +5,10 @@ pub(crate) mod item_impl; mod item_fn; mod item_struct; -use crate::normalization::{ExpandCfg, ExpansionOutput, GenericsNamespace}; +use crate::normalization::{ExpandCfg, Expansion, GenericsNamespace}; use crate::util; use crate::util::prelude::*; -use darling::FromMeta; +use builder_gen::TopLevelConfig; use syn::parse::Parser; use syn::visit::Visit; @@ -27,7 +27,7 @@ fn try_generate_from_derive(item: TokenStream) -> Result { } pub(crate) fn generate_from_attr(params: TokenStream, item: TokenStream) -> TokenStream { - crate::error::with_fallback(item.clone(), || { + crate::error::handle_errors(item.clone(), || { try_generate_from_attr(params.clone(), item) }) .unwrap_or_else(|fallback| [generate_completion_triggers(params), fallback].concat()) @@ -36,29 +36,28 @@ pub(crate) fn generate_from_attr(params: TokenStream, item: TokenStream) -> Toke fn try_generate_from_attr(params: TokenStream, item: TokenStream) -> Result { let item: syn::Item = syn::parse2(item)?; - let macro_path = syn::parse_quote!(::bon::builder); - let ctx = ExpandCfg { - macro_path, + current_macro: format_ident!("builder"), params, item, }; - let (params, item) = match ctx.expand_cfg()? { - ExpansionOutput::Expanded { params, item } => (params, item), - ExpansionOutput::Recurse(output) => return Ok(output), + let input = match ctx.expand_cfg()? { + Expansion::Expanded(input) => input, + Expansion::Recurse(output) => return Ok(output), }; - let nested_meta = &darling::ast::NestedMeta::parse_meta_list(params.clone())?; - - let main_output = match item { + let main_output = match input.item { syn::Item::Fn(item_fn) => { let mut namespace = GenericsNamespace::default(); - namespace.visit_token_stream(params.clone()); + namespace.visit_token_stream(input.params.clone()); namespace.visit_item_fn(&item_fn); - item_fn::generate(FromMeta::from_list(nested_meta)?, item_fn, &namespace)? + let meta_list = darling::ast::NestedMeta::parse_meta_list(input.params.clone())?; + let config = TopLevelConfig::parse_for_fn(&meta_list)?; + + item_fn::generate(config, item_fn, &namespace)? } syn::Item::Struct(struct_item) => { bail!( @@ -73,7 +72,7 @@ fn try_generate_from_attr(params: TokenStream, item: TokenStream) -> Result -pub(crate) fn with_fallback( +pub(crate) fn handle_errors( item: TokenStream, imp: impl FnOnce() -> Result, ) -> Result { + let panic_listener = panic_context::PanicListener::register(); + std::panic::catch_unwind(AssertUnwindSafe(imp)) .unwrap_or_else(|err| { - let msg = err - .downcast::<&str>() - .map(|msg| msg.to_string()) - .or_else(|err| err.downcast::().map(|msg| *msg)) - .unwrap_or_else(|_| "".to_owned()); + let msg = panic_context::message_from_panic_payload(err.as_ref()) + .unwrap_or_else(|| "".to_owned()); let msg = if msg.contains("unsupported proc macro punctuation character") { format!( @@ -33,10 +34,15 @@ pub(crate) fn with_fallback( Github issue: https://github.com/rust-lang/rust-analyzer/issues/18244" ) } else { + let context = panic_listener + .get_last_panic() + .map(|ctx| format!("\n\n{ctx}")) + .unwrap_or_default(); + format!( - "bug in the crate `bon` (proc-macro panicked): `{msg}`;\n\ + "proc-macro panicked (may be a bug in the crate `bon`): `{msg}`;\n\ please report this issue at our Github repository: \ - https://github.com/elastio/bon" + https://github.com/elastio/bon{context}" ) }; diff --git a/bon-macros/src/error/panic_context.rs b/bon-macros/src/error/panic_context.rs new file mode 100644 index 00000000..e551584a --- /dev/null +++ b/bon-macros/src/error/panic_context.rs @@ -0,0 +1,208 @@ +use std::fmt; +use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; + +#[rustversion::since(1.65.0)] +use std::panic::PanicHookInfo as StdPanicHookInfo; + +use std::any::Any; +#[rustversion::before(1.81.0)] +use std::panic::PanicInfo as StdPanicHookInfo; + +fn lock_global_panic_info() -> MutexGuard<'static, GlobalPanicContext> { + /// A lazily initialized global panic log. It aggregates the panics from all threads. + /// This is used to find the info about the panic after the `catch_unwind` call + /// to observe the context of the panic that happened. + static GLOBAL: Mutex = Mutex::new(GlobalPanicContext { + last_panic: None, + initialized: false, + }); + + GLOBAL.lock().unwrap_or_else(PoisonError::into_inner) +} + +struct GlobalPanicContext { + last_panic: Option, + initialized: bool, +} + +/// This struct without any fields exists to make sure that [`PanicListener::register()`] +/// is called first before the code even attempts to get the last panic information. +#[derive(Default)] +pub(super) struct PanicListener { + /// Required to make sure struct is not constructable via a struct literal + /// in the code outside of this module. + _private: (), +} + +impl PanicListener { + pub(super) fn register() -> Self { + let mut global = lock_global_panic_info(); + + if global.initialized { + return Self { _private: () }; + } + + let prev_panic_hook = std::panic::take_hook(); + + std::panic::set_hook(Box::new(move |panic_info| { + { + // Make sure the lock is released before the other panic hook is called + // to prevent potential reentrancy. Therefore this code is in a block. + let mut global = lock_global_panic_info(); + let panic_number = global.last_panic.as_ref().map(|p| p.0.panic_number); + let panic_number = panic_number.unwrap_or(0) + 1; + + global.last_panic = Some(PanicContext::from_std(panic_info, panic_number)); + } + + prev_panic_hook(panic_info); + })); + + global.initialized = true; + drop(global); + + Self { _private: () } + } + + /// Returns the last panic that happened since the [`PanicListener::register()`] call. + // `self` is required to make sure this code runs only after we initialized + // the global panic listener in the `register` method. + #[allow(clippy::unused_self)] + pub(super) fn get_last_panic(&self) -> Option { + lock_global_panic_info().last_panic.clone() + } +} + +/// Contains all the necessary bits of information about the occurred panic. +#[derive(Clone)] +pub(super) struct PanicContext(Arc); + +struct PanicContextShared { + backtrace: backtrace::Backtrace, + + location: Option, + thread_name: Option, + + /// Defines the number of panics that happened before this one. Each panic + /// increments this counter. This is useful to know how many panics happened + /// before the current one. + panic_number: usize, +} + +impl PanicContext { + fn from_std(std_panic_info: &StdPanicHookInfo<'_>, panic_number: usize) -> Self { + let location = std_panic_info.location(); + Self(Arc::new(PanicContextShared { + backtrace: backtrace::Backtrace::capture(), + location: location.map(PanicLocation::from_std), + thread_name: std::thread::current().name().map(String::from), + panic_number, + })) + } +} + +impl fmt::Debug for PanicContext { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Display for PanicContext { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let PanicContextShared { + location, + backtrace, + thread_name, + panic_number, + } = &*self.0; + + write!(f, "panic occurred")?; + + if let Some(location) = location { + write!(f, " at {location}")?; + } + + if let Some(thread_name) = thread_name { + write!(f, " in thread '{thread_name}'")?; + } + + if *panic_number > 1 { + write!(f, " (total panics observed: {panic_number})")?; + } + + // #[rustversion::attr(before(1.65.0), allow(clippy::irrefutable_let_patterns))] + #[allow(clippy::incompatible_msrv)] + if backtrace.status() == backtrace::BacktraceStatus::Captured { + write!(f, "\nbacktrace:\n{backtrace}")?; + } + + Ok(()) + } +} + +/// Extract the message of a panic. +pub(super) fn message_from_panic_payload(payload: &dyn Any) -> Option { + if let Some(str_slice) = payload.downcast_ref::<&str>() { + return Some((*str_slice).to_owned()); + } + if let Some(owned_string) = payload.downcast_ref::() { + return Some(owned_string.clone()); + } + + None +} + +/// Location of the panic call site. +#[derive(Clone)] +struct PanicLocation { + file: String, + line: u32, + col: u32, +} + +impl PanicLocation { + fn from_std(loc: &std::panic::Location<'_>) -> Self { + Self { + file: loc.file().to_owned(), + line: loc.line(), + col: loc.column(), + } + } +} + +impl fmt::Display for PanicLocation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}:{}", self.file, self.line, self.col) + } +} + +#[rustversion::since(1.65.0)] +#[allow(clippy::module_name_repetitions)] +mod backtrace { + pub(super) use std::backtrace::{Backtrace, BacktraceStatus}; +} + +#[rustversion::before(1.65.0)] +mod backtrace { + #[derive(PartialEq)] + pub(super) enum BacktraceStatus { + Captured, + } + + pub(super) struct Backtrace; + + impl Backtrace { + pub(super) fn capture() -> Self { + Self + } + pub(super) fn status(&self) -> BacktraceStatus { + BacktraceStatus::Captured + } + } + + impl std::fmt::Display for Backtrace { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("{update your Rust compiler to >=1.65.0 to see the backtrace}") + } + } +} diff --git a/bon-macros/src/normalization/cfg/mod.rs b/bon-macros/src/normalization/cfg/mod.rs index bf0e5a1b..26588381 100644 --- a/bon-macros/src/normalization/cfg/mod.rs +++ b/bon-macros/src/normalization/cfg/mod.rs @@ -2,32 +2,36 @@ mod parse; mod visit; use crate::util::prelude::*; +use darling::ast::NestedMeta; use parse::CfgSyntax; use std::collections::BTreeSet; +use syn::parse::Parser; -pub(crate) enum ExpansionOutput { - Expanded { - params: TokenStream, - item: syn::Item, - }, +pub(crate) enum Expansion { + Expanded(Expanded), Recurse(TokenStream), } +pub(crate) struct Expanded { + pub(crate) params: TokenStream, + pub(crate) item: syn::Item, +} + pub(crate) struct ExpandCfg { - pub(crate) macro_path: syn::Path, + pub(crate) current_macro: syn::Ident, pub(crate) params: TokenStream, pub(crate) item: syn::Item, } impl ExpandCfg { - pub(crate) fn expand_cfg(mut self) -> Result { + pub(crate) fn expand_cfg(mut self) -> Result { let predicates = self.collect_predicates()?; if predicates.is_empty() { - return Ok(ExpansionOutput::Expanded { + return Ok(Expansion::Expanded(Expanded { params: self.params, item: self.item, - }); + })); } let predicate_results = match parse::parse_predicate_results(self.params.clone())? { @@ -53,10 +57,10 @@ impl ExpandCfg { let predicates = self.collect_predicates()?; if predicates.is_empty() { - return Ok(ExpansionOutput::Expanded { + return Ok(Expansion::Expanded(Expanded { params: self.params, item: self.item, - }); + })); } self.into_recursion(predicate_results.recursion_counter + 1, &predicates) @@ -95,14 +99,30 @@ impl ExpandCfg { self, recursion_counter: usize, predicates: &[TokenStream], - ) -> Result { + ) -> Result { let Self { params, item, - macro_path, - } = &self; + current_macro, + } = self; + + let parsed_params = NestedMeta::parse_meta_list(params.clone())?; + + let bon = parsed_params + .iter() + .find_map(|param| match param { + NestedMeta::Meta(syn::Meta::NameValue(meta)) if meta.path.is_ident("crate") => { + let path = &meta.value; + Some(syn::Path::parse_mod_style.parse2(quote!(#path))) + } + _ => None, + }) + .transpose()? + .unwrap_or_else(|| syn::parse_quote!(::bon)); + + let current_macro = syn::parse_quote!(#bon::#current_macro); - let invocation_name = self.unique_invocation_name()?; + let invocation_name = Self::unique_invocation_name(&item, ¤t_macro)?; let predicates = predicates.iter().enumerate().map(|(i, predicate)| { // We need to insert the recursion counter into the name so that @@ -112,17 +132,17 @@ impl ExpandCfg { }); let expansion = quote! { - ::bon::__eval_cfg_callback! { + #bon::__eval_cfg_callback! { {} #((#predicates))* - #macro_path, + #current_macro, #recursion_counter, ( #params ) #item } }; - Ok(ExpansionOutput::Recurse(expansion)) + Ok(Expansion::Recurse(expansion)) } /// The macro `__eval_cfg_callback` needs to generate a use statement for @@ -151,16 +171,16 @@ impl ExpandCfg { /// /// However, in most of the cases it will be a simple path, so its combination /// with the name of the first function in the `impl` block should be unique enough. - fn unique_invocation_name(&self) -> Result { + fn unique_invocation_name(item: &syn::Item, current_macro: &syn::Path) -> Result { let path_to_ident = |path: &syn::Path| path.segments.iter().map(|segment| &segment.ident).join("_"); // Include the name of the proc macro in the unique name to avoid // collisions when different proc macros are placed on the same item // and they use this code to generate unique names. - let macro_path_str = path_to_ident(&self.macro_path); + let macro_path_str = path_to_ident(current_macro); - let item_name = match &self.item { + let item_name = match item { syn::Item::Fn(item) => item.sig.ident.to_string(), syn::Item::Impl(item) => { let self_ty = item diff --git a/bon-macros/src/parsing/item_params.rs b/bon-macros/src/parsing/item_sig.rs similarity index 78% rename from bon-macros/src/parsing/item_params.rs rename to bon-macros/src/parsing/item_sig.rs index ca7694a3..52caabfe 100644 --- a/bon-macros/src/parsing/item_params.rs +++ b/bon-macros/src/parsing/item_sig.rs @@ -2,14 +2,17 @@ use super::SpannedKey; use crate::util::prelude::*; use darling::FromMeta; +/// "Item signature" is a set of parameters that configures some aspects of +/// an item like a function, struct, struct field, module, trait. All of them +/// have configurable properties that are specified here. #[derive(Debug, Clone, Default)] -pub(crate) struct SymbolParams { +pub(crate) struct ItemSigConfig { pub(crate) name: Option>, pub(crate) vis: Option>, pub(crate) docs: Option>>, } -impl SymbolParams { +impl ItemSigConfig { pub(crate) fn name(&self) -> Option<&syn::Ident> { self.name.as_ref().map(|name| &name.value) } @@ -23,20 +26,20 @@ impl SymbolParams { } } -pub(crate) struct ItemParamsParsing<'a> { +pub(crate) struct ItemSigConfigParsing<'a> { pub(crate) meta: &'a syn::Meta, pub(crate) reject_self_mentions: Option<&'static str>, } -impl ItemParamsParsing<'_> { - pub(crate) fn parse(self) -> Result { +impl ItemSigConfigParsing<'_> { + pub(crate) fn parse(self) -> Result { let meta = self.meta; if let syn::Meta::NameValue(meta) = meta { let val = &meta.value; let name = syn::parse2(val.to_token_stream())?; - return Ok(SymbolParams { + return Ok(ItemSigConfig { name: Some(SpannedKey::new(&meta.path, name)?), vis: None, docs: None, @@ -60,7 +63,7 @@ impl ItemParamsParsing<'_> { } } - let params = SymbolParams { + let params = ItemSigConfig { name: full.name, vis: full.vis, docs: full.doc, diff --git a/bon-macros/src/parsing/mod.rs b/bon-macros/src/parsing/mod.rs index 6b8477e1..de940277 100644 --- a/bon-macros/src/parsing/mod.rs +++ b/bon-macros/src/parsing/mod.rs @@ -1,10 +1,10 @@ mod docs; -mod item_params; +mod item_sig; mod simple_closure; mod spanned_key; pub(crate) use docs::*; -pub(crate) use item_params::*; +pub(crate) use item_sig::*; pub(crate) use simple_closure::*; pub(crate) use spanned_key::*; @@ -12,6 +12,7 @@ use crate::util::prelude::*; use darling::FromMeta; use syn::parse::Parser; use syn::punctuated::Punctuated; +use syn::spanned::Spanned; pub(crate) fn parse_non_empty_paren_meta_list(meta: &syn::Meta) -> Result { require_non_empty_paren_meta_list_or_name_value(meta)?; @@ -81,3 +82,34 @@ where Ok(punctuated) } + +pub(crate) fn parse_path_mod_style(meta: &syn::Meta) -> Result { + let err = |span: Span| err!(&span, "expected a bare path, like `foo::bar`"); + + let expr = match meta { + syn::Meta::NameValue(meta) => &meta.value, + _ => return Err(err(meta.span())), + }; + + let expr = match expr { + syn::Expr::Path(expr) => expr, + _ => return Err(err(expr.span())), + }; + + reject_syntax("attribute", &expr.attrs.first())?; + reject_syntax(" syntax", &expr.qself)?; + + expr.path.require_mod_style()?; + + Ok(expr.path.clone()) +} + +// Lint from nightly. `&Option` is used to reduce syntax at the callsite +#[allow(unknown_lints, clippy::ref_option)] +fn reject_syntax(name: &'static str, syntax: &Option) -> Result { + if let Some(syntax) = syntax { + bail!(syntax, "{name} is not allowed here") + } + + Ok(()) +} diff --git a/bon-macros/src/parsing/simple_closure.rs b/bon-macros/src/parsing/simple_closure.rs index 69b21252..732419f3 100644 --- a/bon-macros/src/parsing/simple_closure.rs +++ b/bon-macros/src/parsing/simple_closure.rs @@ -1,6 +1,6 @@ +use super::reject_syntax; use crate::util::prelude::*; use darling::FromMeta; -use syn::spanned::Spanned; /// Utility type for parsing simple closure syntax that only allows [`syn::PatIdent`] /// inputs and rejects any attributes and prefix keywords like `async`, `move`, `for` @@ -64,16 +64,6 @@ impl FromMeta for SimpleClosure { } } -// Lint from nightly. `&Option` is used to reduce syntax at the callsite -#[allow(unknown_lints, clippy::ref_option)] -fn reject_syntax(name: &'static str, syntax: &Option) -> Result { - if let Some(syntax) = syntax { - bail!(syntax, "{name} is not allowed here") - } - - Ok(()) -} - impl SimpleClosureInput { fn from_pat_ident(pat: syn::PatIdent) -> Result { reject_syntax("attribute", &pat.attrs.first())?; diff --git a/bon-macros/src/parsing/spanned_key.rs b/bon-macros/src/parsing/spanned_key.rs index f90fc822..dbec4311 100644 --- a/bon-macros/src/parsing/spanned_key.rs +++ b/bon-macros/src/parsing/spanned_key.rs @@ -23,6 +23,10 @@ impl SpannedKey { pub(crate) fn into_value(self) -> T { self.value } + + pub(crate) fn key(&self) -> &syn::Ident { + &self.key + } } impl FromMeta for SpannedKey { diff --git a/bon-macros/src/util/attrs.rs b/bon-macros/src/util/attrs.rs index 92503c4c..9475a6ef 100644 --- a/bon-macros/src/util/attrs.rs +++ b/bon-macros/src/util/attrs.rs @@ -1,3 +1,5 @@ +use syn::spanned::Spanned; + pub(crate) trait AttributeExt { fn is_doc_expr(&self) -> bool; fn as_doc_expr(&self) -> Option<&syn::Expr>; @@ -43,7 +45,7 @@ impl AttributeExt for syn::Attribute { syn::Meta::NameValue(meta) => &mut meta.path, }; - *path = syn::parse_quote!(allow); + *path = syn::parse_quote_spanned!(path.span()=> allow); Some(attr) } diff --git a/bon-macros/src/util/ide.rs b/bon-macros/src/util/ide.rs index 8465ac51..a742b01f 100644 --- a/bon-macros/src/util/ide.rs +++ b/bon-macros/src/util/ide.rs @@ -241,7 +241,7 @@ impl CompletionsSchema { // to avoid Rust Analyzer from providing completions for the // `self` keyword in the `use` statement. It works because // `use self::self` is not a valid syntax. - use ::bon::private::ide #(::#module_name)* ::*; + use ::bon::__private::ide #(::#module_name)* ::*; use self::{ #( #paths as _, )* }; } diff --git a/bon-macros/src/util/path.rs b/bon-macros/src/util/path.rs index db4dc282..002a633f 100644 --- a/bon-macros/src/util/path.rs +++ b/bon-macros/src/util/path.rs @@ -1,6 +1,11 @@ +use crate::util::prelude::*; + pub(crate) trait PathExt { /// Check if the path starts with the given segment. fn starts_with_segment(&self, desired_segment: &str) -> bool; + + /// Returns an error if this path has some generic arguments. + fn require_mod_style(&self) -> Result; } impl PathExt for syn::Path { @@ -10,4 +15,16 @@ impl PathExt for syn::Path { .map(|first| first.ident == desired_segment) .unwrap_or(false) } + + fn require_mod_style(&self) -> Result { + if self + .segments + .iter() + .any(|seg| seg.arguments != syn::PathArguments::None) + { + bail!(self, "expected a simple path e.g. `foo::bar`"); + } + + Ok(()) + } } diff --git a/bon/src/private/cfg_eval.rs b/bon/src/__private/cfg_eval.rs similarity index 100% rename from bon/src/private/cfg_eval.rs rename to bon/src/__private/cfg_eval.rs diff --git a/bon/src/private/derives.rs b/bon/src/__private/derives.rs similarity index 100% rename from bon/src/private/derives.rs rename to bon/src/__private/derives.rs diff --git a/bon/src/private/ide.rs b/bon/src/__private/ide.rs similarity index 90% rename from bon/src/private/ide.rs rename to bon/src/__private/ide.rs index 05977ba8..15f84826 100644 --- a/bon/src/private/ide.rs +++ b/bon/src/__private/ide.rs @@ -20,6 +20,9 @@ pub mod builder_top_level { /// See the docs at pub const vis: VisibilityString = VisibilityString; + + /// See the docs at + pub const doc: DocComments = DocComments; } /// See the docs at @@ -76,3 +79,8 @@ pub struct Identifier; /// The presence of this attribute enables the behavior. The attribute has no value. pub struct Flag; + +/// Documentation comments using the syntax `/// doc comment here`. +/// +/// [Rust reference](https://doc.rust-lang.org/reference/comments.html#doc-comments) +pub struct DocComments; diff --git a/bon/src/private/mod.rs b/bon/src/__private/mod.rs similarity index 100% rename from bon/src/private/mod.rs rename to bon/src/__private/mod.rs diff --git a/bon/src/builder_state.rs b/bon/src/builder_state.rs index 4336eebb..14dfca27 100644 --- a/bon/src/builder_state.rs +++ b/bon/src/builder_state.rs @@ -1,5 +1,5 @@ //! The items here are intentionally defined in a private module not inside of the -//! [`crate::private`] module. This is because that module is marked with `#[deprecated]` +//! [`crate::__private`] module. This is because that module is marked with `#[deprecated]` //! which makes all items defined in that module also deprecated. //! //! This is not the desired behavior for the items defined here. They are not deprecated, @@ -7,7 +7,7 @@ //! them through the `bon` crate. Instead, they should use the re-exports from the state //! module generated for the builder. -use crate::private::{Sealed, Set, Unset}; +use crate::__private::{Sealed, Set, Unset}; /// Marker trait that indicates that the member is set, i.e. at least /// one of its setters was called. diff --git a/bon/src/collections.rs b/bon/src/collections.rs index b5b0b186..3cbc2d85 100644 --- a/bon/src/collections.rs +++ b/bon/src/collections.rs @@ -29,8 +29,8 @@ #[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] #[allow(edition_2024_expr_fragment_specifier)] macro_rules! vec { - () => ($crate::private::alloc::vec::Vec::new()); - ($($item:expr),+ $(,)?) => ($crate::private::alloc::vec![$(::core::convert::Into::into($item)),+ ]); + () => ($crate::__private::alloc::vec::Vec::new()); + ($($item:expr),+ $(,)?) => ($crate::__private::alloc::vec![$(::core::convert::Into::into($item)),+ ]); } /// Creates a fixed-size array literal with each element converted with [`Into`]. @@ -74,7 +74,7 @@ macro_rules! arr { #[cfg(test)] mod tests { #[cfg(feature = "alloc")] - use crate::private::alloc::{string::String, vec::Vec}; + use crate::__private::alloc::{string::String, vec::Vec}; use core::num::NonZeroU8; #[cfg(feature = "alloc")] diff --git a/bon/src/lib.rs b/bon/src/lib.rs index cd7349dd..6718fdde 100644 --- a/bon/src/lib.rs +++ b/bon/src/lib.rs @@ -18,11 +18,11 @@ pub use bon_macros::{bon, builder, map, set, Builder}; mod collections; #[doc(hidden)] -#[deprecated = "the items from the `bon::private` module are an implementation detail; \ +#[deprecated = "the items from the `bon::__private` module are an implementation detail; \ they should not be used directly; if you found a need for this, then you are probably \ doing something wrong; feel free to open an issue/discussion in our GitHub repository \ (https://github.com/elastio/bon) or ask for help in our Discord server \ (https://discord.gg/QcBYSamw4c)"] -pub mod private; +pub mod __private; mod builder_state; diff --git a/bon/tests/integration/builder/attr_expose_positional_fn.rs b/bon/tests/integration/builder/attr_expose_positional_fn.rs deleted file mode 100644 index 0a1e892e..00000000 --- a/bon/tests/integration/builder/attr_expose_positional_fn.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::prelude::*; -use core::fmt; - -#[test] -fn method_new_doesnt_require_a_value_for_name() { - struct Sut; - - #[bon] - impl Sut { - #[builder(expose_positional_fn)] - fn new() -> Self { - Self - } - } - - let _: Sut = Sut::builder().build(); - let _: Sut = Sut::new(); - - #[allow(clippy::items_after_statements)] - struct Sut2; - - #[bon] - impl Sut2 { - #[builder(expose_positional_fn(vis = "pub(crate)"))] - fn new() -> Self { - Self - } - } - - let _: Sut2 = Sut2::builder().build(); - let _: Sut2 = Sut2::new(); -} - -#[test] -fn with_nested_params() { - #[builder(expose_positional_fn(name = positional))] - fn sut(arg1: bool, arg2: u32) -> impl fmt::Debug { - (arg1, arg2) - } - - assert_debug_eq(positional(true, 42), expect!["(true, 42)"]); -} - -#[test] -fn simple() { - #[builder(expose_positional_fn = positional)] - fn sut(arg1: u32) -> u32 { - arg1 - } - - assert_debug_eq(positional(42), expect!["42"]); -} diff --git a/bon/tests/integration/builder/attr_top_level_start_fn.rs b/bon/tests/integration/builder/attr_top_level_start_fn.rs new file mode 100644 index 00000000..eb3556cc --- /dev/null +++ b/bon/tests/integration/builder/attr_top_level_start_fn.rs @@ -0,0 +1,90 @@ +use crate::prelude::*; +use core::fmt; + +#[test] +fn test_assoc_method() { + { + struct Sut; + + #[bon] + impl Sut { + #[builder(start_fn = builder)] + fn new() -> Self { + Self + } + + #[builder(start_fn = regular_builder)] + fn regular() {} + } + + let _: Sut = Sut::builder().build(); + let _: Sut = Sut::new(); + + Sut::regular_builder().call(); + Sut::regular(); + } + + { + struct Sut; + + #[bon] + impl Sut { + #[builder(start_fn(name = builder, vis = ""))] + pub(crate) fn new() -> Self { + Self + } + + #[builder(start_fn(name = regular_builder, vis = ""))] + pub(crate) fn regular() {} + } + + let _: Sut = Sut::builder().build(); + let _: Sut = Sut::new(); + + Sut::regular_builder().call(); + Sut::regular(); + } +} + +#[test] +fn test_free_fn() { + { + #[builder(start_fn(name = sut_builder))] + fn sut(arg1: bool, arg2: u32) -> impl fmt::Debug { + (arg1, arg2) + } + + assert_debug_eq(sut(true, 42), expect!["(true, 42)"]); + } + + { + #[builder(start_fn = sut_builder)] + fn sut(arg1: u32) -> u32 { + arg1 + } + + assert_debug_eq(sut(42), expect!["42"]); + } + + { + #[builder(start_fn(name = sut_builder, vis = ""))] + fn sut(arg1: u32) -> u32 { + arg1 + } + + assert_debug_eq(sut(42), expect!["42"]); + } + + { + /// Docs on `sut` + #[builder(start_fn(name = sut_builder, doc { + /// Docs on `sut_builder` + }))] + fn sut(arg1: u32) -> u32 { + arg1 + } + + assert_debug_eq(sut_builder().arg1(42).call(), expect!["42"]); + assert_debug_eq(sut(42), expect!["42"]); + } +} diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs index bdf83a19..0fafd136 100644 --- a/bon/tests/integration/builder/mod.rs +++ b/bon/tests/integration/builder/mod.rs @@ -1,11 +1,11 @@ mod attr_default; mod attr_derive; -mod attr_expose_positional_fn; mod attr_into; mod attr_on; mod attr_overwritable; mod attr_setters; mod attr_skip; +mod attr_top_level_start_fn; mod attr_transparent; mod attr_with; mod cfgs; @@ -109,7 +109,7 @@ fn constructor() { #[bon] impl Counter { - #[builder(expose_positional_fn = new)] + #[builder(start_fn = builder)] fn new(initial: Option) -> Self { Self { val: initial.unwrap_or_default(), diff --git a/bon/tests/integration/ui/compile_fail/attr_start_finish_fn.rs b/bon/tests/integration/ui/compile_fail/attr_start_finish_fn.rs deleted file mode 100644 index 7d643da1..00000000 --- a/bon/tests/integration/ui/compile_fail/attr_start_finish_fn.rs +++ /dev/null @@ -1,11 +0,0 @@ -use bon::Builder; - -#[derive(Builder)] -#[builder(start_fn())] -struct EmptyStartFn {} - -#[derive(Builder)] -#[builder(finish_fn())] -struct EmptyFinisFn {} - -fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_start_finish_fn.stderr b/bon/tests/integration/ui/compile_fail/attr_start_finish_fn.stderr deleted file mode 100644 index e8a17698..00000000 --- a/bon/tests/integration/ui/compile_fail/attr_start_finish_fn.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: expected parameters in parentheses - --> tests/integration/ui/compile_fail/attr_start_finish_fn.rs:4:19 - | -4 | #[builder(start_fn())] - | ^^ - -error: expected parameters in parentheses - --> tests/integration/ui/compile_fail/attr_start_finish_fn.rs:8:20 - | -8 | #[builder(finish_fn())] - | ^^ diff --git a/bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs b/bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs new file mode 100644 index 00000000..aca08674 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs @@ -0,0 +1,53 @@ +use bon::{bon, builder, Builder}; + +#[derive(Builder)] +#[builder(start_fn())] +struct EmptyStartFn {} + +#[derive(Builder)] +#[builder(finish_fn())] +struct EmptyFinisFn {} + +#[derive(Builder)] +#[builder(start_fn)] +struct BareStartFnAttrOnStruct {} + +#[builder(start_fn)] +fn bare_start_fn_on_free_function() {} + +#[builder(start_fn())] +fn empty_paren_start_fn_on_free_function() {} + +#[builder(start_fn(vis = ""))] +fn missing_name_for_start_fn_on_free_function1() {} + +#[builder(start_fn(docs {}))] +fn missing_name_for_start_fn_on_free_function2() {} + +struct AssocCtx; + +#[bon] +impl AssocCtx { + #[builder(start_fn)] + fn bare_start_fn_on_non_new_method() {} +} + +#[bon] +impl AssocCtx { + #[builder(start_fn())] + fn new() {} +} + +#[bon] +impl AssocCtx { + #[builder(start_fn(vis = ""))] + fn missing_name_for_start_fn_on_non_new_method1() {} +} + +#[bon] +impl AssocCtx { + #[builder(start_fn(docs {}))] + fn missing_name_for_start_fn_on_non_new_method2() {} +} + +fn main() {} From 58ccecb6ebdf53f19c89b0f565ae8ef3ef73b5f5 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 24 Oct 2024 22:48:11 +0000 Subject: [PATCH 03/12] Cleanup. Found a bug with relative paths in `crate`. --- bon-macros/src/parsing/mod.rs | 2 +- bon-macros/src/util/ide.rs | 4 +- bon/src/__private/ide.rs | 68 ++++++++++++--- bon/tests/integration/builder/attr_crate.rs | 11 +++ bon/tests/integration/builder/mod.rs | 1 + .../ui/compile_fail/attr_bon.stderr | 2 +- .../ui/compile_fail/attr_builder.stderr | 6 +- .../ui/compile_fail/attr_derive.stderr | 44 +++++----- .../ui/compile_fail/attr_on.stderr | 2 +- .../attr_top_level_start_finish_fn.rs | 33 ++++++-- .../attr_top_level_start_finish_fn.stderr | 83 +++++++++++++++++++ .../ui/compile_fail/wrong_delimiters.rs | 8 +- .../ui/compile_fail/wrong_delimiters.stderr | 64 +++++++------- 13 files changed, 244 insertions(+), 84 deletions(-) create mode 100644 bon/tests/integration/builder/attr_crate.rs create mode 100644 bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.stderr diff --git a/bon-macros/src/parsing/mod.rs b/bon-macros/src/parsing/mod.rs index de940277..cbffddd6 100644 --- a/bon-macros/src/parsing/mod.rs +++ b/bon-macros/src/parsing/mod.rs @@ -33,7 +33,7 @@ pub(crate) fn require_non_empty_paren_meta_list_or_name_value(meta: &syn::Meta) } syn::Meta::Path(path) => bail!( &meta, - "this empty `#[{0}]` attribute is unexpected; \ + "this empty `{0}` attribute is unexpected; \ remove it or pass parameters in parentheses: \ `#[{0}(...)]`", darling::util::path_to_string(path) diff --git a/bon-macros/src/util/ide.rs b/bon-macros/src/util/ide.rs index a742b01f..5bca5326 100644 --- a/bon-macros/src/util/ide.rs +++ b/bon-macros/src/util/ide.rs @@ -138,8 +138,10 @@ pub(crate) fn generate_completion_triggers(meta: Vec) -> TokenStream { let completions = CompletionsSchema::with_children( "builder_top_level", vec![ - CompletionsSchema::leaf("expose_positional_fn"), + CompletionsSchema::leaf("builder_type"), CompletionsSchema::leaf("start_fn"), + CompletionsSchema::leaf("finish_fn"), + CompletionsSchema::leaf("state_mod"), CompletionsSchema::leaf("on").set_custom_filter(|meta| { if !meta.is_empty() { meta.remove(0); diff --git a/bon/src/__private/ide.rs b/bon/src/__private/ide.rs index 15f84826..7d06a6f1 100644 --- a/bon/src/__private/ide.rs +++ b/bon/src/__private/ide.rs @@ -8,6 +8,39 @@ pub mod builder_top_level { use super::*; + /// See the docs at + pub const builder_type: Option = None; + + pub mod builder_type { + use super::*; + + /// See the docs at + pub const name: Identifier = Identifier; + + /// See the docs at + pub const vis: VisibilityString = VisibilityString; + + /// See the docs at + pub const doc: DocComments = DocComments; + } + + /// See the docs at + pub const finish_fn: Option = None; + + /// See the docs at + pub mod finish_fn { + use super::*; + + /// See the docs at + pub const name: Identifier = Identifier; + + /// See the docs at + pub const vis: VisibilityString = VisibilityString; + + /// See the docs at + pub const doc: DocComments = DocComments; + } + /// See the docs at pub const start_fn: Option = None; @@ -25,24 +58,21 @@ pub mod builder_top_level { pub const doc: DocComments = DocComments; } - /// See the docs at - pub const finish_fn: Option = None; - - /// See the docs at - pub const builder_type: Option = None; - - /// See the docs at - pub const expose_positional_fn: Option = None; + /// See the docs at + pub const state_mod: Option = None; - /// See the docs at - pub mod expose_positional_fn { + /// See the docs at + pub mod state_mod { use super::*; - /// See the docs at + /// See the docs at pub const name: Identifier = Identifier; - /// See the docs at - pub const vis: Option = None; + /// See the docs at + pub const vis: VisibilityString = VisibilityString; + + /// See the docs at + pub const doc: DocComments = DocComments; } /// See the docs at @@ -61,6 +91,13 @@ pub mod builder_top_level { /// See the docs at pub use core::clone::Clone; } + + /// The real name of this parameter is `crate` (without the underscore). + /// It's hinted with an underscore due to the limitations of the current + /// completions limitation. This will be fixed in the future. + /// + /// See the docs at + pub const crate_: Option = None; } /// Visibility inside of a string literal. Empty string means private visibility. @@ -84,3 +121,8 @@ pub struct Flag; /// /// [Rust reference](https://doc.rust-lang.org/reference/comments.html#doc-comments) pub struct DocComments; + +/// Simple path that is valid in a `use` statement. Example: `foo::bar::baz`. +/// +/// [Rust reference](https://doc.rust-lang.org/reference/paths.html?highlight=path#simple-paths) +pub struct Path; diff --git a/bon/tests/integration/builder/attr_crate.rs b/bon/tests/integration/builder/attr_crate.rs new file mode 100644 index 00000000..fcb39fc7 --- /dev/null +++ b/bon/tests/integration/builder/attr_crate.rs @@ -0,0 +1,11 @@ +use crate::prelude::*; + +#[test] +fn test_struct() { + #[allow(clippy::single_component_path_imports)] + use bon; + + #[derive(Builder)] + #[builder(crate = self::bon)] + struct Sut {} +} diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs index 0fafd136..aa3d9059 100644 --- a/bon/tests/integration/builder/mod.rs +++ b/bon/tests/integration/builder/mod.rs @@ -1,5 +1,6 @@ mod attr_default; mod attr_derive; +mod attr_crate; mod attr_into; mod attr_on; mod attr_overwritable; diff --git a/bon/tests/integration/ui/compile_fail/attr_bon.stderr b/bon/tests/integration/ui/compile_fail/attr_bon.stderr index 28613d73..dd5ffb97 100644 --- a/bon/tests/integration/ui/compile_fail/attr_bon.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_bon.stderr @@ -1,4 +1,4 @@ -error: `#[bon]` attribute does not accept any parameters yet, but it will in future releases +error: Unknown field: `attrs` --> tests/integration/ui/compile_fail/attr_bon.rs:5:7 | 5 | #[bon(attrs)] diff --git a/bon/tests/integration/ui/compile_fail/attr_builder.stderr b/bon/tests/integration/ui/compile_fail/attr_builder.stderr index 6996c6e6..496b7b99 100644 --- a/bon/tests/integration/ui/compile_fail/attr_builder.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_builder.stderr @@ -10,7 +10,7 @@ error: expected parameters in parentheses 8 | #[builder()] | ^^ -error: this empty `#[builder]` attribute is unexpected; remove it or pass parameters in parentheses: `#[builder(...)]` +error: this empty `builder` attribute is unexpected; remove it or pass parameters in parentheses: `#[builder(...)]` --> tests/integration/ui/compile_fail/attr_builder.rs:13:7 | 13 | #[builder] @@ -22,7 +22,7 @@ error: expected parameters in parentheses 19 | #[builder()] | ^^ -error: this empty `#[builder]` attribute is unexpected; remove it or pass parameters in parentheses: `#[builder(...)]` +error: this empty `builder` attribute is unexpected; remove it or pass parameters in parentheses: `#[builder(...)]` --> tests/integration/ui/compile_fail/attr_builder.rs:24:41 | 24 | fn fn_empty_member_level_builder_attr(#[builder] _x: u32) {} @@ -34,7 +34,7 @@ error: expected parameters in parentheses 27 | fn fn_empty_member_level_builder_attr_with_parens(#[builder()] _x: u32) {} | ^^ -error: this empty `#[builder]` attribute is unexpected; remove it or pass parameters in parentheses: `#[builder(...)]` +error: this empty `builder` attribute is unexpected; remove it or pass parameters in parentheses: `#[builder(...)]` --> tests/integration/ui/compile_fail/attr_builder.rs:34:42 | 34 | fn empty_member_level_builder_attr(#[builder] _x: u32) {} diff --git a/bon/tests/integration/ui/compile_fail/attr_derive.stderr b/bon/tests/integration/ui/compile_fail/attr_derive.stderr index fc60a841..99d8dc89 100644 --- a/bon/tests/integration/ui/compile_fail/attr_derive.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_derive.stderr @@ -35,7 +35,7 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_member` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn clone_member(member: &Option) -> Option { | ^^^^^ required by this bound in `clone_member` @@ -52,7 +52,7 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_member` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn clone_member(member: &Option) -> Option { | ^^^^^ required by this bound in `clone_member` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_member` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn clone_member(member: &Option) -> Option { | ^^^^^ required by this bound in `clone_member` @@ -88,7 +88,7 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` note: required by a bound in `as_dyn_debug` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn as_dyn_debug(member: &T) -> &dyn Debug { | ^^^^^ required by this bound in `as_dyn_debug` @@ -107,7 +107,7 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` note: required by a bound in `as_dyn_debug` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn as_dyn_debug(member: &T) -> &dyn Debug { | ^^^^^ required by this bound in `as_dyn_debug` @@ -126,7 +126,7 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` note: required by a bound in `as_dyn_debug` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn as_dyn_debug(member: &T) -> &dyn Debug { | ^^^^^ required by this bound in `as_dyn_debug` @@ -145,7 +145,7 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` note: required by a bound in `as_dyn_debug` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn as_dyn_debug(member: &T) -> &dyn Debug { | ^^^^^ required by this bound in `as_dyn_debug` @@ -174,7 +174,7 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_member` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn clone_member(member: &Option) -> Option { | ^^^^^ required by this bound in `clone_member` @@ -191,7 +191,7 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_member` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn clone_member(member: &Option) -> Option { | ^^^^^ required by this bound in `clone_member` @@ -208,7 +208,7 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_member` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn clone_member(member: &Option) -> Option { | ^^^^^ required by this bound in `clone_member` @@ -227,7 +227,7 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` note: required by a bound in `as_dyn_debug` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn as_dyn_debug(member: &T) -> &dyn Debug { | ^^^^^ required by this bound in `as_dyn_debug` @@ -246,7 +246,7 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` note: required by a bound in `as_dyn_debug` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn as_dyn_debug(member: &T) -> &dyn Debug { | ^^^^^ required by this bound in `as_dyn_debug` @@ -265,7 +265,7 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` note: required by a bound in `as_dyn_debug` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn as_dyn_debug(member: &T) -> &dyn Debug { | ^^^^^ required by this bound in `as_dyn_debug` @@ -284,7 +284,7 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` note: required by a bound in `as_dyn_debug` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn as_dyn_debug(member: &T) -> &dyn Debug { | ^^^^^ required by this bound in `as_dyn_debug` @@ -319,7 +319,7 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_member` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn clone_member(member: &Option) -> Option { | ^^^^^ required by this bound in `clone_member` @@ -336,7 +336,7 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_member` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn clone_member(member: &Option) -> Option { | ^^^^^ required by this bound in `clone_member` @@ -353,7 +353,7 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_member` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn clone_member(member: &Option) -> Option { | ^^^^^ required by this bound in `clone_member` @@ -372,7 +372,7 @@ error[E0277]: `StructContainsNonTrait` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `StructContainsNonTrait` = note: add `#[derive(Debug)]` to `StructContainsNonTrait` or manually `impl Debug for StructContainsNonTrait` note: required by a bound in `as_dyn_debug` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn as_dyn_debug(member: &T) -> &dyn Debug { | ^^^^^ required by this bound in `as_dyn_debug` @@ -386,7 +386,7 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` note: required by a bound in `as_dyn_debug` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn as_dyn_debug(member: &T) -> &dyn Debug { | ^^^^^ required by this bound in `as_dyn_debug` @@ -405,7 +405,7 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` note: required by a bound in `as_dyn_debug` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn as_dyn_debug(member: &T) -> &dyn Debug { | ^^^^^ required by this bound in `as_dyn_debug` @@ -424,7 +424,7 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` note: required by a bound in `as_dyn_debug` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn as_dyn_debug(member: &T) -> &dyn Debug { | ^^^^^ required by this bound in `as_dyn_debug` @@ -443,7 +443,7 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` note: required by a bound in `as_dyn_debug` - --> src/private/derives.rs + --> src/__private/derives.rs | | pub fn as_dyn_debug(member: &T) -> &dyn Debug { | ^^^^^ required by this bound in `as_dyn_debug` diff --git a/bon/tests/integration/ui/compile_fail/attr_on.stderr b/bon/tests/integration/ui/compile_fail/attr_on.stderr index e7ccc8c3..23be760a 100644 --- a/bon/tests/integration/ui/compile_fail/attr_on.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_on.stderr @@ -22,7 +22,7 @@ error: nested attributes are not allowed in the type pattern of #[builder(on(typ 12 | #[builder(on(fn(#[attr] a: u32), into))] | ^ -error: this empty `#[on]` attribute is unexpected; remove it or pass parameters in parentheses: `#[on(...)]` +error: this empty `on` attribute is unexpected; remove it or pass parameters in parentheses: `#[on(...)]` --> tests/integration/ui/compile_fail/attr_on.rs:15:11 | 15 | #[builder(on)] diff --git a/bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs b/bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs index aca08674..605c0732 100644 --- a/bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs +++ b/bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs @@ -21,33 +21,54 @@ fn empty_paren_start_fn_on_free_function() {} #[builder(start_fn(vis = ""))] fn missing_name_for_start_fn_on_free_function1() {} -#[builder(start_fn(docs {}))] +#[builder(start_fn(doc {}))] fn missing_name_for_start_fn_on_free_function2() {} struct AssocCtx; +struct AssocCtx2; +struct AssocCtx3; +struct AssocCtx4; #[bon] impl AssocCtx { #[builder(start_fn)] - fn bare_start_fn_on_non_new_method() {} + fn new() {} } #[bon] -impl AssocCtx { +impl AssocCtx2 { #[builder(start_fn())] fn new() {} } +#[bon] +impl AssocCtx3 { + #[builder(start_fn(vis = ""))] + fn new() {} +} + +#[bon] +impl AssocCtx4 { + #[builder(start_fn(doc {}))] + fn new() {} +} + +#[bon] +impl AssocCtx { + #[builder(start_fn)] + fn bare_start_fn_on_method() {} +} + #[bon] impl AssocCtx { #[builder(start_fn(vis = ""))] - fn missing_name_for_start_fn_on_non_new_method1() {} + fn missing_name_for_start_fn_on_method1() {} } #[bon] impl AssocCtx { - #[builder(start_fn(docs {}))] - fn missing_name_for_start_fn_on_non_new_method2() {} + #[builder(start_fn(doc {}))] + fn missing_name_for_start_fn_on_method2() {} } fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.stderr b/bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.stderr new file mode 100644 index 00000000..8007567c --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.stderr @@ -0,0 +1,83 @@ +error: expected parameters in parentheses + --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:4:19 + | +4 | #[builder(start_fn())] + | ^^ + +error: expected parameters in parentheses + --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:8:20 + | +8 | #[builder(finish_fn())] + | ^^ + +error: this empty `start_fn` attribute is unexpected; remove it or pass parameters in parentheses: `#[start_fn(...)]` + --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:12:11 + | +12 | #[builder(start_fn)] + | ^^^^^^^^ + +error: this empty `start_fn` attribute is unexpected; remove it or pass parameters in parentheses: `#[start_fn(...)]` + --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:15:11 + | +15 | #[builder(start_fn)] + | ^^^^^^^^ + +error: expected parameters in parentheses + --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:18:19 + | +18 | #[builder(start_fn())] + | ^^ + +error: #[builder(start_fn(vis))] requires that you also specify #[builder(start_fn(name))] which makes the starting function not to replace the positional function under the #[builder] attribute; by default (without the explicit #[builder(start_fn(name))]) the name, visibility and documentation of the positional function are all copied to the starting function, and the positional function under the #[builder] attribute becomes private with #[doc(hidden)] and it's renamed (the name is not guaranteed to be stable) to make it inaccessible even within the current module + --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:21:20 + | +21 | #[builder(start_fn(vis = ""))] + | ^^^ + +error: #[builder(start_fn(doc))] requires that you also specify #[builder(start_fn(name))] which makes the starting function not to replace the positional function under the #[builder] attribute; by default (without the explicit #[builder(start_fn(name))]) the name, visibility and documentation of the positional function are all copied to the starting function, and the positional function under the #[builder] attribute becomes private with #[doc(hidden)] and it's renamed (the name is not guaranteed to be stable) to make it inaccessible even within the current module + --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:24:20 + | +24 | #[builder(start_fn(doc {}))] + | ^^^ + +error: this empty `start_fn` attribute is unexpected; remove it or pass parameters in parentheses: `#[start_fn(...)]` + --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:34:15 + | +34 | #[builder(start_fn)] + | ^^^^^^^^ + +error: expected parameters in parentheses + --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:40:23 + | +40 | #[builder(start_fn())] + | ^^ + +error: #[builder(start_fn(vis))] requires that you also specify #[builder(start_fn(name))] which makes the starting function not to replace the positional function under the #[builder] attribute; by default (without the explicit #[builder(start_fn(name))]) the name, visibility and documentation of the positional function are all copied to the starting function, and the positional function under the #[builder] attribute becomes private with #[doc(hidden)] and it's renamed (the name is not guaranteed to be stable) to make it inaccessible even within the current module + --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:46:24 + | +46 | #[builder(start_fn(vis = ""))] + | ^^^ + +error: #[builder(start_fn(doc))] requires that you also specify #[builder(start_fn(name))] which makes the starting function not to replace the positional function under the #[builder] attribute; by default (without the explicit #[builder(start_fn(name))]) the name, visibility and documentation of the positional function are all copied to the starting function, and the positional function under the #[builder] attribute becomes private with #[doc(hidden)] and it's renamed (the name is not guaranteed to be stable) to make it inaccessible even within the current module + --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:52:24 + | +52 | #[builder(start_fn(doc {}))] + | ^^^ + +error: this empty `start_fn` attribute is unexpected; remove it or pass parameters in parentheses: `#[start_fn(...)]` + --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:58:15 + | +58 | #[builder(start_fn)] + | ^^^^^^^^ + +error: #[builder(start_fn(vis))] requires that you also specify #[builder(start_fn(name))] which makes the starting function not to replace the positional function under the #[builder] attribute; by default (without the explicit #[builder(start_fn(name))]) the name, visibility and documentation of the positional function are all copied to the starting function, and the positional function under the #[builder] attribute becomes private with #[doc(hidden)] and it's renamed (the name is not guaranteed to be stable) to make it inaccessible even within the current module + --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:64:24 + | +64 | #[builder(start_fn(vis = ""))] + | ^^^ + +error: #[builder(start_fn(doc))] requires that you also specify #[builder(start_fn(name))] which makes the starting function not to replace the positional function under the #[builder] attribute; by default (without the explicit #[builder(start_fn(name))]) the name, visibility and documentation of the positional function are all copied to the starting function, and the positional function under the #[builder] attribute becomes private with #[doc(hidden)] and it's renamed (the name is not guaranteed to be stable) to make it inaccessible even within the current module + --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:70:24 + | +70 | #[builder(start_fn(doc {}))] + | ^^^ diff --git a/bon/tests/integration/ui/compile_fail/wrong_delimiters.rs b/bon/tests/integration/ui/compile_fail/wrong_delimiters.rs index 1ba55d70..f421702d 100644 --- a/bon/tests/integration/ui/compile_fail/wrong_delimiters.rs +++ b/bon/tests/integration/ui/compile_fail/wrong_delimiters.rs @@ -33,10 +33,10 @@ struct SquareBracketsInFieldSetters { #[derive(bon::Builder)] #[builder( - builder_type(docs[]), - state_mod(docs[]), - start_fn(docs[]), - finish_fn(docs[]), + builder_type(doc[]), + state_mod(doc[]), + start_fn(doc[]), + finish_fn(doc[]), )] struct SquareBracketsInFieldDoc { #[builder(setters(doc[]))] diff --git a/bon/tests/integration/ui/compile_fail/wrong_delimiters.stderr b/bon/tests/integration/ui/compile_fail/wrong_delimiters.stderr index ddf1778b..e3c8effe 100644 --- a/bon/tests/integration/ui/compile_fail/wrong_delimiters.stderr +++ b/bon/tests/integration/ui/compile_fail/wrong_delimiters.stderr @@ -1,9 +1,3 @@ -error: wrong delimiter, expected parentheses e.g. `start_fn(...)`, but got curly braces: `start_fn{...}` - --> tests/integration/ui/compile_fail/wrong_delimiters.rs:5:5 - | -5 | start_fn{}, - | ^^^^^^^^ - error: wrong delimiter, expected parentheses e.g. `builder_type(...)`, but got curly braces: `builder_type{...}` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:3:5 | @@ -16,6 +10,12 @@ error: wrong delimiter, expected parentheses e.g. `state_mod(...)`, but got curl 4 | state_mod{}, | ^^^^^^^^^ +error: wrong delimiter, expected parentheses e.g. `start_fn(...)`, but got curly braces: `start_fn{...}` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:5:5 + | +5 | start_fn{}, + | ^^^^^^^^ + error: wrong delimiter, expected parentheses e.g. `finish_fn(...)`, but got curly braces: `finish_fn{...}` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:6:5 | @@ -28,12 +28,6 @@ error: wrong delimiter, expected parentheses e.g. `setters(...)`, but got curly 12 | #[builder(setters{})] | ^^^^^^^ -error: wrong delimiter, expected parentheses e.g. `start_fn(...)`, but got square brackets: `start_fn[...]` - --> tests/integration/ui/compile_fail/wrong_delimiters.rs:20:5 - | -20 | start_fn[doc[]], - | ^^^^^^^^ - error: wrong delimiter, expected parentheses e.g. `builder_type(...)`, but got square brackets: `builder_type[...]` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:18:5 | @@ -46,6 +40,12 @@ error: wrong delimiter, expected parentheses e.g. `state_mod(...)`, but got squa 19 | state_mod[doc[]], | ^^^^^^^^^ +error: wrong delimiter, expected parentheses e.g. `start_fn(...)`, but got square brackets: `start_fn[...]` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:20:5 + | +20 | start_fn[doc[]], + | ^^^^^^^^ + error: wrong delimiter, expected parentheses e.g. `finish_fn(...)`, but got square brackets: `finish_fn[...]` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:21:5 | @@ -58,35 +58,29 @@ error: wrong delimiter, expected parentheses e.g. `setters(...)`, but got square 30 | #[builder(setters[])] | ^^^^^^^ -error: Unknown field: `docs`. Did you mean `doc`? - --> tests/integration/ui/compile_fail/wrong_delimiters.rs:38:14 - | -38 | start_fn(docs[]), - | ^^^^ - -error: Unknown field: `docs`. Did you mean `doc`? +error: wrong delimiter, expected curly braces e.g. `doc{...}`, but got square brackets: `doc[...]` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:36:18 | -36 | builder_type(docs[]), - | ^^^^ +36 | builder_type(doc[]), + | ^^^ -error: Unknown field: `docs`. Did you mean `doc`? +error: wrong delimiter, expected curly braces e.g. `doc{...}`, but got square brackets: `doc[...]` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:37:15 | -37 | state_mod(docs[]), - | ^^^^ +37 | state_mod(doc[]), + | ^^^ -error: Unknown field: `docs`. Did you mean `doc`? - --> tests/integration/ui/compile_fail/wrong_delimiters.rs:39:15 +error: wrong delimiter, expected curly braces e.g. `doc{...}`, but got square brackets: `doc[...]` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:38:14 | -39 | finish_fn(docs[]), - | ^^^^ +38 | start_fn(doc[]), + | ^^^ -error: wrong delimiter, expected curly braces e.g. `doc{...}`, but got parentheses: `doc(...)` - --> tests/integration/ui/compile_fail/wrong_delimiters.rs:50:14 +error: wrong delimiter, expected curly braces e.g. `doc{...}`, but got square brackets: `doc[...]` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:39:15 | -50 | start_fn(doc()), - | ^^^ +39 | finish_fn(doc[]), + | ^^^ error: wrong delimiter, expected curly braces e.g. `doc{...}`, but got parentheses: `doc(...)` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:48:18 @@ -100,6 +94,12 @@ error: wrong delimiter, expected curly braces e.g. `doc{...}`, but got parenthes 49 | state_mod(doc()), | ^^^ +error: wrong delimiter, expected curly braces e.g. `doc{...}`, but got parentheses: `doc(...)` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:50:14 + | +50 | start_fn(doc()), + | ^^^ + error: wrong delimiter, expected curly braces e.g. `doc{...}`, but got parentheses: `doc(...)` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:51:15 | From 40fc58f3dca6a8f02bd91127c4286bf4a293c041 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 25 Oct 2024 02:16:50 +0000 Subject: [PATCH 04/12] Fixes for the `crate` attribute. Allow only absolute paths --- bon-macros/src/builder/builder_gen/models.rs | 2 +- .../builder_gen/top_level_config/mod.rs | 2 +- bon-macros/src/builder/item_impl.rs | 6 +- bon-macros/src/parsing/mod.rs | 47 +++++-- bon-macros/src/util/expr.rs | 21 +++ bon-macros/src/util/ide.rs | 17 ++- bon-macros/src/util/mod.rs | 2 + bon/tests/integration/builder/attr_crate.rs | 123 +++++++++++++++++- .../integration/ui/compile_fail/attr_bon.rs | 10 ++ .../ui/compile_fail/attr_bon.stderr | 8 ++ .../integration/ui/compile_fail/attr_crate.rs | 42 ++++++ .../ui/compile_fail/attr_crate.stderr | 41 ++++++ 12 files changed, 297 insertions(+), 24 deletions(-) create mode 100644 bon-macros/src/util/expr.rs create mode 100644 bon/tests/integration/ui/compile_fail/attr_crate.rs create mode 100644 bon/tests/integration/ui/compile_fail/attr_crate.stderr diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index d03ab6b4..0626a7a9 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -1,7 +1,7 @@ use super::member::Member; use super::top_level_config::{DerivesConfig, OnConfig}; use crate::normalization::GenericsNamespace; -use crate::parsing::{SpannedKey, ItemSigConfig}; +use crate::parsing::{ItemSigConfig, SpannedKey}; use crate::util::prelude::*; use std::borrow::Cow; diff --git a/bon-macros/src/builder/builder_gen/top_level_config/mod.rs b/bon-macros/src/builder/builder_gen/top_level_config/mod.rs index 3166ed79..bfb8975e 100644 --- a/bon-macros/src/builder/builder_gen/top_level_config/mod.rs +++ b/bon-macros/src/builder/builder_gen/top_level_config/mod.rs @@ -41,7 +41,7 @@ fn parse_start_fn(meta: &syn::Meta) -> Result { #[derive(Debug, FromMeta)] pub(crate) struct TopLevelConfig { - #[darling(rename = "crate", default, map = Some, with = crate::parsing::parse_path_mod_style)] + #[darling(rename = "crate", default, map = Some, with = crate::parsing::parse_bon_crate_path)] pub(crate) bon: Option, #[darling(default, with = parse_start_fn)] diff --git a/bon-macros/src/builder/item_impl.rs b/bon-macros/src/builder/item_impl.rs index 624044a3..3a79754d 100644 --- a/bon-macros/src/builder/item_impl.rs +++ b/bon-macros/src/builder/item_impl.rs @@ -11,7 +11,7 @@ use syn::visit_mut::VisitMut; #[derive(FromMeta)] pub(crate) struct ImplInputParams { /// Overrides the path to the `bon` crate. - #[darling(rename = "crate", default, map = Some, with = crate::parsing::parse_path_mod_style)] + #[darling(rename = "crate", default, map = Some, with = crate::parsing::parse_bon_crate_path)] bon: Option, } @@ -44,8 +44,8 @@ pub(crate) fn generate( if builder_fns.is_empty() { bail!( &Span::call_site(), - "There are no #[builder] functions in the impl block, so there is no \ - need for a #[bon] attribute on the impl block" + "there are no #[builder] functions in the impl block, so there is no \ + need for a #[bon] attribute here" ); } diff --git a/bon-macros/src/parsing/mod.rs b/bon-macros/src/parsing/mod.rs index cbffddd6..27614fc9 100644 --- a/bon-macros/src/parsing/mod.rs +++ b/bon-macros/src/parsing/mod.rs @@ -83,30 +83,55 @@ where Ok(punctuated) } -pub(crate) fn parse_path_mod_style(meta: &syn::Meta) -> Result { - let err = |span: Span| err!(&span, "expected a bare path, like `foo::bar`"); +fn parse_path_mod_style(meta: &syn::Meta) -> Result { + let err = |span: Span| err!(&span, "expected a simple path, like `foo::bar`"); let expr = match meta { syn::Meta::NameValue(meta) => &meta.value, _ => return Err(err(meta.span())), }; - let expr = match expr { - syn::Expr::Path(expr) => expr, - _ => return Err(err(expr.span())), - }; + Ok(expr.require_path_mod_style()?.clone()) +} + +pub(crate) fn parse_bon_crate_path(meta: &syn::Meta) -> Result { + let path = parse_path_mod_style(meta)?; + + let prefix = &path + .segments + .first() + .ok_or_else(|| err!(&path, "path must have at least one segment"))? + .ident; + + let is_absolute = path.leading_colon.is_some() || prefix == "crate" || prefix == "$crate"; - reject_syntax("attribute", &expr.attrs.first())?; - reject_syntax(" syntax", &expr.qself)?; + if is_absolute { + return Ok(path); + } + + if prefix == "super" || prefix == "self" { + bail!( + &path, + "path must not be relative; specify the path that starts with `crate::` \ + instead; if you want to refer to a reexport from an external crate then \ + use a leading colon like `::crate_name::reexport::path::bon`" + ) + } - expr.path.require_mod_style()?; + let path_str = darling::util::path_to_string(&path); - Ok(expr.path.clone()) + bail!( + &path, + "path must be absolute; if you want to refer to a reexport from an external \ + crate then add a leading colon like `::{path_str}`; if the path leads to a module \ + in the current crate, then specify the absolute path with `crate` like \ + `crate::reexport::path::bon` or `$crate::reexport::path::bon` (if within a macro)" + ) } // Lint from nightly. `&Option` is used to reduce syntax at the callsite #[allow(unknown_lints, clippy::ref_option)] -fn reject_syntax(name: &'static str, syntax: &Option) -> Result { +pub(crate) fn reject_syntax(name: &'static str, syntax: &Option) -> Result { if let Some(syntax) = syntax { bail!(syntax, "{name} is not allowed here") } diff --git a/bon-macros/src/util/expr.rs b/bon-macros/src/util/expr.rs new file mode 100644 index 00000000..36cc2bd4 --- /dev/null +++ b/bon-macros/src/util/expr.rs @@ -0,0 +1,21 @@ +use crate::util::prelude::*; + +pub(crate) trait ExprExt { + fn require_path_mod_style(&self) -> Result<&syn::Path>; +} + +impl ExprExt for syn::Expr { + fn require_path_mod_style(&self) -> Result<&syn::Path> { + let expr = match self { + Self::Path(expr) => expr, + _ => bail!(self, "expected a simple path, like `foo::bar`"), + }; + + crate::parsing::reject_syntax("attribute", &expr.attrs.first())?; + crate::parsing::reject_syntax(" syntax", &expr.qself)?; + + expr.path.require_mod_style()?; + + Ok(&expr.path) + } +} diff --git a/bon-macros/src/util/ide.rs b/bon-macros/src/util/ide.rs index 5bca5326..6a425e81 100644 --- a/bon-macros/src/util/ide.rs +++ b/bon-macros/src/util/ide.rs @@ -135,6 +135,16 @@ fn paths_from_meta(meta: Vec) -> Vec { /// we can hint the IDEs to provide completions for the attributes based on what's /// available in the module the use statement references. pub(crate) fn generate_completion_triggers(meta: Vec) -> TokenStream { + let bon = meta + .iter() + .find_map(|meta| match meta { + Meta::NameValue(meta) => Some(meta.value.as_ref()), + _ => None, + }) + .flatten() + .and_then(|expr| Some(expr.require_path_mod_style().ok()?.clone())) + .unwrap_or_else(|| syn::parse_quote!(::bon)); + let completions = CompletionsSchema::with_children( "builder_top_level", vec![ @@ -151,7 +161,7 @@ pub(crate) fn generate_completion_triggers(meta: Vec) -> TokenStream { ], ); - let completion_triggers = completions.generate_completion_triggers(meta, &[]); + let completion_triggers = completions.generate_completion_triggers(&bon, meta, &[]); quote! { // The special `rust_analyzer` CFG is enabled only when Rust Analyzer is @@ -194,6 +204,7 @@ impl CompletionsSchema { fn generate_completion_triggers( &self, + bon: &syn::Path, mut meta: Vec, module_prefix: &[&syn::Ident], ) -> TokenStream { @@ -228,7 +239,7 @@ impl CompletionsSchema { }) .concat(); - child.generate_completion_triggers(child_metas, &module_name) + child.generate_completion_triggers(bon, child_metas, &module_name) }) .collect::>(); @@ -243,7 +254,7 @@ impl CompletionsSchema { // to avoid Rust Analyzer from providing completions for the // `self` keyword in the `use` statement. It works because // `use self::self` is not a valid syntax. - use ::bon::__private::ide #(::#module_name)* ::*; + use #bon::__private::ide #(::#module_name)* ::*; use self::{ #( #paths as _, )* }; } diff --git a/bon-macros/src/util/mod.rs b/bon-macros/src/util/mod.rs index e80aaec6..5c2e8ae0 100644 --- a/bon-macros/src/util/mod.rs +++ b/bon-macros/src/util/mod.rs @@ -1,4 +1,5 @@ mod attrs; +mod expr; mod fn_arg; mod generic_param; mod ident; @@ -30,6 +31,7 @@ pub(crate) mod prelude { pub(crate) type Result = std::result::Result; pub(crate) use super::attrs::AttributeExt; + pub(crate) use super::expr::ExprExt; pub(crate) use super::fn_arg::FnArgExt; pub(crate) use super::generic_param::GenericParamExt; pub(crate) use super::ident::IdentExt; diff --git a/bon/tests/integration/builder/attr_crate.rs b/bon/tests/integration/builder/attr_crate.rs index fcb39fc7..9ecabf56 100644 --- a/bon/tests/integration/builder/attr_crate.rs +++ b/bon/tests/integration/builder/attr_crate.rs @@ -1,11 +1,124 @@ use crate::prelude::*; +#[allow(clippy::single_component_path_imports)] +use bon; + #[test] fn test_struct() { - #[allow(clippy::single_component_path_imports)] - use bon; + { + #[derive(Builder)] + #[builder( + crate = crate::builder::attr_crate::bon, + derive(Debug, Clone) + )] + struct Sut { + _a: u32, + _b: u32, + } + + let _ = Sut::builder().a(1).b(2).build(); + } + { + macro_rules! in_macro { + () => { + #[derive(Builder)] + #[builder(crate = $crate::builder::attr_crate::bon, derive(Debug, Clone))] + struct Sut { + _a: u32, + _b: u32, + } + }; + } + in_macro!(); + + let _ = Sut::builder().a(1).b(2).build(); + } + { + #[derive(Builder)] + #[builder( + crate = ::bon, + derive(Debug, Clone) + )] + struct Sut { + _a: u32, + _b: u32, + } + + let _ = Sut::builder().a(1).b(2).build(); + } +} + +#[test] +fn test_free_fn() { + { + #[builder( + crate = crate::builder::attr_crate::bon, + derive(Debug, Clone) + )] + fn sut(_a: u32, _b: u32) {} + + sut().a(1).b(2).call(); + } + { + macro_rules! in_macro { + () => { + #[builder(crate = $crate::builder::attr_crate::bon, derive(Debug, Clone))] + fn sut(_a: u32, _b: u32) {} + }; + } + in_macro!(); + + sut().a(1).b(2).call(); + } + { + #[builder( + crate = ::bon, + derive(Debug, Clone) + )] + fn sut(_a: u32, _b: u32) {} + + sut().a(1).b(2).call(); + } +} + +#[test] +fn test_assoc_method() { + { + struct Sut; + + #[bon(crate = crate::builder::attr_crate::bon)] + impl Sut { + #[builder(derive(Debug, Clone))] + fn sut(_a: u32, _b: u32) {} + } + + Sut::sut().a(1).b(2).call(); + } + { + macro_rules! in_macro { + () => { + struct Sut; + + #[bon(crate = $crate::builder::attr_crate::bon)] + impl Sut { + #[builder(derive(Debug, Clone))] + fn sut(_a: u32, _b: u32) {} + } + }; + } + in_macro!(); + + Sut::sut().a(1).b(2).call(); + } + { + struct Sut; + + #[bon(crate = ::bon)] + impl Sut { + #[builder(derive(Debug, Clone))] + fn sut(_a: u32, _b: u32) {} + } - #[derive(Builder)] - #[builder(crate = self::bon)] - struct Sut {} + Sut::sut().a(1).b(2).call(); + } } diff --git a/bon/tests/integration/ui/compile_fail/attr_bon.rs b/bon/tests/integration/ui/compile_fail/attr_bon.rs index c84acc9e..e810dc8d 100644 --- a/bon/tests/integration/ui/compile_fail/attr_bon.rs +++ b/bon/tests/integration/ui/compile_fail/attr_bon.rs @@ -16,5 +16,15 @@ impl BuilderAttrOnReceiver { fn sut(#[builder] &self) {} } +struct NoBuilderMethods; + +#[bon] +impl NoBuilderMethods { + fn not_builder1() {} + fn not_builder2(&self) {} + + const NOT_BUILDER: () = (); +} + fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_bon.stderr b/bon/tests/integration/ui/compile_fail/attr_bon.stderr index dd5ffb97..2d9962db 100644 --- a/bon/tests/integration/ui/compile_fail/attr_bon.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_bon.stderr @@ -9,3 +9,11 @@ error: #[builder] attributes on the receiver are not supported | 16 | fn sut(#[builder] &self) {} | ^ + +error: there are no #[builder] functions in the impl block, so there is no need for a #[bon] attribute here + --> tests/integration/ui/compile_fail/attr_bon.rs:21:1 + | +21 | #[bon] + | ^^^^^^ + | + = note: this error originates in the attribute macro `bon` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/bon/tests/integration/ui/compile_fail/attr_crate.rs b/bon/tests/integration/ui/compile_fail/attr_crate.rs new file mode 100644 index 00000000..31ecf7d0 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_crate.rs @@ -0,0 +1,42 @@ +use bon::{bon, builder}; + +#[builder(crate = self::bon)] +fn relative_1() {} + +#[builder(crate = super::bon)] +fn relative_2() {} + +#[builder(crate = bon)] +fn relative_3() {} + +struct CrateAttrInMethod; + +#[bon] +impl CrateAttrInMethod { + #[builder(crate = ::bon)] + fn method() {} +} + +struct Relative; + +#[bon(crate = self::bon)] +impl Relative { + #[builder] + fn method1() {} +} + +#[bon(crate = super::bon)] +impl Relative { + #[builder] + fn method2() {} +} + +#[bon(crate = bon)] +impl Relative { + #[builder] + fn method3() {} +} + + + +fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_crate.stderr b/bon/tests/integration/ui/compile_fail/attr_crate.stderr new file mode 100644 index 00000000..a7f15c18 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_crate.stderr @@ -0,0 +1,41 @@ +error: path must not be relative; specify the path that starts with `crate::` instead; if you want to refer to a reexport from an external crate then use a leading colon like `::crate_name::reexport::path::bon` + --> tests/integration/ui/compile_fail/attr_crate.rs:3:19 + | +3 | #[builder(crate = self::bon)] + | ^^^^ + +error: path must not be relative; specify the path that starts with `crate::` instead; if you want to refer to a reexport from an external crate then use a leading colon like `::crate_name::reexport::path::bon` + --> tests/integration/ui/compile_fail/attr_crate.rs:6:19 + | +6 | #[builder(crate = super::bon)] + | ^^^^^ + +error: path must be absolute; if you want to refer to a reexport from an external crate then add a leading colon like `::bon`; if the path leads to a module in the current crate, then specify the absolute path with `crate` like `crate::reexport::path::bon` or `$crate::reexport::path::bon` (if within a macro) + --> tests/integration/ui/compile_fail/attr_crate.rs:9:19 + | +9 | #[builder(crate = bon)] + | ^^^ + +error: `crate` parameter should be specified via `#[bon(crate = path::to::bon)]` when impl block syntax is used; no need to specify it in the method's `#[builder]` attribute + --> tests/integration/ui/compile_fail/attr_crate.rs:16:23 + | +16 | #[builder(crate = ::bon)] + | ^ + +error: path must not be relative; specify the path that starts with `crate::` instead; if you want to refer to a reexport from an external crate then use a leading colon like `::crate_name::reexport::path::bon` + --> tests/integration/ui/compile_fail/attr_crate.rs:22:15 + | +22 | #[bon(crate = self::bon)] + | ^^^^ + +error: path must not be relative; specify the path that starts with `crate::` instead; if you want to refer to a reexport from an external crate then use a leading colon like `::crate_name::reexport::path::bon` + --> tests/integration/ui/compile_fail/attr_crate.rs:28:15 + | +28 | #[bon(crate = super::bon)] + | ^^^^^ + +error: path must be absolute; if you want to refer to a reexport from an external crate then add a leading colon like `::bon`; if the path leads to a module in the current crate, then specify the absolute path with `crate` like `crate::reexport::path::bon` or `$crate::reexport::path::bon` (if within a macro) + --> tests/integration/ui/compile_fail/attr_crate.rs:34:15 + | +34 | #[bon(crate = bon)] + | ^^^ From ddc84a2bccdc5b518ab4cf667ae317cda79be4e9 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 25 Oct 2024 02:36:15 +0000 Subject: [PATCH 05/12] Fix doc tests --- website/reference/builder.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/website/reference/builder.md b/website/reference/builder.md index bbbb09ca..232dde91 100644 --- a/website/reference/builder.md +++ b/website/reference/builder.md @@ -315,7 +315,7 @@ If `vis` parameter is not specified, then the visibility of the exposed position ::: code-group -```rust [Free function] +```rust ignore [Free function] use bon::builder; #[builder(expose_positional_fn = example_positional)] // [!code highlight] @@ -331,7 +331,7 @@ example() .call(); ``` -```rust [Associated method] +```rust ignore [Associated method] use bon::bon; struct Example; @@ -365,7 +365,7 @@ So when `#[builder]` is placed on a method called `new`, it'll generate a method **Example:** -```rust +```rust ignore use bon::bon; struct Example { @@ -968,10 +968,6 @@ In general, it's not recommended to annotate optional members with `#[builder(fi ::: -*[Member]: Struct field or a function argument -*[member]: Struct field or a function argument -*[members]: Struct fields or function arguments - ### `into` **Applies to:** @@ -1311,3 +1307,7 @@ Importantly, `Into` conversions for such members work slightly differently from In general, it's not recommended to annotate optional members with `#[builder(start_fn)]` because you can't omit setting them using the positional function call syntax. ::: + +*[Member]: Struct field or a function argument +*[member]: Struct field or a function argument +*[members]: Struct fields or function arguments From 5de11cfae158837d873b1848ed5da7b33e189f7d Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 25 Oct 2024 03:45:27 +0000 Subject: [PATCH 06/12] Fix MSRV --- bon-macros/src/error/panic_context.rs | 67 +++++++++++-------- bon/tests/integration/builder/attr_crate.rs | 16 ++--- .../builder/attr_top_level_start_fn.rs | 2 +- 3 files changed, 48 insertions(+), 37 deletions(-) diff --git a/bon-macros/src/error/panic_context.rs b/bon-macros/src/error/panic_context.rs index e551584a..48c726d6 100644 --- a/bon-macros/src/error/panic_context.rs +++ b/bon-macros/src/error/panic_context.rs @@ -1,23 +1,34 @@ use std::fmt; -use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; #[rustversion::since(1.65.0)] use std::panic::PanicHookInfo as StdPanicHookInfo; use std::any::Any; +use std::cell::RefCell; #[rustversion::before(1.81.0)] use std::panic::PanicInfo as StdPanicHookInfo; +use std::rc::Rc; + +fn with_global_panic_context(f: impl FnOnce(&mut GlobalPanicContext) -> T) -> T { + thread_local! { + /// A lazily initialized global panic log. It aggregates the panics from the + /// current thread. This is used to capture info about the panic after the + /// `catch_unwind` call and observe the context of the panic that happened. + /// + /// Unfortunately, we can't use a global static variable that would be + /// accessible by all threads because `std::sync::Mutex::new` became + /// `const` only in Rust 1.63.0, which is above our MSRV 1.59.0. However, + /// a thread-local works perfectly fine for our use case because we don't + /// spawn threads in proc macros. + static GLOBAL: RefCell = const { + RefCell::new(GlobalPanicContext { + last_panic: None, + initialized: false, + }) + }; + } -fn lock_global_panic_info() -> MutexGuard<'static, GlobalPanicContext> { - /// A lazily initialized global panic log. It aggregates the panics from all threads. - /// This is used to find the info about the panic after the `catch_unwind` call - /// to observe the context of the panic that happened. - static GLOBAL: Mutex = Mutex::new(GlobalPanicContext { - last_panic: None, - initialized: false, - }); - - GLOBAL.lock().unwrap_or_else(PoisonError::into_inner) + GLOBAL.with(|global| f(&mut global.borrow_mut())) } struct GlobalPanicContext { @@ -36,8 +47,10 @@ pub(super) struct PanicListener { impl PanicListener { pub(super) fn register() -> Self { - let mut global = lock_global_panic_info(); + with_global_panic_context(Self::register_with_global) + } + fn register_with_global(global: &mut GlobalPanicContext) -> Self { if global.initialized { return Self { _private: () }; } @@ -45,21 +58,17 @@ impl PanicListener { let prev_panic_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |panic_info| { - { - // Make sure the lock is released before the other panic hook is called - // to prevent potential reentrancy. Therefore this code is in a block. - let mut global = lock_global_panic_info(); + with_global_panic_context(|global| { let panic_number = global.last_panic.as_ref().map(|p| p.0.panic_number); let panic_number = panic_number.unwrap_or(0) + 1; global.last_panic = Some(PanicContext::from_std(panic_info, panic_number)); - } + }); prev_panic_hook(panic_info); })); global.initialized = true; - drop(global); Self { _private: () } } @@ -69,19 +78,19 @@ impl PanicListener { // the global panic listener in the `register` method. #[allow(clippy::unused_self)] pub(super) fn get_last_panic(&self) -> Option { - lock_global_panic_info().last_panic.clone() + with_global_panic_context(|global| global.last_panic.clone()) } } /// Contains all the necessary bits of information about the occurred panic. #[derive(Clone)] -pub(super) struct PanicContext(Arc); +pub(super) struct PanicContext(Rc); struct PanicContextShared { backtrace: backtrace::Backtrace, location: Option, - thread_name: Option, + thread: String, /// Defines the number of panics that happened before this one. Each panic /// increments this counter. This is useful to know how many panics happened @@ -92,10 +101,16 @@ struct PanicContextShared { impl PanicContext { fn from_std(std_panic_info: &StdPanicHookInfo<'_>, panic_number: usize) -> Self { let location = std_panic_info.location(); - Self(Arc::new(PanicContextShared { + let current_thread = std::thread::current(); + let thread_ = current_thread + .name() + .map(String::from) + .unwrap_or_else(|| format!("{:?}", current_thread.id())); + + Self(Rc::new(PanicContextShared { backtrace: backtrace::Backtrace::capture(), location: location.map(PanicLocation::from_std), - thread_name: std::thread::current().name().map(String::from), + thread: thread_, panic_number, })) } @@ -112,7 +127,7 @@ impl fmt::Display for PanicContext { let PanicContextShared { location, backtrace, - thread_name, + thread, panic_number, } = &*self.0; @@ -122,9 +137,7 @@ impl fmt::Display for PanicContext { write!(f, " at {location}")?; } - if let Some(thread_name) = thread_name { - write!(f, " in thread '{thread_name}'")?; - } + write!(f, " in thread '{thread}'")?; if *panic_number > 1 { write!(f, " (total panics observed: {panic_number})")?; diff --git a/bon/tests/integration/builder/attr_crate.rs b/bon/tests/integration/builder/attr_crate.rs index 9ecabf56..740391c4 100644 --- a/bon/tests/integration/builder/attr_crate.rs +++ b/bon/tests/integration/builder/attr_crate.rs @@ -1,14 +1,12 @@ use crate::prelude::*; - -#[allow(clippy::single_component_path_imports)] -use bon; +use bon as lyra; #[test] fn test_struct() { { #[derive(Builder)] #[builder( - crate = crate::builder::attr_crate::bon, + crate = crate::builder::attr_crate::lyra, derive(Debug, Clone) )] struct Sut { @@ -22,7 +20,7 @@ fn test_struct() { macro_rules! in_macro { () => { #[derive(Builder)] - #[builder(crate = $crate::builder::attr_crate::bon, derive(Debug, Clone))] + #[builder(crate = $crate::builder::attr_crate::lyra, derive(Debug, Clone))] struct Sut { _a: u32, _b: u32, @@ -52,7 +50,7 @@ fn test_struct() { fn test_free_fn() { { #[builder( - crate = crate::builder::attr_crate::bon, + crate = crate::builder::attr_crate::lyra, derive(Debug, Clone) )] fn sut(_a: u32, _b: u32) {} @@ -62,7 +60,7 @@ fn test_free_fn() { { macro_rules! in_macro { () => { - #[builder(crate = $crate::builder::attr_crate::bon, derive(Debug, Clone))] + #[builder(crate = $crate::builder::attr_crate::lyra, derive(Debug, Clone))] fn sut(_a: u32, _b: u32) {} }; } @@ -86,7 +84,7 @@ fn test_assoc_method() { { struct Sut; - #[bon(crate = crate::builder::attr_crate::bon)] + #[bon(crate = crate::builder::attr_crate::lyra)] impl Sut { #[builder(derive(Debug, Clone))] fn sut(_a: u32, _b: u32) {} @@ -99,7 +97,7 @@ fn test_assoc_method() { () => { struct Sut; - #[bon(crate = $crate::builder::attr_crate::bon)] + #[bon(crate = $crate::builder::attr_crate::lyra)] impl Sut { #[builder(derive(Debug, Clone))] fn sut(_a: u32, _b: u32) {} diff --git a/bon/tests/integration/builder/attr_top_level_start_fn.rs b/bon/tests/integration/builder/attr_top_level_start_fn.rs index eb3556cc..4dd9e881 100644 --- a/bon/tests/integration/builder/attr_top_level_start_fn.rs +++ b/bon/tests/integration/builder/attr_top_level_start_fn.rs @@ -25,7 +25,7 @@ fn test_assoc_method() { } { - struct Sut; + pub(crate) struct Sut; #[bon] impl Sut { From 22426afe25ffaed231f4ec8842fbdbcb762b7787 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 25 Oct 2024 03:45:59 +0000 Subject: [PATCH 07/12] taplo-fmt --- bon-macros/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bon-macros/Cargo.toml b/bon-macros/Cargo.toml index d21db423..6f9b6c50 100644 --- a/bon-macros/Cargo.toml +++ b/bon-macros/Cargo.toml @@ -58,7 +58,7 @@ quote = "1" syn = { version = "2.0.56", features = ["full", "visit-mut", "visit"] } prettyplease = "0.2" -rustversion = "1.0" +rustversion = "1.0" [features] default = [] From 2927135c05837e223b4da0a3042a034d78d95b69 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 25 Oct 2024 03:47:56 +0000 Subject: [PATCH 08/12] cargo fmt --- bon-macros/src/builder/builder_gen/input_struct.rs | 2 +- bon-macros/src/builder/builder_gen/member/config/mod.rs | 6 +----- bon-macros/src/builder/builder_gen/member/mod.rs | 4 ++-- bon-macros/src/builder/builder_gen/member/named.rs | 2 +- bon-macros/src/builder/builder_gen/mod.rs | 2 +- bon/tests/integration/builder/mod.rs | 2 +- 6 files changed, 7 insertions(+), 11 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 9ffb8838..1193fefb 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -5,7 +5,7 @@ use super::{ }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; use crate::normalization::{GenericsNamespace, SyntaxVariant}; -use crate::parsing::{SpannedKey, ItemSigConfig}; +use crate::parsing::{ItemSigConfig, SpannedKey}; use crate::util::prelude::*; use darling::FromMeta; use std::borrow::Cow; diff --git a/bon-macros/src/builder/builder_gen/member/config/mod.rs b/bon-macros/src/builder/builder_gen/member/config/mod.rs index e4fab79a..75cce9a0 100644 --- a/bon-macros/src/builder/builder_gen/member/config/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/config/mod.rs @@ -218,11 +218,7 @@ impl MemberConfig { } if let Some(with) = &self.with { - self.validate_mutually_exclusive( - ParamName::With, - with.key.span(), - &[ParamName::Into], - )?; + self.validate_mutually_exclusive(ParamName::With, with.key.span(), &[ParamName::Into])?; } Ok(()) diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index af6d789e..34934f9a 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -1,5 +1,5 @@ -mod into_conversion; mod config; +mod into_conversion; mod named; pub(crate) use config::*; @@ -9,8 +9,8 @@ use super::top_level_config::OnConfig; use crate::normalization::SyntaxVariant; use crate::parsing::SpannedKey; use crate::util::prelude::*; -use darling::FromAttributes; use config::MemberConfig; +use darling::FromAttributes; use std::fmt; #[derive(Debug, Clone, Copy)] diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index 3dde1575..83d4de3c 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -1,7 +1,7 @@ use super::config::MemberConfig; use super::{config, MemberOrigin}; -use crate::builder::builder_gen::top_level_config::OnConfig; use crate::builder::builder_gen::member::config::SettersFnParams; +use crate::builder::builder_gen::top_level_config::OnConfig; use crate::normalization::SyntaxVariant; use crate::parsing::{ItemSigConfig, SpannedKey}; use crate::util::prelude::*; diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index e8cfdbca..8fe45b57 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -1,10 +1,10 @@ mod builder_derives; -mod top_level_config; mod finish_fn; mod member; mod models; mod setters; mod state_mod; +mod top_level_config; pub(crate) mod input_fn; pub(crate) mod input_struct; diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs index aa3d9059..225718c6 100644 --- a/bon/tests/integration/builder/mod.rs +++ b/bon/tests/integration/builder/mod.rs @@ -1,6 +1,6 @@ +mod attr_crate; mod attr_default; mod attr_derive; -mod attr_crate; mod attr_into; mod attr_on; mod attr_overwritable; From 795ffa903e8dac936f1a7d2d876bd4c7545bd78c Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 25 Oct 2024 03:48:26 +0000 Subject: [PATCH 09/12] Fix link on website --- website/reference/builder.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/reference/builder.md b/website/reference/builder.md index 232dde91..e5ff9ba3 100644 --- a/website/reference/builder.md +++ b/website/reference/builder.md @@ -6,7 +6,7 @@ outline: [2, 3] You can generate a builder using three different kinds of syntax (struct, free function, associated method). They all share two common groups of attributes. -- [Top-level attributes](#item-attributes) - placed on a `struct` or `fn` declaration. +- [Top-level attributes](#top-level-attributes) - placed on a `struct` or `fn` declaration. - [Member attributes](#member-attributes) - placed on a `struct` field or `fn` argument. See examples. Make sure to click through the tabs: From b2a1ff3c5616c95915240481cc327e58e144c942 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 25 Oct 2024 04:00:50 +0000 Subject: [PATCH 10/12] Allow lint from nightly --- Cargo.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 29da93e4..4defdfa9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -147,6 +147,12 @@ missing_crate_level_docs = "warn" unescaped_backticks = "warn" [workspace.lints.rust] +# This lints is used only to warn about the changes in drop order during the +# transition from Rust 2021 to Rust 2024. There are too many instances of this +# lint in our code base and some of them may be false positives. So we just allow +# this lint globally. +tail_expr_drop_order = { level = "allow", priority = 2 } + unsafe_code = "warn" unstable_features = "warn" From f2c3e63b734bbe1684a6c39110a4b7438c8d920f Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 25 Oct 2024 15:10:12 +0000 Subject: [PATCH 11/12] Fix typo --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4defdfa9..b6e4bd1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -147,7 +147,7 @@ missing_crate_level_docs = "warn" unescaped_backticks = "warn" [workspace.lints.rust] -# This lints is used only to warn about the changes in drop order during the +# This lint is used only to warn about the changes in drop order during the # transition from Rust 2021 to Rust 2024. There are too many instances of this # lint in our code base and some of them may be false positives. So we just allow # this lint globally. From 6d4ea701e81ac04ad20786b8883d2cb415c75afc Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 25 Oct 2024 16:54:59 +0000 Subject: [PATCH 12/12] Self-review --- bon-macros/src/bon.rs | 4 +- .../src/builder/builder_gen/finish_fn.rs | 7 ++- .../src/builder/builder_gen/input_fn.rs | 8 +-- .../src/builder/builder_gen/input_struct.rs | 24 ++++----- .../builder/builder_gen/member/config/mod.rs | 2 +- .../builder_gen/member/config/setters.rs | 6 +-- .../builder_gen/member/into_conversion.rs | 12 ++--- .../src/builder/builder_gen/member/mod.rs | 38 +++++++------- .../src/builder/builder_gen/member/named.rs | 30 +++++------ .../src/builder/builder_gen/setters/mod.rs | 14 +++--- .../builder_gen/top_level_config/mod.rs | 2 + bon-macros/src/builder/item_impl.rs | 3 +- bon-macros/src/builder/mod.rs | 8 +-- bon-macros/src/error/panic_context.rs | 33 ++++++------ bon-macros/src/normalization/cfg/mod.rs | 24 ++++----- bon-macros/src/parsing/item_sig.rs | 4 +- bon-macros/src/parsing/mod.rs | 6 +-- bon-macros/src/util/ide.rs | 2 +- bon/tests/integration/builder/attr_bon.rs | 35 +++++++++++++ .../builder/attr_top_level_start_fn.rs | 6 ++- bon/tests/integration/builder/mod.rs | 1 + .../ui/compile_fail/attr_builder.stderr | 6 +-- .../integration/ui/compile_fail/attr_crate.rs | 16 ++++-- .../ui/compile_fail/attr_crate.stderr | 50 +++++++++++++------ .../ui/compile_fail/attr_on.stderr | 2 +- .../attr_top_level_start_finish_fn.stderr | 8 +-- 26 files changed, 208 insertions(+), 143 deletions(-) create mode 100644 bon/tests/integration/builder/attr_bon.rs diff --git a/bon-macros/src/bon.rs b/bon-macros/src/bon.rs index 7157e9d5..d24c8678 100644 --- a/bon-macros/src/bon.rs +++ b/bon-macros/src/bon.rs @@ -14,7 +14,7 @@ pub(crate) fn try_generate(params: TokenStream, item: TokenStream) -> Result Result return Ok(output), }; - let params = NestedMeta::parse_meta_list(input.params)?; + let params = NestedMeta::parse_meta_list(input.config)?; let params = FromMeta::from_list(¶ms)?; match input.item { diff --git a/bon-macros/src/builder/builder_gen/finish_fn.rs b/bon-macros/src/builder/builder_gen/finish_fn.rs index 1f06d704..b4c1a69a 100644 --- a/bon-macros/src/builder/builder_gen/finish_fn.rs +++ b/bon-macros/src/builder/builder_gen/finish_fn.rs @@ -31,16 +31,15 @@ impl super::BuilderGenCtx { self.#named_members_field.#index }; - let param_default = member + let default = member .config .default .as_ref() .map(|default| default.value.as_ref()); - match param_default { + match default { Some(Some(default)) => { - let has_into = member.config.into.is_present(); - let default = if has_into { + let default = if member.config.into.is_present() { quote! { Into::into((|| #default)()) } } else { quote! { #default } diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index 3593d94c..7820a303 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -82,7 +82,7 @@ impl<'a> FnInputCtx<'a> { .collect() }), - // Override on the start fn to use the the generics from the + // Override on the start fn to use the generics from the // target function itself. We must not duplicate the generics // from the impl block here generics: Some(Generics::new( @@ -226,7 +226,7 @@ impl<'a> FnInputCtx<'a> { orig.attrs.extend([syn::parse_quote!(#[doc(hidden)])]); } - // Also remove any `#[builder]` attributes that were meant for this proc macro. + // Remove any `#[builder]` attributes that were meant for this proc macro. orig.attrs.retain(|attr| !attr.path().is_ident("builder")); // Remove all doc comments attributes from function arguments, because they are @@ -383,10 +383,10 @@ impl<'a> FnInputCtx<'a> { // A special case for the starting function named `builder`. // We don't insert the `Builder` suffix in this case because - // we this special case should be compatible with deriving + // this special case should be compatible with deriving // a builder from a struct. // - // We can arrive inside of this if only if the function under + // We can arrive inside of this branch only if the function under // the macro is called `new` or `builder` without `start_fn` // name override, or if the `start_fn = builder/start_fn(name = builder)` // is specified in the macro invocation explicitly. diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 1193fefb..de458114 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -12,7 +12,7 @@ use std::borrow::Cow; use syn::visit::Visit; use syn::visit_mut::VisitMut; -fn parse_top_level_params(item_struct: &syn::ItemStruct) -> Result { +fn parse_top_level_config(item_struct: &syn::ItemStruct) -> Result { let meta = item_struct .attrs .iter() @@ -45,13 +45,13 @@ fn parse_top_level_params(item_struct: &syn::ItemStruct) -> Result, - params: TopLevelConfig, + config: TopLevelConfig, struct_ty: syn::Type, } impl StructInputCtx { pub(crate) fn new(orig_struct: syn::ItemStruct) -> Result { - let params = parse_top_level_params(&orig_struct)?; + let params = parse_top_level_config(&orig_struct)?; let generic_args = orig_struct .generics @@ -79,7 +79,7 @@ impl StructInputCtx { Ok(Self { struct_item, - params, + config: params, struct_ty, }) } @@ -119,7 +119,7 @@ impl StructInputCtx { }) .collect::>>()?; - let members = Member::from_raw(&self.params.on, MemberOrigin::StructField, members)?; + let members = Member::from_raw(&self.config.on, MemberOrigin::StructField, members)?; let generics = Generics::new( self.struct_item @@ -140,7 +140,7 @@ impl StructInputCtx { name: start_fn_ident, vis: start_fn_vis, docs: start_fn_docs, - } = self.params.start_fn; + } = self.config.start_fn; let start_fn_ident = start_fn_ident .map(SpannedKey::into_value) @@ -150,7 +150,7 @@ impl StructInputCtx { name: finish_fn_ident, vis: finish_fn_vis, docs: finish_fn_docs, - } = self.params.finish_fn; + } = self.config.finish_fn; let finish_fn_ident = finish_fn_ident .map(SpannedKey::into_value) @@ -208,14 +208,14 @@ impl StructInputCtx { .collect(); let builder_type = { - let ItemSigConfig { name, vis, docs } = self.params.builder_type; + let ItemSigConfig { name, vis, docs } = self.config.builder_type; let builder_ident = name.map(SpannedKey::into_value).unwrap_or_else(|| { format_ident!("{}Builder", self.struct_item.norm.ident.raw_name()) }); BuilderTypeParams { - derives: self.params.derive, + derives: self.config.derive, ident: builder_ident, docs: docs.map(SpannedKey::into_value), vis: vis.map(SpannedKey::into_value), @@ -226,20 +226,20 @@ impl StructInputCtx { namespace.visit_item_struct(&self.struct_item.orig); BuilderGenCtx::new(BuilderGenCtxParams { - bon: self.params.bon, + bon: self.config.bon, namespace: Cow::Owned(namespace), members, allow_attrs, - on: self.params.on, + on: self.config.on, assoc_method_ctx, generics, orig_item_vis: self.struct_item.norm.vis, builder_type, - state_mod: self.params.state_mod, + state_mod: self.config.state_mod, start_fn, finish_fn, }) diff --git a/bon-macros/src/builder/builder_gen/member/config/mod.rs b/bon-macros/src/builder/builder_gen/member/config/mod.rs index 75cce9a0..e2c9a552 100644 --- a/bon-macros/src/builder/builder_gen/member/config/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/config/mod.rs @@ -36,7 +36,7 @@ pub(crate) struct MemberConfig { /// Configurations for the setter methods. #[darling(with = crate::parsing::parse_non_empty_paren_meta_list)] - pub(crate) setters: Option, + pub(crate) setters: Option, /// Where to place the member in the generated builder methods API. /// By default the member is treated like a named parameter that diff --git a/bon-macros/src/builder/builder_gen/member/config/setters.rs b/bon-macros/src/builder/builder_gen/member/config/setters.rs index 59d94636..72a9c1c2 100644 --- a/bon-macros/src/builder/builder_gen/member/config/setters.rs +++ b/bon-macros/src/builder/builder_gen/member/config/setters.rs @@ -19,7 +19,7 @@ fn parse_docs(meta: &syn::Meta) -> Result>> { } #[derive(Debug, FromMeta)] -pub(crate) struct SettersMeta { +pub(crate) struct SettersConfig { pub(crate) name: Option>, pub(crate) vis: Option>, @@ -27,11 +27,11 @@ pub(crate) struct SettersMeta { pub(crate) docs: Option>>, #[darling(flatten)] - pub(crate) fns: SettersFnParams, + pub(crate) fns: SettersFnsConfig, } #[derive(Debug, FromMeta)] -pub(crate) struct SettersFnParams { +pub(crate) struct SettersFnsConfig { /// Config for the setter that accepts the value of type T for a member of /// type `Option` or with `#[builder(default)]`. /// diff --git a/bon-macros/src/builder/builder_gen/member/into_conversion.rs b/bon-macros/src/builder/builder_gen/member/into_conversion.rs index 0661e380..6da745bc 100644 --- a/bon-macros/src/builder/builder_gen/member/into_conversion.rs +++ b/bon-macros/src/builder/builder_gen/member/into_conversion.rs @@ -4,7 +4,7 @@ use crate::builder::builder_gen::top_level_config::OnConfig; use crate::util::prelude::*; impl NamedMember { - pub(super) fn merge_param_into(&mut self, on: &[OnConfig]) -> Result { + pub(super) fn merge_config_into(&mut self, on: &[OnConfig]) -> Result { // `with` is mutually exclusive with `into`. So there is nothing to merge here // if `with` is present. if self.config.with.is_some() { @@ -31,16 +31,16 @@ impl NamedMember { } impl PositionalFnArgMember { - pub(crate) fn merge_param_into(&mut self, on: &[OnConfig]) -> Result { + pub(crate) fn merge_config_into(&mut self, on: &[OnConfig]) -> Result { // Positional members are never optional. Users must always specify them, so there // is no need for us to look into the `Option` generic parameter, because the // `Option` itself is the target of the into conversion, not the `T` inside it. let scrutinee = self.ty.orig.as_ref(); - self.meta.into = EvalBlanketFlagParam { + self.config.into = EvalBlanketFlagParam { on, param_name: BlanketParamName::Into, - member_config: &self.meta, + member_config: &self.config, scrutinee, origin: self.origin, } @@ -53,7 +53,7 @@ impl PositionalFnArgMember { let ty = &self.ty.norm; let ident = &self.ident; - if self.meta.into.is_present() { + if self.config.into.is_present() { quote! { #ident: impl Into<#ty> } } else { quote! { #ident: #ty } @@ -63,7 +63,7 @@ impl PositionalFnArgMember { pub(crate) fn init_expr(&self) -> TokenStream { let ident = &self.ident; - if self.meta.into.is_present() { + if self.config.into.is_present() { quote! { Into::into(#ident) } diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 34934f9a..1ad23689 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -71,7 +71,7 @@ pub(crate) struct PositionalFnArgMember { pub(crate) ty: SyntaxVariant>, /// Parameters configured by the user explicitly via attributes - pub(crate) meta: MemberConfig, + pub(crate) config: MemberConfig, } /// Member that was skipped by the user with `#[builder(skip)]` @@ -114,9 +114,9 @@ impl Member { } } - let params = MemberConfig::from_attributes(member.attrs)?; - params.validate(origin)?; - Ok((member, params)) + let config = MemberConfig::from_attributes(member.attrs)?; + config.validate(origin)?; + Ok((member, config)) }) .collect::>>()? .into_iter() @@ -126,30 +126,30 @@ impl Member { for index in 0.. { let next = members.next_if(|(_, meta)| meta.start_fn.is_present()); - let (member, meta) = match next { + let (member, config) = match next { Some(item) => item, None => break, }; - let base = PositionalFnArgMember::new(origin, member, on, meta)?; + let base = PositionalFnArgMember::new(origin, member, on, config)?; output.push(Self::StartFnArg(StartFnArgMember { base, index: index.into(), })); } - while let Some((member, params)) = - members.next_if(|(_, params)| params.finish_fn.is_present()) + while let Some((member, config)) = + members.next_if(|(_, config)| config.finish_fn.is_present()) { - let member = PositionalFnArgMember::new(origin, member, on, params)?; + let member = PositionalFnArgMember::new(origin, member, on, config)?; output.push(Self::FinishFnArg(member)); } let mut named_count = 0; - for (member, params) in members { + for (member, config) in members { let RawMember { attrs, ident, ty } = member; - if let Some(value) = params.skip { + if let Some(value) = config.skip { output.push(Self::Skipped(SkippedMember { ident, norm_ty: ty.norm, @@ -161,7 +161,7 @@ impl Member { let active_flag = |flag: darling::util::Flag| flag.is_present().then(|| flag); let incorrect_order = - active_flag(params.finish_fn).or_else(|| active_flag(params.start_fn)); + active_flag(config.finish_fn).or_else(|| active_flag(config.start_fn)); if let Some(attr) = incorrect_order { bail!( @@ -189,13 +189,13 @@ impl Member { let mut member = NamedMember { index: named_count.into(), origin, - name: MemberName::new(ident, ¶ms), + name: MemberName::new(ident, &config), ty, - config: params, + config, docs, }; - member.merge_on_params(on)?; + member.merge_on_config(on)?; member.validate()?; output.push(Self::Named(member)); @@ -251,8 +251,8 @@ impl PositionalFnArgMember { fn new( origin: MemberOrigin, member: RawMember<'_>, - on_params: &[OnConfig], - params: MemberConfig, + on: &[OnConfig], + config: MemberConfig, ) -> Result { let RawMember { attrs: _, @@ -264,10 +264,10 @@ impl PositionalFnArgMember { origin, ident, ty, - meta: params, + config, }; - me.merge_param_into(on_params)?; + me.merge_config_into(on)?; Ok(me) } diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index 83d4de3c..6da9e658 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -1,6 +1,6 @@ use super::config::MemberConfig; use super::{config, MemberOrigin}; -use crate::builder::builder_gen::member::config::SettersFnParams; +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}; @@ -40,8 +40,8 @@ pub(crate) struct MemberName { } impl MemberName { - pub(crate) fn new(orig: syn::Ident, params: &MemberConfig) -> Self { - let snake = params.name.clone().unwrap_or_else(|| { + 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 @@ -113,7 +113,7 @@ impl NamedMember { return true; } - let SettersFnParams { some_fn, option_fn } = &setters.fns; + let SettersFnsConfig { some_fn, option_fn } = &setters.fns; matches!( (some_fn.as_deref(), option_fn.as_deref()), ( @@ -131,7 +131,7 @@ impl NamedMember { )?; } - self.validate_setters_params()?; + self.validate_setters_config()?; if self.config.transparent.is_present() && !self.ty.norm.is_option() { bail!( @@ -144,14 +144,14 @@ impl NamedMember { Ok(()) } - fn validate_setters_params(&self) -> Result { + fn validate_setters_config(&self) -> Result { let setters = match &self.config.setters { Some(setters) => setters, None => return Ok(()), }; if self.is_required() { - let SettersFnParams { some_fn, option_fn } = &setters.fns; + let SettersFnsConfig { some_fn, option_fn } = &setters.fns; let unexpected_setter = option_fn.as_ref().or(some_fn.as_ref()); @@ -165,16 +165,16 @@ impl NamedMember { } } - if let SettersFnParams { + 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, |params| ¶ms.name)?; - Self::validate_unused_setters_cfg(setter_fns, &setters.vis, |params| ¶ms.vis)?; - Self::validate_unused_setters_cfg(setter_fns, &setters.docs, |params| ¶ms.docs)?; + 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(()) @@ -258,14 +258,14 @@ impl NamedMember { self.index == other.index } - pub(crate) fn merge_on_params(&mut self, on_params: &[OnConfig]) -> Result { - self.merge_param_into(on_params)?; + pub(crate) fn merge_on_config(&mut self, on: &[OnConfig]) -> Result { + self.merge_config_into(on)?; // FIXME: refactor this to make it more consistent with `into` - // and allow for non-boolean flags in `OnParams`. E.g. add support + // 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: on_params, + on, param_name: config::BlanketParamName::Overwritable, member_config: &self.config, scrutinee: self.underlying_norm_ty(), diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index e69528bf..48bf088b 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -366,11 +366,11 @@ impl SettersItems { let SettersCtx { member, base } = ctx; let builder_type = &base.builder_type; - let params = member.config.setters.as_ref(); + let config = member.config.setters.as_ref(); - let common_name = params.and_then(|params| params.name.as_deref()); - let common_vis = params.and_then(|params| params.vis.as_deref()); - let common_docs = params.and_then(|params| params.docs.as_deref().map(Vec::as_slice)); + let common_name = config.and_then(|config| config.name.as_deref()); + let common_vis = config.and_then(|config| config.vis.as_deref()); + let common_docs = config.and_then(|config| config.docs.as_deref().map(Vec::as_slice)); let doc = |docs: &str| iter::once(syn::parse_quote!(#[doc = #docs])); @@ -390,14 +390,14 @@ impl SettersItems { }); } - let some_fn = params.and_then(|params| params.fns.some_fn.as_deref()); + let some_fn = config.and_then(|config| config.fns.some_fn.as_deref()); let some_fn_name = some_fn .and_then(ItemSigConfig::name) .or(common_name) .unwrap_or(&member.name.snake) .clone(); - let option_fn = params.and_then(|params| params.fns.option_fn.as_deref()); + let option_fn = config.and_then(|config| config.fns.option_fn.as_deref()); let option_fn_name = option_fn .and_then(ItemSigConfig::name) .cloned() @@ -470,7 +470,7 @@ impl SettersItems { docs: some_fn_docs, }; - let option_fn = params.and_then(|params| params.fns.option_fn.as_deref()); + let option_fn = config.and_then(|config| config.fns.option_fn.as_deref()); let option_fn = SetterItem { name: option_fn_name, diff --git a/bon-macros/src/builder/builder_gen/top_level_config/mod.rs b/bon-macros/src/builder/builder_gen/top_level_config/mod.rs index bfb8975e..17c612e1 100644 --- a/bon-macros/src/builder/builder_gen/top_level_config/mod.rs +++ b/bon-macros/src/builder/builder_gen/top_level_config/mod.rs @@ -41,6 +41,8 @@ fn parse_start_fn(meta: &syn::Meta) -> Result { #[derive(Debug, FromMeta)] pub(crate) struct TopLevelConfig { + /// Overrides the path to the `bon` crate. This is usedfule when the macro is + /// wrapped in another macro that also reexports `bon`. #[darling(rename = "crate", default, map = Some, with = crate::parsing::parse_bon_crate_path)] pub(crate) bon: Option, diff --git a/bon-macros/src/builder/item_impl.rs b/bon-macros/src/builder/item_impl.rs index 3a79754d..4831db4c 100644 --- a/bon-macros/src/builder/item_impl.rs +++ b/bon-macros/src/builder/item_impl.rs @@ -10,7 +10,8 @@ use syn::visit_mut::VisitMut; #[derive(FromMeta)] pub(crate) struct ImplInputParams { - /// Overrides the path to the `bon` crate. + /// Overrides the path to the `bon` crate. This is usedfule when the macro is + /// wrapped in another macro that also reexports `bon`. #[darling(rename = "crate", default, map = Some, with = crate::parsing::parse_bon_crate_path)] bon: Option, } diff --git a/bon-macros/src/builder/mod.rs b/bon-macros/src/builder/mod.rs index 4a8e6c08..2afbede5 100644 --- a/bon-macros/src/builder/mod.rs +++ b/bon-macros/src/builder/mod.rs @@ -38,7 +38,7 @@ fn try_generate_from_attr(params: TokenStream, item: TokenStream) -> Result Result { let mut namespace = GenericsNamespace::default(); - namespace.visit_token_stream(input.params.clone()); + namespace.visit_token_stream(input.config.clone()); namespace.visit_item_fn(&item_fn); - let meta_list = darling::ast::NestedMeta::parse_meta_list(input.params.clone())?; + let meta_list = darling::ast::NestedMeta::parse_meta_list(input.config.clone())?; let config = TopLevelConfig::parse_for_fn(&meta_list)?; item_fn::generate(config, item_fn, &namespace)? @@ -72,7 +72,7 @@ fn try_generate_from_attr(params: TokenStream, item: TokenStream) -> Result(f: impl FnOnce(&mut GlobalPanicContext) -> T) -> T { thread_local! { - /// A lazily initialized global panic log. It aggregates the panics from the + /// A lazily initialized global panic context. It aggregates the panics from the /// current thread. This is used to capture info about the panic after the /// `catch_unwind` call and observe the context of the panic that happened. /// @@ -59,10 +61,10 @@ impl PanicListener { std::panic::set_hook(Box::new(move |panic_info| { with_global_panic_context(|global| { - let panic_number = global.last_panic.as_ref().map(|p| p.0.panic_number); - let panic_number = panic_number.unwrap_or(0) + 1; + let panics_count = global.last_panic.as_ref().map(|p| p.0.panics_count); + let panics_count = panics_count.unwrap_or(0) + 1; - global.last_panic = Some(PanicContext::from_std(panic_info, panic_number)); + global.last_panic = Some(PanicContext::from_std(panic_info, panics_count)); }); prev_panic_hook(panic_info); @@ -95,11 +97,11 @@ struct PanicContextShared { /// Defines the number of panics that happened before this one. Each panic /// increments this counter. This is useful to know how many panics happened /// before the current one. - panic_number: usize, + panics_count: usize, } impl PanicContext { - fn from_std(std_panic_info: &StdPanicHookInfo<'_>, panic_number: usize) -> Self { + fn from_std(std_panic_info: &StdPanicHookInfo<'_>, panics_count: usize) -> Self { let location = std_panic_info.location(); let current_thread = std::thread::current(); let thread_ = current_thread @@ -111,7 +113,7 @@ impl PanicContext { backtrace: backtrace::Backtrace::capture(), location: location.map(PanicLocation::from_std), thread: thread_, - panic_number, + panics_count, })) } } @@ -128,7 +130,7 @@ impl fmt::Display for PanicContext { location, backtrace, thread, - panic_number, + panics_count, } = &*self.0; write!(f, "panic occurred")?; @@ -139,8 +141,8 @@ impl fmt::Display for PanicContext { write!(f, " in thread '{thread}'")?; - if *panic_number > 1 { - write!(f, " (total panics observed: {panic_number})")?; + if *panics_count > 1 { + write!(f, " (total panics observed: {panics_count})")?; } // #[rustversion::attr(before(1.65.0), allow(clippy::irrefutable_let_patterns))] @@ -190,7 +192,6 @@ impl fmt::Display for PanicLocation { } #[rustversion::since(1.65.0)] -#[allow(clippy::module_name_repetitions)] mod backtrace { pub(super) use std::backtrace::{Backtrace, BacktraceStatus}; } diff --git a/bon-macros/src/normalization/cfg/mod.rs b/bon-macros/src/normalization/cfg/mod.rs index 26588381..020fb47f 100644 --- a/bon-macros/src/normalization/cfg/mod.rs +++ b/bon-macros/src/normalization/cfg/mod.rs @@ -13,13 +13,13 @@ pub(crate) enum Expansion { } pub(crate) struct Expanded { - pub(crate) params: TokenStream, + pub(crate) config: TokenStream, pub(crate) item: syn::Item, } pub(crate) struct ExpandCfg { pub(crate) current_macro: syn::Ident, - pub(crate) params: TokenStream, + pub(crate) config: TokenStream, pub(crate) item: syn::Item, } @@ -29,18 +29,18 @@ impl ExpandCfg { if predicates.is_empty() { return Ok(Expansion::Expanded(Expanded { - params: self.params, + config: self.config, item: self.item, })); } - let predicate_results = match parse::parse_predicate_results(self.params.clone())? { + let predicate_results = match parse::parse_predicate_results(self.config.clone())? { Some(predicate_results) => predicate_results, None => return self.into_recursion(0, &predicates), }; - // Update the parameters to remove the `@cfgs(...)` prefix from them - self.params = predicate_results.rest; + // Update the config to remove the `@cfgs(...)` prefix from them + self.config = predicate_results.rest; let true_predicates: BTreeSet<_> = predicates .iter() @@ -58,7 +58,7 @@ impl ExpandCfg { if predicates.is_empty() { return Ok(Expansion::Expanded(Expanded { - params: self.params, + config: self.config, item: self.item, })); } @@ -101,16 +101,14 @@ impl ExpandCfg { predicates: &[TokenStream], ) -> Result { let Self { - params, + config, item, current_macro, } = self; - let parsed_params = NestedMeta::parse_meta_list(params.clone())?; - - let bon = parsed_params + let bon = NestedMeta::parse_meta_list(config.clone())? .iter() - .find_map(|param| match param { + .find_map(|meta| match meta { NestedMeta::Meta(syn::Meta::NameValue(meta)) if meta.path.is_ident("crate") => { let path = &meta.value; Some(syn::Path::parse_mod_style.parse2(quote!(#path))) @@ -137,7 +135,7 @@ impl ExpandCfg { #((#predicates))* #current_macro, #recursion_counter, - ( #params ) + ( #config ) #item } }; diff --git a/bon-macros/src/parsing/item_sig.rs b/bon-macros/src/parsing/item_sig.rs index 52caabfe..7288ebde 100644 --- a/bon-macros/src/parsing/item_sig.rs +++ b/bon-macros/src/parsing/item_sig.rs @@ -63,12 +63,12 @@ impl ItemSigConfigParsing<'_> { } } - let params = ItemSigConfig { + let config = ItemSigConfig { name: full.name, vis: full.vis, docs: full.doc, }; - Ok(params) + Ok(config) } } diff --git a/bon-macros/src/parsing/mod.rs b/bon-macros/src/parsing/mod.rs index 27614fc9..15788d18 100644 --- a/bon-macros/src/parsing/mod.rs +++ b/bon-macros/src/parsing/mod.rs @@ -35,7 +35,7 @@ pub(crate) fn require_non_empty_paren_meta_list_or_name_value(meta: &syn::Meta) &meta, "this empty `{0}` attribute is unexpected; \ remove it or pass parameters in parentheses: \ - `#[{0}(...)]`", + `{0}(...)`", darling::util::path_to_string(path) ), syn::Meta::NameValue(_) => {} @@ -84,11 +84,9 @@ where } fn parse_path_mod_style(meta: &syn::Meta) -> Result { - let err = |span: Span| err!(&span, "expected a simple path, like `foo::bar`"); - let expr = match meta { syn::Meta::NameValue(meta) => &meta.value, - _ => return Err(err(meta.span())), + _ => bail!(meta, "expected a simple path, like `foo::bar`"), }; Ok(expr.require_path_mod_style()?.clone()) diff --git a/bon-macros/src/util/ide.rs b/bon-macros/src/util/ide.rs index 6a425e81..3a2bcc38 100644 --- a/bon-macros/src/util/ide.rs +++ b/bon-macros/src/util/ide.rs @@ -138,7 +138,7 @@ pub(crate) fn generate_completion_triggers(meta: Vec) -> TokenStream { let bon = meta .iter() .find_map(|meta| match meta { - Meta::NameValue(meta) => Some(meta.value.as_ref()), + Meta::NameValue(meta) if meta.path.is_ident("crate") => Some(meta.value.as_ref()), _ => None, }) .flatten() diff --git a/bon/tests/integration/builder/attr_bon.rs b/bon/tests/integration/builder/attr_bon.rs new file mode 100644 index 00000000..77651467 --- /dev/null +++ b/bon/tests/integration/builder/attr_bon.rs @@ -0,0 +1,35 @@ +use crate::prelude::*; + +#[test] +fn builder_method_special_case() { + struct Sut; + + #[bon] + impl Sut { + #[builder] + fn builder() {} + } + + let _: SutBuilder = Sut::builder(); + let builder: SutBuilder = Sut::builder(); + + builder.build(); +} + +#[test] +fn builder_start_fn_special_case() { + struct Sut; + + #[bon] + impl Sut { + #[builder(start_fn = builder)] + fn some_other_name() {} + } + + let _: SutBuilder = Sut::builder(); + let builder: SutBuilder = Sut::builder(); + + builder.build(); + + Sut::some_other_name(); +} diff --git a/bon/tests/integration/builder/attr_top_level_start_fn.rs b/bon/tests/integration/builder/attr_top_level_start_fn.rs index 4dd9e881..7e972bcc 100644 --- a/bon/tests/integration/builder/attr_top_level_start_fn.rs +++ b/bon/tests/integration/builder/attr_top_level_start_fn.rs @@ -17,7 +17,8 @@ fn test_assoc_method() { fn regular() {} } - let _: Sut = Sut::builder().build(); + let builder: SutBuilder = Sut::builder(); + let _: Sut = builder.build(); let _: Sut = Sut::new(); Sut::regular_builder().call(); @@ -38,7 +39,8 @@ fn test_assoc_method() { pub(crate) fn regular() {} } - let _: Sut = Sut::builder().build(); + let builder: SutBuilder = Sut::builder(); + let _: Sut = builder.build(); let _: Sut = Sut::new(); Sut::regular_builder().call(); diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs index 225718c6..c0663186 100644 --- a/bon/tests/integration/builder/mod.rs +++ b/bon/tests/integration/builder/mod.rs @@ -1,3 +1,4 @@ +mod attr_bon; mod attr_crate; mod attr_default; mod attr_derive; diff --git a/bon/tests/integration/ui/compile_fail/attr_builder.stderr b/bon/tests/integration/ui/compile_fail/attr_builder.stderr index 496b7b99..da7ad31c 100644 --- a/bon/tests/integration/ui/compile_fail/attr_builder.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_builder.stderr @@ -10,7 +10,7 @@ error: expected parameters in parentheses 8 | #[builder()] | ^^ -error: this empty `builder` attribute is unexpected; remove it or pass parameters in parentheses: `#[builder(...)]` +error: this empty `builder` attribute is unexpected; remove it or pass parameters in parentheses: `builder(...)` --> tests/integration/ui/compile_fail/attr_builder.rs:13:7 | 13 | #[builder] @@ -22,7 +22,7 @@ error: expected parameters in parentheses 19 | #[builder()] | ^^ -error: this empty `builder` attribute is unexpected; remove it or pass parameters in parentheses: `#[builder(...)]` +error: this empty `builder` attribute is unexpected; remove it or pass parameters in parentheses: `builder(...)` --> tests/integration/ui/compile_fail/attr_builder.rs:24:41 | 24 | fn fn_empty_member_level_builder_attr(#[builder] _x: u32) {} @@ -34,7 +34,7 @@ error: expected parameters in parentheses 27 | fn fn_empty_member_level_builder_attr_with_parens(#[builder()] _x: u32) {} | ^^ -error: this empty `builder` attribute is unexpected; remove it or pass parameters in parentheses: `#[builder(...)]` +error: this empty `builder` attribute is unexpected; remove it or pass parameters in parentheses: `builder(...)` --> tests/integration/ui/compile_fail/attr_builder.rs:34:42 | 34 | fn empty_member_level_builder_attr(#[builder] _x: u32) {} diff --git a/bon/tests/integration/ui/compile_fail/attr_crate.rs b/bon/tests/integration/ui/compile_fail/attr_crate.rs index 31ecf7d0..3560cef6 100644 --- a/bon/tests/integration/ui/compile_fail/attr_crate.rs +++ b/bon/tests/integration/ui/compile_fail/attr_crate.rs @@ -1,4 +1,16 @@ -use bon::{bon, builder}; +use bon::{bon, builder, Builder}; + +#[derive(Builder)] +#[builder(crate = self::bon)] +struct Relative1 {} + +#[derive(Builder)] +#[builder(crate = super::bon)] +struct Relative2 {} + +#[derive(Builder)] +#[builder(crate = bon)] +struct Relative3 {} #[builder(crate = self::bon)] fn relative_1() {} @@ -37,6 +49,4 @@ impl Relative { fn method3() {} } - - fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_crate.stderr b/bon/tests/integration/ui/compile_fail/attr_crate.stderr index a7f15c18..9225dbbb 100644 --- a/bon/tests/integration/ui/compile_fail/attr_crate.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_crate.stderr @@ -1,41 +1,59 @@ error: path must not be relative; specify the path that starts with `crate::` instead; if you want to refer to a reexport from an external crate then use a leading colon like `::crate_name::reexport::path::bon` - --> tests/integration/ui/compile_fail/attr_crate.rs:3:19 + --> tests/integration/ui/compile_fail/attr_crate.rs:4:19 | -3 | #[builder(crate = self::bon)] +4 | #[builder(crate = self::bon)] | ^^^^ error: path must not be relative; specify the path that starts with `crate::` instead; if you want to refer to a reexport from an external crate then use a leading colon like `::crate_name::reexport::path::bon` - --> tests/integration/ui/compile_fail/attr_crate.rs:6:19 + --> tests/integration/ui/compile_fail/attr_crate.rs:8:19 | -6 | #[builder(crate = super::bon)] +8 | #[builder(crate = super::bon)] | ^^^^^ error: path must be absolute; if you want to refer to a reexport from an external crate then add a leading colon like `::bon`; if the path leads to a module in the current crate, then specify the absolute path with `crate` like `crate::reexport::path::bon` or `$crate::reexport::path::bon` (if within a macro) - --> tests/integration/ui/compile_fail/attr_crate.rs:9:19 - | -9 | #[builder(crate = bon)] - | ^^^ + --> tests/integration/ui/compile_fail/attr_crate.rs:12:19 + | +12 | #[builder(crate = bon)] + | ^^^ + +error: path must not be relative; specify the path that starts with `crate::` instead; if you want to refer to a reexport from an external crate then use a leading colon like `::crate_name::reexport::path::bon` + --> tests/integration/ui/compile_fail/attr_crate.rs:15:19 + | +15 | #[builder(crate = self::bon)] + | ^^^^ + +error: path must not be relative; specify the path that starts with `crate::` instead; if you want to refer to a reexport from an external crate then use a leading colon like `::crate_name::reexport::path::bon` + --> tests/integration/ui/compile_fail/attr_crate.rs:18:19 + | +18 | #[builder(crate = super::bon)] + | ^^^^^ + +error: path must be absolute; if you want to refer to a reexport from an external crate then add a leading colon like `::bon`; if the path leads to a module in the current crate, then specify the absolute path with `crate` like `crate::reexport::path::bon` or `$crate::reexport::path::bon` (if within a macro) + --> tests/integration/ui/compile_fail/attr_crate.rs:21:19 + | +21 | #[builder(crate = bon)] + | ^^^ error: `crate` parameter should be specified via `#[bon(crate = path::to::bon)]` when impl block syntax is used; no need to specify it in the method's `#[builder]` attribute - --> tests/integration/ui/compile_fail/attr_crate.rs:16:23 + --> tests/integration/ui/compile_fail/attr_crate.rs:28:23 | -16 | #[builder(crate = ::bon)] +28 | #[builder(crate = ::bon)] | ^ error: path must not be relative; specify the path that starts with `crate::` instead; if you want to refer to a reexport from an external crate then use a leading colon like `::crate_name::reexport::path::bon` - --> tests/integration/ui/compile_fail/attr_crate.rs:22:15 + --> tests/integration/ui/compile_fail/attr_crate.rs:34:15 | -22 | #[bon(crate = self::bon)] +34 | #[bon(crate = self::bon)] | ^^^^ error: path must not be relative; specify the path that starts with `crate::` instead; if you want to refer to a reexport from an external crate then use a leading colon like `::crate_name::reexport::path::bon` - --> tests/integration/ui/compile_fail/attr_crate.rs:28:15 + --> tests/integration/ui/compile_fail/attr_crate.rs:40:15 | -28 | #[bon(crate = super::bon)] +40 | #[bon(crate = super::bon)] | ^^^^^ error: path must be absolute; if you want to refer to a reexport from an external crate then add a leading colon like `::bon`; if the path leads to a module in the current crate, then specify the absolute path with `crate` like `crate::reexport::path::bon` or `$crate::reexport::path::bon` (if within a macro) - --> tests/integration/ui/compile_fail/attr_crate.rs:34:15 + --> tests/integration/ui/compile_fail/attr_crate.rs:46:15 | -34 | #[bon(crate = bon)] +46 | #[bon(crate = bon)] | ^^^ diff --git a/bon/tests/integration/ui/compile_fail/attr_on.stderr b/bon/tests/integration/ui/compile_fail/attr_on.stderr index 23be760a..500ecc73 100644 --- a/bon/tests/integration/ui/compile_fail/attr_on.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_on.stderr @@ -22,7 +22,7 @@ error: nested attributes are not allowed in the type pattern of #[builder(on(typ 12 | #[builder(on(fn(#[attr] a: u32), into))] | ^ -error: this empty `on` attribute is unexpected; remove it or pass parameters in parentheses: `#[on(...)]` +error: this empty `on` attribute is unexpected; remove it or pass parameters in parentheses: `on(...)` --> tests/integration/ui/compile_fail/attr_on.rs:15:11 | 15 | #[builder(on)] diff --git a/bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.stderr b/bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.stderr index 8007567c..ed04a4b6 100644 --- a/bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.stderr @@ -10,13 +10,13 @@ error: expected parameters in parentheses 8 | #[builder(finish_fn())] | ^^ -error: this empty `start_fn` attribute is unexpected; remove it or pass parameters in parentheses: `#[start_fn(...)]` +error: this empty `start_fn` attribute is unexpected; remove it or pass parameters in parentheses: `start_fn(...)` --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:12:11 | 12 | #[builder(start_fn)] | ^^^^^^^^ -error: this empty `start_fn` attribute is unexpected; remove it or pass parameters in parentheses: `#[start_fn(...)]` +error: this empty `start_fn` attribute is unexpected; remove it or pass parameters in parentheses: `start_fn(...)` --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:15:11 | 15 | #[builder(start_fn)] @@ -40,7 +40,7 @@ error: #[builder(start_fn(doc))] requires that you also specify #[builder(start_ 24 | #[builder(start_fn(doc {}))] | ^^^ -error: this empty `start_fn` attribute is unexpected; remove it or pass parameters in parentheses: `#[start_fn(...)]` +error: this empty `start_fn` attribute is unexpected; remove it or pass parameters in parentheses: `start_fn(...)` --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:34:15 | 34 | #[builder(start_fn)] @@ -64,7 +64,7 @@ error: #[builder(start_fn(doc))] requires that you also specify #[builder(start_ 52 | #[builder(start_fn(doc {}))] | ^^^ -error: this empty `start_fn` attribute is unexpected; remove it or pass parameters in parentheses: `#[start_fn(...)]` +error: this empty `start_fn` attribute is unexpected; remove it or pass parameters in parentheses: `start_fn(...)` --> tests/integration/ui/compile_fail/attr_top_level_start_finish_fn.rs:58:15 | 58 | #[builder(start_fn)]