diff --git a/conformance/tests/update_from.rs b/conformance/tests/update_from.rs index ba1c83983..4d2e481c1 100644 --- a/conformance/tests/update_from.rs +++ b/conformance/tests/update_from.rs @@ -642,3 +642,42 @@ fn a_standing_group_member_conflicts_with_a_sibling_flag() { "standing --yaml still conflicts with --strict", ); } + +/// A counted `u8` whose presence is "not the default". The generated +/// `!= Default::default()` must name `u8`, or `serde_json`'s `PartialEq for u8` +/// makes the comparison ambiguous — hk's CLI hit that on every build that pulls +/// `serde_json` into the same crate. +#[derive(Cli, Debug, PartialEq)] +#[usage(bin = "counted", completion)] +struct Counted { + #[usage(short, long, global, count, overrides("--quiet"))] + verbose: u8, + #[usage(short, long, global, overrides("--verbose"))] + quiet: bool, + #[usage(subcommand)] + command: CountedCmd, +} + +#[derive(Subcommands, Debug, PartialEq)] +enum CountedCmd { + Run, +} + +#[test] +fn a_standing_count_compiles_beside_serde_json() { + // Keep `serde_json` live in this module so its `PartialEq` impls stay in scope. + let _ = serde_json::json!({"n": 1u8}); + + let a = argv(["-vv", "run"]); + let mut counted = Counted::parse_from(&a).expect("two -v"); + assert_eq!(counted.verbose, 2); + + // A second line that says nothing about `-v` must keep the standing count — + // presence is "not the default", and that comparison has to name `u8`. + let a = argv(["run"]); + counted + .try_update_from(&a) + .expect("the standing count answers for itself"); + assert_eq!(counted.verbose, 2); + assert!(!counted.quiet); +} diff --git a/derive/src/codegen.rs b/derive/src/codegen.rs index 8cda2081c..eb6c9c2f1 100644 --- a/derive/src/codegen.rs +++ b/derive/src/codegen.rs @@ -3233,7 +3233,13 @@ fn standing_presence(field: &Field) -> Option { }), Kind::Flag { .. } | Kind::Arg { .. } => Some(match field.shape { Shape::Bool => quote!(__usage_s.#ident), - Shape::Count => quote!(__usage_s.#ident != ::std::default::Default::default()), + // Name the field's type: `Default::default()` alone is ambiguous when an + // adopter also brings `serde_json::Value`'s `PartialEq` into scope + // (hk's `verbose: u8` count under a workspace that pulls serde_json). + Shape::Count => { + let ty = &field.ty; + quote!(__usage_s.#ident != <#ty as ::std::default::Default>::default()) + } Shape::Optional => quote!(__usage_s.#ident.is_some()), // Nowhere to put "absent", so the field always holds something. The same // reading of the type that makes it required in the first place. @@ -4918,7 +4924,8 @@ fn merge_present(field: &Field) -> TokenStream { match field.shape { Shape::Bool => quote!(partial.#given || partial.#ident), Shape::Count => { - quote!(partial.#given || partial.#ident != ::std::default::Default::default()) + let ty = &field.ty; + quote!(partial.#given || partial.#ident != <#ty as ::std::default::Default>::default()) } Shape::Optional => quote!(partial.#given || partial.#ident.is_some()), Shape::Required | Shape::Many => quote!(partial.#given || !partial.#ident.is_empty()),