diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f240cd5a4..dfe8e3117 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -97,10 +97,10 @@ jobs: cargo update -p serde_bytes --precise 0.11.16 cargo update -p indexmap --precise 2.5.0 cargo update -p once_cell --precise 1.20.3 - - run: cargo clippy -- -D warnings - - run: cargo clippy --features integer128 -- -D warnings - - run: cargo clippy --features indexmap -- -D warnings - - run: cargo clippy --all-features -- -D warnings + - run: cargo clippy -- -D warnings -A unknown-lints + - run: cargo clippy --features integer128 -- -D warnings -A unknown-lints + - run: cargo clippy --features indexmap -- -D warnings -A unknown-lints + - run: cargo clippy --all-features -- -D warnings -A unknown-lints clippy-fuzz: name: "Clippy: Fuzzer" diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b9e5d62c..f9b956512 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### API Changes - Add `ron::Options::to_io_writer` and `ron::Options::to_io_writer_pretty` to allow writing into an `io::Writer` ([#561](https://github.com/ron-rs/ron/pull/561)) +- Breaking: `ron::value::Number` is now non-exhaustive, to avoid breaking `match`es when feature unification enables more of its variants than expected ([#564](https://github.com/ron-rs/ron/pull/564)) ## [0.9.0] - 2025-03-18 diff --git a/src/value/number.rs b/src/value/number.rs index b38016b55..0791c8193 100644 --- a/src/value/number.rs +++ b/src/value/number.rs @@ -5,8 +5,34 @@ use std::{ use serde::{de::Visitor, Serialize, Serializer}; -/// A wrapper for any numeric primitive type in Rust +/// A wrapper for any numeric primitive type in Rust. +/// +/// Some varints of the `Number` enum are enabled by features: +/// - `Number::I128` and `Number::U128` by the `integer128` feature +/// +/// To ensure that feature unification does not break `match`ing over `Number`, +/// the `Number` enum is non-exhaustive. +/// +///
+/// Exhaustively matching on Number in tests +/// +/// If you want to ensure that you exhaustively handle every variant, you can +/// match on the hidden `Number::__NonExhaustive` variant. +/// +///
+/// Matching on this variant means that your code may break when RON is +/// upgraded or when feature unification enables further variants in the +/// Number enum than your code expects. +///
+/// +/// It is your responsibility to only *ever* match on `Number::__NonExhaustive` +/// inside tests, e.g. by using `#[cfg(test)]` on the particular match arm, to +/// ensure that only your tests break (e.g. in CI) when your code is not +/// exhaustively matching on every variant, e.g. after a version upgrade or +/// feature unification. +///
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Hash, Ord)] +#[cfg_attr(doc, non_exhaustive)] pub enum Number { I8(i8), I16(i16), @@ -22,6 +48,14 @@ pub enum Number { U128(u128), F32(F32), F64(F64), + #[cfg(not(doc))] + #[allow(private_interfaces)] + __NonExhaustive(private::Never), +} + +mod private { + #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Hash, Ord)] + pub enum Never {} } impl Serialize for Number { @@ -41,6 +75,7 @@ impl Serialize for Number { Self::U128(v) => serializer.serialize_u128(*v), Self::F32(v) => serializer.serialize_f32(v.get()), Self::F64(v) => serializer.serialize_f64(v.get()), + Self::__NonExhaustive(never) => match *never {}, } } } @@ -65,6 +100,7 @@ impl Number { Self::U128(v) => visitor.visit_u128(*v), Self::F32(v) => visitor.visit_f32(v.get()), Self::F64(v) => visitor.visit_f64(v.get()), + Self::__NonExhaustive(never) => match *never {}, } } } @@ -216,6 +252,7 @@ impl Number { Number::U128(v) => v as f64, Number::F32(v) => f64::from(v.get()), Number::F64(v) => v.get(), + Self::__NonExhaustive(never) => match never {}, } } }