diff --git a/bon-macros/src/builder/builder_gen/input_fn/mod.rs b/bon-macros/src/builder/builder_gen/input_fn/mod.rs index 9547cf48..0df8292f 100644 --- a/bon-macros/src/builder/builder_gen/input_fn/mod.rs +++ b/bon-macros/src/builder/builder_gen/input_fn/mod.rs @@ -12,6 +12,7 @@ use crate::util::prelude::*; use std::borrow::Cow; use std::rc::Rc; use syn::punctuated::Punctuated; +use syn::spanned::Spanned; use syn::visit_mut::VisitMut; pub(crate) struct FnInputCtx<'a> { @@ -97,6 +98,11 @@ impl<'a> FnInputCtx<'a> { .collect(), params.fn_item.norm.sig.generics.where_clause.clone(), )), + // We don't use params.fn_item.orig.span() here because that span + // only contains the annotations before the function and the + // function name. Block contains function name + implementation + // which is what rustdoc normally links to. + span: Some(params.fn_item.orig.block.span()), }; let self_ty_prefix = params.impl_ctx.as_deref().and_then(|impl_ctx| { diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index caeb3037..be8343fa 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -187,6 +187,7 @@ impl StructInputCtx { vis: start_fn_vis.map(SpannedKey::into_value), docs: start_fn_docs, generics: None, + span: None, }; let assoc_method_ctx = Some(AssocMethodCtxParams { diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 4462b3d2..0741043c 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -85,6 +85,9 @@ pub(super) struct StartFn { /// Overrides the default generics pub(super) generics: Option, + + /// Preserve a span the documentation for `start_fn` should link to. + pub(super) span: Span, } pub(super) struct StartFnParams { @@ -97,6 +100,9 @@ pub(super) struct StartFnParams { /// Overrides the default generics pub(super) generics: Option, + /// Preserve a span the documentation for `start_fn` should link to. + /// Defaults to macro callsite if not supplied. + pub(super) span: Option, } pub(super) struct BuilderType { @@ -309,6 +315,7 @@ impl BuilderGenCtx { vis: start_fn.vis.unwrap_or_else(|| builder_type.vis.clone()), docs: start_fn.docs, generics: start_fn.generics, + span: start_fn.span.unwrap_or_else(Span::call_site), }; let finish_fn = FinishFn { diff --git a/bon-macros/src/builder/builder_gen/start_fn.rs b/bon-macros/src/builder/builder_gen/start_fn.rs index e61e59e7..1a168ee8 100644 --- a/bon-macros/src/builder/builder_gen/start_fn.rs +++ b/bon-macros/src/builder/builder_gen/start_fn.rs @@ -96,8 +96,23 @@ impl super::BuilderGenCtx { }; let const_ = &self.const_; - - syn::parse_quote! { + // add the `clippy::needless_lifetimes` lint if before rust version 1.87 + // Rust version 1.87 includes a clippy change where `needless_lifetimes` + // was split with the more complex part of the lint going to + // `elidable_lifetime_names`. For versions since 1.87 we want to block + // `elidable_lifetime_names` (See + // https://github.com/elastio/bon/pull/341#discussion_r2398893516 for + // an explanation). + let needless_lifetime_lint = if rustversion::cfg!(before(1.87)) { + format_ident!("needless_lifetimes") + } else { + format_ident!("elidable_lifetime_names") + }; + + // Construct using a span which links to our original implementation. + // This ensures rustdoc doesn't just link every method to the macro + // callsite. + syn::parse_quote_spanned! {self.start_fn.span=> #(#docs)* #[inline(always)] #[allow( @@ -108,6 +123,7 @@ impl super::BuilderGenCtx { // Let's keep it as non-const for now to avoid restricting ourselfves to only // const operations. clippy::missing_const_for_fn, + clippy::#needless_lifetime_lint )] #vis #const_ fn #start_fn_ident< #(#generics_decl),* >( #receiver diff --git a/bon/src/lib.rs b/bon/src/lib.rs index 398a3402..a8cd04a8 100644 --- a/bon/src/lib.rs +++ b/bon/src/lib.rs @@ -14,7 +14,7 @@ // Rexport all macros from the proc-macro crate. pub use bon_macros::{bon, builder, map, set, Builder}; -/// Small utility declarative macros for creating colletions with [`Into`] conversions. +/// Small utility declarative macros for creating collections with [`Into`] conversions. mod collections; #[doc(hidden)] diff --git a/bon/tests/integration/builder/attr_bon.rs b/bon/tests/integration/builder/attr_bon.rs index 18045e5d..936be0ab 100644 --- a/bon/tests/integration/builder/attr_bon.rs +++ b/bon/tests/integration/builder/attr_bon.rs @@ -102,6 +102,7 @@ fn receiver_variations() { } #[builder] + #[allow(dead_code)] fn mut_self_as_pin_mut_self(mut self: Pin<&mut Self>) { #[allow(clippy::self_assignment, unused_assignments)] { diff --git a/bon/tests/integration/builder/attr_const.rs b/bon/tests/integration/builder/attr_const.rs index 260624dc..495eb33b 100644 --- a/bon/tests/integration/builder/attr_const.rs +++ b/bon/tests/integration/builder/attr_const.rs @@ -205,6 +205,7 @@ mod msrv_1_61 { #[test] const fn test_function() { #[builder(const)] + #[allow(unreachable_pub)] pub const fn sut(#[builder(getter)] _x: u32) {} sut().x(1).call(); @@ -217,6 +218,7 @@ mod msrv_1_61 { #[bon] impl Sut { #[builder(const)] + #[allow(unreachable_pub)] pub const fn sut(#[builder(getter)] _x: 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 6782f8b8..28fd9d10 100644 --- a/bon/tests/integration/builder/attr_top_level_start_fn.rs +++ b/bon/tests/integration/builder/attr_top_level_start_fn.rs @@ -52,6 +52,7 @@ fn test_method() { fn test_function() { { #[builder(start_fn(name = sut_builder))] + #[allow(dead_code)] fn sut(arg1: bool, arg2: u32) -> impl fmt::Debug { (arg1, arg2) } @@ -61,6 +62,7 @@ fn test_function() { { #[builder(start_fn = sut_builder)] + #[allow(dead_code)] fn sut(arg1: u32) -> u32 { arg1 } @@ -70,6 +72,7 @@ fn test_function() { { #[builder(start_fn(name = sut_builder, vis = ""))] + #[allow(dead_code)] fn sut(arg1: u32) -> u32 { arg1 }