Skip to content

Commit

Permalink
check redundant prelude imports
Browse files Browse the repository at this point in the history
detects unnecessary imports in std::prelude that can be eliminated.

For example import:

```rust
use std::{option::{Iter, IterMut, IntoIter, Option::{self, Some}}, convert::{TryFrom, TryInto}, mem::drop};
```

delete : `Option::{self, Some}`  and `mem::drop`
  • Loading branch information
surechen committed Nov 29, 2023
1 parent f440b5f commit 1b5d4e6
Show file tree
Hide file tree
Showing 99 changed files with 1,369 additions and 205 deletions.
254 changes: 254 additions & 0 deletions compiler/rustc_lint/src/redundant_prelude_imports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
use crate::{lints::RedundantPreludeImportsDiag, EarlyContext, EarlyLintPass, LintContext};
use rustc_ast as ast;
use rustc_span::symbol::{sym, Symbol};
use rustc_span::Span;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
use std::string::ToString;

declare_lint! {
/// The `redundant_prelude_imports` lint detects unnecessary imports in
/// std::prelude that can be eliminated.
///
/// ### Example
///
/// ```rust
/// # #![allow(unused)]
/// # #![warn(redundant_prelude_imports)]
/// use std::mem::drop;
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// unnecessary prelude import 'std::mem::drop'
pub(super) REDUNDANT_PRELUDE_IMPORTS,
Allow,
"detects unnecessary imports in std::prelude that can be eliminated"
}

declare_lint_pass!(RedundantPreludeImports => [REDUNDANT_PRELUDE_IMPORTS]);

#[derive(Debug)]
struct RedundantImport<'a, 'b> {
item: &'a ast::Item,
prelude_imports: &'b BTreeMap<&'b str, Symbol>,
remove_imports: Vec<(Span, Vec<String>)>,
reserve_imports: Vec<(Span, Vec<String>)>,
}

impl EarlyLintPass for RedundantPreludeImports {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
if let ast::ItemKind::Use(ref use_tree) = item.kind {
// Use in std::prelude
let prelude_imports: BTreeMap<&str, Symbol> = BTreeMap::from([
("std::marker::Copy", sym::rust_2015),
("std::marker::Send", sym::rust_2015),
("std::marker::Sized", sym::rust_2015),
("std::marker::Sync", sym::rust_2015),
("std::marker::Unpin", sym::rust_2015),
("std::ops::Drop", sym::rust_2015),
("std::ops::Fn", sym::rust_2015),
("std::ops::FnMut", sym::rust_2015),
("std::ops::FnOnce", sym::rust_2015),
("std::mem::drop", sym::rust_2015),
("std::boxed::Box", sym::rust_2015),
("std::borrow::ToOwned", sym::rust_2015),
("std::clone::Clone", sym::rust_2015),
("std::cmp::PartialEq", sym::rust_2015),
("std::cmp::PartialOrd", sym::rust_2015),
("std::cmp::Eq", sym::rust_2015),
("std::cmp::Ord", sym::rust_2015),
("std::convert::AsRef", sym::rust_2015),
("std::convert::AsMut", sym::rust_2015),
("std::convert::Into", sym::rust_2015),
("std::convert::From", sym::rust_2015),
("std::default::Default", sym::rust_2015),
("std::iter::Iterator", sym::rust_2015),
("std::iter::Extend", sym::rust_2015),
("std::iter::IntoIterator", sym::rust_2015),
("std::iter::DoubleEndedIterator", sym::rust_2015),
("std::iter::ExactSizeIterator", sym::rust_2015),
("std::option::Option::self", sym::rust_2015),
("std::option::Option::Some", sym::rust_2015),
("std::option::Option::None", sym::rust_2015),
("std::result::Result", sym::rust_2015),
("std::result::Result::Ok", sym::rust_2015),
("std::result::Result::Err", sym::rust_2015),
("std::string::String", sym::rust_2015),
("std::string::ToString", sym::rust_2015),
("std::vec::Vec", sym::rust_2015),
("std::convert::TryFrom", sym::rust_2018),
("std::convert::TryInto", sym::rust_2018),
("std::iter::FromIterator", sym::rust_2018),
]);
let mut redundant = RedundantImport {
item: item,
prelude_imports: &prelude_imports,
remove_imports: vec![],
reserve_imports: vec![],
};
let path = vec![];
self.check_use_tree(cx, use_tree, &path, &mut redundant);
if redundant.remove_imports.len() > 0 {
// Delete the usetrees imported by std::prelude.
// Use the prefix tree to make suggestion msg for the remaining ones.
// For import :
// use std::{option::{Iter, IterMut, IntoIter, Option::{self, Some}},
// convert::{TryFrom, TryInto}, mem::drop};
// Replace to:
// use std::{option::{Iter, IterMut, IntoIter}, convert::{TryFrom, TryInto}};
let replace = if redundant.reserve_imports.len() == 0 {
"".to_string()
} else {
let mut tree =
MakeSuggestionTree::new(MakeSuggestionNode::new("std".to_string()));
redundant.reserve_imports.iter().for_each(|v| {
tree.add_node(&v.1);
});
let mut use_replace = "use ".to_string();
use_replace.push_str(&tree.make_suggestion());
use_replace.push(';');
use_replace
};
let lint_msg =
&redundant.remove_imports.into_iter().fold("".to_string(), |mut acc, x| {
acc.push_str(" '");
acc.push_str(
&*cx.sess().parse_sess.source_map().span_to_snippet(x.0).unwrap(),
);
acc.push_str("'");
acc
});
cx.emit_spanned_lint(
REDUNDANT_PRELUDE_IMPORTS,
item.span,
RedundantPreludeImportsDiag {
redundant_crates: lint_msg.to_string(),
suggestion: item.span,
replace: replace,
},
);
}
}
}
}

#[derive(Debug)]
struct MakeSuggestionNode {
prefix: String,
child: Rc<RefCell<Vec<MakeSuggestionNode>>>,
}

impl MakeSuggestionNode {
fn new(prefix: String) -> Self {
Self { prefix: prefix, child: Rc::new(RefCell::new(vec![])) }
}

fn add_child(&mut self, segment: &Vec<String>) {
let len = segment.len();
if len <= 1 || segment[0] != self.prefix {
return;
}
let mut find = false;
let child_len = self.child.borrow().len();
let mut child_segment = segment.clone();
child_segment.remove(0);
let find_path = child_segment[0].clone();
for j in 0..child_len {
if self.child.borrow()[j].prefix == find_path {
find = true;
self.child.borrow_mut()[j].add_child(&child_segment);
break;
}
}
if !find {
let mut child_node = MakeSuggestionNode::new(find_path);
child_node.add_child(&child_segment);
self.child.borrow_mut().push(child_node);
}
}

fn make_suggestion(&self) -> String {
let mut str = self.prefix.clone();
if self.child.borrow().len() > 0 {
str.push_str("::");
let mut child_import = self.child.borrow().iter().fold("".to_string(), |mut acc, x| {
let temp = x.make_suggestion();
acc.push_str(&*temp);
acc.push_str(", ");
acc
});
child_import = child_import[0..child_import.len() - 2].to_string();
if self.child.borrow().len() > 1 {
str.push_str("{");
str.push_str(&*child_import);
str.push_str("}");
} else {
str.push_str(&*child_import);
}
}
str
}
}

#[derive(Debug)]
struct MakeSuggestionTree {
root: MakeSuggestionNode,
}

impl MakeSuggestionTree {
fn new(node: MakeSuggestionNode) -> Self {
MakeSuggestionTree { root: node }
}

fn add_node(&mut self, segment: &Vec<String>) {
self.root.add_child(segment);
}

fn make_suggestion(&self) -> String {
self.root.make_suggestion()
}
}

impl RedundantPreludeImports {
fn check_use_tree(
&self,
cx: &EarlyContext<'_>,
use_tree: &ast::UseTree,
pre_path: &Vec<String>,
redundant: &mut RedundantImport<'_, '_>,
) {
let mut pre_path = pre_path.clone();
use_tree.prefix.segments.iter().for_each(|p| {
pre_path.push(p.ident.to_string());
});
match use_tree.kind {
ast::UseTreeKind::Nested(ref items) => {
for (tree, _) in items {
// Recursive process nested usetree.
self.check_use_tree(cx, tree, &pre_path, redundant);
}
}
ast::UseTreeKind::Simple(None) => {
let path = pre_path.iter().fold("".to_string(), |mut acc, x| {
acc.push_str(&*x);
acc.push_str("::");
acc
});
if let Some(val) = redundant.prelude_imports.get(&path[0..path.len()-2])
&& (*val == sym::rust_2015
|| (*val == sym::rust_2018 && redundant.item.span.at_least_rust_2018())
|| (*val == sym::rust_2021 && redundant.item.span.at_least_rust_2021())
|| (*val == sym::rust_2024 && redundant.item.span.at_least_rust_2024())) {
redundant.remove_imports.push((use_tree.span, pre_path.clone()));
} else {
redundant.reserve_imports.push((use_tree.span, pre_path));
}
}
_ => {}
}
}
}
10 changes: 5 additions & 5 deletions compiler/rustc_resolve/src/build_reduced_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::Namespace::{self, MacroNS, TypeNS, ValueNS};
use crate::{errors, BindingKey, MacroData, NameBindingData};
use crate::{Determinacy, ExternPreludeEntry, Finalize, Module, ModuleKind, ModuleOrUniformRoot};
use crate::{NameBinding, NameBindingKind, ParentScope, PathResult, PerNS, ResolutionError};
use crate::{Resolver, ResolverArenas, Segment, ToNameBinding, VisResolutionError};
use crate::{Resolver, ResolverArenas, Segment, ToNameBinding, Used, VisResolutionError};

use rustc_ast::visit::{self, AssocCtxt, Visitor};
use rustc_ast::{self as ast, AssocItem, AssocItemKind, MetaItemKind, StmtKind};
Expand Down Expand Up @@ -358,7 +358,7 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> {
root_span,
root_id,
vis: Cell::new(Some(vis)),
used: Cell::new(false),
used: Default::default(),
});

self.r.indeterminate_imports.push(import);
Expand Down Expand Up @@ -852,7 +852,7 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> {
span: item.span,
module_path: Vec::new(),
vis: Cell::new(Some(vis)),
used: Cell::new(used),
used: Cell::new(used.then_some(Used::Other)),
});
self.r.potentially_unused_imports.push(import);
let imported_binding = self.r.import(binding, import);
Expand Down Expand Up @@ -1061,7 +1061,7 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> {
span,
module_path: Vec::new(),
vis: Cell::new(Some(ty::Visibility::Restricted(CRATE_DEF_ID))),
used: Cell::new(false),
used: Default::default(),
})
};

Expand Down Expand Up @@ -1225,7 +1225,7 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> {
span,
module_path: Vec::new(),
vis: Cell::new(Some(vis)),
used: Cell::new(true),
used: Cell::new(Some(Used::Other)),
});
let import_binding = self.r.import(binding, import);
self.r.define(self.r.graph_root, ident, MacroNS, import_binding);
Expand Down
22 changes: 21 additions & 1 deletion compiler/rustc_resolve/src/check_unused.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,26 @@ impl<'a, 'b, 'tcx> Visitor<'a> for UnusedImportCheckVisitor<'a, 'b, 'tcx> {
}
} else {
self.check_import(id);
let import = self.r.redundant_imports.remove(&id);
if let Some(import) = import {
if let ImportKind::Single {
source,
target,
ref source_bindings,
ref target_bindings,
..
} = import.kind
&& !self.unused_imports.contains_key(&self.base_id)
{
self.r.check_for_redundant_imports(
source,
import,
source_bindings,
target_bindings,
target,
);
}
}
}

visit::walk_use_tree(self, use_tree, id);
Expand Down Expand Up @@ -286,7 +306,7 @@ impl Resolver<'_, '_> {

for import in self.potentially_unused_imports.iter() {
match import.kind {
_ if import.used.get()
_ if import.used.get().is_some()
|| import.expect_vis().is_public()
|| import.span.is_dummy() =>
{
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_resolve/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ use crate::errors::{
};
use crate::imports::{Import, ImportKind};
use crate::late::{PatternSource, Rib};
use crate::path_names_to_string;
use crate::{errors as errs, BindingKey};
use crate::{path_names_to_string, Used};
use crate::{AmbiguityError, AmbiguityErrorMisc, AmbiguityKind, BindingError, Finalize};
use crate::{HasGenericParams, MacroRulesScope, Module, ModuleKind, ModuleOrUniformRoot};
use crate::{LexicalScopeBinding, NameBinding, NameBindingKind, PrivacyError, VisResolutionError};
Expand Down Expand Up @@ -1482,7 +1482,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
);
// Silence the 'unused import' warning we might get,
// since this diagnostic already covers that import.
self.record_use(ident, binding, false);
self.record_use(ident, binding, Some(Used::Other));
return;
}
}
Expand Down

0 comments on commit 1b5d4e6

Please sign in to comment.