Make all rustc lints part of at least one group

Clippy adds every lint to at least one group which makes it easy to blanket allow/deny/expect/forbid/warn lints and override lints as you see fit. I realize this can be problematic, so Clippy has the warn-by-default blanket_clippy_restriction_lints lint.

Could rustc do something similar? As of rustc 1.93.0, there are are 37 allow-by-default lints and 27 deny-by-default lints that are not part of any group per the output of rustc -Whelp. I finally got tired of manually updating my [lints.rust] table in Cargo.toml when I upgrade rustc, that I hacked up a program that does it for me. This has a few problems:

  1. Unless I publish to crates.io, it doesn't help others.
  2. One has to robustly or conservatively parse the output of rustc -Whelp which has problems: the former is more susceptible to false positives and negatives while the latter (which I chose) is more susceptible to breakage seeing how rustc -Whelp doesn't have some stabilized format.
  3. Even if correct, every update to rustc will cause [lints.rust] to be possibly out-of-date until the program is run again. While this would be true even if every lint were added to at least one group, new/removed groups would obviously be less frequent.

rustc could add a similar warn-by-default lint as blanket_clippy_restriction_lints that fires when one denys/forbids a group of lints one likely shouldn't in addition to a warn-by-default lint that fires when one allows/expects a group of lints that likely should retain their deny-by-default status.

Should be noted that there is already overlap among groups including some groups that are a subset of the union of other groups (e.g., keyword-idents is a proper subset of the union of rust-2018-compatibility and rust-2024-compatibility), so I think even an allowed and denied group would be a quick and dirty solution—unstable lints (e.g., fuzzy-provenance-casts) should probably have their own unstable group though.

3 Likes

What do you see as the benefit of putting lints in at least one group? It sounds like you want to make sure your [lints.rust] affects all lints, but why do you want to do that?

2 Likes

The same reason Clippy has added all lints to at least one group. It's not a coincidence that there aren't any Clippy lints that are groupless. It is not ideal that one must read about any new lints that are added to enjoy their benefit. By using groups, one can benefit from a lint they didn't know existed. For example, trivial-casts is something I likely wouldn't have known about if it weren't for my "obsession" of staying on top of the lints; and it has found cases where I could remove such casts. As stated above, I realize that grouping all lints doesn't prevent the possibility of someone not benefiting from a new lint they didn't know about due to a new group; but mathematically new groups would be less likely.

What benefit is there to having lints that are not grouped? If you want to maintain the status quo, then just don't use these new groups.

3 Likes

One benefit is that it stops people from enabling new lints that are intentionally allow-by-default because they don't work well enough yet just because they decided that they wanted to turn everything on.

warn(clippy::pedantic) is, IMHO, an anti-pattern but that doesn't stop people from thinking it's a good idea.


To me, the right thing is having some new lint groups, like the one that's been talked about a bunch to add a "I'm a high-usage library who wants to be extra careful about semver so want nitpicks about things that don't matter to most code" group.

But I see no reason to say that everything has to be in a group. Especially given rustc's much higher bar for lints in general: if something is warn-by-default, why would it need to be in another group too?

3 Likes

Wouldn't that be covered by the blanket_clippy_restriction_lints analog?

deny(clippy::pedantic) has saved me, and I accept the consequences of keeping my libraries up-to-date when a new lint is added I don't want. Similarly, for binaries I go even further than the typical justification of not denying groups of lints by allowing everything including the deny-by-default lints before publishing to crates.io since it's expected for end users to install such crates, and I don't want such builds to fail or even warn them unlike library-only crates which only affects developers since cargo downgrades deny to warn for dependencies.

It doesn't since such a lint is already in a group: warnings. At most you seem to be against an allowed group, but why isn't there a denied group? I'm guessing it's because you're worried people will similarly "abuse" such a group by allowing/expecting it when they should keep it enabled, but again that's where a blanket_clippy_restriction_lints analog would help.

What default-deny lints do you want at a different level? Have you filed issues about that?

I don't think allow(denied) is something that anyone should ever do.

3 Likes

I already answered that question. When I'm developing code, I deny everything and individually allow/expect lints I don't want to use since I don't subscribe to the "anti-pattern" belief you do. I've found bugs that I wouldn't have if I didn't globally deny even problematic lint groups. For library-only packages this never changes. For packages with at least one binary crate though, I allow everything right before publishing to crates.io for the same reason people object to denying too many lints (e.g., clippy::nursery and clippy::restriction): I don't want builds to suddenly start breaking or annoying end users with warnings. I would never globally allow all deny-by-default lints during development.

I'll also add that unlike the dozen or so Clippy lints that I allow after denying the groups they are in, I have yet to allow any of theallow-by-default lints that are not in a group illustrating to me that globally denying such lints isn't nearly as annoying or problematic as some of the Clippy lints. I can have a higher tolerance for things though, so there certainly would be developers that would re-allow some of the lints (e.g., unused-results).

Can't you mark those lints as warn rather than deny and then use -Dwarnings in CI? Cargo already hides all warnings for non-local dependencies.

2 Likes

That doesn't solve the problem though since I'd still have to maintain the curated list of lints without groups. The problem is not about what to do when a lint is triggered (e.g., deny), it's about applying a specific action (e.g., warn) to all lints. If every lint were part of at least one group, the problem is reduced to simply specifying the lint groups and their corresponding action and priority. While yes rustc could always add/remove lint groups, that would statistically be less frequent than individual lints.

Excluding renamed lints, there are 63 allowed-by-default lints in rustc. Of these 23 are exclusively for edition migrations and should not be enabled outside of it (or would never trigger if you have already upgraded to the respective edition), 9 are only usable or relevant on nightly (unstable lints and the unstable_features lint), leaving just 31 lints you may want to enable at all. A bunch of these are allowed-by-default because they have known false-positives and a bunch are restrictions that only make sense to enable individually on a project-by-project basis. For completeness the list of 31 lints I am referring to is:

ambiguous_negative_literals
closure_returning_async_block
deref_into_dyn_supertrait
elided_lifetimes_in_paths
explicit_outlives_requirements
ffi_unwind_calls
let_underscore_drop
linker_messages
macro_use_extern_crate
meta_variable_misuse
missing_copy_implementations
missing_debug_implementations
missing_docs
non_ascii_idents
redundant_imports
redundant_lifetimes
single_use_lifetimes
trivial_casts
trivial_numeric_casts
unit_bindings
unnameable_types
unreachable_pub
unsafe_code
unused_crate_dependencies
unused_extern_crates
unused_import_braces
unused_lifetimes
unused_macro_rules
unused_qualifications
unused_results
variant_size_differences
3 Likes

That doesn't change my stance. I accept false positives and negatives; and as stated, I have yet to allow any of them on a global level because I'm not affected by such a thing. If this doesn't happen, then so be it and I'll continue to use the program I wrote that generates them all.

Is that rustc 1.93.0? If so, then rustc -Whelp is missing 3 of them that would be nice for it to output. The 60 lints that are reported from rustc -Whelp as allow-by-default are the following:

  1. absolute-paths-not-starting-with-crate
  2. ambiguous-negative-literals
  3. closure-returning-async-block
  4. deprecated-in-future
  5. deprecated-safe-2024
  6. deref-into-dyn-supertrait
  7. edition-2024-expr-fragment-specifier
  8. elided-lifetimes-in-paths
  9. explicit-outlives-requirements
  10. ffi-unwind-calls
  11. fuzzy-provenance-casts*
  12. if-let-rescope
  13. impl-trait-overcaptures
  14. impl-trait-redundant-captures
  15. keyword-idents-2018
  16. keyword-idents-2024
  17. let-underscore-drop
  18. linker-messages
  19. lossy-provenance-casts*
  20. macro-use-extern-crate
  21. meta-variable-misuse
  22. missing-copy-implementations
  23. missing-debug-implementations
  24. missing-docs
  25. missing-unsafe-on-extern
  26. multiple-supertrait-upcastable*
  27. must-not-suspend*
  28. non-ascii-idents
  29. non-exhaustive-omitted-patterns*
  30. redundant-imports
  31. redundant-lifetimes
  32. resolving-to-items-shadowing-supertrait-items*
  33. rust-2021-incompatible-closure-captures
  34. rust-2021-incompatible-or-patterns
  35. rust-2021-prefixes-incompatible-syntax
  36. rust-2021-prelude-collisions
  37. rust-2024-guarded-string-incompatible-syntax
  38. rust-2024-incompatible-pat
  39. rust-2024-prelude-collisions
  40. shadowing-supertrait-items*
  41. single-use-lifetimes
  42. tail-expr-drop-order
  43. trivial-casts
  44. trivial-numeric-casts
  45. unit-bindings
  46. unnameable-types
  47. unqualified-local-imports*
  48. unreachable-pub
  49. unsafe-attr-outside-unsafe
  50. unsafe-code
  51. unsafe-op-in-unsafe-fn
  52. unstable-features
  53. unused-crate-dependencies
  54. unused-extern-crates
  55. unused-import-braces
  56. unused-lifetimes
  57. unused-macro-rules
  58. unused-qualifications
  59. unused-results
  60. variant-size-differences

What are the 3 other lints you are finding, and why isn't rustc -Whelp outputting them? If you wouldn't mind at least reporting the 63 lints you found, I can figure out which ones are missing myself.

The following 37 of those 60 are not part of a group:

  1. ambiguous-negative-literals
  2. closure-returning-async-block
  3. deprecated-in-future
  4. deref-into-dyn-supertrait
  5. ffi-unwind-calls
  6. fuzzy-provenance-casts*
  7. impl-trait-redundant-captures
  8. linker-messages
  9. lossy-provenance-casts*
  10. macro-use-extern-crate
  11. meta-variable-misuse
  12. missing-copy-implementations
  13. missing-debug-implementations
  14. missing-docs
  15. multiple-supertrait-upcastable*
  16. must-not-suspend*
  17. non-ascii-idents
  18. non-exhaustive-omitted-patterns*
  19. redundant-imports
  20. redundant-lifetimes
  21. resolving-to-items-shadowing-supertrait-items*
  22. shadowing-supertrait-items*
  23. single-use-lifetimes
  24. trivial-casts
  25. trivial-numeric-casts
  26. unit-bindings
  27. unnameable-types
  28. unqualified-local-imports
  29. unreachable-pub
  30. unsafe-code
  31. unstable-features
  32. unused-crate-dependencies
  33. unused-import-braces*
  34. unused-lifetimes
  35. unused-qualifications
  36. unused-results
  37. variant-size-differences

* : unstable lint

I used the list at Allowed-by-default Lints - The rustc book There seems to be some divergence. For example it lists async-idents which you don't have, while you have deprecated-in-future, which is not listed there.

Ah, yeah I stopped using that list since I think rustc -Whelp is more accurate and I wanted something I could use programmatically to overcome what I consider a limitation. For what it's worth, async-idents has been renamed to keyword-idents-2018.

If you don't mind my asking, why wouldn't you publish said tool (assuming it already exists, which my understanding is that it does)? I follow a similar practice of enabling quite a lot of lints and allow-ing them when I decide they are not appropriate, and it would be nice to have a tool making it easier to know when new lints are available (or when they get renamed, which recently caused some confusion for me).

On another note, when a lint is added/changed, are you updating all of your projects manually? If so, if they're similar enough to benefit from roughly the same lints, you could consider using a common base for your projects. You can update the lints there, and then you just need to rebase (I'm assuming you are using git, but I'd think you could achieve something similar with any version control system... which I am also assuming you are using).

I write a lot of stuff just for me, and I feel it'd be "inappropriate" for me to release it to others if I haven't taken the proper amount of time and care. I recently just hacked something up not really thinking about "others".

I went ahead and took some time to make it "good enough" that others may find it useful, so here you go.

The output of rustc -Whelp is pretty basic, and you can likely just rely on your favorite regex app (e.g., ripgrep) to parse the lints out for you. You'll still have to rely on systemd.timer(5), cron(8), or whatever other tool you want to schedule some automatic process to execute the necessary command(s). Currently I run rustup update and lints everyday; and in the event there is a difference between the output of lints and the current file I have, I get an e-mail sent notifying me that I need to update things.

Currently I do update everything manually after being notified by cron(8), but I may change that.

1 Like

That makes sense. Thank you for working on this and sharing it!