Skip to content

Activate more lints #3394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft

Conversation

theodorebje
Copy link
Contributor

@theodorebje theodorebje commented Jul 2, 2025

Motivation

Draft PR.
Earlier discussion: #3393

The purpose of this PR is to activate and fix more lints for the codebase. This will improve codebase readability and maintainability. The ultimate goal is to have as many lints from the pedantic and nursery lint groups as possible. For now, only a subset of lints are enabled.

Discussions welcome.

Solution

New lints:

I will update this list once I activate more lints.

Sub-PRs

Theodore Bjernhed added 8 commits July 2, 2025 14:16
Add `#[must_use]` attribute to functions where the attribute could be
added. This fixes the `must_use_candidate` clippy lint.
In `impl`s, `Self` can be used instead of the struct/enum being
implemented, to reduce unnecessary repetition. This commit uses `Self`
as often as possible. This fixes the `use_self` clippy lint.
The `request_parts` and `request` modules were marked as `pub(crate)`,
which is misleading since the public parts are re-exported by `lib.rs`
anyway.

Attempting to mark them as `pub`, like suggested by the
`redundant_pub_crate` clippy lint causes another Rust-native lint to
appear: `unreachable-pub`. To avoid both of these lints, it's best to
simply mark the whole module as private, and re-export the public
traits separately. Since these public traits are re-exported by `lib.rs`
anyway, this does not affect the public API.
The `else` block adds unnecessary indentation and verbosity. To avoid
this, we can simply move `return None` statement outside the `if`
statement. This fixes the `redundant_else` clippy lint.
Semicolons at the end of if statements evaluating to `()` are
unnecessary and may be removed to avoid confusion and visual clutter.
This fixes the `unnecessary_semicolon` clippy lint.
`let...else` provides a standard construct for this same pattern that
people can easily recognize (and is IMO much less confusing). It's also
more compact. This fixes the `manual_let_else` clippy lint.
`if let Some(v) = ... { y } else { x }` and
`match .. { Some(v) => y, None/_ => x }` are more idiomatically done
with the `Option::map_or*` methods. Using the dedicated methods results
in both clearer and more concise code than using an `if let` expression.
This fixes the `option_if_let_else` clippy lint.

NOTE: Clippy's suggested method for `try_downcast` couldn't compile
properly, so I rewrote the `if let` statement slightly using
`Option::take`.
Some functions and methods aren't marked as `const`, even though they
could be. Not marking them as `const` prevents callers of the
function/method from being `const` as well. This fixes the
`missing_const_for_fn` clippy lint. I also marked some methods and
functions as `const` that clippy didn't find.

NOTE: Removing the `const` marker again from public functions/methods in
the future will break API compatibility.
NOTE-2: I marked some of the `status` methods as const. I don't know how
axum's internals work very well, so it's possible that I may have broken
something, but all tests are passing sooo...
@theodorebje theodorebje changed the title Clippy pedantic Activate more lints Jul 2, 2025
Theodore Bjernhed added 2 commits July 2, 2025 16:15
Seems to not have been found by clippy on my machine. This fixes the
`manual_let_else` clippy lint.

See-also: ddf5830
Forgot to run `cargo-fmt` during a pervious commit.

See-also: d8b6ef2
@theodorebje

This comment was marked as outdated.

Theodore Bjernhed added 5 commits July 2, 2025 16:46
`if !..else` is a confusing syntax, as it does the inverse of what the
rest of the lines says. By reordering the `if` statement we can make the
`if` statement more clear. This fixes the `if_not_else` clippy lint.
The `.map(_).unwrap_or*(_)` methods are inconcise and hard-to-read. This
commit replaces them with the dedicated  `map_or*` methods instead. This
fixes the `map_unwrap_or` clippy lint.

NOTE: This makes it harder to search through the entire codebase for
`unwrap`s.
Although these semicolons are optional, they aren't optional when
extending the block with new code. To avoid having to change the
previous last lines when adding more code to these functions, we should
add the optional semicolon. This fixes the
`semicolon_if_nothing_returned` clippy lint.

NOTE: I `#[allow]`ed the `forever` function. Adding a semicolon at the
end of `forever` causes a type inference error (E0282).
`assert!` is much more concise and readable than manually triggering a
panic via an `if` statement. Once I did this, clippy also found a couple
of conditions that could be simplified. I applied those simplifications.
This fixes the `manual_assert` clippy lint.
Using `()` makes it clearer to both the reader and the compiler that the
pattern contains no data. It will also trigger a compilation error if
the type changes, which makes the pattern-matching more robust, safe,
and less prone to uncaught logic errors. This fixes the
`ignored_unit_patterns` clippy lint.
@theodorebje

This comment was marked as resolved.

Wildcard imports can pollute the namespace, and can lead to confusing
error messages or even unexpected behaviour. This commit uses explicit
imports instead. Although slightly more verbose, it can save a lot of
debugging efforts in the future.

Exceptions to this rule are prelude imports, imports of `pub use`
`re-exports`, and `super::*` imports in tests.

This fixes the `wildcard_imports` clippy lint.
@mladedav
Copy link
Collaborator

mladedav commented Jul 2, 2025

Only None. Backticks are for code. The lint description also mentions that.

Methods returning `Self` often create new instances of a type without
causing any side-effects (such as interacting with other types). Unlike
for ede4d85, it is almost guaranteed
that the user intended to use the newly created value, and simply just
forgot. This fixes the `return_self_not_must_use` clippy lint.

See-also: ede4d85
@theodorebje
Copy link
Contributor Author

theodorebje commented Jul 2, 2025

Updated list of non-activated lints:

43 redundant_pub_crate
28 needless_pass_by_value
24 missing_errors_doc
16 similar_names
12 missing_panics_doc
09 redundant_closure_for_method_calls
08 default_trait_access
07 doc_markdown
07 doc_link_with_quotes
06 trivially_copy_pass_by_ref
05 too_many_lines
05 ref_option
04 used_underscore_binding
04 match_same_arms
03 no_effect_underscore_binding
03 missing_fields_in_debug
03 items_after_statements
03 cast_possible_truncation
02 single_match_else
02 redundant_clone
02 mismatching_type_param_order
02 implicit_clone
02 format_push_string
02 explicit_iter_loop
02 equatable_if_let
01 unused_peekable
01 unused_async
01 needless_pass_by_ref_mut
01 future_not_send
01 derive_partial_eq_without_eq
01 debug_assert_with_mut_call

Theodore Bjernhed added 2 commits July 2, 2025 18:27
- Add backticks to the documentation for `Utf8Bytes`
- Wrap the example of a `range` in a code block
- [Exclude `WebSocket`](tokio-rs#3394 (comment))
  from being seen as an identifier by clippy.

This fixes the `doc_markdown` clippy lint.
f978792 caused a compilation warning to
appear when compiling without the `http2` feature flag. This commit
fixes that by only importing `InvalidProtocolPseudoheader` when the
`http2` feature is enabled.
Comment on lines 538 to +540
});
} else {
return None;
}
return None;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also change the whole if-else to something like

return (types_that_consume_the_request[0].0 != number_of_inputs - 1).then(|| {
    // construct the compile_error
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't that cause the if statement at the bottom of the function to become dead code? Should I refactor how the construction of the compile_error is done?

if types_that_consume_the_request.len() == 2 { /* compile error */ } else { /* compile error */ }

Taken from here.

@jplatte
Copy link
Member

jplatte commented Jul 2, 2025

This PR is pretty large. Can you maybe split it up? Doesn't have to be one PR for every lint, but ones that lead to lots of changes like the must_use one seem worth splitting.

Copy link
Collaborator

@mladedav mladedav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stylistic opinions I wrote are just mine and other people may view things differently, but I kind of don't agree with some of the lints. That said if other people prefer e.g. Self over typenames in all permissible contexts, I can live with that (as long as clippy reminds me to use that).

In any case, thank you for trying to improve the codebase.

Comment on lines +19 to 20
#[must_use]
pub fn into_inner(self) -> BoxError {
Copy link
Collaborator

@mladedav mladedav Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For errors (and possibly other cases such as builders) we might want to add #[must_use] to the type rather than methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to avoid #[must_use] for the methods here, since BoxError is a type alias and not a "real" type. Adding #[must_use] to the Error struct doesn't do anything, either. Any ideas?

Comment on lines +56 to +71
|| {
let check_inputs_impls_from_request =
check_inputs_impls_from_request(&item_fn, state_ty, kind);

quote! {
#check_inputs_impls_from_request
#check_future_send
}
},
|check_input_order| {
quote! {
#check_input_order
#check_future_send
}
},
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm honestly not a fan of this either. This is nice when it's just one-liners (or at least the default branch is a one-liner), but these bigger blocks are just more indented versions of the original, effectively with else branch first and after that the if branch and with worse readability as to where does it end and that it is an if...else (instead of keyword just a block ends and then another closure is provided).

!path.is_empty(),
"Paths must start with a `/`. Use \"/\" for root routes"
);
assert!(path.as_bytes()[0] == b'/', "Paths must start with /");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use assert_eq for this. There is another case like this in this file.

I think there might also be a lint for this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should, in this case. We don't really want to be printing the first byte of the path in case the assertion fails, we just want the message.

self.content_size = Some(len);
self
}

/// Return a range response.
///
/// ```rust
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to add no_run here so that it's not considered as a doctest.

But regardless, it's not valid rust either, so I'd probably mark it as such (maybe text instead of rust? Not sure).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What the heck even is this line?

@@ -54,7 +54,7 @@ macro_rules! __define_rejection {
}

/// Get the status code used for this rejection.
pub fn status(&self) -> http::StatusCode {
pub const fn status(&self) -> http::StatusCode {
Copy link
Collaborator

@mladedav mladedav Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think most of these really make sense. I don't think anyone would build a rejection in a const context to then call this.

I did not go through all of this commit though, there might be some useful ones here, but this lint might also be one to split out of this PR.

@mladedav
Copy link
Collaborator

mladedav commented Jul 2, 2025

From the list, one thing I think we really want is the future_not_send lint. It's just horrible to suddenly find out I can't use something because I'm in a future to be tokio::spawned so I'd rather prevent that.

@theodorebje
Copy link
Contributor Author

Thanks for the constructive feedback, everyone! I'll look through it all tomorrow.

@theodorebje
Copy link
Contributor Author

From the list, one thing I think we really want is the future_not_send lint.
@mladedav The problematic line for that one seems to be:

pub async fn from_path(path: impl AsRef<Path>) -> io::Result<FileStream<ReaderStream<File>>> {}

(here). AsRef doesn't implement Send.

I don't have that deep knowledge about the codebase yet, so before I edit that line to fix the lint warning, what do you think would be the best type to take?

@mladedav
Copy link
Collaborator

mladedav commented Jul 3, 2025

You can just change it to path: impl AsRef<Path> + Sync.

I wouldn't expect anyone to use anything that isn't Sync there so I wouldn't expect to break someone with this, but it is a breaking change regardless. The good news is that since people wouldn't use !Sync types as parameters here, it's not really a problem the method can theoretically be !Send.

So if you have more breaking changes in a separate PR, you can put that there.

@theodorebje
Copy link
Contributor Author

Thanks! Since this PR already includes breaking-ish changes, and I’m trying to group the (big) commits by function, I think I'll include this one-line fix in the main draft PR instead, if that's OK for you?

@mladedav
Copy link
Collaborator

mladedav commented Jul 3, 2025

Sure, feel free to put it here.

Now that I look at it closer, I think it should also include + Send but clippy doesn't seem to mind when that's missing.

@theodorebje
Copy link
Contributor Author

Regarding redundant_pub_crate, it seems to be a bunch of false positives by Clippy: rust-lang/rust-clippy#5369.

@jplatte
Copy link
Member

jplatte commented Jul 4, 2025

option_if_let_else, map_unwrap_or: these rewrite code to use map_or / map_or_else. Unlike David, I don't just dislike these methods when one or both of the arguments is a big closure. I dislike these methods in general. I'd much rather go the opposite direction and disallow both 😄

missing_const_for_fn: let's add const where people have an actual use case for it, not in every place possible up-front

ignored_unit_patterns: just looks weird and I don't think there's a big advantage

@theodorebje
Copy link
Contributor Author

@jplatte Do you also dislike manual_let_else (ddf5830) or only the ones you mentioned above?

@jplatte
Copy link
Member

jplatte commented Jul 5, 2025

Only the ones mentioned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants