Merge commit '0e87918536b9833bbc6c683d1f9d51ee2bf03ef1' into clippyup
This commit is contained in:
@@ -169,9 +169,9 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
|
||||
(Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
|
||||
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
|
||||
(Struct(lse), Struct(rse)) => {
|
||||
eq_path(&lse.path, &rse.path) &&
|
||||
eq_struct_rest(&lse.rest, &rse.rest) &&
|
||||
unordered_over(&lse.fields, &rse.fields, |l, r| eq_field(l, r))
|
||||
eq_path(&lse.path, &rse.path)
|
||||
&& eq_struct_rest(&lse.rest, &rse.rest)
|
||||
&& unordered_over(&lse.fields, &rse.fields, |l, r| eq_field(l, r))
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
@@ -409,7 +409,7 @@ pub fn eq_use_tree(l: &UseTree, r: &UseTree) -> bool {
|
||||
}
|
||||
|
||||
pub fn eq_anon_const(l: &AnonConst, r: &AnonConst) -> bool {
|
||||
eq_expr(&l.value, &r.value)
|
||||
eq_expr(&l.value, &r.value)
|
||||
}
|
||||
|
||||
pub fn eq_use_tree_kind(l: &UseTreeKind, r: &UseTreeKind) -> bool {
|
||||
|
||||
@@ -8,14 +8,16 @@ use std::env;
|
||||
|
||||
fn docs_link(diag: &mut DiagnosticBuilder<'_>, lint: &'static Lint) {
|
||||
if env::var("CLIPPY_DISABLE_DOCS_LINKS").is_err() {
|
||||
diag.help(&format!(
|
||||
"for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}",
|
||||
&option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| {
|
||||
// extract just major + minor version and ignore patch versions
|
||||
format!("rust-{}", n.rsplitn(2, '.').nth(1).unwrap())
|
||||
}),
|
||||
lint.name_lower().replacen("clippy::", "", 1)
|
||||
));
|
||||
if let Some(lint) = lint.name_lower().strip_prefix("clippy::") {
|
||||
diag.help(&format!(
|
||||
"for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}",
|
||||
&option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| {
|
||||
// extract just major + minor version and ignore patch versions
|
||||
format!("rust-{}", n.rsplitn(2, '.').nth(1).unwrap())
|
||||
}),
|
||||
lint
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
//! - or-fun-call
|
||||
//! - option-if-let-else
|
||||
|
||||
use crate::{is_ctor_or_promotable_const_function, is_type_diagnostic_item};
|
||||
use crate::is_ctor_or_promotable_const_function;
|
||||
use crate::ty::is_type_diagnostic_item;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
|
||||
use rustc_hir::intravisit;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use crate::consts::{constant_context, constant_simple};
|
||||
use crate::{differing_macro_contexts, snippet_opt};
|
||||
use crate::differing_macro_contexts;
|
||||
use crate::source::snippet_opt;
|
||||
use rustc_ast::ast::InlineAsmTemplatePiece;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{
|
||||
BinOpKind, Block, BlockCheckMode, BodyId, BorrowKind, CaptureBy, Expr, ExprKind, ExprField, PatField, FnRetTy,
|
||||
GenericArg, GenericArgs, Guard, HirId, InlineAsmOperand, Lifetime, LifetimeName, ParamName, Pat, PatKind, Path,
|
||||
BinOpKind, Block, BlockCheckMode, BodyId, BorrowKind, CaptureBy, Expr, ExprField, ExprKind, FnRetTy, GenericArg,
|
||||
GenericArgs, Guard, HirId, InlineAsmOperand, Lifetime, LifetimeName, ParamName, Pat, PatField, PatKind, Path,
|
||||
PathSegment, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding,
|
||||
};
|
||||
use rustc_lexer::{tokenize, TokenKind};
|
||||
@@ -79,10 +80,6 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
|
||||
pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool {
|
||||
self.inter_expr().eq_path_segments(left, right)
|
||||
}
|
||||
|
||||
pub fn eq_ty_kind(&mut self, left: &TyKind<'_>, right: &TyKind<'_>) -> bool {
|
||||
self.inter_expr().eq_ty_kind(left, right)
|
||||
}
|
||||
}
|
||||
|
||||
struct HirEqInterExpr<'a, 'b, 'tcx> {
|
||||
@@ -251,7 +248,7 @@ impl HirEqInterExpr<'_, '_, '_> {
|
||||
(&ExprKind::Struct(ref l_path, ref lf, ref lo), &ExprKind::Struct(ref r_path, ref rf, ref ro)) => {
|
||||
self.eq_qpath(l_path, r_path)
|
||||
&& both(lo, ro, |l, r| self.eq_expr(l, r))
|
||||
&& over(lf, rf, |l, r| self.eq_field(l, r))
|
||||
&& over(lf, rf, |l, r| self.eq_expr_field(l, r))
|
||||
},
|
||||
(&ExprKind::Tup(l_tup), &ExprKind::Tup(r_tup)) => self.eq_exprs(l_tup, r_tup),
|
||||
(&ExprKind::Unary(l_op, ref le), &ExprKind::Unary(r_op, ref re)) => l_op == r_op && self.eq_expr(le, re),
|
||||
@@ -266,7 +263,7 @@ impl HirEqInterExpr<'_, '_, '_> {
|
||||
over(left, right, |l, r| self.eq_expr(l, r))
|
||||
}
|
||||
|
||||
fn eq_field(&mut self, left: &ExprField<'_>, right: &ExprField<'_>) -> bool {
|
||||
fn eq_expr_field(&mut self, left: &ExprField<'_>, right: &ExprField<'_>) -> bool {
|
||||
left.ident.name == right.ident.name && self.eq_expr(&left.expr, &right.expr)
|
||||
}
|
||||
|
||||
@@ -290,7 +287,7 @@ impl HirEqInterExpr<'_, '_, '_> {
|
||||
left.name == right.name
|
||||
}
|
||||
|
||||
fn eq_fieldpat(&mut self, left: &PatField<'_>, right: &PatField<'_>) -> bool {
|
||||
fn eq_pat_field(&mut self, left: &PatField<'_>, right: &PatField<'_>) -> bool {
|
||||
let (PatField { ident: li, pat: lp, .. }, PatField { ident: ri, pat: rp, .. }) = (&left, &right);
|
||||
li.name == ri.name && self.eq_pat(lp, rp)
|
||||
}
|
||||
@@ -300,7 +297,7 @@ impl HirEqInterExpr<'_, '_, '_> {
|
||||
match (&left.kind, &right.kind) {
|
||||
(&PatKind::Box(ref l), &PatKind::Box(ref r)) => self.eq_pat(l, r),
|
||||
(&PatKind::Struct(ref lp, ref la, ..), &PatKind::Struct(ref rp, ref ra, ..)) => {
|
||||
self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_fieldpat(l, r))
|
||||
self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_pat_field(l, r))
|
||||
},
|
||||
(&PatKind::TupleStruct(ref lp, ref la, ls), &PatKind::TupleStruct(ref rp, ref ra, rs)) => {
|
||||
self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_pat(l, r)) && ls == rs
|
||||
@@ -377,13 +374,9 @@ impl HirEqInterExpr<'_, '_, '_> {
|
||||
left.ident.name == right.ident.name && both(&left.args, &right.args, |l, r| self.eq_path_parameters(l, r))
|
||||
}
|
||||
|
||||
fn eq_ty(&mut self, left: &Ty<'_>, right: &Ty<'_>) -> bool {
|
||||
self.eq_ty_kind(&left.kind, &right.kind)
|
||||
}
|
||||
|
||||
#[allow(clippy::similar_names)]
|
||||
fn eq_ty_kind(&mut self, left: &TyKind<'_>, right: &TyKind<'_>) -> bool {
|
||||
match (left, right) {
|
||||
fn eq_ty(&mut self, left: &Ty<'_>, right: &Ty<'_>) -> bool {
|
||||
match (&left.kind, &right.kind) {
|
||||
(&TyKind::Slice(ref l_vec), &TyKind::Slice(ref r_vec)) => self.eq_ty(l_vec, r_vec),
|
||||
(&TyKind::Array(ref lt, ref ll_id), &TyKind::Array(ref rt, ref rl_id)) => {
|
||||
let cx = self.inner.cx;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -61,7 +61,6 @@ pub const IO_READ: [&str; 3] = ["std", "io", "Read"];
|
||||
pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
|
||||
pub const IPADDR_V4: [&str; 4] = ["std", "net", "IpAddr", "V4"];
|
||||
pub const IPADDR_V6: [&str; 4] = ["std", "net", "IpAddr", "V6"];
|
||||
pub const ITERATOR: [&str; 5] = ["core", "iter", "traits", "iterator", "Iterator"];
|
||||
#[cfg(feature = "internal-lints")]
|
||||
pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
|
||||
#[cfg(feature = "internal-lints")]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{get_pat_name, match_var, snippet};
|
||||
use crate::source::snippet;
|
||||
use crate::{get_pat_name, match_var};
|
||||
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
|
||||
use rustc_hir::{Body, BodyId, Expr, ExprKind, Param};
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
420
clippy_utils/src/source.rs
Normal file
420
clippy_utils/src/source.rs
Normal file
@@ -0,0 +1,420 @@
|
||||
//! Utils for extracting, inspecting or transforming source code
|
||||
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
use crate::line_span;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_span::hygiene;
|
||||
use rustc_span::{BytePos, Pos, Span, SyntaxContext};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`.
|
||||
/// Also takes an `Option<String>` which can be put inside the braces.
|
||||
pub fn expr_block<'a, T: LintContext>(
|
||||
cx: &T,
|
||||
expr: &Expr<'_>,
|
||||
option: Option<String>,
|
||||
default: &'a str,
|
||||
indent_relative_to: Option<Span>,
|
||||
) -> Cow<'a, str> {
|
||||
let code = snippet_block(cx, expr.span, default, indent_relative_to);
|
||||
let string = option.unwrap_or_default();
|
||||
if expr.span.from_expansion() {
|
||||
Cow::Owned(format!("{{ {} }}", snippet_with_macro_callsite(cx, expr.span, default)))
|
||||
} else if let ExprKind::Block(_, _) = expr.kind {
|
||||
Cow::Owned(format!("{}{}", code, string))
|
||||
} else if string.is_empty() {
|
||||
Cow::Owned(format!("{{ {} }}", code))
|
||||
} else {
|
||||
Cow::Owned(format!("{{\n{};\n{}\n}}", code, string))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new Span that extends the original Span to the first non-whitespace char of the first
|
||||
/// line.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let x = ();
|
||||
/// // ^^
|
||||
/// // will be converted to
|
||||
/// let x = ();
|
||||
/// // ^^^^^^^^^^
|
||||
/// ```
|
||||
pub fn first_line_of_span<T: LintContext>(cx: &T, span: Span) -> Span {
|
||||
first_char_in_first_line(cx, span).map_or(span, |first_char_pos| span.with_lo(first_char_pos))
|
||||
}
|
||||
|
||||
fn first_char_in_first_line<T: LintContext>(cx: &T, span: Span) -> Option<BytePos> {
|
||||
let line_span = line_span(cx, span);
|
||||
snippet_opt(cx, line_span).and_then(|snip| {
|
||||
snip.find(|c: char| !c.is_whitespace())
|
||||
.map(|pos| line_span.lo() + BytePos::from_usize(pos))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the indentation of the line of a span
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let x = ();
|
||||
/// // ^^ -- will return 0
|
||||
/// let x = ();
|
||||
/// // ^^ -- will return 4
|
||||
/// ```
|
||||
pub fn indent_of<T: LintContext>(cx: &T, span: Span) -> Option<usize> {
|
||||
snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace()))
|
||||
}
|
||||
|
||||
// If the snippet is empty, it's an attribute that was inserted during macro
|
||||
// expansion and we want to ignore those, because they could come from external
|
||||
// sources that the user has no control over.
|
||||
// For some reason these attributes don't have any expansion info on them, so
|
||||
// we have to check it this way until there is a better way.
|
||||
pub fn is_present_in_source<T: LintContext>(cx: &T, span: Span) -> bool {
|
||||
if let Some(snippet) = snippet_opt(cx, span) {
|
||||
if snippet.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns the positon just before rarrow
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// fn into(self) -> () {}
|
||||
/// ^
|
||||
/// // in case of unformatted code
|
||||
/// fn into2(self)-> () {}
|
||||
/// ^
|
||||
/// fn into3(self) -> () {}
|
||||
/// ^
|
||||
/// ```
|
||||
pub fn position_before_rarrow(s: &str) -> Option<usize> {
|
||||
s.rfind("->").map(|rpos| {
|
||||
let mut rpos = rpos;
|
||||
let chars: Vec<char> = s.chars().collect();
|
||||
while rpos > 1 {
|
||||
if let Some(c) = chars.get(rpos - 1) {
|
||||
if c.is_whitespace() {
|
||||
rpos -= 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
rpos
|
||||
})
|
||||
}
|
||||
|
||||
/// Reindent a multiline string with possibility of ignoring the first line.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option<usize>) -> Cow<'_, str> {
|
||||
let s_space = reindent_multiline_inner(&s, ignore_first, indent, ' ');
|
||||
let s_tab = reindent_multiline_inner(&s_space, ignore_first, indent, '\t');
|
||||
reindent_multiline_inner(&s_tab, ignore_first, indent, ' ').into()
|
||||
}
|
||||
|
||||
fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>, ch: char) -> String {
|
||||
let x = s
|
||||
.lines()
|
||||
.skip(ignore_first as usize)
|
||||
.filter_map(|l| {
|
||||
if l.is_empty() {
|
||||
None
|
||||
} else {
|
||||
// ignore empty lines
|
||||
Some(l.char_indices().find(|&(_, x)| x != ch).unwrap_or((l.len(), ch)).0)
|
||||
}
|
||||
})
|
||||
.min()
|
||||
.unwrap_or(0);
|
||||
let indent = indent.unwrap_or(0);
|
||||
s.lines()
|
||||
.enumerate()
|
||||
.map(|(i, l)| {
|
||||
if (ignore_first && i == 0) || l.is_empty() {
|
||||
l.to_owned()
|
||||
} else if x > indent {
|
||||
l.split_at(x - indent).1.to_owned()
|
||||
} else {
|
||||
" ".repeat(indent - x) + l
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
/// Converts a span to a code snippet if available, otherwise use default.
|
||||
///
|
||||
/// This is useful if you want to provide suggestions for your lint or more generally, if you want
|
||||
/// to convert a given `Span` to a `str`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,ignore
|
||||
/// snippet(cx, expr.span, "..")
|
||||
/// ```
|
||||
pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
|
||||
snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from)
|
||||
}
|
||||
|
||||
/// Same as `snippet`, but it adapts the applicability level by following rules:
|
||||
///
|
||||
/// - Applicability level `Unspecified` will never be changed.
|
||||
/// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
|
||||
/// - If the default value is used and the applicability level is `MachineApplicable`, change it to
|
||||
/// `HasPlaceholders`
|
||||
pub fn snippet_with_applicability<'a, T: LintContext>(
|
||||
cx: &T,
|
||||
span: Span,
|
||||
default: &'a str,
|
||||
applicability: &mut Applicability,
|
||||
) -> Cow<'a, str> {
|
||||
if *applicability != Applicability::Unspecified && span.from_expansion() {
|
||||
*applicability = Applicability::MaybeIncorrect;
|
||||
}
|
||||
snippet_opt(cx, span).map_or_else(
|
||||
|| {
|
||||
if *applicability == Applicability::MachineApplicable {
|
||||
*applicability = Applicability::HasPlaceholders;
|
||||
}
|
||||
Cow::Borrowed(default)
|
||||
},
|
||||
From::from,
|
||||
)
|
||||
}
|
||||
|
||||
/// Same as `snippet`, but should only be used when it's clear that the input span is
|
||||
/// not a macro argument.
|
||||
pub fn snippet_with_macro_callsite<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
|
||||
snippet(cx, span.source_callsite(), default)
|
||||
}
|
||||
|
||||
/// Converts a span to a code snippet. Returns `None` if not available.
|
||||
pub fn snippet_opt<T: LintContext>(cx: &T, span: Span) -> Option<String> {
|
||||
cx.sess().source_map().span_to_snippet(span).ok()
|
||||
}
|
||||
|
||||
/// Converts a span (from a block) to a code snippet if available, otherwise use default.
|
||||
///
|
||||
/// This trims the code of indentation, except for the first line. Use it for blocks or block-like
|
||||
/// things which need to be printed as such.
|
||||
///
|
||||
/// The `indent_relative_to` arg can be used, to provide a span, where the indentation of the
|
||||
/// resulting snippet of the given span.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// snippet_block(cx, block.span, "..", None)
|
||||
/// // where, `block` is the block of the if expr
|
||||
/// if x {
|
||||
/// y;
|
||||
/// }
|
||||
/// // will return the snippet
|
||||
/// {
|
||||
/// y;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// snippet_block(cx, block.span, "..", Some(if_expr.span))
|
||||
/// // where, `block` is the block of the if expr
|
||||
/// if x {
|
||||
/// y;
|
||||
/// }
|
||||
/// // will return the snippet
|
||||
/// {
|
||||
/// y;
|
||||
/// } // aligned with `if`
|
||||
/// ```
|
||||
/// Note that the first line of the snippet always has 0 indentation.
|
||||
pub fn snippet_block<'a, T: LintContext>(
|
||||
cx: &T,
|
||||
span: Span,
|
||||
default: &'a str,
|
||||
indent_relative_to: Option<Span>,
|
||||
) -> Cow<'a, str> {
|
||||
let snip = snippet(cx, span, default);
|
||||
let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
|
||||
reindent_multiline(snip, true, indent)
|
||||
}
|
||||
|
||||
/// Same as `snippet_block`, but adapts the applicability level by the rules of
|
||||
/// `snippet_with_applicability`.
|
||||
pub fn snippet_block_with_applicability<'a, T: LintContext>(
|
||||
cx: &T,
|
||||
span: Span,
|
||||
default: &'a str,
|
||||
indent_relative_to: Option<Span>,
|
||||
applicability: &mut Applicability,
|
||||
) -> Cow<'a, str> {
|
||||
let snip = snippet_with_applicability(cx, span, default, applicability);
|
||||
let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
|
||||
reindent_multiline(snip, true, indent)
|
||||
}
|
||||
|
||||
/// Same as `snippet_with_applicability`, but first walks the span up to the given context. This
|
||||
/// will result in the macro call, rather then the expansion, if the span is from a child context.
|
||||
/// If the span is not from a child context, it will be used directly instead.
|
||||
///
|
||||
/// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR node
|
||||
/// would result in `box []`. If given the context of the address of expression, this function will
|
||||
/// correctly get a snippet of `vec![]`.
|
||||
///
|
||||
/// This will also return whether or not the snippet is a macro call.
|
||||
pub fn snippet_with_context(
|
||||
cx: &LateContext<'_>,
|
||||
span: Span,
|
||||
outer: SyntaxContext,
|
||||
default: &'a str,
|
||||
applicability: &mut Applicability,
|
||||
) -> (Cow<'a, str>, bool) {
|
||||
let outer_span = hygiene::walk_chain(span, outer);
|
||||
let (span, is_macro_call) = if outer_span.ctxt() == outer {
|
||||
(outer_span, span.ctxt() != outer)
|
||||
} else {
|
||||
// The span is from a macro argument, and the outer context is the macro using the argument
|
||||
if *applicability != Applicability::Unspecified {
|
||||
*applicability = Applicability::MaybeIncorrect;
|
||||
}
|
||||
// TODO: get the argument span.
|
||||
(span, false)
|
||||
};
|
||||
|
||||
(
|
||||
snippet_with_applicability(cx, span, default, applicability),
|
||||
is_macro_call,
|
||||
)
|
||||
}
|
||||
|
||||
/// Removes block comments from the given `Vec` of lines.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// without_block_comments(vec!["/*", "foo", "*/"]);
|
||||
/// // => vec![]
|
||||
///
|
||||
/// without_block_comments(vec!["bar", "/*", "foo", "*/"]);
|
||||
/// // => vec!["bar"]
|
||||
/// ```
|
||||
pub fn without_block_comments(lines: Vec<&str>) -> Vec<&str> {
|
||||
let mut without = vec![];
|
||||
|
||||
let mut nest_level = 0;
|
||||
|
||||
for line in lines {
|
||||
if line.contains("/*") {
|
||||
nest_level += 1;
|
||||
continue;
|
||||
} else if line.contains("*/") {
|
||||
nest_level -= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if nest_level == 0 {
|
||||
without.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
without
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{reindent_multiline, without_block_comments};
|
||||
|
||||
#[test]
|
||||
fn test_reindent_multiline_single_line() {
|
||||
assert_eq!("", reindent_multiline("".into(), false, None));
|
||||
assert_eq!("...", reindent_multiline("...".into(), false, None));
|
||||
assert_eq!("...", reindent_multiline(" ...".into(), false, None));
|
||||
assert_eq!("...", reindent_multiline("\t...".into(), false, None));
|
||||
assert_eq!("...", reindent_multiline("\t\t...".into(), false, None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn test_reindent_multiline_block() {
|
||||
assert_eq!("\
|
||||
if x {
|
||||
y
|
||||
} else {
|
||||
z
|
||||
}", reindent_multiline(" if x {
|
||||
y
|
||||
} else {
|
||||
z
|
||||
}".into(), false, None));
|
||||
assert_eq!("\
|
||||
if x {
|
||||
\ty
|
||||
} else {
|
||||
\tz
|
||||
}", reindent_multiline(" if x {
|
||||
\ty
|
||||
} else {
|
||||
\tz
|
||||
}".into(), false, None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn test_reindent_multiline_empty_line() {
|
||||
assert_eq!("\
|
||||
if x {
|
||||
y
|
||||
|
||||
} else {
|
||||
z
|
||||
}", reindent_multiline(" if x {
|
||||
y
|
||||
|
||||
} else {
|
||||
z
|
||||
}".into(), false, None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn test_reindent_multiline_lines_deeper() {
|
||||
assert_eq!("\
|
||||
if x {
|
||||
y
|
||||
} else {
|
||||
z
|
||||
}", reindent_multiline("\
|
||||
if x {
|
||||
y
|
||||
} else {
|
||||
z
|
||||
}".into(), true, Some(8)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_without_block_comments_lines_without_block_comments() {
|
||||
let result = without_block_comments(vec!["/*", "", "*/"]);
|
||||
println!("result: {:?}", result);
|
||||
assert!(result.is_empty());
|
||||
|
||||
let result = without_block_comments(vec!["", "/*", "", "*/", "#[crate_type = \"lib\"]", "/*", "", "*/", ""]);
|
||||
assert_eq!(result, vec!["", "#[crate_type = \"lib\"]", ""]);
|
||||
|
||||
let result = without_block_comments(vec!["/* rust", "", "*/"]);
|
||||
assert!(result.is_empty());
|
||||
|
||||
let result = without_block_comments(vec!["/* one-line comment */"]);
|
||||
assert!(result.is_empty());
|
||||
|
||||
let result = without_block_comments(vec!["/* nested", "/* multi-line", "comment", "*/", "test", "*/"]);
|
||||
assert!(result.is_empty());
|
||||
|
||||
let result = without_block_comments(vec!["/* nested /* inline /* comment */ test */ */"]);
|
||||
assert!(result.is_empty());
|
||||
|
||||
let result = without_block_comments(vec!["foo", "bar", "baz"]);
|
||||
assert_eq!(result, vec!["foo", "bar", "baz"]);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
//! Contains utility functions to generate suggestions.
|
||||
#![deny(clippy::missing_docs_in_private_items)]
|
||||
|
||||
use crate::{higher, snippet, snippet_opt, snippet_with_macro_callsite};
|
||||
use crate::higher;
|
||||
use crate::source::{snippet, snippet_opt, snippet_with_macro_callsite};
|
||||
use rustc_ast::util::parser::AssocOp;
|
||||
use rustc_ast::{ast, token};
|
||||
use rustc_ast_pretty::pprust::token_kind_to_string;
|
||||
|
||||
305
clippy_utils/src/ty.rs
Normal file
305
clippy_utils/src/ty.rs
Normal file
@@ -0,0 +1,305 @@
|
||||
//! Util methods for [`rustc_middle::ty`]
|
||||
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rustc_ast::ast::Mutability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{TyKind, Unsafety};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
|
||||
use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TypeFoldable, UintTy};
|
||||
use rustc_span::sym;
|
||||
use rustc_span::symbol::Symbol;
|
||||
use rustc_span::DUMMY_SP;
|
||||
use rustc_trait_selection::traits::query::normalize::AtExt;
|
||||
|
||||
use crate::{match_def_path, must_use_attr};
|
||||
|
||||
pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
ty.is_copy_modulo_regions(cx.tcx.at(DUMMY_SP), cx.param_env)
|
||||
}
|
||||
|
||||
/// Checks whether a type can be partially moved.
|
||||
pub fn can_partially_move_ty(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
if has_drop(cx, ty) || is_copy(cx, ty) {
|
||||
return false;
|
||||
}
|
||||
match ty.kind() {
|
||||
ty::Param(_) => false,
|
||||
ty::Adt(def, subs) => def.all_fields().any(|f| !is_copy(cx, f.ty(cx.tcx, subs))),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Walks into `ty` and returns `true` if any inner type is the same as `other_ty`
|
||||
pub fn contains_ty(ty: Ty<'_>, other_ty: Ty<'_>) -> bool {
|
||||
ty.walk().any(|inner| match inner.unpack() {
|
||||
GenericArgKind::Type(inner_ty) => ty::TyS::same_type(other_ty, inner_ty),
|
||||
GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Walks into `ty` and returns `true` if any inner type is an instance of the given adt
|
||||
/// constructor.
|
||||
pub fn contains_adt_constructor(ty: Ty<'_>, adt: &AdtDef) -> bool {
|
||||
ty.walk().any(|inner| match inner.unpack() {
|
||||
GenericArgKind::Type(inner_ty) => inner_ty.ty_adt_def() == Some(adt),
|
||||
GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true if ty has `iter` or `iter_mut` methods
|
||||
pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<Symbol> {
|
||||
// FIXME: instead of this hard-coded list, we should check if `<adt>::iter`
|
||||
// exists and has the desired signature. Unfortunately FnCtxt is not exported
|
||||
// so we can't use its `lookup_method` method.
|
||||
let into_iter_collections: &[Symbol] = &[
|
||||
sym::vec_type,
|
||||
sym::option_type,
|
||||
sym::result_type,
|
||||
sym::BTreeMap,
|
||||
sym::BTreeSet,
|
||||
sym::vecdeque_type,
|
||||
sym::LinkedList,
|
||||
sym::BinaryHeap,
|
||||
sym::hashset_type,
|
||||
sym::hashmap_type,
|
||||
sym::PathBuf,
|
||||
sym::Path,
|
||||
sym::Receiver,
|
||||
];
|
||||
|
||||
let ty_to_check = match probably_ref_ty.kind() {
|
||||
ty::Ref(_, ty_to_check, _) => ty_to_check,
|
||||
_ => probably_ref_ty,
|
||||
};
|
||||
|
||||
let def_id = match ty_to_check.kind() {
|
||||
ty::Array(..) => return Some(sym::array),
|
||||
ty::Slice(..) => return Some(sym::slice),
|
||||
ty::Adt(adt, _) => adt.did,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
for &name in into_iter_collections {
|
||||
if cx.tcx.is_diagnostic_item(name, def_id) {
|
||||
return Some(cx.tcx.item_name(def_id));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Checks whether a type implements a trait.
|
||||
/// See also `get_trait_def_id`.
|
||||
pub fn implements_trait<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
trait_id: DefId,
|
||||
ty_params: &[GenericArg<'tcx>],
|
||||
) -> bool {
|
||||
// Do not check on infer_types to avoid panic in evaluate_obligation.
|
||||
if ty.has_infer_types() {
|
||||
return false;
|
||||
}
|
||||
let ty = cx.tcx.erase_regions(ty);
|
||||
if ty.has_escaping_bound_vars() {
|
||||
return false;
|
||||
}
|
||||
let ty_params = cx.tcx.mk_substs(ty_params.iter());
|
||||
cx.tcx.type_implements_trait((trait_id, ty, ty_params, cx.param_env))
|
||||
}
|
||||
|
||||
/// Checks whether this type implements `Drop`.
|
||||
pub fn has_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
match ty.ty_adt_def() {
|
||||
Some(def) => def.has_dtor(cx.tcx),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns whether the type has #[must_use] attribute
|
||||
pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
match ty.kind() {
|
||||
ty::Adt(ref adt, _) => must_use_attr(&cx.tcx.get_attrs(adt.did)).is_some(),
|
||||
ty::Foreign(ref did) => must_use_attr(&cx.tcx.get_attrs(*did)).is_some(),
|
||||
ty::Slice(ref ty)
|
||||
| ty::Array(ref ty, _)
|
||||
| ty::RawPtr(ty::TypeAndMut { ref ty, .. })
|
||||
| ty::Ref(_, ref ty, _) => {
|
||||
// for the Array case we don't need to care for the len == 0 case
|
||||
// because we don't want to lint functions returning empty arrays
|
||||
is_must_use_ty(cx, *ty)
|
||||
},
|
||||
ty::Tuple(ref substs) => substs.types().any(|ty| is_must_use_ty(cx, ty)),
|
||||
ty::Opaque(ref def_id, _) => {
|
||||
for (predicate, _) in cx.tcx.explicit_item_bounds(*def_id) {
|
||||
if let ty::PredicateKind::Trait(trait_predicate, _) = predicate.kind().skip_binder() {
|
||||
if must_use_attr(&cx.tcx.get_attrs(trait_predicate.trait_ref.def_id)).is_some() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
},
|
||||
ty::Dynamic(binder, _) => {
|
||||
for predicate in binder.iter() {
|
||||
if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() {
|
||||
if must_use_attr(&cx.tcx.get_attrs(trait_ref.def_id)).is_some() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Per https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/infer/at/struct.At.html#method.normalize
|
||||
// this function can be removed once the `normalizie` method does not panic when normalization does
|
||||
// not succeed
|
||||
/// Checks if `Ty` is normalizable. This function is useful
|
||||
/// to avoid crashes on `layout_of`.
|
||||
pub fn is_normalizable<'tcx>(cx: &LateContext<'tcx>, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
is_normalizable_helper(cx, param_env, ty, &mut HashMap::new())
|
||||
}
|
||||
|
||||
fn is_normalizable_helper<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
cache: &mut HashMap<Ty<'tcx>, bool>,
|
||||
) -> bool {
|
||||
if let Some(&cached_result) = cache.get(ty) {
|
||||
return cached_result;
|
||||
}
|
||||
// prevent recursive loops, false-negative is better than endless loop leading to stack overflow
|
||||
cache.insert(ty, false);
|
||||
let result = cx.tcx.infer_ctxt().enter(|infcx| {
|
||||
let cause = rustc_middle::traits::ObligationCause::dummy();
|
||||
if infcx.at(&cause, param_env).normalize(ty).is_ok() {
|
||||
match ty.kind() {
|
||||
ty::Adt(def, substs) => def.variants.iter().all(|variant| {
|
||||
variant
|
||||
.fields
|
||||
.iter()
|
||||
.all(|field| is_normalizable_helper(cx, param_env, field.ty(cx.tcx, substs), cache))
|
||||
}),
|
||||
_ => ty.walk().all(|generic_arg| match generic_arg.unpack() {
|
||||
GenericArgKind::Type(inner_ty) if inner_ty != ty => {
|
||||
is_normalizable_helper(cx, param_env, inner_ty, cache)
|
||||
},
|
||||
_ => true, // if inner_ty == ty, we've already checked it
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
cache.insert(ty, result);
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns true iff the given type is a primitive (a bool or char, any integer or floating-point
|
||||
/// number type, a str, or an array, slice, or tuple of those types).
|
||||
pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool {
|
||||
match ty.kind() {
|
||||
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true,
|
||||
ty::Ref(_, inner, _) if *inner.kind() == ty::Str => true,
|
||||
ty::Array(inner_type, _) | ty::Slice(inner_type) => is_recursively_primitive_type(inner_type),
|
||||
ty::Tuple(inner_types) => inner_types.types().all(is_recursively_primitive_type),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the type is equal to a diagnostic item
|
||||
///
|
||||
/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem`
|
||||
pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool {
|
||||
match ty.kind() {
|
||||
ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the type is equal to a lang item
|
||||
pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: hir::LangItem) -> bool {
|
||||
match ty.kind() {
|
||||
ty::Adt(adt, _) => cx.tcx.lang_items().require(lang_item).unwrap() == adt.did,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the passed `typ` is `isize` or `usize`.
|
||||
pub fn is_isize_or_usize(typ: Ty<'_>) -> bool {
|
||||
matches!(typ.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
|
||||
}
|
||||
|
||||
/// Checks if type is struct, enum or union type with the given def path.
|
||||
///
|
||||
/// If the type is a diagnostic item, use `is_type_diagnostic_item` instead.
|
||||
/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem`
|
||||
pub fn match_type(cx: &LateContext<'_>, ty: Ty<'_>, path: &[&str]) -> bool {
|
||||
match ty.kind() {
|
||||
ty::Adt(adt, _) => match_def_path(cx, adt.did, path),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Peels off all references on the type. Returns the underlying type and the number of references
|
||||
/// removed.
|
||||
pub fn peel_mid_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) {
|
||||
fn peel(ty: Ty<'_>, count: usize) -> (Ty<'_>, usize) {
|
||||
if let ty::Ref(_, ty, _) = ty.kind() {
|
||||
peel(ty, count + 1)
|
||||
} else {
|
||||
(ty, count)
|
||||
}
|
||||
}
|
||||
peel(ty, 0)
|
||||
}
|
||||
|
||||
/// Peels off all references on the type.Returns the underlying type, the number of references
|
||||
/// removed, and whether the pointer is ultimately mutable or not.
|
||||
pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) {
|
||||
fn f(ty: Ty<'_>, count: usize, mutability: Mutability) -> (Ty<'_>, usize, Mutability) {
|
||||
match ty.kind() {
|
||||
ty::Ref(_, ty, Mutability::Mut) => f(ty, count + 1, mutability),
|
||||
ty::Ref(_, ty, Mutability::Not) => f(ty, count + 1, Mutability::Not),
|
||||
_ => (ty, count, mutability),
|
||||
}
|
||||
}
|
||||
f(ty, 0, Mutability::Mut)
|
||||
}
|
||||
|
||||
/// Returns `true` if the given type is an `unsafe` function.
|
||||
pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
match ty.kind() {
|
||||
ty::FnDef(..) | ty::FnPtr(_) => ty.fn_sig(cx.tcx).unsafety() == Unsafety::Unsafe,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the base type for HIR references and pointers.
|
||||
pub fn walk_ptrs_hir_ty<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
|
||||
match ty.kind {
|
||||
TyKind::Ptr(ref mut_ty) | TyKind::Rptr(_, ref mut_ty) => walk_ptrs_hir_ty(&mut_ty.ty),
|
||||
_ => ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the base type for references and raw pointers, and count reference
|
||||
/// depth.
|
||||
pub fn walk_ptrs_ty_depth(ty: Ty<'_>) -> (Ty<'_>, usize) {
|
||||
fn inner(ty: Ty<'_>, depth: usize) -> (Ty<'_>, usize) {
|
||||
match ty.kind() {
|
||||
ty::Ref(_, ty, _) => inner(ty, depth + 1),
|
||||
_ => (ty, depth),
|
||||
}
|
||||
}
|
||||
inner(ty, 0)
|
||||
}
|
||||
@@ -7,8 +7,8 @@ use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
|
||||
use rustc_hir::{Expr, ExprKind, HirId, Path};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::mir::FakeReadCause;
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_middle::mir::FakeReadCause;
|
||||
use rustc_middle::ty;
|
||||
use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
|
||||
|
||||
@@ -79,7 +79,7 @@ impl<'tcx> Delegate<'tcx> for MutVarsDelegate {
|
||||
self.update(&cmt)
|
||||
}
|
||||
|
||||
fn fake_read(&mut self, _: rustc_typeck::expr_use_visitor::Place<'tcx>, _: FakeReadCause, _:HirId) { }
|
||||
fn fake_read(&mut self, _: rustc_typeck::expr_use_visitor::Place<'tcx>, _: FakeReadCause, _: HirId) {}
|
||||
}
|
||||
|
||||
pub struct ParamBindingIdCollector {
|
||||
|
||||
Reference in New Issue
Block a user