cargo / bon-macros / audit
cargo : bon-macros @ 3.9.1
PE Patrick Elsen signed 2026-05-27 published 2026-05-27

src/builder/builder_gen/getters.rs

299 lines · rust · 1 line annotation

use super::member::{GetterConfig, GetterKind};use super::{BuilderGenCtx, NamedMember};use crate::parsing::SpannedKey;use crate::util::prelude::*;use syn::punctuated::Punctuated;use syn::spanned::Spanned;pub(crate) struct GettersCtx<'a> {    base: &'a BuilderGenCtx,    member: &'a NamedMember,    config: &'a GetterConfig,}impl<'a> GettersCtx<'a> {    pub(crate) fn new(base: &'a BuilderGenCtx, member: &'a NamedMember) -> Option<Self> {        Some(Self {            base,            member,            config: member.config.getter.as_ref()?,        })    }    pub(crate) fn getter_methods(self) -> Result<TokenStream> {        let name = self.config.name.as_deref().cloned().unwrap_or_else(|| {            syn::Ident::new(                &format!("get_{}", self.member.name.snake.raw_name()),                self.member.name.snake.span(),            )        });        let vis = self            .config            .vis            .as_deref()            .unwrap_or(&self.base.builder_type.vis)            .clone();        let docs = self.config.docs.as_deref().cloned().unwrap_or_else(|| {            let header = format!(                "_**Getter.**_ Returns `{}`, which must be set before calling this method.\n\n",                self.member.name.snake,            );            std::iter::once(syn::parse_quote!(#[doc = #header]))                .chain(self.member.docs.iter().cloned())                .collect()        });        let return_ty = self.return_ty()?;        let body = self.body();        let state_var = &self.base.state_var;        let member_pascal = &self.member.name.pascal;        let state_mod = &self.base.state_mod.ident;        let const_ = &self.base.const_;        let fn_modifiers = self.member.respan(quote!(#vis #const_));        // It's important to keep the span of `self` the same across all        // references to it. Otherwise `self`s that have different spans will        // be treated as totally different symbols due to the hygiene rules.        let self_ = quote!(self);        let where_clause = self.member.is_stateful().then(|| {            quote! {                where                    #state_var::#member_pascal: #state_mod::IsSet,            }        });        Ok(quote_spanned! {self.member.span=>            #( #docs )*            #[allow(                // This is intentional. We want the builder syntax to compile away                clippy::inline_always,                clippy::missing_const_for_fn,            )]            #[inline(always)]            #[must_use = "this method has no side effects; it only returns a value"]            #(#fn_modifiers)* fn #name(&#self_) -> #return_ty            #where_clause            {                #body            }        })    }
    fn body(&self) -> TokenStream {        let index = &self.member.index;        let member = quote! {            self.__unsafe_private_named.#index        };        let bon = &self.base.bon;        match self.config.kind.as_deref() {            Some(GetterKind::Copy) => {                // Use a `_` type hint with the span of the original type                // to make the compiler point to the original type in case                // if the type doesn't implement `Copy`.                let span = self.member.underlying_orig_ty().span();                let ty = quote_spanned!(span=> _);                let copy = quote! {                    #bon::__::better_errors::copy_member::<#ty>(&#member)                };                if !self.member.is_required() {                    return copy;                }                quote! {                    // SAFETY: the method requires S::{Member}: IsSet, so it's Some                    unsafe {                        ::core::option::Option::unwrap_unchecked(#copy)                    }                }            }            Some(GetterKind::Clone) => {                // Use a `_` type hint with the span of the original type                // to make the compiler point to the original type in case                // if the type doesn't implement `Clone`.                let span = self.member.underlying_orig_ty().span();                let ty = quote_spanned!(span=> _);                let clone = quote! {                    <#ty as ::core::clone::Clone>::clone                };                if !self.member.is_required() {                    return quote! {                        #clone(&#member)                    };                }                quote! {                    match &#member {                        Some(value) => #clone(value),                        // SAFETY: the method requires S::{Member}: IsSet, so it's Some                        None => unsafe {                            ::core::hint::unreachable_unchecked()                        },                    }                }            }            Some(GetterKind::Deref(ty)) => {                // Assign the span of the deref target type to the `value` variable                // so that compiler points to that type if there is a type mismatch.                let span = ty.span();                let value = quote_spanned!(span=> value);                if !self.member.is_required() {                    return quote! {                        // Explicit match is important to trigger an implicit deref coercion                        // that can potentially do multiple derefs to the reach the target type.                        match &#member {                            Some(#value) => Some(#value),                            None => None,                        }                    };                }                quote! {                    // Explicit match is important to trigger an implicit deref coercion                    // that can potentially do multiple derefs to the reach the target type.                    match &#member {                        Some(#value) => #value,                        // SAFETY: the method requires S::{Member}: IsSet, so it's Some                        None => unsafe {                            ::core::hint::unreachable_unchecked()                        },                    }                }            }            None => {                if !self.member.is_required() {                    return quote! {                        ::core::option::Option::as_ref(&#member)                    };                }                quote! {                    match &#member {                        Some(value) => value,                        // SAFETY: the method requires S::{Member}: IsSet, so it's Some                        None => unsafe {                            ::core::hint::unreachable_unchecked()                        },                    }                }            }        }    }
Line 87–191

All occurrences of unsafe { ... } in this file appear inside quote! { ... } macro invocations. They are token streams assembled for code generation and execute in the downstream compilation context, not inside this proc macro. The macro itself contains no unsafe Rust. The safety invariant for each generated unsafe block (Option is Some because of the typestate IsSet bound) is documented by SAFETY comments in the token-stream literal. This justifies uses-unsafe = false for the macro crate itself.

    fn return_ty(&self) -> Result<TokenStream> {        let underlying_return_ty = self.underlying_return_ty()?;        Ok(if self.member.is_required() {            quote! { #underlying_return_ty }        } else {            // We are not using the fully qualified path to `Option` here            // to make function signature in IDE popus shorter and more            // readable.            quote! { Option<#underlying_return_ty> }        })    }    fn underlying_return_ty(&self) -> Result<TokenStream> {        let ty = self.member.underlying_norm_ty();        let kind = match &self.config.kind {            Some(kind) => kind,            None => return Ok(quote! { &#ty }),        };        match &kind.value {            GetterKind::Copy | GetterKind::Clone => Ok(quote! { #ty }),            GetterKind::Deref(Some(deref_target)) => Ok(quote! { &#deref_target }),            GetterKind::Deref(None) => Self::infer_deref_target(ty, kind),        }    }    fn infer_deref_target(        underlying_member_ty: &syn::Type,        kind: &SpannedKey<GetterKind>,    ) -> Result<TokenStream> {        use quote_spanned as qs;        let span = underlying_member_ty.span();        #[allow(clippy::type_complexity)]        let deref_target_inference_table: &[(_, &dyn Fn(&Punctuated<_, _>) -> _)] = &[            ("Vec", &|args| args.first().map(|arg| qs!(span=> [#arg]))),            ("Box", &|args| args.first().map(ToTokens::to_token_stream)),            ("Rc", &|args| args.first().map(ToTokens::to_token_stream)),            ("Arc", &|args| args.first().map(ToTokens::to_token_stream)),            ("String", &|args| args.is_empty().then(|| qs!(span=> str))),            ("CString", &|args| {                // CStr is available via `core` since 1.64.0:                // https://blog.rust-lang.org/2022/09/22/Rust-1.64.0.html#c-compatible-ffi-types-in-core-and-alloc                let module = if rustversion::cfg!(since(1.64.0)) {                    format_ident!("core")                } else {                    format_ident!("std")                };                args.is_empty().then(|| qs!(span=> ::#module::ffi::CStr))            }),            ("OsString", &|args| {                args.is_empty().then(|| qs!(span=> ::std::ffi::OsStr))            }),            ("PathBuf", &|args| {                args.is_empty().then(|| qs!(span=> ::std::path::Path))            }),            ("Cow", &|args| {                args.iter()                    .find(|arg| matches!(arg, syn::GenericArgument::Type(_)))                    .map(ToTokens::to_token_stream)            }),        ];        let err = || {            let inferable_types = deref_target_inference_table                .iter()                .map(|(name, _)| format!("- {name}"))                .join("\n");            err!(                &kind.key,                "can't infer the `Deref::Target` for the getter from the member's type; \                please specify the return type (target of the deref coercion) explicitly \                in parentheses without the leading `&`;\n\                example: `#[builder(getter(deref(TargetTypeHere))]`\n\                \n\                automatic deref target detection is supported only for the following types:\n\                {inferable_types}",            )        };        let path = underlying_member_ty.as_path_no_qself().ok_or_else(err)?;        let last_segment = path.segments.last().ok_or_else(err)?;        let empty_punctuated = Punctuated::new();        let args = match &last_segment.arguments {            syn::PathArguments::AngleBracketed(args) => &args.args,            _ => &empty_punctuated,        };        let last_segment_ident_str = last_segment.ident.to_string();        let inferred = deref_target_inference_table            .iter()            .find(|(name, _)| last_segment_ident_str == *name)            .and_then(|(_, infer)| infer(args))            .ok_or_else(err)?;        Ok(quote!(&#inferred))    }}