From d0746be4cfde1cd8fec745bc772934af13e33c42 Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Sun, 9 Aug 2026 22:55:50 +0200 Subject: [PATCH] Replace `cfg(all())`/`cfg(any())` with `cfg(true)`/`cfg(false)` This makes the meaning clearer and is supported since Rust 1.88. Ensure that `serde_as` knows how to deal with `cfg(true)`/`cfg(false)`. For the `schemars` support it can understand `cfg_attr` and thus needs to parse `true`/`false` as well. This is not covered by the `syn::Meta` type, so we need to add a new type `CfgAttrCondition` that can represent `true`/`false` as well as the generic `Meta` expression. --- serde_with/CHANGELOG.md | 3 ++ serde_with/src/guide/serde_as.md | 12 ++--- serde_with/src/lib.rs | 8 +-- serde_with/src/rust.rs | 10 ++-- serde_with/tests/schemars_0_8/main.rs | 16 +++--- serde_with/tests/schemars_0_9/main.rs | 16 +++--- serde_with/tests/schemars_1/main.rs | 16 +++--- serde_with_macros/CHANGELOG.md | 3 ++ serde_with_macros/src/utils.rs | 76 ++++++++++++++++++++++----- 9 files changed, 108 insertions(+), 52 deletions(-) diff --git a/serde_with/CHANGELOG.md b/serde_with/CHANGELOG.md index e5dbde9a..17de5be3 100644 --- a/serde_with/CHANGELOG.md +++ b/serde_with/CHANGELOG.md @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * Update `syn` and `darling` dependencies to use `syn` v3 (#992) * Update dev-dependencies to newer versions (#993) * Update `base64` to a newer version. This should not have any API change, but some error messages might change. (#993) +* `serde_as` can now parse `cfg_attr(true, ...)` and `cfg_attr(false, ...)` (#995) + `true`/`false` are new literals as of Rust 1.88 but need to be parsed explicitly with the `syn` types. + This is used when emitting `schemars` annotations. ## [3.22.0] - 2026-08-09 diff --git a/serde_with/src/guide/serde_as.md b/serde_with/src/guide/serde_as.md index cadf2006..e991d658 100644 --- a/serde_with/src/guide/serde_as.md +++ b/serde_with/src/guide/serde_as.md @@ -148,7 +148,7 @@ Our goal is to serialize this `Data` struct. Currently, we do not have anything we can use to replace `???` with, since `_` only works if `RemoteType` would implement `Serialize`, which it does not. ```rust -# #[cfg(any())] { +# #[cfg(false)] { #[serde_as] #[derive(serde::Serialize)] struct Data { @@ -163,7 +163,7 @@ The `SerializeAs` implementation is **always** written for a local type. This allows it to seamlessly work with types from dependencies without running into orphan rule problems. ```rust -# #[cfg(any())] { +# #[cfg(false)] { struct LocalType; impl SerializeAs for LocalType { @@ -192,7 +192,7 @@ As can be seen, this is mostly boilerplate, since the most part is encapsulated The final `Data` struct will now look like: ```rust -# #[cfg(any())] { +# #[cfg(false)] { #[serde_as] #[derive(serde::Serialize)] struct Data { @@ -209,7 +209,7 @@ This is a special functionality of serde, where it derives the de/serialization You can find all the details in the [official serde documentation](https://serde.rs/remote-derive.html). ```rust -# #[cfg(any())] { +# #[cfg(false)] { // Pretend that this is somebody else's crate, not a module. mod other_crate { // Neither Serde nor the other crate provides Serialize and Deserialize @@ -242,7 +242,7 @@ We can write this implementation. The implementation for `DeserializeAs` works analogue. ```rust -# #[cfg(any())] { +# #[cfg(false)] { impl SerializeAs for DurationDef { fn serialize_as(value: &Duration, serializer: S) -> Result where @@ -257,7 +257,7 @@ impl SerializeAs for DurationDef { This now allows us to use `Duration` for serialization. ```rust -# #[cfg(any())] { +# #[cfg(false)] { use other_crate::Duration; #[serde_as] diff --git a/serde_with/src/lib.rs b/serde_with/src/lib.rs index fbc03d6e..9d7a4a2a 100644 --- a/serde_with/src/lib.rs +++ b/serde_with/src/lib.rs @@ -1857,7 +1857,7 @@ pub struct PickFirst(PhantomData); /// Deserializing works analogue, by deserializing a `T` and then converting into `O`. /// /// ```rust -/// # #[cfg(any())] { +/// # #[cfg(false)] { /// struct S { /// #[serde_as(as = "FromInto")] /// value: O, @@ -1939,7 +1939,7 @@ pub struct FromInto(PhantomData); /// Deserializing works analogue, by deserializing a `T` and then converting into `O`. /// /// ```rust -/// # #[cfg(any())] { +/// # #[cfg(false)] { /// struct S { /// #[serde_as(as = "FromIntoRef")] /// value: O, @@ -2020,7 +2020,7 @@ pub struct FromIntoRef(PhantomData); /// Deserializing works analogue, by deserializing a `T` and then converting into `O`. /// /// ```rust -/// # #[cfg(any())] { +/// # #[cfg(false)] { /// struct S { /// #[serde_as(as = "TryFromInto")] /// value: O, @@ -2109,7 +2109,7 @@ pub struct TryFromInto(PhantomData); /// Deserializing works analogue, by deserializing a `T` and then converting into `O`. /// /// ```rust -/// # #[cfg(any())] { +/// # #[cfg(false)] { /// struct S { /// #[serde_as(as = "TryFromIntoRef")] /// value: O, diff --git a/serde_with/src/rust.rs b/serde_with/src/rust.rs index f6e3945f..b3f5a9a3 100644 --- a/serde_with/src/rust.rs +++ b/serde_with/src/rust.rs @@ -18,7 +18,7 @@ use crate::prelude::*; /// This cannot work, since there is no way to tell the `Vec` to skip the inner `DoubleOption` if it is `None`. /// /// ```rust -/// # #[cfg(any())] { +/// # #[cfg(false)] { /// # struct Foobar { /// #[serde_as(as = "Vec>")] /// data: Vec>>, @@ -182,7 +182,7 @@ pub mod unwrap_or_skip { /// The `_` is a placeholder which works for any type which implements [`Serialize`]/[`Deserialize`]. /// /// ```rust -/// # #[cfg(any())] { +/// # #[cfg(false)] { /// #[serde_as] /// #[derive(Deserialize, Serialize)] /// struct A { @@ -298,7 +298,7 @@ pub mod sets_duplicate_value_is_error { /// The `_` is a placeholder which works for any type which implements [`Serialize`]/[`Deserialize`]. /// /// ```rust -/// # #[cfg(any())] { +/// # #[cfg(false)] { /// #[serde_as] /// #[derive(Deserialize, Serialize)] /// struct A { @@ -418,7 +418,7 @@ pub mod maps_duplicate_key_is_error { /// The `_` is a placeholder which works for any type which implements [`Serialize`]/[`Deserialize`]. /// /// ```rust -/// # #[cfg(any())] { +/// # #[cfg(false)] { /// #[serde_as] /// #[derive(Deserialize, Serialize)] /// struct A { @@ -506,7 +506,7 @@ pub mod sets_last_value_wins { /// The `_` is a placeholder which works for any type which implements [`Serialize`]/[`Deserialize`]. /// /// ```rust -/// # #[cfg(any())] { +/// # #[cfg(false)] { /// #[serde_as] /// #[derive(Deserialize, Serialize)] /// struct A { diff --git a/serde_with/tests/schemars_0_8/main.rs b/serde_with/tests/schemars_0_8/main.rs index 6a4bf97d..d84f0eae 100644 --- a/serde_with/tests/schemars_0_8/main.rs +++ b/serde_with/tests/schemars_0_8/main.rs @@ -132,8 +132,8 @@ fn schemars_other_cfg_attrs() { #[derive(JsonSchema, Serialize)] struct Test { #[serde_as(as = "DisplayFromStr")] - #[cfg_attr(any(), arbitrary("some" |weird| syntax::()))] - #[cfg_attr(any(), schemars(with = "i32"))] + #[cfg_attr(false, arbitrary("some" |weird| syntax::()))] + #[cfg_attr(false, schemars(with = "i32"))] custom: i32, } @@ -152,11 +152,11 @@ fn schemars_custom_with() { custom: i32, #[serde_as(as = "DisplayFromStr")] - #[cfg_attr(any(), schemars(with = "i32"))] + #[cfg_attr(false, schemars(with = "i32"))] with_disabled: i32, #[serde_as(as = "DisplayFromStr")] - #[cfg_attr(all(), schemars(with = "i32"))] + #[cfg_attr(true, schemars(with = "i32"))] always_enabled: i32, } @@ -226,11 +226,11 @@ fn schemars_custom_schema_with() { custom: i32, #[serde_as(as = "DisplayFromStr")] - #[cfg_attr(any(), schemars(schema_with = "custom_int"))] + #[cfg_attr(false, schemars(schema_with = "custom_int"))] with_disabled: i32, #[serde_as(as = "DisplayFromStr")] - #[cfg_attr(all(), schemars(schema_with = "custom_int"))] + #[cfg_attr(true, schemars(schema_with = "custom_int"))] always_enabled: i32, } @@ -494,7 +494,7 @@ mod derive { #[serde_as] #[derive(Serialize)] - #[cfg_attr(all(), derive(JsonSchema))] + #[cfg_attr(true, derive(JsonSchema))] struct Enabled { #[serde_as(as = "DisplayFromStr")] field: u32, @@ -503,7 +503,7 @@ mod derive { #[allow(dead_code)] #[serde_as] #[derive(Serialize)] - #[cfg_attr(any(), derive(JsonSchema))] + #[cfg_attr(false, derive(JsonSchema))] struct Disabled { // If we are incorrectly adding `#[schemars(with = ...)]` attributes // then we should get an error on this field. diff --git a/serde_with/tests/schemars_0_9/main.rs b/serde_with/tests/schemars_0_9/main.rs index bf48ab87..257aac2e 100644 --- a/serde_with/tests/schemars_0_9/main.rs +++ b/serde_with/tests/schemars_0_9/main.rs @@ -133,8 +133,8 @@ fn schemars_other_cfg_attrs() { #[derive(JsonSchema, Serialize)] struct Test { #[serde_as(as = "DisplayFromStr")] - #[cfg_attr(any(), arbitrary("some" |weird| syntax::()))] - #[cfg_attr(any(), schemars(with = "i32"))] + #[cfg_attr(false, arbitrary("some" |weird| syntax::()))] + #[cfg_attr(false, schemars(with = "i32"))] custom: i32, } @@ -153,11 +153,11 @@ fn schemars_custom_with() { custom: i32, #[serde_as(as = "DisplayFromStr")] - #[cfg_attr(any(), schemars(with = "i32"))] + #[cfg_attr(false, schemars(with = "i32"))] with_disabled: i32, #[serde_as(as = "DisplayFromStr")] - #[cfg_attr(all(), schemars(with = "i32"))] + #[cfg_attr(true, schemars(with = "i32"))] always_enabled: i32, } @@ -225,11 +225,11 @@ fn schemars_custom_schema_with() { custom: i32, #[serde_as(as = "DisplayFromStr")] - #[cfg_attr(any(), schemars(schema_with = "custom_int"))] + #[cfg_attr(false, schemars(schema_with = "custom_int"))] with_disabled: i32, #[serde_as(as = "DisplayFromStr")] - #[cfg_attr(all(), schemars(schema_with = "custom_int"))] + #[cfg_attr(true, schemars(schema_with = "custom_int"))] always_enabled: i32, } @@ -493,7 +493,7 @@ mod derive { #[serde_as] #[derive(Serialize)] - #[cfg_attr(all(), derive(JsonSchema))] + #[cfg_attr(true, derive(JsonSchema))] struct Enabled { #[serde_as(as = "DisplayFromStr")] field: u32, @@ -502,7 +502,7 @@ mod derive { #[allow(dead_code)] #[serde_as] #[derive(Serialize)] - #[cfg_attr(any(), derive(JsonSchema))] + #[cfg_attr(false, derive(JsonSchema))] struct Disabled { // If we are incorrectly adding `#[schemars(with = ...)]` attributes // then we should get an error on this field. diff --git a/serde_with/tests/schemars_1/main.rs b/serde_with/tests/schemars_1/main.rs index 4a3a94b7..30efae8b 100644 --- a/serde_with/tests/schemars_1/main.rs +++ b/serde_with/tests/schemars_1/main.rs @@ -133,8 +133,8 @@ fn schemars_other_cfg_attrs() { #[derive(JsonSchema, Serialize)] struct Test { #[serde_as(as = "DisplayFromStr")] - #[cfg_attr(any(), arbitrary("some" |weird| syntax::()))] - #[cfg_attr(any(), schemars(with = "i32"))] + #[cfg_attr(false, arbitrary("some" |weird| syntax::()))] + #[cfg_attr(false, schemars(with = "i32"))] custom: i32, } @@ -153,11 +153,11 @@ fn schemars_custom_with() { custom: i32, #[serde_as(as = "DisplayFromStr")] - #[cfg_attr(any(), schemars(with = "i32"))] + #[cfg_attr(false, schemars(with = "i32"))] with_disabled: i32, #[serde_as(as = "DisplayFromStr")] - #[cfg_attr(all(), schemars(with = "i32"))] + #[cfg_attr(true, schemars(with = "i32"))] always_enabled: i32, } @@ -217,11 +217,11 @@ fn schemars_custom_schema_with() { custom: i32, #[serde_as(as = "DisplayFromStr")] - #[cfg_attr(any(), schemars(schema_with = "custom_int"))] + #[cfg_attr(false, schemars(schema_with = "custom_int"))] with_disabled: i32, #[serde_as(as = "DisplayFromStr")] - #[cfg_attr(all(), schemars(schema_with = "custom_int"))] + #[cfg_attr(true, schemars(schema_with = "custom_int"))] always_enabled: i32, } @@ -492,7 +492,7 @@ mod derive { #[serde_as] #[derive(Serialize)] - #[cfg_attr(all(), derive(JsonSchema))] + #[cfg_attr(true, derive(JsonSchema))] struct Enabled { #[serde_as(as = "DisplayFromStr")] field: u32, @@ -501,7 +501,7 @@ mod derive { #[allow(dead_code)] #[serde_as] #[derive(Serialize)] - #[cfg_attr(any(), derive(JsonSchema))] + #[cfg_attr(false, derive(JsonSchema))] struct Disabled { // If we are incorrectly adding `#[schemars(with = ...)]` attributes // then we should get an error on this field. diff --git a/serde_with_macros/CHANGELOG.md b/serde_with_macros/CHANGELOG.md index c3770149..3501911a 100644 --- a/serde_with_macros/CHANGELOG.md +++ b/serde_with_macros/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed * Update `syn` and `darling` dependencies to use `syn` v3 (#992) +* `serde_as` can now parse `cfg_attr(true, ...)` and `cfg_attr(false, ...)` (#995) + `true`/`false` are new literals as of Rust 1.88 but need to be parsed explicitly with the `syn` types. + This is used when emitting `schemars` annotations. ## [3.22.0] - 2026-08-09 diff --git a/serde_with_macros/src/utils.rs b/serde_with_macros/src/utils.rs index 0b27f2f9..d33170ee 100644 --- a/serde_with_macros/src/utils.rs +++ b/serde_with_macros/src/utils.rs @@ -8,7 +8,8 @@ use syn::{ parse::{Parse, ParseStream}, parse_quote, punctuated::Punctuated, - Attribute, DeriveInput, Generics, Meta, Path, PathSegment, Token, TypeGenerics, WhereClause, + Attribute, DeriveInput, Generics, LitBool, Meta, Path, PathSegment, Token, TypeGenerics, + WhereClause, }; /// Merge multiple [`syn::Error`] into one. @@ -85,7 +86,7 @@ impl ToTokens for DeImplGenerics<'_> { /// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ /// ``` struct CfgAttr { - condition: Meta, + condition: CfgAttrCondition, _comma: Token![,], metas: Punctuated, } @@ -100,6 +101,61 @@ impl Parse for CfgAttr { } } +/// Represents the condition of a `#[cfg_attr]` attribute. +/// +/// ```text +/// #[cfg_attr(feature = "things", derive(Macro))] +/// ^^^^^^^^^^^^^^^^^^ +/// ``` +/// +/// ```text +/// #[cfg_attr(true, derive(Macro))] +/// ^^^^ +/// ``` +/// +/// ```text +/// #[cfg_attr(all(), derive(Macro))] +/// ^^^^^ +/// ``` +/// +/// Find more information about the syntax in the reference: +/// +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub(crate) enum CfgAttrCondition { + /// `false` keyword + False, + /// `true` keyword + True, + /// Generic `Meta` expression, e.g. `feature = "things"`, `all(...)`, `any(...)`, `not(...)` + Meta(Meta), +} + +impl Parse for CfgAttrCondition { + fn parse(input: ParseStream<'_>) -> syn::Result { + if input.peek(LitBool) { + let lit_bool = input.parse::()?; + if lit_bool.value { + Ok(Self::True) + } else { + Ok(Self::False) + } + } else { + Ok(Self::Meta(input.parse()?)) + } + } +} + +impl ToTokens for CfgAttrCondition { + fn to_tokens(&self, tokens: &mut TokenStream2) { + match self { + Self::False => tokens.extend(quote::quote!(false)), + Self::True => tokens.extend(quote::quote!(true)), + Self::Meta(meta) => meta.to_tokens(tokens), + } + } +} + /// Determine if there is a `#[derive(JsonSchema)]` on this struct. pub(crate) fn has_derive_jsonschema(input: TokenStream) -> syn::Result { fn parse_derive_args(input: ParseStream<'_>) -> syn::Result> { @@ -123,7 +179,7 @@ pub(crate) fn has_derive_jsonschema(input: TokenStream) -> syn::Result { let config = if meta @@ -158,14 +214,14 @@ pub(crate) fn has_derive_jsonschema(input: TokenStream) -> syn::Result; -impl From for SchemaFieldConfig { - fn from(meta: Meta) -> Self { - Self::Lazy(meta.into()) +impl From for SchemaFieldConfig { + fn from(cfg_attr_condition: CfgAttrCondition) -> Self { + Self::Lazy(SchemaFieldCondition(cfg_attr_condition)) } } #[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub(crate) struct SchemaFieldCondition(pub(crate) Meta); +pub(crate) struct SchemaFieldCondition(pub(crate) CfgAttrCondition); impl BitAnd for SchemaFieldCondition { type Output = Self; @@ -203,12 +259,6 @@ impl Not for SchemaFieldCondition { } } -impl From for SchemaFieldCondition { - fn from(meta: Meta) -> Self { - Self(meta) - } -} - /// Get a `#[cfg]` expression under which this field has a `#[schemars]` attribute /// with a `with = ...` argument. pub(crate) fn schemars_with_attr_if(