Skip to content

0.14.2: Patch derive leaks #[cfg(feature = "nesting")] into generated code, breaking downstream builds under -D warnings #185

Description

@kp-mariappan-ramasamy

Summary

In 0.14.2 the Patch derive emits #[cfg(feature = "nesting")] / #[cfg(not(feature = "nesting"))] attributes into the generated code. Those cfgs are then evaluated against the consuming crate's feature set rather than struct-patch's, so any downstream crate that doesn't happen to define a feature called nesting trips rustc's unexpected_cfgs lint. Under -D warnings (or RUSTFLAGS="-D warnings" in CI) this is a hard build failure.

This is a regression in 0.14.2; 0.14.1 is fine.

Reproduction

Any crate that derives Patch and builds with -D warnings:

# Cargo.toml
[dependencies]
struct-patch = "0.14.2"
use struct_patch::Patch;

#[derive(Clone, Debug, PartialEq, Patch)]
pub struct Config {
    pub host: String,
    pub port: u16,
}
$ cargo clippy --all-targets -- -D warnings
error: unexpected `cfg` condition value: `nesting`
 --> src/lib.rs:3:36
  |
3 | #[derive(Clone, Debug, PartialEq, Patch)]
  |                                   ^^^^^
  |
  = note: expected values for `feature` are: (the consuming crate's own features)
  = note: using a cfg inside a derive macro will use the cfgs from the destination crate
          and not the ones from the defining crate
  = help: try referring to `Patch` crate for guidance on how handle this unexpected cfg
  = note: `-D unexpected-cfgs` implied by `-D warnings`
  = note: this error originates in the derive macro `Patch`

Enabling features = ["nesting"] on the struct-patch dependency does not help, since the cfg is resolved in the downstream crate, which still has no such feature.

Cause

Introduced by #184 (add log on nesting feature, merged 2026-09-12, released as 0.14.2).

The attributes were placed inside the quote! { .. } block instead of outside it, so they end up in the macro output rather than gating the code generation. In derive/src/patch.rs, inside let patch_impl = quote! { (opens at line 830):

  • derive/src/patch.rs:887#[cfg(not(feature = "nesting"))] on fn apply_with_log<F: FnMut(&str)>
  • derive/src/patch.rs:941#[cfg(feature = "nesting")] on fn apply_with_log<F: FnMut(&[&str], &str)>

The same pattern appears inside let box_impl = quote! { (opens at line 1138):

  • derive/src/patch.rs:1147 and derive/src/patch.rs:1156

derive/src/filler.rs picked up equivalents in the same PR (e.g. line 323 inside its quote! block).

For contrast, the neighbouring uses in the same file are correct — e.g. patch.rs:771/:782 and :808/:827 sit outside quote! and gate a TokenStream variable, which is the intended shape.

Effect on generated code

Because feature = "nesting" is essentially never set in a consumer crate, the #[cfg(feature = "nesting")] arm is always dropped and the non-nesting arm always selected — so users who enable struct-patch/nesting silently get the non-nesting apply_with_log signature, in addition to the lint error everyone else gets.

Suggested fix

Move the gating out of quote!, following the existing pattern at patch.rs:771-827: build the two apply_with_log implementations into a TokenStream selected by a proc-macro-level #[cfg], and interpolate a single #apply_with_log_impl into quote!.

Related

#73 is the same class of bug for the merge feature.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions