rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
99.14k stars 12.8k forks source link

Lint for removing `extern crate` has false positive when crate is used #50672

Closed alexcrichton closed 6 years ago

alexcrichton commented 6 years ago

The following code

#![feature(rust_2018_preview)]
#![deny(unnecessary_extern_crate)]
#![allow(unused_imports)]

extern crate libc;

mod foo {
    use crate::libc;
}

fn main() {}

generates an error:

error: `extern crate` is unnecessary in the new edition
 --> src/main.rs:5:1
  |
5 | extern crate libc;
  | ^^^^^^^^^^^^^^^^^^ help: remove it
  |
note: lint level defined here
 --> src/main.rs:2:9
  |
2 | #![deny(unnecessary_extern_crate)]
  |         ^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

error: Could not compile `playground`.

but if the extern crate libc; is removed it no longer compiles!

This situation has come up on two occasions so far:

  1. In attempting to fix https://github.com/rust-lang/rust/issues/50660 the lint started firing for use libc; and rustfix rewrote it to use crate::libc;. This worked in the 2015 edition because extern crate libc; was declared at the top. Later this unnecessary_extern_crate lint ended up showing the error above.
  2. Discovered in https://github.com/rust-lang/rust/pull/50665 the use *; statement is entirely disallowed in the 2018 edition, so we instead recommend the replacement use crate::*;. This, however, will also break if one of the identifiers used is an extern crate as the lint currently suggests removing extern crate
alexcrichton commented 6 years ago

cc @Manishearth

Manishearth commented 6 years ago

I and @nikomatsakis discussed this a bunch when implementing this and decided not to fix this problem. An earlier commit of mine there had an attempt which would try and ask you to directly import crates when used as use (0+ local modules in chain)::cratename::(optional foo). It's tricky to get right though. I can dig up the commit if we want that.

Manishearth commented 6 years ago

The fix is in https://github.com/Manishearth/rust/commit/c904d98bc59c17e61332f1d2179ebe3b920835be

It's not perfect for a bunch of reasons (also might not do precisely what I said in the commit above, but close)

alexcrichton commented 6 years ago

While I can't say for sure I'd be pretty certain that this pattern shows up in the wild (especially use foo::krate_name::bar). If we go with the status quo then it means crates with this pattern won't be able to use rustfix as currently it's an all-or-nothing wrt fixes. Given the experience I've had so far with rustfix I think we want to strive for the highest fidelity lints we can which to me means that we'll likely want to fix this.

Manishearth commented 6 years ago

Okay, so paging this in a bit more, while https://github.com/Manishearth/rust/commit/c904d98bc59c17e61332f1d2179ebe3b920835be is a pretty decent lint, we can't write suggestions for it. The problem is that use trees get split out in HIR, which means that the suggestions here can't work to well in the presence of these. It's hard to even detect that there's a use tree involved (in some cases)

Also, doing the span-splicing to emit the correct suggestion is tricky as well, we don't store spans for path segments.

Manishearth commented 6 years ago

So basically this is doable as a rustfixable lint, but it will be buggy and/or very imperfect.

nikomatsakis commented 6 years ago

I have a few questions. My memory is that, when an extern crate appears somewhere other than the crate root, we are going to leave it in place, and let the user fix it manually.

However, if the extern crate appeared at the crate root, we would remove it, and then try not to rewrite paths like use foo::bar (which is technically accessing crate::foo::bar today, so to speak).

This approach breaks if the code uses use crate::foo::bar, but that's not a common pattern today. But where else does it break?

nikomatsakis commented 6 years ago

One answer to my question would be use self::foo::bar or use super::foo::bar.

nikomatsakis commented 6 years ago

I am reminded that I initially wanted to do a kind of global pass to see where and how the extern crate was used, and only suggest removing it if it is not used from paths like those above. I'm not sure how easy/hard that would be to do but it seems like it would address this issue.

nikomatsakis commented 6 years ago

OK, so, it seems like the lint that @Manishearth pointed us at (https://github.com/Manishearth/rust/commit/c904d98bc59c17e61332f1d2179ebe3b920835be) does the following:

As @Manishearth said, writing suggestions for this case is tricky, at least in its full generality. But we could certainly do something like the following:

That means we'd suppress the "not actionable" error but issue lints that guide you towards making the extern crate into something we can remove.

However, there is still the downside that the paths must be manually changed. I suspect we can overcome that in practice a lot of the time — I mean there are tricky cases, but we can look for the plain vanilla case and just issue the suggestion in that case (for example, by checking that the span of the use statement begins with use self::foo::bar:: or whatever).

alexcrichton commented 6 years ago

I discussed with @nikomatsakis a bit on discord and agree that perhaps the best thing to do here is to avoid linting anything at all, but rather wait till the lint is higher accuracy to turn it on by default.

nikomatsakis commented 6 years ago

Pending fix in #51015