Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
kind: Fixed
body: |-
**`buffa-remote-derive`: a constant or const generic named `iter`, `v`, `s`, `payload`, `value`, `key` or `u` no longer breaks the derives** (#693). The generated methods bound parameters with those names, and an identifier pattern that names a constant in scope is a constant pattern, so `#[derive(ProtoList)]` on `struct L<T, const iter: usize>(Vec<T>)`, or any derive beside a `const v: u8`, failed with E0308 inside the derive. A static, unit struct or tuple struct with one of those names failed the same way. Every name the generated impls bind now starts with `__buffa_`, like the derives' other generated names. A one-segment override path such as `new = value` or `insert = key` now calls the function of that name instead of resolving to a generated parameter.
time: 2026-10-10T17:56:01.000000Z
1 change: 1 addition & 0 deletions buffa-remote-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ ecow = { workspace = true }
smallvec = { version = "1", default-features = false }
smallbox = { version = "0.8", default-features = false }
indexmap = { version = "2", default-features = false, features = ["std", "serde"] }
syn = { workspace = true, features = ["visit"] }
4 changes: 2 additions & 2 deletions buffa-remote-derive/src/box_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub fn derive(input: DeriveInput) -> syn::Result<TokenStream> {
let into_inner_call =
remote_field::overridable_call(&overrides, "into_inner", field_ty, "into_inner");

let ctor_new = remote.construct(quote! { #new_call(value) });
let ctor_new = remote.construct(quote! { #new_call(__buffa_value) });

let arbitrary_impl = forwarders::arbitrary(
&remote,
Expand Down Expand Up @@ -60,7 +60,7 @@ pub fn derive(input: DeriveInput) -> syn::Result<TokenStream> {

impl #impl_generics ::buffa::ProtoBox<#element_ty> for #ident #ty_generics #where_clause {
#[inline]
fn new(value: #element_ty) -> Self {
fn new(__buffa_value: #element_ty) -> Self {
#ctor_new
}

Expand Down
9 changes: 5 additions & 4 deletions buffa-remote-derive/src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ pub fn derive(input: DeriveInput) -> syn::Result<TokenStream> {
let as_bytes =
remote_field::qualified_call(field_ty, quote! { ::core::convert::AsRef<[u8]> }, "as_ref");

let ctor_from_vec = remote.construct(quote! { #from_vec(v) });
let ctor_from_wire = remote.construct(quote! { #from_vec(payload.as_slice().to_vec()) });
let ctor_from_vec = remote.construct(quote! { #from_vec(__buffa_vec) });
let ctor_from_wire =
remote.construct(quote! { #from_vec(__buffa_payload.as_slice().to_vec()) });

// Unlike the `ProtoBox`/`MapStorage` overrides there is no conventional
// method name to default to: absent the key, nothing is generated and
Expand Down Expand Up @@ -69,15 +70,15 @@ pub fn derive(input: DeriveInput) -> syn::Result<TokenStream> {

impl #impl_generics ::core::convert::From<::buffa::alloc::vec::Vec<u8>> for #ident #ty_generics #where_clause {
#[inline]
fn from(v: ::buffa::alloc::vec::Vec<u8>) -> Self {
fn from(__buffa_vec: ::buffa::alloc::vec::Vec<u8>) -> Self {
#ctor_from_vec
}
}

impl #impl_generics ::buffa::ProtoBytes for #ident #ty_generics #where_clause {
#[inline]
fn from_wire(
payload: ::buffa::WirePayload<'_>,
__buffa_payload: ::buffa::WirePayload<'_>,
) -> ::core::result::Result<Self, ::buffa::DecodeError> {
::core::result::Result::Ok(#ctor_from_wire)
}
Expand Down
93 changes: 89 additions & 4 deletions buffa-remote-derive/src/forwarders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,10 @@ pub fn arbitrary(
TakeRest::Seed => quote! {
#[inline]
fn arbitrary_take_rest(
u: ::#krate::Unstructured<#lifetime>,
__buffa_unstructured: ::#krate::Unstructured<#lifetime>,
) -> ::#krate::Result<Self> {
let __buffa_seed: #seed = ::#krate::Arbitrary::arbitrary_take_rest(u)?;
let __buffa_seed: #seed =
::#krate::Arbitrary::arbitrary_take_rest(__buffa_unstructured)?;
::core::result::Result::Ok(#build)
}
},
Expand All @@ -290,9 +291,9 @@ pub fn arbitrary(
{
#[inline]
fn arbitrary(
u: &mut ::#krate::Unstructured<#lifetime>,
__buffa_unstructured: &mut ::#krate::Unstructured<#lifetime>,
) -> ::#krate::Result<Self> {
let __buffa_seed: #seed = ::#krate::Arbitrary::arbitrary(u)?;
let __buffa_seed: #seed = ::#krate::Arbitrary::arbitrary(__buffa_unstructured)?;
::core::result::Result::Ok(#build)
}

Expand Down Expand Up @@ -393,6 +394,90 @@ mod tests {
}
}

/// Every binding an expansion introduces starts with `__buffa_`: the
/// parameters, the closure parameters and the `let` bindings. A plain
/// name becomes a constant pattern beside a constant of that name, and
/// takes the place of a one-segment override path with that name (#652).
#[test]
fn every_binding_starts_with_the_reserved_prefix() {
struct Bindings(Vec<syn::Ident>);
impl<'ast> syn::visit::Visit<'ast> for Bindings {
fn visit_pat_ident(&mut self, pat: &'ast syn::PatIdent) {
self.0.push(pat.ident.clone());
syn::visit::visit_pat_ident(self, pat);
}
}
// A named field and every override key, beside the tuple structs
// without overrides that the other helpers expand.
type Derive = fn(syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream>;
let overridden: [(&'static str, Derive, syn::DeriveInput); 5] = [
(
"string",
crate::string::derive,
parse_quote! {
#[buffa(remote = Remote, arbitrary, serde)]
struct S { inner: Remote }
},
),
(
"bytes",
crate::bytes::derive,
parse_quote! {
#[buffa(remote = Remote, as_shared = shared, arbitrary, serde)]
struct B { inner: Remote }
},
),
(
"list",
crate::list::derive,
parse_quote! {
#[buffa(remote = Remote, arbitrary, serde)]
struct L<T> { inner: Remote }
},
),
(
"box",
crate::box_ptr::derive,
parse_quote! {
#[buffa(remote = Remote, new = make, into_inner = take, arbitrary, serde)]
struct P<T> { inner: Remote }
},
),
(
"map",
crate::map::derive,
parse_quote! {
#[buffa(
remote = Remote, len = count, insert = put, clear = wipe, iter = entries,
arbitrary, serde,
)]
struct M<K, V> { inner: Remote }
},
),
];
let overridden = overridden.into_iter().map(|(name, derive, input)| {
(
name,
derive(input).expect("overridden expansion").to_string(),
)
});
let arbitrary = expansions()
.into_iter()
.map(|(name, _, keyed)| (name, keyed));
for (name, expansion) in arbitrary.chain(serde_expansions()).chain(overridden) {
let file: syn::File = syn::parse_str(&expansion).expect("the expansion parses");
let mut bindings = Bindings(Vec::new());
syn::visit::Visit::visit_file(&mut bindings, &file);
assert!(!bindings.0.is_empty(), "{name}: no bindings:\n{expansion}");
for binding in bindings.0 {
assert!(
binding.to_string().starts_with("__buffa_"),
"{name}: binding `{binding}`:\n{expansion}"
);
}
}
}

/// The string impls call `serde` and the `ProtoString` surface only, so a
/// crate can use the key on a string newtype without `buffa/json`. The
/// bytes impls take base64 from `buffa::json_helpers`.
Expand Down
21 changes: 10 additions & 11 deletions buffa-remote-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,17 +335,16 @@
//!
//! # Reserved identifiers
//!
//! The generated impls declare lifetimes and type parameters of their own,
//! and each of those names starts with `__buffa` or `__Buffa` (for example
//! `'__buffa_iter` and `__BuffaIter`). Keep those two prefixes out of the
//! newtype's own lifetime and generic parameter names and out of the types and
//! override paths it names. A parameter such as `'a` or `T` cannot collide with
//! a generated one.
//!
//! The generated methods also bind the parameters `value`, `key` and `u`.
//! Write a `new` or `insert` override as a path with two or more segments,
//! such as `Type::method` or `self::helper`, because a bare `value`, `key` or
//! `u` resolves to the parameter.
//! Every name the generated impls introduce starts with `__buffa` or
//! `__Buffa`: their lifetimes, type parameters, function and closure
//! parameters, and local bindings and items (for example `'__buffa_iter`,
//! `__BuffaIter` and `__buffa_value`). These are also the parameter names the
//! newtype's rustdoc shows for its trait impls. Keep the two prefixes out of
//! the newtype's own lifetime and generic parameter names, out of the types
//! and override paths it names, and out of every item in scope at the derive.
//! A name such as `'a`, `T`, `const N`, a constant or static `value`, or the
//! path in an override such as `insert = key` cannot collide with a generated
//! one.
//!
//! # Why a `remote` attribute that just repeats the field's type?
//!
Expand Down
12 changes: 6 additions & 6 deletions buffa-remote-derive/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ pub fn derive(input: DeriveInput) -> syn::Result<TokenStream> {
"extend",
);

let ctor_from_iter = remote.construct(quote! { #from_iter(iter) });
let ctor_from_vec = remote.construct(quote! { #from_vec(v) });
let ctor_from_iter = remote.construct(quote! { #from_iter(__buffa_iter) });
let ctor_from_vec = remote.construct(quote! { #from_vec(__buffa_vec) });

// The `ProtoList` impl needs bounds beyond the struct's own (the element
// bounds, `Extend`, `Default`), so it can't reuse `#where_clause` like
Expand Down Expand Up @@ -87,15 +87,15 @@ pub fn derive(input: DeriveInput) -> syn::Result<TokenStream> {
impl #impl_generics ::core::iter::FromIterator<#element_ty> for #ident #ty_generics #where_clause {
#[inline]
fn from_iter<__BuffaIter: ::core::iter::IntoIterator<Item = #element_ty>>(
iter: __BuffaIter,
__buffa_iter: __BuffaIter,
) -> Self {
#ctor_from_iter
}
}

impl #impl_generics ::core::convert::From<::buffa::alloc::vec::Vec<#element_ty>> for #ident #ty_generics #where_clause {
#[inline]
fn from(v: ::buffa::alloc::vec::Vec<#element_ty>) -> Self {
fn from(__buffa_vec: ::buffa::alloc::vec::Vec<#element_ty>) -> Self {
#ctor_from_vec
}
}
Expand All @@ -104,8 +104,8 @@ pub fn derive(input: DeriveInput) -> syn::Result<TokenStream> {
#list_where_clause
{
#[inline]
fn push(&mut self, value: #element_ty) {
#extend(&mut #accessor, ::core::iter::once(value));
fn push(&mut self, __buffa_value: #element_ty) {
#extend(&mut #accessor, ::core::iter::once(__buffa_value));
}

// Reinitializes via `Default` rather than forwarding to a native
Expand Down
4 changes: 2 additions & 2 deletions buffa-remote-derive/src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ pub fn derive(input: DeriveInput) -> syn::Result<TokenStream> {
}

#[inline]
fn storage_insert(&mut self, key: #key_ty, value: #value_ty) {
#insert_call(&mut #accessor, key, value);
fn storage_insert(&mut self, __buffa_key: #key_ty, __buffa_value: #value_ty) {
#insert_call(&mut #accessor, __buffa_key, __buffa_value);
}

#[inline]
Expand Down
15 changes: 7 additions & 8 deletions buffa-remote-derive/src/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ pub fn derive(input: DeriveInput) -> syn::Result<TokenStream> {
let as_str =
remote_field::qualified_call(field_ty, quote! { ::core::convert::AsRef<str> }, "as_ref");

let ctor_from_string = remote.construct(quote! { #from_string(s) });
let ctor_from_str = remote.construct(quote! { #from_str(s) });
let ctor_from_wire = remote.construct(quote! { #from_str(s) });
let ctor_from_string = remote.construct(quote! { #from_string(__buffa_string) });
let ctor_from_str = remote.construct(quote! { #from_str(__buffa_str) });

let arbitrary_impl = forwarders::arbitrary(
&remote,
Expand Down Expand Up @@ -62,29 +61,29 @@ pub fn derive(input: DeriveInput) -> syn::Result<TokenStream> {

impl #impl_generics ::core::convert::From<::buffa::alloc::string::String> for #ident #ty_generics #where_clause {
#[inline]
fn from(s: ::buffa::alloc::string::String) -> Self {
fn from(__buffa_string: ::buffa::alloc::string::String) -> Self {
#ctor_from_string
}
}

impl #impl_generics ::core::convert::From<&str> for #ident #ty_generics #where_clause {
#[inline]
fn from(s: &str) -> Self {
fn from(__buffa_str: &str) -> Self {
#ctor_from_str
}
}

impl #impl_generics ::buffa::ProtoString for #ident #ty_generics #where_clause {
#[inline]
fn copy_from_str(s: &str) -> Self {
fn copy_from_str(__buffa_str: &str) -> Self {
#ctor_from_str
}

#[inline]
fn from_wire(
payload: ::buffa::WirePayload<'_>,
__buffa_payload: ::buffa::WirePayload<'_>,
) -> ::core::result::Result<Self, ::buffa::DecodeError> {
payload.to_str().map(|s| #ctor_from_wire)
__buffa_payload.to_str().map(|__buffa_str| #ctor_from_str)
}
}

Expand Down
Loading
Loading