Skip to content

Don't run late lints that are disabled in the entire crate #106983

Closed
@Manishearth

Description

@Manishearth
Member

Related: #59024

Rustc comes with a respectable number of lints, but clippy comes with a lot. Furthermore, Clippy has the concept of a "restriction" lint: a lint that is probably not useful to most users (and is not itself a judgement on "good rust style") but will be useful for codebases operating under certain constraints (for example, the lints that forbid unwrap/expect/panic/indexing).

The current linting infrastructure runs all lints at all times, only using lint levels to determine if a lint must be emitted. This basically means that most codebases running clippy are spending a lot of time running the code for lints they never see. This is true for rustc as well, though it has fewer Allow lints by default.

I proposed #59024 in the past so that lint code only gets run when the lint is enabled. There are some tricky architecture constraints around hotswapping the lint list whilst traversing the AST.

However, I don't think there's anything stopping us from doing this at a global level: it should not be hard or expensive to collect the list of enabled lints in the crate by the time late lint passes run, just collect all non-allow() lint attributes and combine them with the defaults, removing any that were manually disabled at the crate level.

Thoughts? Clippy is not super slow and this isn't a bottleneck, but as we add lints this becomes more of a problem, and it also prevents us from adding potentially computationally expensive restriction lints (one of the best things about restriction lints is that we can be more flexible around them!)

cc @rust-lang/clippy

Activity

Mark-Simulacrum

Mark-Simulacrum commented on Jan 17, 2023

@Mark-Simulacrum
Member
added
A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.
T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
I-slowIssue: Problems and improvements with respect to performance of generated code.
on Jan 17, 2023
flip1995

flip1995 commented on Jan 19, 2023

@flip1995
Member

I think the biggest challenge that we need to solve is that Lint<>LintPass is (or can be) a many-to-many relationship. One Lint can be emitted from multiple passes and one LintPass can emit multiple lints. AFAIK we don't do the former in Clippy, but we do a lot of the latter.

I think introducing a policy that allow-by-default lints must be in their own pass is not feasible and would worsen the dev experience of Clippy.

We do however have a list of all lints a lint pass can emit. So I think the best first step (and maybe this is already enough) would be to check if any of those lints are >=warn-by-default at some point and then run lint passes depending on that.

xFrednet

xFrednet commented on Jan 19, 2023

@xFrednet
Member

While working on #[expect] I noticed that a few rustc lints, like non_ascii_idents, already implement this behavior. (Playground). I like the idea, but found it confusing, that #[warn/deny/forbid()] attributes didn't work on items/statements/expressions. If this behavior is implemented, there should be some kind of warning, if a user tries to enable such a lint on non-crate levels. Maybe with a new lint, that clearly explains that this lint first has to be enabled on a crate level.

I think introducing a policy that allow-by-default lints must be in their own pass is not feasible and would worsen the dev experience of Clippy.

Agreed! What we could do, on the implementation level, is to first check if the lint is enabled, before we do most of the individual linting logic. Basically, how is_lint_allowed is used in rare cases right now. This might be annoying if we do it for all lints, but for restriction lints this could definitely make sense.

Manishearth

Manishearth commented on Jan 19, 2023

@Manishearth
MemberAuthor

@flip1995 This is an optimization and doesn't have to be perfect, we can exclude passes which are all-allowed, as you said.

I think a policy to try and put allow-by-default lints into their own passes unless they are related is fine and is already roughly what Clippy does.

I like the idea, but found it confusing, that #[warn/deny/forbid()] attributes didn't work on items/statements/expressions. If this behavior is implemented, there should be some kind of warning, if a user tries to enable such a lint on non-crate levels. Maybe with a new lint, that clearly explains that this lint first has to be enabled on a crate level.

You've misunderstood the proposal, when I say "disabled in the entire crate", I mean "disabled in the entire crate and never enabled ever". This is for lints whose name is never mentioned in an allow attribute at all.

Agreed! What we could do, on the implementation level, is to first check if the lint is enabled, before we do most of the individual linting logic. Basically, how is_lint_allowed is used in rare cases right now. This might be annoying if we do it for all lints, but for restriction lints this could definitely make sense.

Nah, because that breaks the ability to enable the lint at the item level.

I was thinking that we could have rustc mutate the lint pass vector (or create a new one) based on this. Everything else works the same.

RalfJung

RalfJung commented on Sep 23, 2023

@RalfJung
Member

This has to be done very carefully -- while impl_lint_pass takes a list of lints that is somehow associated with the pass, it is currently entirely undocumented what that list is for, so we cannot expect that this list means "this pass will only emit the following lints (and can be skipped if those lints are all allowed)".

Also, it's not currently possible to have the same lint in that list for multiple passes (that will ICE saying something about a lint being registered twice), so for lints that are emitted from multiple different places, this list will necessarily be incomplete.

(I'm entirely talking about rustc lints here, I don't know much about Clippy.)

While working on #[expect] I noticed that a few rustc lints, like non_ascii_idents, already implement this behavior. (Playground). I like the idea, but found it confusing, that #[warn/deny/forbid()] attributes didn't work on items/statements/expressions.

That sounds like a bug. If you have an example where a lint cannot be enabled with function-level warn, please file an issue.

Manishearth

Manishearth commented on Sep 23, 2023

@Manishearth
MemberAuthor

so we cannot expect that this list means "this pass will only emit the following lints (and can be skipped if those lints are all allowed)".

I would be very surprised if any rustc lint violated this. Clippy, a project with way more lints certainly doesn't.

so for lints that are emitted from multiple different places, this list will necessarily be incomplete.

yeah but those are builtins and emitted outside of lint passes entirely. Worst that can happen there is the optimization doesn't skip some lints.

21 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.I-slowIssue: Problems and improvements with respect to performance of generated code.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

    Development

    Participants

    @RalfJung@estebank@Manishearth@Alexendoo@Mark-Simulacrum

    Issue actions

      Don't run late lints that are disabled in the entire crate · Issue #106983 · rust-lang/rust