I propose to add a new lint for making imports in codebases more consistent, which fires when items are imported inconsistently.
For example, many items in the std::fs, std::io, std::iter, std::env and other modules are conventionally referred to via the parent path:
use std::fs;
use std::fmt;
use std::io;
fs::read("Cargo.toml")?;
impl fmt::Write for Struct { ... }
fn save_to_disk(file: &str) -> io::Result<()> { ... }It is discouraged to import those items directly:
use std::fs::read;
use std::fmt::Write;
use std::io::Result; // shadows std::result::Result, causing confusion
read("Cargo.toml")?;
// is this io::Write or fmt::Write?
impl Write for Struct { ... }
// shadows std::result::Result, causing confusion
fn save_to_disk(file: &str) -> Result<()> {To enforce this style, there is a new attribute #[import_style]. This attribute configures the new wrong_import_style_via_super warn-by-default lint.
The #[import_style] attribute tells how others should import your item.
For example, we can annotate fs::read with it:
Note
All syntax in this proposal is just placeholder.
// std::fs
#[import_style(via = super)]
fn read() {}When someone imports read directly, the lint wrong_import_style_via_super fires:
use std::fs::read;
// ^^^^ do not directly import
// instead, `use std::fs` then refer as `fs::read`The basic MVP would be to implement #[import_style(via = super)], with no further configuration.
The rest of this proposal goes into how this feature can be further extended to cover other common patterns.
Specifically, the use-cases the extensions support are:
-
Items imported directly
use rustc_middle::ty::Tycommonly is referred to directly asTy.We should allow linting other ways of referring to
Ty, for consumer crates, the current crate, or a specific module. -
Types imported , but the module name should be renamed. Like
use rustc_hir as hir, then refer ashir::Ty. Other ways of referring are linted. -
Override import style for external paths
-
Ignore the import style for external path
The proposal provides a bird's eye view of the feature, there are many details left out that can be figured out during the implementation.
We can require that the item is imported directly. Let's say we want to require that rustc_middle::ty::Ty is always used as Ty, with no path segments:
// rustc_middle::ty
#[import_style(via = self)]
struct Ty;Then not using the item directly triggers the lint wrong_import_style_via_self:
use rustc_middle::ty;
// + ::Ty
// import `Ty` directly
fn f(_: ty::Ty) {}
// ^^^^ wrong import style, `Ty` should be
// imported directly.The following is required:
use rustc_middle::ty::Ty;
fn f(_: Ty) {}The wrong_import_style_via_self lint is allow-by-default
to prevent libraries from polluting the user's scope.
The import_style field also accepts an as key, which requires the item to be renamed when it is imported directly.
For example:
// syn::parse
#[import_style(via = self, as = SynParse)]
trait Parse {All of the following usage is wrong:
// refer as `syn::parse::Parse`
use syn::parse; // then refer as `parse::Parse`
use syn::parse::Parse; // then refer as ParseYou must import Parse directly, then rename it:
use syn::parse::Parse as SynParseIf via = self is omitted, then Parse needs to be renamed only when imported directly.
This cannot be used with via = super.
We have an issue with paths that are too awkward:
impl syn::parse::Parse for RenameRule {syn::parse::Parseis too long to type, and contains duplication in "parse".use syn::parse, then refer asparse::Parselooks odd + not clear that it's fromsynuse syn::parse::Parsethen refer asParsehas large potential to conflict with other types, it's also not clear that the type is fromsyn
Given that, one may reasonably require the following style in their codebase:
use syn::parse::Parse as SynParse;
impl SynParse for RenameRule {To enforce this, the syn crate could add import_style attribute to Parse:
#[import_style(via = self, as = SynParse)]
trait Parse {But they might not do that, and we still want to enforce that style in our codebase.
To accomodate this, we can configure the import style for other items:
#![import_style(for = syn::parse::Parse, via = self, as = SynParse)]This works much like a #[warn]:
- On functions, sets the import style within the body
- On modules, sets the import style for the item to all sub-items recursively
- On the crate root, sets the import style for the whole crate
It may also be desireable to ignore the import style of specific items, which can be done with ::ignore:
#![import_style::ignore(std::fs::read)]That accepts a list of paths. For any path that is ignored, all sub-items in that path is also ignored.
This ignores all #[import_style] attributes on items in std::io and std::fs:
#![import_style::ignore(std::fs, std::io)]In which case this no longer triggers the wrong_import_style_via_super lint:
use std::fs::read;
read("Cargo.toml"), // OK- Design: @nik-rev
- Implementation: @nik-rev
- Lang liaison: @joshtriplett