Skip to content
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

rustc: Rearchitect lints to be emitted more eagerly #43522

Merged
merged 1 commit into from
Aug 10, 2017

Conversation

alexcrichton
Copy link
Member

@alexcrichton alexcrichton commented Jul 28, 2017

In preparation for incremental compilation this commit refactors the lint
handling infrastructure in the compiler to be more "eager" and overall more
incremental-friendly. Many passes of the compiler can emit lints at various
points but before this commit all lints were buffered in a table to be emitted
at the very end of compilation. This commit changes these lints to be emitted
immediately during compilation using pre-calculated lint level-related data
structures.

Linting today is split into two phases, one set of "early" lints run on the
syntax::ast and a "late" set of lints run on the HIR. This commit moves the
"early" lints to running as late as possible in compilation, just before HIR
lowering. This notably means that we're catching resolve-related lints just
before HIR lowering. The early linting remains a pass very similar to how it was
before, maintaining context of the current lint level as it walks the tree.

Post-HIR, however, linting is structured as a method on the TyCtxt which
transitively executes a query to calculate lint levels. Each request to lint on
a TyCtxt will query the entire crate's 'lint level data structure' and then go
from there about whether the lint should be emitted or not.

The query depends on the entire HIR crate but should be very quick to calculate
(just a quick walk of the HIR) and the red-green system should notice that the
lint level data structure rarely changes, and should hopefully preserve
incrementality.

Overall this resulted in a pretty big change to the test suite now that lints
are emitted much earlier in compilation (on-demand vs only at the end). This in
turn necessitated the addition of many #![allow(warnings)] directives
throughout the compile-fail test suite and a number of updates to the UI test
suite.

Closes #42511

@rust-highfive
Copy link
Collaborator

r? @pnkfelix

(rust_highfive has picked a reviewer for you, use r? to override)

@alexcrichton
Copy link
Member Author

r? @michaelwoerister

@petrochenkov
Copy link
Contributor

Can compiletest set allow(unused) for all tests by default through command line?
Then tests checking lints from the unused group specifically could override the default in source.

@@ -9,16 +9,18 @@
// except according to those terms.
//

#![deny(overflowing_literals)]
#![deny(const_err)]
#![warn(overflowing_literals)]
Copy link
Contributor

Choose a reason for hiding this comment

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

I see a few tests changed from deny(lint) to warn(lint), did they start failing with deny?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think what happened is that errors were emitted earlier in compilation, and then presumably something had abort_if_errors which aborted compilation before the next lint location was reached. Before this PR all those errors were buffered until the end, but now that they're emitted on-demand they presumably cause compilation to abort halfway through (or somethign like that)

19 | #[allow(unused, unused_variables, bad_style)]
| ^^^^^^^^^ overruled by previous forbid

error: aborting due to 3 previous errors
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks like a regression - the "overruled by outer forbid" is reported only for one lint. (But this may be intended?)

Copy link
Member Author

Choose a reason for hiding this comment

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

This is currently intentioanl and I felt it wasn't really a "big enough" regression to investigate a fix for.

@@ -804,6 +799,10 @@ pub fn phase_2_configure_and_expand<F>(sess: &Session,
})
})?;

time(time_passes,
"early lint checks",
|| lint::check_ast_crate(sess, &krate));
Copy link
Contributor

Choose a reason for hiding this comment

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

So, with this PR lints can no longer be reported during lowering to HIR, right?
It's too late for "early lints" and too early for "late lints", but previously they could be reported as "late".
What if I have a lint that should work this way?

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct, yes. Presumably we could just empty the "lint buffer" at the end though if need be instead of at the end of the early lint passes.

@alexcrichton
Copy link
Member Author

Can compiletest set allow(unused) for all tests by default through command line?

It could! I ended up just taking the alternate strategy of tagging tests.

@shepmaster shepmaster added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jul 28, 2017
@eddyb
Copy link
Member

eddyb commented Jul 28, 2017

It could! I ended up just taking the alternate strategy of tagging tests.

Hah! It'd be nice to have avoided this in the first place (by writing the code to not produce warnings). I think I hit something similar a while back. cc @nikomatsakis

@petrochenkov
Copy link
Contributor

It'd be nice to have avoided this in the first place (by writing the code to not produce warnings).

It's hard to not hit any of unused / dead code warnings when writing a compile-fail test.
I'd still prefer allow(unused) to boilerplate code "using" everything defined in the test and distracting from the test's primary purpose.

Not producing other kinds of warnings in tests may be quite reasonable though. This PR for example adds allow(safe_extern_statics) which certainly shouldn't happen.

@alexcrichton
Copy link
Member Author

So... start over and add -A unused to compile-fail tests? I don't really want to oscillate on this, it was hard enough to do once.

Not producing other kinds of warnings in tests may be quite reasonable though. This PR for example adds allow(safe_extern_statics) which certainly shouldn't happen.

Can you elaborate on this? The safe_extern_statics lint was needed to get the tests compiling again. Presumably we can delete the tests once it's a hard error.

pub fn lint_level_at_node(self, lint: &'static Lint, mut id: NodeId)
-> (lint::Level, lint::LintSource)
{
let sets = self.lint_levels(LOCAL_CRATE);
Copy link
Contributor

Choose a reason for hiding this comment

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

This creates an edge to the crate-wide lint_levels and will force a recompilation of everything with a lint assigned too it whenever the crate-wide lint_levels changes, even with red-green. If e.g. you have a ton of functions that are allow(everything) and have an allowed lint, you'll have to recompile them every time you add an item.

Until we get red-green this is hard to improve, but if you made LintLevelMap a query that would work. I suppose you should add a // FIXME: red-green or something.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah yeah when discussing with @nikomatsakis it sounded like red/green would solve this problem because while the lint levels map would always be calculated it would rarely change (in theory) and should also be quick to reconstruct.

Copy link
Contributor

Choose a reason for hiding this comment

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

The lint level map by itself would be recalculated every time you add an item (at least how I read it), even with red/green..

Copy link
Member

Choose a reason for hiding this comment

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

Indeed. This is where I have only one suggestion: make the lint computation bottom-up instead of top-down, i.e. the query gets its parent node's lint levels, then appends the ones from its own attributes and returns. That should have really good tracking.

Copy link
Member

Choose a reason for hiding this comment

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

Then again, if the map is only for items, then I actually wanted to have this done during HIR lowering anyway so the cost might be hidden there shrug.

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 this is an urgent problem: Yes the map will be recomputed every time but if it has not actually changed, red/green will detect that via it's unchanged ICH (= the maps stable hash). I don't think we should make this more complicated until profiling shows it to be a problem.

@michaelwoerister
Copy link
Member

michaelwoerister commented Jul 31, 2017

Thanks for the huge PR, @alexcrichton :)

Some comments:

  • I'm a bit skeptical too about all those new unused XXX warnings, especially in the UI tests. In most cases they seem to distract from the actual purpose of the test. I'd be fine with either allowing unused stuff in compile-fail by default or adding the respective attributes to the specific tests.
  • Although I'm okay with having one global LintLevels map and rebuilding it during every compilation, I also think it is problematic that we have a dependency on DepNode::Krate now. The failing tests on travis seem to indicate that there are cases where this dependency is introduced also in non-error cases and if so then I expect this to happen in every non-trivial project, which would essentially break incr. comp. until red/green arrives. There are at least three ways of fixing this:
    • Block this PR on red/green. This is very undesirable because of bit-rot.
    • Make the map available only through a function/method (e.g. of the HIR map) that will manually register DepNode::Hir dependencies along the path from the queried node to the HIR root. This is additional implementation work and will be obsolete when red/green arrives, so not ideal either.
    • Wrap building the map in dep_graph.with_ignore() until red/green arrives. We don't cache any warnings or error messages yet, so this should be harmless, right @nikomatsakis?

@shepmaster shepmaster added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 4, 2017
In preparation for incremental compilation this commit refactors the lint
handling infrastructure in the compiler to be more "eager" and overall more
incremental-friendly. Many passes of the compiler can emit lints at various
points but before this commit all lints were buffered in a table to be emitted
at the very end of compilation. This commit changes these lints to be emitted
immediately during compilation using pre-calculated lint level-related data
structures.

Linting today is split into two phases, one set of "early" lints run on the
`syntax::ast` and a "late" set of lints run on the HIR. This commit moves the
"early" lints to running as late as possible in compilation, just before HIR
lowering. This notably means that we're catching resolve-related lints just
before HIR lowering. The early linting remains a pass very similar to how it was
before, maintaining context of the current lint level as it walks the tree.

Post-HIR, however, linting is structured as a method on the `TyCtxt` which
transitively executes a query to calculate lint levels. Each request to lint on
a `TyCtxt` will query the entire crate's 'lint level data structure' and then go
from there about whether the lint should be emitted or not.

The query depends on the entire HIR crate but should be very quick to calculate
(just a quick walk of the HIR) and the red-green system should notice that the
lint level data structure rarely changes, and should hopefully preserve
incrementality.

Overall this resulted in a pretty big change to the test suite now that lints
are emitted much earlier in compilation (on-demand vs only at the end). This in
turn necessitated the addition of many `#![allow(warnings)]` directives
throughout the compile-fail test suite and a number of updates to the UI test
suite.
@alexcrichton
Copy link
Member Author

Ok I've updated to use with_ignore which solves the incremental test issue as well and hopefully will paper over the problem until red/green comes I think?

I also hardcoded -A unused in compiletest for compile-fail and UI tests.

@alexcrichton
Copy link
Member Author

re-r? @michaelwoerister

@michaelwoerister
Copy link
Member

Cool! I'll take a look at it tomorrow.

@michaelwoerister
Copy link
Member

@bors r+

Thank you, Alex!

@bors
Copy link
Contributor

bors commented Aug 10, 2017

📌 Commit 0374e6a has been approved by michaelwoerister

bors added a commit that referenced this pull request Aug 10, 2017
rustc: Rearchitect lints to be emitted more eagerly

In preparation for incremental compilation this commit refactors the lint
handling infrastructure in the compiler to be more "eager" and overall more
incremental-friendly. Many passes of the compiler can emit lints at various
points but before this commit all lints were buffered in a table to be emitted
at the very end of compilation. This commit changes these lints to be emitted
immediately during compilation using pre-calculated lint level-related data
structures.

Linting today is split into two phases, one set of "early" lints run on the
`syntax::ast` and a "late" set of lints run on the HIR. This commit moves the
"early" lints to running as late as possible in compilation, just before HIR
lowering. This notably means that we're catching resolve-related lints just
before HIR lowering. The early linting remains a pass very similar to how it was
before, maintaining context of the current lint level as it walks the tree.

Post-HIR, however, linting is structured as a method on the `TyCtxt` which
transitively executes a query to calculate lint levels. Each request to lint on
a `TyCtxt` will query the entire crate's 'lint level data structure' and then go
from there about whether the lint should be emitted or not.

The query depends on the entire HIR crate but should be very quick to calculate
(just a quick walk of the HIR) and the red-green system should notice that the
lint level data structure rarely changes, and should hopefully preserve
incrementality.

Overall this resulted in a pretty big change to the test suite now that lints
are emitted much earlier in compilation (on-demand vs only at the end). This in
turn necessitated the addition of many `#![allow(warnings)]` directives
throughout the compile-fail test suite and a number of updates to the UI test
suite.

Closes #42511
@bors
Copy link
Contributor

bors commented Aug 10, 2017

⌛ Testing commit 0374e6a with merge 2400ebf...

@bors
Copy link
Contributor

bors commented Aug 10, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: michaelwoerister
Pushing 2400ebf to master...

@oli-obk
Copy link
Contributor

oli-obk commented Aug 11, 2017

I think this broke #[allow(unknown_lints)] (as in it doesn't work anymore for early lints)

@alexcrichton
Copy link
Member Author

@oli-obk do you have a test case or an issue report for that?

alexcrichton added a commit to alexcrichton/rust that referenced this pull request Aug 13, 2017
The lint refactoring in rust-lang#43522 didn't account for `#[allow(unknown_lints)]`
happening at the same node as an unknown lint itself, so this commit updates the
handling to ensure that the local set of lint configuration being built is
queried before looking at the chain of lint levels.

Closes rust-lang#43809
bors added a commit that referenced this pull request Aug 16, 2017
rustc: Fix `unknown_lints` next to an unknown lint

The lint refactoring in #43522 didn't account for `#[allow(unknown_lints)]`
happening at the same node as an unknown lint itself, so this commit updates the
handling to ensure that the local set of lint configuration being built is
queried before looking at the chain of lint levels.

Closes #43809
NotFound,
Removed,
}

pub enum CheckLintNameResult<'a> {
Ok(&'a [LintId]),
// Lint doesn't exist
Copy link
Member

Choose a reason for hiding this comment

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

Please make it a doc comment

Ok(&'a [LintId]),
// Lint doesn't exist
NoLint,
// The lint is either renamed or removed. This is the warning
Copy link
Member

Choose a reason for hiding this comment

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

Same here

None => (Allow, Default),
}
}
// Checks the validity of lint names derived from the command line
Copy link
Member

Choose a reason for hiding this comment

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

And here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

refactor lint handling to be more "eager"