From 9e886bbc16553775de4a4028cd385d09654404b4 Mon Sep 17 00:00:00 2001 From: ematipico Date: Wed, 14 Oct 2026 10:23:06 +0100 Subject: [PATCH 1/4] chore: add some good rules to clippy --- Cargo.toml | 81 +++++++++++++++++++++++++++++++++++++++++++++ clippy.toml | 1 + rust-toolchain.toml | 6 ++++ src/link.rs | 12 +++---- src/link/openapi.rs | 2 +- src/types.rs | 2 +- 6 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 clippy.toml create mode 100644 rust-toolchain.toml diff --git a/Cargo.toml b/Cargo.toml index 359d22756..184531c7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,3 +48,84 @@ required-features = ["cli"] [[test]] name = "wasm" required-features = ["cli", "wasm"] + + +[lints.clippy] +# https://rust-lang.github.io/rust-clippy/rust-1.88.0/index.html +allow_attributes = "deny" +assigning_clones = "warn" +cfg_not_test = "warn" +checked_conversions = "warn" +cloned_instead_of_copied = "warn" +copy_iterator = "warn" +dbg_macro = "warn" +doc_link_with_quotes = "warn" +empty_drop = "warn" +empty_enum_variants_with_brackets = "warn" +empty_enums = "warn" +expl_impl_clone_on_copy = "warn" +explicit_into_iter_loop = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp_const = "warn" +fn_params_excessive_bools = "warn" +get_unwrap = "warn" +implicit_clone = "warn" +implicit_hasher = "warn" +index_refutable_slice = "warn" +inefficient_to_string = "warn" +infinite_loop = "warn" +invalid_upcast_comparisons = "warn" +iter_filter_is_ok = "warn" +iter_not_returning_iterator = "warn" +large_stack_arrays = "warn" +large_types_passed_by_value = "warn" +lossy_float_literal = "warn" +macro_use_imports = "warn" +manual_is_variant_and = "warn" +manual_ok_or = "warn" +manual_string_new = "warn" +map_flatten = "warn" +map_unwrap_or = "warn" +mem_forget = "warn" +mismatching_type_param_order = "warn" +mut_mut = "warn" +naive_bytecount = "warn" +needless_bitwise_bool = "warn" +needless_continue = "warn" +needless_for_each = "warn" +no_effect_underscore_binding = "warn" +option_as_ref_cloned = "warn" +rc_buffer = "warn" +rc_mutex = "warn" +ref_binding_to_reference = "warn" +ref_option_ref = "warn" +rest_pat_in_fully_bound_structs = "warn" +single_char_pattern = "warn" +stable_sort_primitive = "warn" +str_split_at_newline = "warn" +string_lit_chars_any = "warn" +unnecessary_box_returns = "warn" +unnecessary_join = "warn" +unnested_or_patterns = "warn" +unreadable_literal = "warn" +verbose_bit_mask = "warn" +verbose_file_reads = "warn" +zero_sized_map_values = "warn" + +[lints.rust] +# https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html +ambiguous_negative_literals = "warn" +dead_code = "warn" +explicit_outlives_requirements = "warn" +# that's for salsa +impl_trait_overcaptures = "warn" +impl_trait_redundant_captures = "allow" +missing_unsafe_on_extern = "warn" +redundant_lifetimes = "warn" +trivial_numeric_casts = "warn" +unit_bindings = "warn" +unsafe_attr_outside_unsafe = "warn" +unused_import_braces = "warn" +unused_lifetimes = "warn" +unused_macro_rules = "warn" diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 000000000..4296655a0 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +allow-dbg-in-tests = true diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 000000000..cf2654ed8 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,6 @@ +[toolchain] +# The default profile includes rustc, rust-std, cargo, rust-docs, rustfmt and clippy. +# https://rust-lang.github.io/rustup/concepts/profiles.html +profile = "default" +channel = "1.99.0" +targets = ["wasm32-unknown-unknown"] diff --git a/src/link.rs b/src/link.rs index 454db0d6f..17e72a2bd 100644 --- a/src/link.rs +++ b/src/link.rs @@ -1006,7 +1006,7 @@ impl<'d> Builder<'d> { fn push_union( &mut self, members: &[TypeId], - discriminator: Option>, + discriminator: Option, exclusive: bool, ) -> TypeId { let members = self.members.extend(members.iter().copied()); @@ -1752,7 +1752,7 @@ impl<'d> Builder<'d> { fn union_of( &mut self, members: &[TypeId], - discriminator: Option>, + discriminator: Option, mut exclusive: bool, ) -> TypeId { let mut out = Vec::with_capacity(members.len()); @@ -1825,7 +1825,7 @@ impl<'d> Builder<'d> { &mut self, discriminator: &'d Discriminator, members: &[(Schema, TypeId)], - ) -> Box { + ) -> DiscriminatorData { let doc = self.doc; let wire_name = doc.str(discriminator.property_name); let mut name = Name::new(wire_name); @@ -1875,12 +1875,12 @@ impl<'d> Builder<'d> { } } let wire_name = self.strings.push(wire_name); - Box::new(DiscriminatorData { + DiscriminatorData { wire_name, name, cases, default, - }) + } } /// Returns the type a discriminator's mapping names: a `$ref`, or the @@ -1956,7 +1956,7 @@ impl<'d> Builder<'d> { }; let object = doc.get(object); let has_ref = !object.reference.is_empty(); - if !object.description.is_empty() && !(has_ref && self.v30) { + if !(object.description.is_empty() || has_ref && self.v30) { return doc.str(object.description); } if has_ref { diff --git a/src/link/openapi.rs b/src/link/openapi.rs index f79c7d837..51f1b3767 100644 --- a/src/link/openapi.rs +++ b/src/link/openapi.rs @@ -1766,7 +1766,7 @@ impl<'de> Deserialize<'de> for Count { fn visit_f64(self, v: f64) -> Result { // 2^64 is the first float that does not fit in a `u64`. - if v >= 0.0 && v.fract() == 0.0 && v < 18_446_744_073_709_551_616.0 { + if v >= 0.0 && v.fract() == 0.0 && v < 18_446_744_073_709_552_000.0 { Ok(Count(v as u64)) } else { Err(E::invalid_value(de::Unexpected::Float(v), &self)) diff --git a/src/types.rs b/src/types.rs index 5255d898a..2aecf1d54 100644 --- a/src/types.rs +++ b/src/types.rs @@ -604,7 +604,7 @@ pub(crate) struct UnionData { /// Whether a value is at most one of the members. pub(crate) exclusive: bool, /// Boxed, since most unions have none, to keep `TypeData` small. - pub(crate) discriminator: Option>, + pub(crate) discriminator: Option, } pub(crate) struct DiscriminatorData { From 17e6fec8810f0e0f43c3ff613fc34db883b62d3a Mon Sep 17 00:00:00 2001 From: ematipico Date: Wed, 14 Oct 2026 10:37:03 +0100 Subject: [PATCH 2/4] fix CI toolchain --- .github/workflows/rust.yml | 61 ++++++++++++++++++++++++-------------- justfile | 21 +++++++++++++ src/link.rs | 13 ++------ 3 files changed, 62 insertions(+), 33 deletions(-) create mode 100644 justfile diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index cdd0b53c4..96b24ccfc 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -7,6 +7,9 @@ on: - 'Cargo.toml' - 'Cargo.lock' - 'rustfmt.toml' + - 'clippy.toml' + - 'rust-toolchain.toml' + - 'justfile' - 'src/**' - 'tests/**' - 'benches/**' @@ -17,6 +20,9 @@ on: - 'Cargo.toml' - 'Cargo.lock' - 'rustfmt.toml' + - 'clippy.toml' + - 'rust-toolchain.toml' + - 'justfile' - 'src/**' - 'tests/**' - 'benches/**' @@ -41,15 +47,19 @@ jobs: steps: - uses: actions/checkout@v7.0.1 - - name: Install Rust - run: rustup toolchain install stable --profile minimal --component rustfmt && rustup default stable + # Installs the channel pinned in rust-toolchain.toml, and caches + # ~/.cargo/registry and target/debug. + - name: Install toolchain + uses: moonrepo/setup-rust@abb2d32350334249b178c401e5ec5836e0cd88d3 # v1.3.0 + with: + components: rustfmt + bins: just@1.58.0 + cache-base: main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Check formatting - run: cargo fmt --check - - # The wasm guest is its own workspace, so the root `cargo fmt` skips it. - - name: Check formatting of the wasm guest - run: cargo fmt --check --manifest-path tests/wasm/ts/Cargo.toml + run: just fmt clippy: name: Clippy @@ -59,17 +69,19 @@ jobs: steps: - uses: actions/checkout@v7.0.1 - - name: Install Rust - run: rustup toolchain install stable --profile minimal --component clippy && rustup default stable - - - uses: Swatinem/rust-cache@v2.9.2 + # Installs the channel pinned in rust-toolchain.toml, and caches + # ~/.cargo/registry and target/debug. + - name: Install toolchain + uses: moonrepo/setup-rust@abb2d32350334249b178c401e5ec5836e0cd88d3 # v1.3.0 + with: + components: clippy + bins: just@1.58.0 + cache-base: main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Clippy (all features) - run: cargo clippy --locked --all-targets --all-features -- -D warnings - - # The library on its own, as users with `default-features = false` build it. - - name: Clippy (no default features) - run: cargo clippy --locked --all-targets --no-default-features -- -D warnings + - name: Clippy + run: just lint test: name: Test @@ -79,11 +91,16 @@ jobs: steps: - uses: actions/checkout@v7.0.1 - # tests/wasm.rs builds the guest in tests/wasm/ts for wasm32-wasip2. - - name: Install Rust - run: rustup toolchain install stable --profile minimal --target wasm32-wasip2 && rustup default stable - - - uses: Swatinem/rust-cache@v2.9.2 + # Installs the channel pinned in rust-toolchain.toml, and caches + # ~/.cargo/registry and target/debug. tests/wasm.rs builds the guest in + # tests/wasm/ts for wasm32-wasip2. + - name: Install toolchain + uses: moonrepo/setup-rust@abb2d32350334249b178c401e5ec5836e0cd88d3 # v1.3.0 + with: + targets: wasm32-wasip2 + cache-base: main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Test run: cargo test --locked --all-features diff --git a/justfile b/justfile new file mode 100644 index 000000000..98cc64363 --- /dev/null +++ b/justfile @@ -0,0 +1,21 @@ +# Project commands. Run `just --list` to see them all. CI runs these same +# recipes, so a recipe that passes here passes there. + +alias f := fmt +alias l := lint + +# The wasm guest is its own workspace, so the root `cargo fmt` skips it and it +# gets its own run. + +# Check formatting, including the wasm guest +fmt: + cargo fmt --check + cargo fmt --check --manifest-path tests/wasm/ts/Cargo.toml + +# The second run checks the library alone, as users with +# `default-features = false` build it. + +# Run clippy with all features, then with no default features +lint: + cargo clippy --locked --all-targets --all-features -- -D warnings + cargo clippy --locked --all-targets --no-default-features -- -D warnings diff --git a/src/link.rs b/src/link.rs index 17e72a2bd..0255688c2 100644 --- a/src/link.rs +++ b/src/link.rs @@ -1003,12 +1003,7 @@ impl<'d> Builder<'d> { /// Returns the union of `members`, which must be different, with /// `discriminator`, which is `exclusive` if a value is at most one of /// them. - fn push_union( - &mut self, - members: &[TypeId], - discriminator: Option, - exclusive: bool, - ) -> TypeId { + fn push_union(&mut self, members: &[TypeId], discriminator: Option, exclusive: bool) -> TypeId { let members = self.members.extend(members.iter().copied()); self.push(TypeData::Union(UnionData { members, @@ -1821,11 +1816,7 @@ impl<'d> Builder<'d> { /// Returns the discriminator of a union of `members`, each with its /// schema. - fn discriminator( - &mut self, - discriminator: &'d Discriminator, - members: &[(Schema, TypeId)], - ) -> DiscriminatorData { + fn discriminator(&mut self, discriminator: &'d Discriminator, members: &[(Schema, TypeId)]) -> DiscriminatorData { let doc = self.doc; let wire_name = doc.str(discriminator.property_name); let mut name = Name::new(wire_name); From 3151a81ff6de2f2e5613f49b43c72cfec2b60505 Mon Sep 17 00:00:00 2001 From: ematipico Date: Wed, 14 Oct 2026 10:39:36 +0100 Subject: [PATCH 3/4] fix: more errors --- src/json.rs | 2 +- src/plugin/wasm.rs | 2 +- src/types.rs | 2 +- tests/parse/common.rs | 2 +- tests/parse/types/unions.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/json.rs b/src/json.rs index cccd1ab2f..4d27a9cc5 100644 --- a/src/json.rs +++ b/src/json.rs @@ -194,7 +194,7 @@ mod tests { #[test] fn integers_beyond_2_to_the_53_lose_precision() { - assert_eq!(read("9007199254740993"), Json::Number(9007199254740992.0)); + assert_eq!(read("9007199254740993"), Json::Number(9_007_199_254_740_992.0)); } #[test] diff --git a/src/plugin/wasm.rs b/src/plugin/wasm.rs index c099138d5..9223b019c 100644 --- a/src/plugin/wasm.rs +++ b/src/plugin/wasm.rs @@ -605,7 +605,7 @@ impl wit::Host for State { TypeData::Union(union) => wit::TypeInfo::Union(wit::UnionInfo { members: forge.members.get(union.members).iter().map(|id| id.0).collect(), exclusive: union.exclusive, - discriminator: union.discriminator.as_deref().map(|data| wit::DiscriminatorInfo { + discriminator: union.discriminator.as_ref().map(|data| wit::DiscriminatorInfo { wire_name: s(data.wire_name), name: words(&data.name), cases: data.cases.iter().map(|&(value, id)| (s(value), id.0)).collect(), diff --git a/src/types.rs b/src/types.rs index 2aecf1d54..18cadcb00 100644 --- a/src/types.rs +++ b/src/types.rs @@ -603,7 +603,7 @@ pub(crate) struct UnionData { pub(crate) members: Span, /// Whether a value is at most one of the members. pub(crate) exclusive: bool, - /// Boxed, since most unions have none, to keep `TypeData` small. + /// Most unions have none. pub(crate) discriminator: Option, } diff --git a/tests/parse/common.rs b/tests/parse/common.rs index a4645a106..ed8854cd2 100644 --- a/tests/parse/common.rs +++ b/tests/parse/common.rs @@ -80,7 +80,7 @@ pub fn models(forge: &Forge) -> Vec<&str> { .chain(forge.groups().flat_map(|group| group.models())) .map(|model| model.schema_name()) .collect(); - names.sort(); + names.sort_unstable(); names } diff --git a/tests/parse/types/unions.rs b/tests/parse/types/unions.rs index 3aec6f3a8..45f11f81d 100644 --- a/tests/parse/types/unions.rs +++ b/tests/parse/types/unions.rs @@ -389,7 +389,7 @@ fn members_with_only_required_are_dropped() { )); let mut names = field_names(object(response_type(&forge, "a.get"))); - names.sort(); + names.sort_unstable(); assert_eq!(names, ["appeals", "type"]); } From df3837932c75d68aaac8d8770a30a0a13a8c67be Mon Sep 17 00:00:00 2001 From: ematipico Date: Wed, 14 Oct 2026 10:42:09 +0100 Subject: [PATCH 4/4] properly check --- .github/workflows/rust.yml | 2 +- justfile | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 96b24ccfc..9eb2ddc5d 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -59,7 +59,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Check formatting - run: just fmt + run: just fmt-check clippy: name: Clippy diff --git a/justfile b/justfile index 98cc64363..36399325e 100644 --- a/justfile +++ b/justfile @@ -7,8 +7,13 @@ alias l := lint # The wasm guest is its own workspace, so the root `cargo fmt` skips it and it # gets its own run. -# Check formatting, including the wasm guest +# Format the code, including the wasm guest fmt: + cargo fmt + cargo fmt --manifest-path tests/wasm/ts/Cargo.toml + +# Check formatting without changing files, as CI does +fmt-check: cargo fmt --check cargo fmt --check --manifest-path tests/wasm/ts/Cargo.toml