diff --git a/PLAN.md b/PLAN.md index a9a940e5b..863cdf053 100644 --- a/PLAN.md +++ b/PLAN.md @@ -707,6 +707,9 @@ feature list is not an exhaustive audit. arity with distinct `value_names`); optional-value shapes that need per-occurrence presence semantics and arbitrary `value_parser` callbacks produce targeted migration diagnostics instead of a generic unknown-option error. + The generated build also takes a zero-cost shared reference to every field, so a + parse-only compatibility flag does not trip an adopter's `deny(dead_code)` merely + because application code intentionally accepts and ignores it. - [x] **Command-with-arguments completion hints.** `ExecutablePath`, `CommandName`, `CommandString`, and `CommandWithArguments` lower to shell-native completion types. A forwarded argv vector offers commands for diff --git a/derive/src/codegen.rs b/derive/src/codegen.rs index 6d1798c0a..5522db710 100644 --- a/derive/src/codegen.rs +++ b/derive/src/codegen.rs @@ -31,10 +31,19 @@ fn built_value(cli: &Cli, sub_build: &TokenStream, fields: &[TokenStream]) -> To debug_assert!(cli.fields.is_empty()); quote!(Self) } else { + let field_reads = cli.fields.iter().map(|field| &field.ident); quote! { - Self { - #sub_build - #(#fields),* + { + let __usage_built = Self { + #sub_build + #(#fields),* + }; + // A parsed compatibility flag may intentionally be accepted and ignored. + // Reading every field here keeps `#[deny(dead_code)]` from blaming the + // adopter for a field the derive itself owns. These shared references + // generate no runtime work after optimization. + #(let _ = &__usage_built.#field_reads;)* + __usage_built } } } diff --git a/usage-rs/tests/facade.rs b/usage-rs/tests/facade.rs index 374970480..c1f0b9a5b 100644 --- a/usage-rs/tests/facade.rs +++ b/usage-rs/tests/facade.rs @@ -481,6 +481,15 @@ struct ClapSpellings { path: Option, } +#[deny(dead_code)] +#[derive(Cli)] +#[command(bin = "parse-only-field")] +struct ParseOnlyField { + /// Accepted for compatibility even though the application intentionally ignores it. + #[arg(long)] + compatibility: bool, +} + #[derive(Cli)] #[command(bin = "clap-override-id")] struct ClapOverrideId { @@ -1121,6 +1130,12 @@ fn clap_field_ids_and_aliases_need_no_rewrite() { ); } +#[test] +fn parse_only_fields_do_not_trigger_dead_code() { + ParseOnlyField::parse_from(&[OsStr::new("--compatibility")]) + .expect("the compatibility flag should still parse"); +} + #[test] fn clap_value_arity_stays_on_each_flag_occurrence() { let parsed = FixedArity::parse_from(&[