Auto merge of #84008 - Dylan-DPC:rollup-invxvg8, r=Dylan-DPC
Rollup of 6 pull requests Successful merges: - #80733 (Improve links in inline code in `core::pin`.) - #81764 (Stabilize `rustdoc::bare_urls` lint) - #81938 (Stabilize `peekable_peek_mut`) - #83980 (Fix outdated crate names in compiler docs) - #83992 (Merge idents when generating source content) - #84001 (Update Clippy) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
2e495d2e84
391 changed files with 10850 additions and 6311 deletions
|
@ -1,3 +1,3 @@
|
|||
//! Definitions shared by macros / syntax extensions and e.g. librustc_middle.
|
||||
//! Definitions shared by macros / syntax extensions and e.g. `rustc_middle`.
|
||||
|
||||
pub mod allocator;
|
||||
|
|
|
@ -59,7 +59,7 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
|
|||
|
||||
/// Requirements for a `StableHashingContext` to be used in this crate.
|
||||
/// This is a hack to allow using the `HashStable_Generic` derive macro
|
||||
/// instead of implementing everything in librustc_middle.
|
||||
/// instead of implementing everything in `rustc_middle`.
|
||||
pub trait HashStableContext: rustc_span::HashStableContext {
|
||||
fn hash_attr(&mut self, _: &ast::Attribute, hasher: &mut StableHasher);
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ struct LoweringContext<'a, 'hir: 'a> {
|
|||
|
||||
/// HACK(Centril): there is a cyclic dependency between the parser and lowering
|
||||
/// if we don't have this function pointer. To avoid that dependency so that
|
||||
/// librustc_middle is independent of the parser, we use dynamic dispatch here.
|
||||
/// `rustc_middle` is independent of the parser, we use dynamic dispatch here.
|
||||
nt_to_tokenstream: NtToTokenstream,
|
||||
|
||||
/// Used to allocate HIR nodes.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Codegen vtables and vtable accesses.
|
||||
//!
|
||||
//! See librustc_codegen_llvm/meth.rs for reference
|
||||
//! See `rustc_codegen_ssa/src/meth.rs` for reference.
|
||||
// FIXME dedup this logic between miri, cg_llvm and cg_clif
|
||||
|
||||
use crate::prelude::*;
|
||||
|
|
|
@ -19,7 +19,7 @@ pub struct Svh {
|
|||
impl Svh {
|
||||
/// Creates a new `Svh` given the hash. If you actually want to
|
||||
/// compute the SVH from some HIR, you want the `calculate_svh`
|
||||
/// function found in `librustc_incremental`.
|
||||
/// function found in `rustc_incremental`.
|
||||
pub fn new(hash: u64) -> Svh {
|
||||
Svh { hash }
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! # Feature gates
|
||||
//!
|
||||
//! This crate declares the set of past and present unstable features in the compiler.
|
||||
//! Feature gate checking itself is done in `librustc_ast_passes/feature_gate.rs`
|
||||
//! Feature gate checking itself is done in `rustc_ast_passes/src/feature_gate.rs`
|
||||
//! at the moment.
|
||||
//!
|
||||
//! Features are enabled in programs via the crate-level attributes of
|
||||
|
|
|
@ -9,7 +9,7 @@ use rustc_span::def_id::{DefPathHash, LocalDefId};
|
|||
|
||||
/// Requirements for a `StableHashingContext` to be used in this crate.
|
||||
/// This is a hack to allow using the `HashStable_Generic` derive macro
|
||||
/// instead of implementing everything in librustc_middle.
|
||||
/// instead of implementing everything in `rustc_middle`.
|
||||
pub trait HashStableContext:
|
||||
rustc_ast::HashStableContext + rustc_target::HashStableContext
|
||||
{
|
||||
|
|
|
@ -18,8 +18,8 @@ pub static WEAK_ITEMS_REFS: SyncLazy<StableMap<Symbol, LangItem>> = SyncLazy::ne
|
|||
map
|
||||
});
|
||||
|
||||
/// The `check_name` argument avoids the need for `librustc_hir` to depend on
|
||||
/// `librustc_session`.
|
||||
/// The `check_name` argument avoids the need for `rustc_hir` to depend on
|
||||
/// `rustc_session`.
|
||||
pub fn link_name<'a, F>(check_name: F, attrs: &'a [ast::Attribute]) -> Option<Symbol>
|
||||
where
|
||||
F: Fn(&'a ast::Attribute, Symbol) -> bool
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//!
|
||||
//! - **Type inference.** The type inference code can be found in the `infer` module;
|
||||
//! this code handles low-level equality and subtyping operations. The
|
||||
//! type check pass in the compiler is found in the `librustc_typeck` crate.
|
||||
//! type check pass in the compiler is found in the `rustc_typeck` crate.
|
||||
//!
|
||||
//! For more information about how rustc works, see the [rustc dev guide].
|
||||
//!
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
//! Low-level Rust lexer.
|
||||
//!
|
||||
//! The idea with `librustc_lexer` is to make a reusable library,
|
||||
//! The idea with `rustc_lexer` is to make a reusable library,
|
||||
//! by separating out pure lexing and rustc-specific concerns, like spans,
|
||||
//! error reporting, and interning. So, rustc_lexer operates directly on `&str`,
|
||||
//! produces simple tokens which are a pair of type-tag and a bit of original text,
|
||||
//! and does not report errors, instead storing them as flags on the token.
|
||||
//!
|
||||
//! Tokens produced by this lexer are not yet ready for parsing the Rust syntax.
|
||||
//! For that see [`librustc_parse::lexer`], which converts this basic token stream
|
||||
//! For that see [`rustc_parse::lexer`], which converts this basic token stream
|
||||
//! into wide tokens used by actual parser.
|
||||
//!
|
||||
//! The purpose of this crate is to convert raw sources into a labeled sequence
|
||||
|
@ -17,7 +17,7 @@
|
|||
//! The main entity of this crate is the [`TokenKind`] enum which represents common
|
||||
//! lexeme types.
|
||||
//!
|
||||
//! [`librustc_parse::lexer`]: ../rustc_parse/lexer/index.html
|
||||
//! [`rustc_parse::lexer`]: ../rustc_parse/lexer/index.html
|
||||
// We want to be able to build this crate with a stable compiler, so no
|
||||
// `#![feature]` attributes should be added.
|
||||
|
||||
|
|
|
@ -2280,7 +2280,7 @@ declare_lint! {
|
|||
}
|
||||
|
||||
declare_lint_pass!(
|
||||
/// Check for used feature gates in `INCOMPLETE_FEATURES` in `librustc_feature/active.rs`.
|
||||
/// Check for used feature gates in `INCOMPLETE_FEATURES` in `rustc_feature/src/active.rs`.
|
||||
IncompleteFeatures => [INCOMPLETE_FEATURES]
|
||||
);
|
||||
|
||||
|
|
|
@ -464,15 +464,16 @@ impl<'s> LintLevelsBuilder<'s> {
|
|||
// we don't warn about the name change.
|
||||
if let CheckLintNameResult::Warning(_, Some(new_name)) = lint_result {
|
||||
// Ignore any errors or warnings that happen because the new name is inaccurate
|
||||
if let CheckLintNameResult::Ok(ids) =
|
||||
store.check_lint_name(&new_name, tool_name)
|
||||
{
|
||||
// NOTE: `new_name` already includes the tool name, so we don't have to add it again.
|
||||
if let CheckLintNameResult::Ok(ids) = store.check_lint_name(&new_name, None) {
|
||||
let src =
|
||||
LintLevelSource::Node(Symbol::intern(&new_name), li.span(), reason);
|
||||
for &id in ids {
|
||||
self.check_gated_lint(id, attr.span);
|
||||
self.insert_spec(&mut specs, id, (level, src));
|
||||
}
|
||||
} else {
|
||||
panic!("renamed lint does not exist: {}", new_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//! This module contains `HashStable` implementations for various data types
|
||||
//! from librustc_ast in no particular order.
|
||||
//! from `rustc_ast` in no particular order.
|
||||
|
||||
use crate::ich::StableHashingContext;
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ static_assert_size!(InterpErrorInfo<'_>, 8);
|
|||
/// Packages the kind of error we got from the const code interpreter
|
||||
/// up with a Rust-level backtrace of where the error occurred.
|
||||
/// Thsese should always be constructed by calling `.into()` on
|
||||
/// a `InterpError`. In `librustc_mir::interpret`, we have `throw_err_*`
|
||||
/// a `InterpError`. In `rustc_mir::interpret`, we have `throw_err_*`
|
||||
/// macros for this.
|
||||
#[derive(Debug)]
|
||||
pub struct InterpErrorInfo<'tcx>(Box<InterpErrorInfoInner<'tcx>>);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
//! which makes a canonical query by replacing unbound inference
|
||||
//! variables and regions, so that results can be reused more broadly.
|
||||
//! The providers for the queries defined here can be found in
|
||||
//! `librustc_traits`.
|
||||
//! `rustc_traits`.
|
||||
|
||||
use crate::ich::StableHashingContext;
|
||||
use crate::infer::canonical::{Canonical, QueryResponse};
|
||||
|
|
|
@ -44,7 +44,7 @@ use std::ops::ControlFlow;
|
|||
/// This trait is implemented for every type that can be folded.
|
||||
/// Basically, every type that has a corresponding method in `TypeFolder`.
|
||||
///
|
||||
/// To implement this conveniently, use the derive macro located in librustc_macros.
|
||||
/// To implement this conveniently, use the derive macro located in `rustc_macros`.
|
||||
pub trait TypeFoldable<'tcx>: fmt::Debug + Clone {
|
||||
fn super_fold_with<F: TypeFolder<'tcx>>(self, folder: &mut F) -> Self;
|
||||
fn fold_with<F: TypeFolder<'tcx>>(self, folder: &mut F) -> Self {
|
||||
|
|
|
@ -80,7 +80,7 @@ impl BoundRegionKind {
|
|||
/// Defines the kinds of types.
|
||||
///
|
||||
/// N.B., if you change this, you'll probably want to change the corresponding
|
||||
/// AST structure in `librustc_ast/ast.rs` as well.
|
||||
/// AST structure in `rustc_ast/src/ast.rs` as well.
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, TyEncodable, TyDecodable, Debug)]
|
||||
#[derive(HashStable)]
|
||||
#[rustc_diagnostic_item = "TyKind"]
|
||||
|
@ -2116,7 +2116,7 @@ impl<'tcx> TyS<'tcx> {
|
|||
///
|
||||
/// Note that during type checking, we use an inference variable
|
||||
/// to represent the closure kind, because it has not yet been
|
||||
/// inferred. Once upvar inference (in `src/librustc_typeck/check/upvar.rs`)
|
||||
/// inferred. Once upvar inference (in `rustc_typeck/src/check/upvar.rs`)
|
||||
/// is complete, that type variable will be unified.
|
||||
pub fn to_opt_closure_kind(&self) -> Option<ty::ClosureKind> {
|
||||
match self.kind() {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//! This module provides linkage between RegionInferenceContext and
|
||||
//! librustc_graphviz traits, specialized to attaching borrowck analysis
|
||||
//! `rustc_graphviz` traits, specialized to attaching borrowck analysis
|
||||
//! data to rendered labels.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
|
|
@ -128,7 +128,7 @@ impl<'a> StringReader<'a> {
|
|||
}
|
||||
|
||||
/// Turns simple `rustc_lexer::TokenKind` enum into a rich
|
||||
/// `librustc_ast::TokenKind`. This turns strings into interned
|
||||
/// `rustc_ast::TokenKind`. This turns strings into interned
|
||||
/// symbols and runs additional validation.
|
||||
fn cook_lexer_token(&self, token: rustc_lexer::TokenKind, start: BytePos) -> Option<TokenKind> {
|
||||
Some(match token {
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
//! could not be instantiated because the current compilation session
|
||||
//! contained no `DefId` for thing that had been removed.
|
||||
//!
|
||||
//! `DepNode` definition happens in `librustc_middle` with the `define_dep_nodes!()` macro.
|
||||
//! `DepNode` definition happens in `rustc_middle` with the `define_dep_nodes!()` macro.
|
||||
//! This macro defines the `DepKind` enum and a corresponding `DepConstructor` enum. The
|
||||
//! `DepConstructor` enum links a `DepKind` to the parameters that are needed at runtime in order
|
||||
//! to construct a valid `DepNode` fingerprint.
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
//! Paths in macros, imports, expressions, types, patterns are resolved here.
|
||||
//! Label and lifetime names are resolved here as well.
|
||||
//!
|
||||
//! Type-relative name resolution (methods, fields, associated items) happens in `librustc_typeck`.
|
||||
//! Type-relative name resolution (methods, fields, associated items) happens in `rustc_typeck`.
|
||||
|
||||
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
|
||||
#![feature(box_patterns)]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
`librustc_target` contains some very low-level details that are
|
||||
`rustc_target` contains some very low-level details that are
|
||||
specific to different compilation targets and so forth.
|
||||
|
||||
For more information about how rustc works, see the [rustc dev guide].
|
||||
|
|
|
@ -28,5 +28,5 @@ pub mod spec;
|
|||
|
||||
/// Requirements for a `StableHashingContext` to be used in this crate.
|
||||
/// This is a hack to allow using the `HashStable_Generic` derive macro
|
||||
/// instead of implementing everything in librustc_middle.
|
||||
/// instead of implementing everything in `rustc_middle`.
|
||||
pub trait HashStableContext {}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
//! which makes a canonical query by replacing unbound inference
|
||||
//! variables and regions, so that results can be reused more broadly.
|
||||
//! The providers for the queries defined here can be found in
|
||||
//! `librustc_traits`.
|
||||
//! `rustc_traits`.
|
||||
|
||||
pub mod dropck_outlives;
|
||||
pub mod evaluate_obligation;
|
||||
|
|
|
@ -233,7 +233,6 @@ impl<I: Iterator> Peekable<I> {
|
|||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(peekable_peek_mut)]
|
||||
/// let mut iter = [1, 2, 3].iter().peekable();
|
||||
///
|
||||
/// // Like with `peek()`, we can see into the future without advancing the iterator.
|
||||
|
@ -251,7 +250,7 @@ impl<I: Iterator> Peekable<I> {
|
|||
/// assert_eq!(iter.collect::<Vec<_>>(), vec![&5, &3]);
|
||||
/// ```
|
||||
#[inline]
|
||||
#[unstable(feature = "peekable_peek_mut", issue = "78302")]
|
||||
#[stable(feature = "peekable_peek_mut", since = "1.53.0")]
|
||||
pub fn peek_mut(&mut self) -> Option<&mut I::Item> {
|
||||
let iter = &mut self.iter;
|
||||
self.peeked.get_or_insert_with(|| iter.next()).as_mut()
|
||||
|
|
|
@ -937,20 +937,16 @@ pub trait Iterator {
|
|||
Enumerate::new(self)
|
||||
}
|
||||
|
||||
/// Creates an iterator which can use [`peek`] to look at the next element of
|
||||
/// the iterator without consuming it.
|
||||
/// Creates an iterator which can use the [`peek`] and [`peek_mut`] methods
|
||||
/// to look at the next element of the iterator without consuming it. See
|
||||
/// their documentation for more information.
|
||||
///
|
||||
/// Adds a [`peek`] method to an iterator. See its documentation for
|
||||
/// more information.
|
||||
/// Note that the underlying iterator is still advanced when [`peek`] or
|
||||
/// [`peek_mut`] are called for the first time: In order to retrieve the
|
||||
/// next element, [`next`] is called on the underlying iterator, hence any
|
||||
/// side effects (i.e. anything other than fetching the next value) of
|
||||
/// the [`next`] method will occur.
|
||||
///
|
||||
/// Note that the underlying iterator is still advanced when [`peek`] is
|
||||
/// called for the first time: In order to retrieve the next element,
|
||||
/// [`next`] is called on the underlying iterator, hence any side effects (i.e.
|
||||
/// anything other than fetching the next value) of the [`next`] method
|
||||
/// will occur.
|
||||
///
|
||||
/// [`peek`]: Peekable::peek
|
||||
/// [`next`]: Iterator::next
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -977,6 +973,32 @@ pub trait Iterator {
|
|||
/// assert_eq!(iter.peek(), None);
|
||||
/// assert_eq!(iter.next(), None);
|
||||
/// ```
|
||||
///
|
||||
/// Using [`peek_mut`] to mutate the next item without advancing the
|
||||
/// iterator:
|
||||
///
|
||||
/// ```
|
||||
/// let xs = [1, 2, 3];
|
||||
///
|
||||
/// let mut iter = xs.iter().peekable();
|
||||
///
|
||||
/// // `peek_mut()` lets us see into the future
|
||||
/// assert_eq!(iter.peek_mut(), Some(&mut &1));
|
||||
/// assert_eq!(iter.peek_mut(), Some(&mut &1));
|
||||
/// assert_eq!(iter.next(), Some(&1));
|
||||
///
|
||||
/// if let Some(mut p) = iter.peek_mut() {
|
||||
/// assert_eq!(*p, &2);
|
||||
/// // put a value into the iterator
|
||||
/// *p = &1000;
|
||||
/// }
|
||||
///
|
||||
/// // The value reappears as the iterator continues
|
||||
/// assert_eq!(iter.collect::<Vec<_>>(), vec![&1000, &3]);
|
||||
/// ```
|
||||
/// [`peek`]: Peekable::peek
|
||||
/// [`peek_mut`]: Peekable::peek_mut
|
||||
/// [`next`]: Iterator::next
|
||||
#[inline]
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
fn peekable(self) -> Peekable<Self>
|
||||
|
|
|
@ -298,7 +298,8 @@ pub mod primitive;
|
|||
unused_imports,
|
||||
unsafe_op_in_unsafe_fn
|
||||
)]
|
||||
#[allow(rustdoc::non_autolinks)]
|
||||
#[cfg_attr(bootstrap, allow(rustdoc::non_autolinks))]
|
||||
#[cfg_attr(not(bootstrap), allow(rustdoc::bare_urls))]
|
||||
// FIXME: This annotation should be moved into rust-lang/stdarch after clashing_extern_declarations is
|
||||
// merged. It currently cannot because bootstrap fails as the lint hasn't been defined yet.
|
||||
#[allow(clashing_extern_declarations)]
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
//! as moving an object with pointers to itself will invalidate them, which could cause undefined
|
||||
//! behavior.
|
||||
//!
|
||||
//! At a high level, a [`Pin<P>`] ensures that the pointee of any pointer type
|
||||
//! At a high level, a <code>[Pin]\<P></code> ensures that the pointee of any pointer type
|
||||
//! `P` has a stable location in memory, meaning it cannot be moved elsewhere
|
||||
//! and its memory cannot be deallocated until it gets dropped. We say that the
|
||||
//! pointee is "pinned". Things get more subtle when discussing types that
|
||||
|
@ -14,13 +14,15 @@
|
|||
//! for more details.
|
||||
//!
|
||||
//! By default, all types in Rust are movable. Rust allows passing all types by-value,
|
||||
//! and common smart-pointer types such as [`Box<T>`] and `&mut T` allow replacing and
|
||||
//! moving the values they contain: you can move out of a [`Box<T>`], or you can use [`mem::swap`].
|
||||
//! [`Pin<P>`] wraps a pointer type `P`, so [`Pin`]`<`[`Box`]`<T>>` functions much like a regular
|
||||
//! [`Box<T>`]: when a [`Pin`]`<`[`Box`]`<T>>` gets dropped, so do its contents, and the memory gets
|
||||
//! deallocated. Similarly, [`Pin`]`<&mut T>` is a lot like `&mut T`. However, [`Pin<P>`] does
|
||||
//! not let clients actually obtain a [`Box<T>`] or `&mut T` to pinned data, which implies that you
|
||||
//! cannot use operations such as [`mem::swap`]:
|
||||
//! and common smart-pointer types such as <code>[Box]\<T></code> and <code>[&mut] T</code> allow
|
||||
//! replacing and moving the values they contain: you can move out of a <code>[Box]\<T></code>,
|
||||
//! or you can use [`mem::swap`]. <code>[Pin]\<P></code> wraps a pointer type `P`, so
|
||||
//! <code>[Pin]<[Box]\<T>></code> functions much like a regular <code>[Box]\<T></code>:
|
||||
//! when a <code>[Pin]<[Box]\<T>></code> gets dropped, so do its contents, and the memory gets
|
||||
//! deallocated. Similarly, <code>[Pin]<[&mut] T></code> is a lot like <code>[&mut] T</code>.
|
||||
//! However, <code>[Pin]\<P></code> does not let clients actually obtain a <code>[Box]\<T></code>
|
||||
//! or <code>[&mut] T</code> to pinned data, which implies that you cannot use operations such
|
||||
//! as [`mem::swap`]:
|
||||
//!
|
||||
//! ```
|
||||
//! use std::pin::Pin;
|
||||
|
@ -32,18 +34,18 @@
|
|||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! It is worth reiterating that [`Pin<P>`] does *not* change the fact that a Rust compiler
|
||||
//! considers all types movable. [`mem::swap`] remains callable for any `T`. Instead, [`Pin<P>`]
|
||||
//! prevents certain *values* (pointed to by pointers wrapped in [`Pin<P>`]) from being
|
||||
//! moved by making it impossible to call methods that require `&mut T` on them
|
||||
//! (like [`mem::swap`]).
|
||||
//! It is worth reiterating that <code>[Pin]\<P></code> does *not* change the fact that a Rust
|
||||
//! compiler considers all types movable. [`mem::swap`] remains callable for any `T`. Instead,
|
||||
//! <code>[Pin]\<P></code> prevents certain *values* (pointed to by pointers wrapped in
|
||||
//! <code>[Pin]\<P></code>) from being moved by making it impossible to call methods that require
|
||||
//! <code>[&mut] T</code> on them (like [`mem::swap`]).
|
||||
//!
|
||||
//! [`Pin<P>`] can be used to wrap any pointer type `P`, and as such it interacts with
|
||||
//! [`Deref`] and [`DerefMut`]. A [`Pin<P>`] where `P: Deref` should be considered
|
||||
//! as a "`P`-style pointer" to a pinned `P::Target` -- so, a [`Pin`]`<`[`Box`]`<T>>` is
|
||||
//! an owned pointer to a pinned `T`, and a [`Pin`]`<`[`Rc`]`<T>>` is a reference-counted
|
||||
//! pointer to a pinned `T`.
|
||||
//! For correctness, [`Pin<P>`] relies on the implementations of [`Deref`] and
|
||||
//! <code>[Pin]\<P></code> can be used to wrap any pointer type `P`, and as such it interacts with
|
||||
//! [`Deref`] and [`DerefMut`]. A <code>[Pin]\<P></code> where <code>P: [Deref]</code> should be
|
||||
//! considered as a "`P`-style pointer" to a pinned <code>P::[Target]</code> – so, a
|
||||
//! <code>[Pin]<[Box]\<T>></code> is an owned pointer to a pinned `T`, and a
|
||||
//! <code>[Pin]<[Rc]\<T>></code> is a reference-counted pointer to a pinned `T`.
|
||||
//! For correctness, <code>[Pin]\<P></code> relies on the implementations of [`Deref`] and
|
||||
//! [`DerefMut`] not to move out of their `self` parameter, and only ever to
|
||||
//! return a pointer to pinned data when they are called on a pinned pointer.
|
||||
//!
|
||||
|
@ -53,19 +55,19 @@
|
|||
//! rely on having a stable address. This includes all the basic types (like
|
||||
//! [`bool`], [`i32`], and references) as well as types consisting solely of these
|
||||
//! types. Types that do not care about pinning implement the [`Unpin`]
|
||||
//! auto-trait, which cancels the effect of [`Pin<P>`]. For `T: Unpin`,
|
||||
//! [`Pin`]`<`[`Box`]`<T>>` and [`Box<T>`] function identically, as do [`Pin`]`<&mut T>` and
|
||||
//! `&mut T`.
|
||||
//! auto-trait, which cancels the effect of <code>[Pin]\<P></code>. For <code>T: [Unpin]</code>,
|
||||
//! <code>[Pin]<[Box]\<T>></code> and <code>[Box]\<T></code> function identically, as do
|
||||
//! <code>[Pin]<[&mut] T></code> and <code>[&mut] T</code>.
|
||||
//!
|
||||
//! Note that pinning and [`Unpin`] only affect the pointed-to type `P::Target`, not the pointer
|
||||
//! type `P` itself that got wrapped in [`Pin<P>`]. For example, whether or not [`Box<T>`] is
|
||||
//! [`Unpin`] has no effect on the behavior of [`Pin`]`<`[`Box`]`<T>>` (here, `T` is the
|
||||
//! pointed-to type).
|
||||
//! Note that pinning and [`Unpin`] only affect the pointed-to type <code>P::[Target]</code>,
|
||||
//! not the pointer type `P` itself that got wrapped in <code>[Pin]\<P></code>. For example,
|
||||
//! whether or not <code>[Box]\<T></code> is [`Unpin`] has no effect on the behavior of
|
||||
//! <code>[Pin]<[Box]\<T>></code> (here, `T` is the pointed-to type).
|
||||
//!
|
||||
//! # Example: self-referential struct
|
||||
//!
|
||||
//! Before we go into more details to explain the guarantees and choices
|
||||
//! associated with `Pin<T>`, we discuss some examples for how it might be used.
|
||||
//! associated with <code>[Pin]\<P></code>, we discuss some examples for how it might be used.
|
||||
//! Feel free to [skip to where the theoretical discussion continues](#drop-guarantee).
|
||||
//!
|
||||
//! ```rust
|
||||
|
@ -129,7 +131,7 @@
|
|||
//!
|
||||
//! To make this work, every element has pointers to its predecessor and successor in
|
||||
//! the list. Elements can only be added when they are pinned, because moving the elements
|
||||
//! around would invalidate the pointers. Moreover, the [`Drop`] implementation of a linked
|
||||
//! around would invalidate the pointers. Moreover, the [`Drop`][Drop] implementation of a linked
|
||||
//! list element will patch the pointers of its predecessor and successor to remove itself
|
||||
//! from the list.
|
||||
//!
|
||||
|
@ -149,8 +151,8 @@
|
|||
//! when [`drop`] is called*. Only once [`drop`] returns or panics, the memory may be reused.
|
||||
//!
|
||||
//! Memory can be "invalidated" by deallocation, but also by
|
||||
//! replacing a [`Some(v)`] by [`None`], or calling [`Vec::set_len`] to "kill" some elements
|
||||
//! off of a vector. It can be repurposed by using [`ptr::write`] to overwrite it without
|
||||
//! replacing a <code>[Some]\(v)</code> by [`None`], or calling [`Vec::set_len`] to "kill" some
|
||||
//! elements off of a vector. It can be repurposed by using [`ptr::write`] to overwrite it without
|
||||
//! calling the destructor first. None of this is allowed for pinned data without calling [`drop`].
|
||||
//!
|
||||
//! This is exactly the kind of guarantee that the intrusive linked list from the previous
|
||||
|
@ -158,25 +160,25 @@
|
|||
//!
|
||||
//! Notice that this guarantee does *not* mean that memory does not leak! It is still
|
||||
//! completely okay not ever to call [`drop`] on a pinned element (e.g., you can still
|
||||
//! call [`mem::forget`] on a [`Pin`]`<`[`Box`]`<T>>`). In the example of the doubly-linked
|
||||
//! call [`mem::forget`] on a <code>[Pin]<[Box]\<T>></code>). In the example of the doubly-linked
|
||||
//! list, that element would just stay in the list. However you may not free or reuse the storage
|
||||
//! *without calling [`drop`]*.
|
||||
//!
|
||||
//! # `Drop` implementation
|
||||
//!
|
||||
//! If your type uses pinning (such as the two examples above), you have to be careful
|
||||
//! when implementing [`Drop`]. The [`drop`] function takes `&mut self`, but this
|
||||
//! when implementing [`Drop`][Drop]. The [`drop`] function takes <code>[&mut] self</code>, but this
|
||||
//! is called *even if your type was previously pinned*! It is as if the
|
||||
//! compiler automatically called [`Pin::get_unchecked_mut`].
|
||||
//!
|
||||
//! This can never cause a problem in safe code because implementing a type that
|
||||
//! relies on pinning requires unsafe code, but be aware that deciding to make
|
||||
//! use of pinning in your type (for example by implementing some operation on
|
||||
//! [`Pin`]`<&Self>` or [`Pin`]`<&mut Self>`) has consequences for your [`Drop`]
|
||||
//! implementation as well: if an element of your type could have been pinned,
|
||||
//! you must treat [`Drop`] as implicitly taking [`Pin`]`<&mut Self>`.
|
||||
//! <code>[Pin]<[&]Self></code> or <code>[Pin]<[&mut] Self></code>) has consequences for your
|
||||
//! [`Drop`][Drop]implementation as well: if an element of your type could have been pinned,
|
||||
//! you must treat [`Drop`][Drop] as implicitly taking <code>[Pin]<[&mut] Self></code>.
|
||||
//!
|
||||
//! For example, you could implement `Drop` as follows:
|
||||
//! For example, you could implement [`Drop`][Drop] as follows:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! # use std::pin::Pin;
|
||||
|
@ -204,18 +206,18 @@
|
|||
//! # Projections and Structural Pinning
|
||||
//!
|
||||
//! When working with pinned structs, the question arises how one can access the
|
||||
//! fields of that struct in a method that takes just [`Pin`]`<&mut Struct>`.
|
||||
//! fields of that struct in a method that takes just <code>[Pin]<[&mut] Struct></code>.
|
||||
//! The usual approach is to write helper methods (so called *projections*)
|
||||
//! that turn [`Pin`]`<&mut Struct>` into a reference to the field, but what
|
||||
//! type should that reference have? Is it [`Pin`]`<&mut Field>` or `&mut Field`?
|
||||
//! that turn <code>[Pin]<[&mut] Struct></code> into a reference to the field, but what type should
|
||||
//! that reference have? Is it <code>[Pin]<[&mut] Field></code> or <code>[&mut] Field</code>?
|
||||
//! The same question arises with the fields of an `enum`, and also when considering
|
||||
//! container/wrapper types such as [`Vec<T>`], [`Box<T>`], or [`RefCell<T>`].
|
||||
//! (This question applies to both mutable and shared references, we just
|
||||
//! use the more common case of mutable references here for illustration.)
|
||||
//! container/wrapper types such as <code>[Vec]\<T></code>, <code>[Box]\<T></code>,
|
||||
//! or <code>[RefCell]\<T></code>. (This question applies to both mutable and shared references,
|
||||
//! we just use the more common case of mutable references here for illustration.)
|
||||
//!
|
||||
//! It turns out that it is actually up to the author of the data structure
|
||||
//! to decide whether the pinned projection for a particular field turns
|
||||
//! [`Pin`]`<&mut Struct>` into [`Pin`]`<&mut Field>` or `&mut Field`. There are some
|
||||
//! It turns out that it is actually up to the author of the data structure to decide whether
|
||||
//! the pinned projection for a particular field turns <code>[Pin]<[&mut] Struct></code>
|
||||
//! into <code>[Pin]<[&mut] Field></code> or <code>[&mut] Field</code>. There are some
|
||||
//! constraints though, and the most important constraint is *consistency*:
|
||||
//! every field can be *either* projected to a pinned reference, *or* have
|
||||
//! pinning removed as part of the projection. If both are done for the same field,
|
||||
|
@ -230,12 +232,12 @@
|
|||
//! ## Pinning *is not* structural for `field`
|
||||
//!
|
||||
//! It may seem counter-intuitive that the field of a pinned struct might not be pinned,
|
||||
//! but that is actually the easiest choice: if a [`Pin`]`<&mut Field>` is never created,
|
||||
//! but that is actually the easiest choice: if a <code>[Pin]<[&mut] Field></code> is never created,
|
||||
//! nothing can go wrong! So, if you decide that some field does not have structural pinning,
|
||||
//! all you have to ensure is that you never create a pinned reference to that field.
|
||||
//!
|
||||
//! Fields without structural pinning may have a projection method that turns
|
||||
//! [`Pin`]`<&mut Struct>` into `&mut Field`:
|
||||
//! <code>[Pin]<[&mut] Struct></code> into <code>[&mut] Field</code>:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! # use std::pin::Pin;
|
||||
|
@ -249,16 +251,16 @@
|
|||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! You may also `impl Unpin for Struct` *even if* the type of `field`
|
||||
//! You may also <code>impl [Unpin] for Struct</code> *even if* the type of `field`
|
||||
//! is not [`Unpin`]. What that type thinks about pinning is not relevant
|
||||
//! when no [`Pin`]`<&mut Field>` is ever created.
|
||||
//! when no <code>[Pin]<[&mut] Field></code> is ever created.
|
||||
//!
|
||||
//! ## Pinning *is* structural for `field`
|
||||
//!
|
||||
//! The other option is to decide that pinning is "structural" for `field`,
|
||||
//! meaning that if the struct is pinned then so is the field.
|
||||
//!
|
||||
//! This allows writing a projection that creates a [`Pin`]`<&mut Field>`, thus
|
||||
//! This allows writing a projection that creates a <code>[Pin]<[&mut] Field></code>, thus
|
||||
//! witnessing that the field is pinned:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
|
@ -278,34 +280,36 @@
|
|||
//! 1. The struct must only be [`Unpin`] if all the structural fields are
|
||||
//! [`Unpin`]. This is the default, but [`Unpin`] is a safe trait, so as the author of
|
||||
//! the struct it is your responsibility *not* to add something like
|
||||
//! `impl<T> Unpin for Struct<T>`. (Notice that adding a projection operation
|
||||
//! <code>impl\<T> [Unpin] for Struct\<T></code>. (Notice that adding a projection operation
|
||||
//! requires unsafe code, so the fact that [`Unpin`] is a safe trait does not break
|
||||
//! the principle that you only have to worry about any of this if you use `unsafe`.)
|
||||
//! the principle that you only have to worry about any of this if you use [`unsafe`].)
|
||||
//! 2. The destructor of the struct must not move structural fields out of its argument. This
|
||||
//! is the exact point that was raised in the [previous section][drop-impl]: `drop` takes
|
||||
//! `&mut self`, but the struct (and hence its fields) might have been pinned before.
|
||||
//! You have to guarantee that you do not move a field inside your [`Drop`] implementation.
|
||||
//! In particular, as explained previously, this means that your struct must *not*
|
||||
//! be `#[repr(packed)]`.
|
||||
//! is the exact point that was raised in the [previous section][drop-impl]: [`drop`] takes
|
||||
//! <code>[&mut] self</code>, but the struct (and hence its fields) might have been pinned
|
||||
//! before. You have to guarantee that you do not move a field inside your [`Drop`][Drop]
|
||||
//! implementation. In particular, as explained previously, this means that your struct
|
||||
//! must *not* be `#[repr(packed)]`.
|
||||
//! See that section for how to write [`drop`] in a way that the compiler can help you
|
||||
//! not accidentally break pinning.
|
||||
//! 3. You must make sure that you uphold the [`Drop` guarantee][drop-guarantee]:
|
||||
//! once your struct is pinned, the memory that contains the
|
||||
//! content is not overwritten or deallocated without calling the content's destructors.
|
||||
//! This can be tricky, as witnessed by [`VecDeque<T>`]: the destructor of [`VecDeque<T>`]
|
||||
//! can fail to call [`drop`] on all elements if one of the destructors panics. This violates
|
||||
//! the [`Drop`] guarantee, because it can lead to elements being deallocated without
|
||||
//! their destructor being called. ([`VecDeque<T>`] has no pinning projections, so this
|
||||
//! This can be tricky, as witnessed by <code>[VecDeque]\<T></code>: the destructor of
|
||||
//! <code>[VecDeque]\<T></code> can fail to call [`drop`] on all elements if one of the
|
||||
//! destructors panics. This violates the [`Drop`][Drop] guarantee, because it can lead to
|
||||
//! elements being deallocated without their destructor being called.
|
||||
//! (<code>[VecDeque]\<T></code> has no pinning projections, so this
|
||||
//! does not cause unsoundness.)
|
||||
//! 4. You must not offer any other operations that could lead to data being moved out of
|
||||
//! the structural fields when your type is pinned. For example, if the struct contains an
|
||||
//! [`Option<T>`] and there is a `take`-like operation with type
|
||||
//! `fn(Pin<&mut Struct<T>>) -> Option<T>`,
|
||||
//! that operation can be used to move a `T` out of a pinned `Struct<T>` -- which means
|
||||
//! <code>[Option]\<T></code> and there is a [`take`][Option::take]-like operation with type
|
||||
//! <code>fn([Pin]<[&mut] Struct\<T>>) -> [Option]\<T></code>,
|
||||
//! that operation can be used to move a `T` out of a pinned `Struct<T>` – which means
|
||||
//! pinning cannot be structural for the field holding this data.
|
||||
//!
|
||||
//! For a more complex example of moving data out of a pinned type, imagine if [`RefCell<T>`]
|
||||
//! had a method `fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T>`.
|
||||
//! For a more complex example of moving data out of a pinned type,
|
||||
//! imagine if <code>[RefCell]\<T></code> had a method
|
||||
//! <code>fn get_pin_mut(self: [Pin]<[&mut] Self>) -> [Pin]<[&mut] T></code>.
|
||||
//! Then we could do the following:
|
||||
//! ```compile_fail
|
||||
//! fn exploit_ref_cell<T>(rc: Pin<&mut RefCell<T>>) {
|
||||
|
@ -315,60 +319,65 @@
|
|||
//! let content = &mut *b; // And here we have `&mut T` to the same data.
|
||||
//! }
|
||||
//! ```
|
||||
//! This is catastrophic, it means we can first pin the content of the [`RefCell<T>`]
|
||||
//! (using `RefCell::get_pin_mut`) and then move that content using the mutable
|
||||
//! reference we got later.
|
||||
//! This is catastrophic, it means we can first pin the content of the
|
||||
//! <code>[RefCell]\<T></code> (using <code>[RefCell]::get_pin_mut</code>) and then move that
|
||||
//! content using the mutable reference we got later.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! For a type like [`Vec<T>`], both possibilities (structural pinning or not) make sense.
|
||||
//! A [`Vec<T>`] with structural pinning could have `get_pin`/`get_pin_mut` methods to get
|
||||
//! pinned references to elements. However, it could *not* allow calling
|
||||
//! [`pop`][Vec::pop] on a pinned [`Vec<T>`] because that would move the (structurally pinned)
|
||||
//! contents! Nor could it allow [`push`][Vec::push], which might reallocate and thus also move the
|
||||
//! contents.
|
||||
//! For a type like <code>[Vec]\<T></code>, both possibilities (structural pinning or not) make
|
||||
//! sense. A <code>[Vec]\<T></code> with structural pinning could have `get_pin`/`get_pin_mut`
|
||||
//! methods to get pinned references to elements. However, it could *not* allow calling
|
||||
//! [`pop`][Vec::pop] on a pinned <code>[Vec]\<T></code> because that would move the (structurally
|
||||
//! pinned) contents! Nor could it allow [`push`][Vec::push], which might reallocate and thus also
|
||||
//! move the contents.
|
||||
//!
|
||||
//! A [`Vec<T>`] without structural pinning could `impl<T> Unpin for Vec<T>`, because the contents
|
||||
//! are never pinned and the [`Vec<T>`] itself is fine with being moved as well.
|
||||
//! A <code>[Vec]\<T></code> without structural pinning could
|
||||
//! <code>impl\<T> [Unpin] for [Vec]\<T></code>, because the contents are never pinned
|
||||
//! and the <code>[Vec]\<T></code> itself is fine with being moved as well.
|
||||
//! At that point pinning just has no effect on the vector at all.
|
||||
//!
|
||||
//! In the standard library, pointer types generally do not have structural pinning,
|
||||
//! and thus they do not offer pinning projections. This is why `Box<T>: Unpin` holds for all `T`.
|
||||
//! It makes sense to do this for pointer types, because moving the `Box<T>`
|
||||
//! does not actually move the `T`: the [`Box<T>`] can be freely movable (aka `Unpin`) even if
|
||||
//! the `T` is not. In fact, even [`Pin`]`<`[`Box`]`<T>>` and [`Pin`]`<&mut T>` are always
|
||||
//! [`Unpin`] themselves, for the same reason: their contents (the `T`) are pinned, but the
|
||||
//! pointers themselves can be moved without moving the pinned data. For both [`Box<T>`] and
|
||||
//! [`Pin`]`<`[`Box`]`<T>>`, whether the content is pinned is entirely independent of whether the
|
||||
//! and thus they do not offer pinning projections. This is why <code>[Box]\<T>: [Unpin]</code>
|
||||
//! holds for all `T`. It makes sense to do this for pointer types, because moving the
|
||||
//! <code>[Box]\<T></code> does not actually move the `T`: the <code>[Box]\<T></code> can be freely
|
||||
//! movable (aka [`Unpin`]) even if the `T` is not. In fact, even <code>[Pin]<[Box]\<T>></code> and
|
||||
//! <code>[Pin]<[&mut] T></code> are always [`Unpin`] themselves, for the same reason:
|
||||
//! their contents (the `T`) are pinned, but the pointers themselves can be moved without moving
|
||||
//! the pinned data. For both <code>[Box]\<T></code> and <code>[Pin]<[Box]\<T>></code>,
|
||||
//! whether the content is pinned is entirely independent of whether the
|
||||
//! pointer is pinned, meaning pinning is *not* structural.
|
||||
//!
|
||||
//! When implementing a [`Future`] combinator, you will usually need structural pinning
|
||||
//! for the nested futures, as you need to get pinned references to them to call [`poll`].
|
||||
//! But if your combinator contains any other data that does not need to be pinned,
|
||||
//! you can make those fields not structural and hence freely access them with a
|
||||
//! mutable reference even when you just have [`Pin`]`<&mut Self>` (such as in your own
|
||||
//! mutable reference even when you just have <code>[Pin]<[&mut] Self></code> (such as in your own
|
||||
//! [`poll`] implementation).
|
||||
//!
|
||||
//! [`Deref`]: crate::ops::Deref
|
||||
//! [`DerefMut`]: crate::ops::DerefMut
|
||||
//! [`mem::swap`]: crate::mem::swap
|
||||
//! [`mem::forget`]: crate::mem::forget
|
||||
//! [`Box<T>`]: ../../std/boxed/struct.Box.html
|
||||
//! [`Vec<T>`]: ../../std/vec/struct.Vec.html
|
||||
//! [`Vec::set_len`]: ../../std/vec/struct.Vec.html#method.set_len
|
||||
//! [`Box`]: ../../std/boxed/struct.Box.html
|
||||
//! [Vec::pop]: ../../std/vec/struct.Vec.html#method.pop
|
||||
//! [Vec::push]: ../../std/vec/struct.Vec.html#method.push
|
||||
//! [`Rc`]: ../../std/rc/struct.Rc.html
|
||||
//! [`RefCell<T>`]: crate::cell::RefCell
|
||||
//! [`drop`]: Drop::drop
|
||||
//! [`VecDeque<T>`]: ../../std/collections/struct.VecDeque.html
|
||||
//! [`Some(v)`]: Some
|
||||
//! [`ptr::write`]: crate::ptr::write
|
||||
//! [`Future`]: crate::future::Future
|
||||
//! [Deref]: crate::ops::Deref "ops::Deref"
|
||||
//! [`Deref`]: crate::ops::Deref "ops::Deref"
|
||||
//! [Target]: crate::ops::Deref::Target "ops::Deref::Target"
|
||||
//! [`DerefMut`]: crate::ops::DerefMut "ops::DerefMut"
|
||||
//! [`mem::swap`]: crate::mem::swap "mem::swap"
|
||||
//! [`mem::forget`]: crate::mem::forget "mem::forget"
|
||||
//! [Vec]: ../../std/vec/struct.Vec.html "Vec"
|
||||
//! [`Vec::set_len`]: ../../std/vec/struct.Vec.html#method.set_len "Vec::set_len"
|
||||
//! [Box]: ../../std/boxed/struct.Box.html "Box"
|
||||
//! [Vec::pop]: ../../std/vec/struct.Vec.html#method.pop "Vec::pop"
|
||||
//! [Vec::push]: ../../std/vec/struct.Vec.html#method.push "Vec::push"
|
||||
//! [Rc]: ../../std/rc/struct.Rc.html "rc::Rc"
|
||||
//! [RefCell]: crate::cell::RefCell "cell::RefCell"
|
||||
//! [`drop`]: Drop::drop "Drop::drop"
|
||||
//! [VecDeque]: ../../std/collections/struct.VecDeque.html "collections::VecDeque"
|
||||
//! [`ptr::write`]: crate::ptr::write "ptr::write"
|
||||
//! [`Future`]: crate::future::Future "future::Future"
|
||||
//! [drop-impl]: #drop-implementation
|
||||
//! [drop-guarantee]: #drop-guarantee
|
||||
//! [`poll`]: crate::future::Future::poll
|
||||
//! [`poll`]: crate::future::Future::poll "future::Future::poll"
|
||||
//! [&]: ../../std/primitive.reference.html "shared reference"
|
||||
//! [&mut]: ../../std/primitive.reference.html "mutable reference"
|
||||
//! [`unsafe`]: ../../std/keyword.unsafe.html "keyword unsafe"
|
||||
|
||||
#![stable(feature = "pin", since = "1.33.0")]
|
||||
|
||||
|
|
|
@ -66,7 +66,6 @@
|
|||
#![feature(unwrap_infallible)]
|
||||
#![feature(option_result_unwrap_unchecked)]
|
||||
#![feature(result_into_ok_or_err)]
|
||||
#![feature(peekable_peek_mut)]
|
||||
#![feature(ptr_metadata)]
|
||||
#![feature(once_cell)]
|
||||
#![feature(unsized_tuple_coercion)]
|
||||
|
|
|
@ -294,17 +294,14 @@ warning: unclosed HTML tag `h1`
|
|||
warning: 2 warnings emitted
|
||||
```
|
||||
|
||||
## non_autolinks
|
||||
## bare_urls
|
||||
|
||||
This lint is **nightly-only** and **warns by default**. It detects links which
|
||||
could use the "automatic" link syntax. For example:
|
||||
This lint is **warn-by-default**. It detects URLs which are not links.
|
||||
For example:
|
||||
|
||||
```rust
|
||||
/// http://example.org
|
||||
/// [http://example.com](http://example.com)
|
||||
/// [http://example.net]
|
||||
///
|
||||
/// [http://example.com]: http://example.com
|
||||
pub fn foo() {}
|
||||
```
|
||||
|
||||
|
@ -312,22 +309,18 @@ Which will give:
|
|||
|
||||
```text
|
||||
warning: this URL is not a hyperlink
|
||||
--> foo.rs:1:5
|
||||
--> links.rs:1:5
|
||||
|
|
||||
1 | /// http://example.org
|
||||
| ^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://example.org>`
|
||||
|
|
||||
= note: `#[warn(rustdoc::non_autolinks)]` on by default
|
||||
|
||||
warning: unneeded long form for URL
|
||||
--> foo.rs:2:5
|
||||
|
|
||||
2 | /// [http://example.com](http://example.com)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://example.com>`
|
||||
= note: `#[warn(rustdoc::bare_urls)]` on by default
|
||||
|
||||
warning: this URL is not a hyperlink
|
||||
--> foo.rs:3:6
|
||||
--> links.rs:3:6
|
||||
|
|
||||
3 | /// [http://example.net]
|
||||
| ^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://example.net>`
|
||||
|
||||
warning: 2 warnings emitted
|
||||
```
|
||||
|
|
|
@ -136,6 +136,16 @@ impl Iterator for TokenIter<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_real_ident_class(text: &str, edition: Edition) -> Class {
|
||||
match text {
|
||||
"ref" | "mut" => Class::RefKeyWord,
|
||||
"self" | "Self" => Class::Self_,
|
||||
"false" | "true" => Class::Bool,
|
||||
_ if Symbol::intern(text).is_reserved(|| edition) => Class::KeyWord,
|
||||
_ => Class::Ident,
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes program tokens, classifying strings of text by highlighting
|
||||
/// category (`Class`).
|
||||
struct Classifier<'a> {
|
||||
|
@ -144,6 +154,8 @@ struct Classifier<'a> {
|
|||
in_macro: bool,
|
||||
in_macro_nonterminal: bool,
|
||||
edition: Edition,
|
||||
byte_pos: u32,
|
||||
src: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Classifier<'a> {
|
||||
|
@ -155,6 +167,68 @@ impl<'a> Classifier<'a> {
|
|||
in_macro: false,
|
||||
in_macro_nonterminal: false,
|
||||
edition,
|
||||
byte_pos: 0,
|
||||
src,
|
||||
}
|
||||
}
|
||||
|
||||
/// Concatenate colons and idents as one when possible.
|
||||
fn get_full_ident_path(&mut self) -> Vec<(TokenKind, usize, usize)> {
|
||||
let start = self.byte_pos as usize;
|
||||
let mut pos = start;
|
||||
let mut has_ident = false;
|
||||
let edition = self.edition;
|
||||
|
||||
loop {
|
||||
let mut nb = 0;
|
||||
while let Some((TokenKind::Colon, _)) = self.tokens.peek() {
|
||||
self.tokens.next();
|
||||
nb += 1;
|
||||
}
|
||||
// Ident path can start with "::" but if we already have content in the ident path,
|
||||
// the "::" is mandatory.
|
||||
if has_ident && nb == 0 {
|
||||
return vec![(TokenKind::Ident, start, pos)];
|
||||
} else if nb != 0 && nb != 2 {
|
||||
if has_ident {
|
||||
return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
|
||||
} else {
|
||||
return vec![(TokenKind::Colon, pos, pos + nb)];
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((Class::Ident, text)) = self.tokens.peek().map(|(token, text)| {
|
||||
if *token == TokenKind::Ident {
|
||||
let class = get_real_ident_class(text, edition);
|
||||
(class, text)
|
||||
} else {
|
||||
// Doesn't matter which Class we put in here...
|
||||
(Class::Comment, text)
|
||||
}
|
||||
}) {
|
||||
// We only "add" the colon if there is an ident behind.
|
||||
pos += text.len() + nb;
|
||||
has_ident = true;
|
||||
self.tokens.next();
|
||||
} else if nb > 0 && has_ident {
|
||||
return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
|
||||
} else if nb > 0 {
|
||||
return vec![(TokenKind::Colon, pos, pos + nb)];
|
||||
} else if has_ident {
|
||||
return vec![(TokenKind::Ident, start, pos)];
|
||||
} else {
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps the tokens iteration to ensure that the byte_pos is always correct.
|
||||
fn next(&mut self) -> Option<(TokenKind, &'a str)> {
|
||||
if let Some((kind, text)) = self.tokens.next() {
|
||||
self.byte_pos += text.len() as u32;
|
||||
Some((kind, text))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,8 +239,25 @@ impl<'a> Classifier<'a> {
|
|||
/// token is used.
|
||||
fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'a>)) {
|
||||
with_default_session_globals(|| {
|
||||
while let Some((token, text)) = self.tokens.next() {
|
||||
loop {
|
||||
if self
|
||||
.tokens
|
||||
.peek()
|
||||
.map(|t| matches!(t.0, TokenKind::Colon | TokenKind::Ident))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let tokens = self.get_full_ident_path();
|
||||
for (token, start, end) in tokens {
|
||||
let text = &self.src[start..end];
|
||||
self.advance(token, text, sink);
|
||||
self.byte_pos += text.len() as u32;
|
||||
}
|
||||
}
|
||||
if let Some((token, text)) = self.next() {
|
||||
self.advance(token, text, sink);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -203,12 +294,12 @@ impl<'a> Classifier<'a> {
|
|||
},
|
||||
TokenKind::And => match lookahead {
|
||||
Some(TokenKind::And) => {
|
||||
let _and = self.tokens.next();
|
||||
self.next();
|
||||
sink(Highlight::Token { text: "&&", class: Some(Class::Op) });
|
||||
return;
|
||||
}
|
||||
Some(TokenKind::Eq) => {
|
||||
let _eq = self.tokens.next();
|
||||
self.next();
|
||||
sink(Highlight::Token { text: "&=", class: Some(Class::Op) });
|
||||
return;
|
||||
}
|
||||
|
@ -260,7 +351,7 @@ impl<'a> Classifier<'a> {
|
|||
match lookahead {
|
||||
// Case 1: #![inner_attribute]
|
||||
Some(TokenKind::Bang) => {
|
||||
let _not = self.tokens.next().unwrap();
|
||||
self.next();
|
||||
if let Some(TokenKind::OpenBracket) = self.peek() {
|
||||
self.in_attribute = true;
|
||||
sink(Highlight::EnterSpan { class: Class::Attribute });
|
||||
|
@ -304,20 +395,18 @@ impl<'a> Classifier<'a> {
|
|||
sink(Highlight::Token { text, class: None });
|
||||
return;
|
||||
}
|
||||
TokenKind::Ident => match text {
|
||||
"ref" | "mut" => Class::RefKeyWord,
|
||||
"self" | "Self" => Class::Self_,
|
||||
"false" | "true" => Class::Bool,
|
||||
TokenKind::Ident => match get_real_ident_class(text, self.edition) {
|
||||
Class::Ident => match text {
|
||||
"Option" | "Result" => Class::PreludeTy,
|
||||
"Some" | "None" | "Ok" | "Err" => Class::PreludeVal,
|
||||
// Keywords are also included in the identifier set.
|
||||
_ if Symbol::intern(text).is_reserved(|| self.edition) => Class::KeyWord,
|
||||
_ if self.in_macro_nonterminal => {
|
||||
self.in_macro_nonterminal = false;
|
||||
Class::MacroNonTerminal
|
||||
}
|
||||
_ => Class::Ident,
|
||||
},
|
||||
c => c,
|
||||
},
|
||||
TokenKind::RawIdent => Class::Ident,
|
||||
TokenKind::Lifetime { .. } => Class::Lifetime,
|
||||
};
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
</style>
|
||||
<pre><code><span class="attribute">#![<span class="ident">crate_type</span> <span class="op">=</span> <span class="string">"lib"</span>]</span>
|
||||
|
||||
<span class="kw">use</span> <span class="ident">std::path</span>::{<span class="ident">Path</span>, <span class="ident">PathBuf</span>};
|
||||
|
||||
<span class="attribute">#[<span class="ident">cfg</span>(<span class="ident">target_os</span> <span class="op">=</span> <span class="string">"linux"</span>)]</span>
|
||||
<span class="kw">fn</span> <span class="ident">main</span>() {
|
||||
<span class="kw">let</span> <span class="ident">foo</span> <span class="op">=</span> <span class="bool-val">true</span> <span class="op">&&</span> <span class="bool-val">false</span> <span class="op">|</span><span class="op">|</span> <span class="bool-val">true</span>;
|
||||
|
@ -19,6 +21,14 @@
|
|||
<span class="kw">let</span> <span class="kw">_</span> <span class="op">=</span> <span class="kw-2">*</span><span class="ident">foo</span>;
|
||||
<span class="macro">mac!</span>(<span class="ident">foo</span>, <span class="kw-2">&</span><span class="kw-2">mut</span> <span class="ident">bar</span>);
|
||||
<span class="macro">assert!</span>(<span class="self">self</span>.<span class="ident">length</span> <span class="op"><</span> <span class="ident">N</span> <span class="op">&&</span> <span class="ident">index</span> <span class="op"><</span><span class="op">=</span> <span class="self">self</span>.<span class="ident">length</span>);
|
||||
<span class="ident">::std::env::var</span>(<span class="string">"gateau"</span>).<span class="ident">is_ok</span>();
|
||||
<span class="attribute">#[<span class="ident">rustfmt::skip</span>]</span>
|
||||
<span class="kw">let</span> <span class="ident">s</span>:<span class="ident">std</span><span class="ident">::path::PathBuf</span> <span class="op">=</span> <span class="ident">std::path::PathBuf::new</span>();
|
||||
<span class="kw">let</span> <span class="kw-2">mut</span> <span class="ident">s</span> <span class="op">=</span> <span class="ident">String::new</span>();
|
||||
|
||||
<span class="kw">match</span> <span class="kw-2">&</span><span class="ident">s</span> {
|
||||
<span class="kw-2">ref</span> <span class="kw-2">mut</span> <span class="ident">x</span> <span class="op">=</span><span class="op">></span> {}
|
||||
}
|
||||
}
|
||||
|
||||
<span class="macro">macro_rules!</span> <span class="ident">bar</span> {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#![crate_type = "lib"]
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn main() {
|
||||
let foo = true && false || true;
|
||||
|
@ -9,6 +11,14 @@ fn main() {
|
|||
let _ = *foo;
|
||||
mac!(foo, &mut bar);
|
||||
assert!(self.length < N && index <= self.length);
|
||||
::std::env::var("gateau").is_ok();
|
||||
#[rustfmt::skip]
|
||||
let s:std::path::PathBuf = std::path::PathBuf::new();
|
||||
let mut s = String::new();
|
||||
|
||||
match &s {
|
||||
ref mut x => {}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! bar {
|
||||
|
|
|
@ -148,14 +148,13 @@ declare_rustdoc_lint! {
|
|||
}
|
||||
|
||||
declare_rustdoc_lint! {
|
||||
/// The `non_autolinks` lint detects when a URL could be written using
|
||||
/// only angle brackets. This is a `rustdoc` only lint, see the
|
||||
/// documentation in the [rustdoc book].
|
||||
/// The `bare_urls` lint detects when a URL is not a hyperlink.
|
||||
/// This is a `rustdoc` only lint, see the documentation in the [rustdoc book].
|
||||
///
|
||||
/// [rustdoc book]: ../../../rustdoc/lints.html#non_autolinks
|
||||
NON_AUTOLINKS,
|
||||
/// [rustdoc book]: ../../../rustdoc/lints.html#bare_urls
|
||||
BARE_URLS,
|
||||
Warn,
|
||||
"detects URLs that could be written using only angle brackets"
|
||||
"detects URLs that are not hyperlinks"
|
||||
}
|
||||
|
||||
crate static RUSTDOC_LINTS: Lazy<Vec<&'static Lint>> = Lazy::new(|| {
|
||||
|
@ -166,7 +165,7 @@ crate static RUSTDOC_LINTS: Lazy<Vec<&'static Lint>> = Lazy::new(|| {
|
|||
PRIVATE_DOC_TESTS,
|
||||
INVALID_CODEBLOCK_ATTRIBUTES,
|
||||
INVALID_HTML_TAGS,
|
||||
NON_AUTOLINKS,
|
||||
BARE_URLS,
|
||||
MISSING_CRATE_LEVEL_DOCS,
|
||||
]
|
||||
});
|
||||
|
@ -185,4 +184,6 @@ crate fn register_lints(_sess: &Session, lint_store: &mut LintStore) {
|
|||
}
|
||||
lint_store
|
||||
.register_renamed("intra_doc_link_resolution_failure", "rustdoc::broken_intra_doc_links");
|
||||
lint_store.register_renamed("non_autolinks", "rustdoc::bare_urls");
|
||||
lint_store.register_renamed("rustdoc::non_autolinks", "rustdoc::bare_urls");
|
||||
}
|
||||
|
|
|
@ -4,37 +4,42 @@ use crate::core::DocContext;
|
|||
use crate::fold::DocFolder;
|
||||
use crate::html::markdown::opts;
|
||||
use core::ops::Range;
|
||||
use pulldown_cmark::{Event, LinkType, Parser, Tag};
|
||||
use pulldown_cmark::{Event, Parser, Tag};
|
||||
use regex::Regex;
|
||||
use rustc_errors::Applicability;
|
||||
use std::lazy::SyncLazy;
|
||||
use std::mem;
|
||||
|
||||
crate const CHECK_NON_AUTOLINKS: Pass = Pass {
|
||||
name: "check-non-autolinks",
|
||||
run: check_non_autolinks,
|
||||
description: "detects URLs that could be linkified",
|
||||
crate const CHECK_BARE_URLS: Pass = Pass {
|
||||
name: "check-bare-urls",
|
||||
run: check_bare_urls,
|
||||
description: "detects URLs that are not hyperlinks",
|
||||
};
|
||||
|
||||
const URL_REGEX: &str = concat!(
|
||||
const URL_REGEX: SyncLazy<Regex> = SyncLazy::new(|| {
|
||||
Regex::new(concat!(
|
||||
r"https?://", // url scheme
|
||||
r"([-a-zA-Z0-9@:%._\+~#=]{2,256}\.)+", // one or more subdomains
|
||||
r"[a-zA-Z]{2,63}", // root domain
|
||||
r"\b([-a-zA-Z0-9@:%_\+.~#?&/=]*)" // optional query or url fragments
|
||||
);
|
||||
))
|
||||
.expect("failed to build regex")
|
||||
});
|
||||
|
||||
struct NonAutolinksLinter<'a, 'tcx> {
|
||||
struct BareUrlsLinter<'a, 'tcx> {
|
||||
cx: &'a mut DocContext<'tcx>,
|
||||
regex: Regex,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> NonAutolinksLinter<'a, 'tcx> {
|
||||
impl<'a, 'tcx> BareUrlsLinter<'a, 'tcx> {
|
||||
fn find_raw_urls(
|
||||
&self,
|
||||
text: &str,
|
||||
range: Range<usize>,
|
||||
f: &impl Fn(&DocContext<'_>, &str, &str, Range<usize>),
|
||||
) {
|
||||
trace!("looking for raw urls in {}", text);
|
||||
// For now, we only check "full" URLs (meaning, starting with "http://" or "https://").
|
||||
for match_ in self.regex.find_iter(&text) {
|
||||
for match_ in URL_REGEX.find_iter(&text) {
|
||||
let url = match_.as_str();
|
||||
let url_range = match_.range();
|
||||
f(
|
||||
|
@ -47,18 +52,11 @@ impl<'a, 'tcx> NonAutolinksLinter<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
crate fn check_non_autolinks(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
|
||||
if !cx.tcx.sess.is_nightly_build() {
|
||||
krate
|
||||
} else {
|
||||
let mut coll =
|
||||
NonAutolinksLinter { cx, regex: Regex::new(URL_REGEX).expect("failed to build regex") };
|
||||
|
||||
coll.fold_crate(krate)
|
||||
}
|
||||
crate fn check_bare_urls(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
|
||||
BareUrlsLinter { cx }.fold_crate(krate)
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> DocFolder for NonAutolinksLinter<'a, 'tcx> {
|
||||
impl<'a, 'tcx> DocFolder for BareUrlsLinter<'a, 'tcx> {
|
||||
fn fold_item(&mut self, item: Item) -> Option<Item> {
|
||||
let hir_id = match DocContext::as_local_hir_id(self.cx.tcx, item.def_id) {
|
||||
Some(hir_id) => hir_id,
|
||||
|
@ -73,7 +71,7 @@ impl<'a, 'tcx> DocFolder for NonAutolinksLinter<'a, 'tcx> {
|
|||
let sp = super::source_span_for_markdown_range(cx.tcx, &dox, &range, &item.attrs)
|
||||
.or_else(|| span_of_attrs(&item.attrs))
|
||||
.unwrap_or(item.span.inner());
|
||||
cx.tcx.struct_span_lint_hir(crate::lint::NON_AUTOLINKS, hir_id, sp, |lint| {
|
||||
cx.tcx.struct_span_lint_hir(crate::lint::BARE_URLS, hir_id, sp, |lint| {
|
||||
lint.build(msg)
|
||||
.span_suggestion(
|
||||
sp,
|
||||
|
@ -89,37 +87,16 @@ impl<'a, 'tcx> DocFolder for NonAutolinksLinter<'a, 'tcx> {
|
|||
|
||||
while let Some((event, range)) = p.next() {
|
||||
match event {
|
||||
Event::Start(Tag::Link(kind, _, _)) => {
|
||||
let ignore = matches!(kind, LinkType::Autolink | LinkType::Email);
|
||||
let mut title = String::new();
|
||||
|
||||
while let Some((event, range)) = p.next() {
|
||||
match event {
|
||||
Event::End(Tag::Link(_, url, _)) => {
|
||||
// NOTE: links cannot be nested, so we don't need to
|
||||
// check `kind`
|
||||
if url.as_ref() == title && !ignore && self.regex.is_match(&url)
|
||||
{
|
||||
report_diag(
|
||||
self.cx,
|
||||
"unneeded long form for URL",
|
||||
&url,
|
||||
range,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
Event::Text(s) if !ignore => title.push_str(&s),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Text(s) => self.find_raw_urls(&s, range, &report_diag),
|
||||
Event::Start(Tag::CodeBlock(_)) => {
|
||||
// We don't want to check the text inside the code blocks.
|
||||
// We don't want to check the text inside code blocks or links.
|
||||
Event::Start(tag @ (Tag::CodeBlock(_) | Tag::Link(..))) => {
|
||||
while let Some((event, _)) = p.next() {
|
||||
match event {
|
||||
Event::End(Tag::CodeBlock(_)) => break,
|
||||
Event::End(end)
|
||||
if mem::discriminant(&end) == mem::discriminant(&tag) =>
|
||||
{
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
|
@ -12,8 +12,8 @@ use crate::core::DocContext;
|
|||
mod stripper;
|
||||
crate use stripper::*;
|
||||
|
||||
mod non_autolinks;
|
||||
crate use self::non_autolinks::CHECK_NON_AUTOLINKS;
|
||||
mod bare_urls;
|
||||
crate use self::bare_urls::CHECK_BARE_URLS;
|
||||
|
||||
mod strip_hidden;
|
||||
crate use self::strip_hidden::STRIP_HIDDEN;
|
||||
|
@ -90,7 +90,7 @@ crate const PASSES: &[Pass] = &[
|
|||
COLLECT_TRAIT_IMPLS,
|
||||
CALCULATE_DOC_COVERAGE,
|
||||
CHECK_INVALID_HTML_TAGS,
|
||||
CHECK_NON_AUTOLINKS,
|
||||
CHECK_BARE_URLS,
|
||||
];
|
||||
|
||||
/// The list of passes run by default.
|
||||
|
@ -105,7 +105,7 @@ crate const DEFAULT_PASSES: &[ConditionalPass] = &[
|
|||
ConditionalPass::always(CHECK_CODE_BLOCK_SYNTAX),
|
||||
ConditionalPass::always(CHECK_INVALID_HTML_TAGS),
|
||||
ConditionalPass::always(PROPAGATE_DOC_CFG),
|
||||
ConditionalPass::always(CHECK_NON_AUTOLINKS),
|
||||
ConditionalPass::always(CHECK_BARE_URLS),
|
||||
];
|
||||
|
||||
/// The list of default passes run when `--doc-coverage` is passed to rustdoc.
|
||||
|
|
|
@ -4,3 +4,8 @@
|
|||
// stable channel.
|
||||
//! [x]
|
||||
//~^ ERROR unresolved link
|
||||
|
||||
#![deny(rustdoc::non_autolinks)]
|
||||
//~^ WARNING renamed to `rustdoc::bare_urls`
|
||||
//! http://example.com
|
||||
//~^ ERROR not a hyperlink
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
warning: lint `rustdoc::non_autolinks` has been renamed to `rustdoc::bare_urls`
|
||||
--> $DIR/renamed-lint-still-applies.rs:8:9
|
||||
|
|
||||
LL | #![deny(rustdoc::non_autolinks)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `rustdoc::bare_urls`
|
||||
|
|
||||
= note: `#[warn(renamed_and_removed_lints)]` on by default
|
||||
|
||||
error: unresolved link to `x`
|
||||
--> $DIR/renamed-lint-still-applies.rs:5:6
|
||||
|
|
||||
|
@ -12,5 +20,17 @@ LL | #![deny(broken_intra_doc_links)]
|
|||
= note: `#[deny(rustdoc::broken_intra_doc_links)]` implied by `#[deny(broken_intra_doc_links)]`
|
||||
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
|
||||
|
||||
error: aborting due to previous error
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/renamed-lint-still-applies.rs:10:5
|
||||
|
|
||||
LL | //! http://example.com
|
||||
| ^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://example.com>`
|
||||
|
|
||||
note: the lint level is defined here
|
||||
--> $DIR/renamed-lint-still-applies.rs:8:9
|
||||
|
|
||||
LL | #![deny(rustdoc::non_autolinks)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: aborting due to 2 previous errors; 1 warning emitted
|
||||
|
||||
|
|
|
@ -8,8 +8,12 @@
|
|||
//~^ ERROR unknown lint: `rustdoc::x`
|
||||
#![deny(intra_doc_link_resolution_failure)]
|
||||
//~^ ERROR renamed to `rustdoc::broken_intra_doc_links`
|
||||
|
||||
#![deny(non_autolinks)]
|
||||
//~^ ERROR renamed to `rustdoc::bare_urls`
|
||||
#![deny(rustdoc::non_autolinks)]
|
||||
//~^ ERROR renamed to `rustdoc::bare_urls`
|
||||
|
||||
#![deny(private_doc_tests)]
|
||||
// FIXME: the old names for rustdoc lints should warn by default once `rustdoc::` makes it to the
|
||||
// stable channel.
|
||||
|
||||
|
|
|
@ -28,19 +28,31 @@ note: the lint level is defined here
|
|||
LL | #![deny(renamed_and_removed_lints)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: lint `non_autolinks` has been renamed to `rustdoc::bare_urls`
|
||||
--> $DIR/unknown-renamed-lints.rs:11:9
|
||||
|
|
||||
LL | #![deny(non_autolinks)]
|
||||
| ^^^^^^^^^^^^^ help: use the new name: `rustdoc::bare_urls`
|
||||
|
||||
error: lint `rustdoc::non_autolinks` has been renamed to `rustdoc::bare_urls`
|
||||
--> $DIR/unknown-renamed-lints.rs:13:9
|
||||
|
|
||||
LL | #![deny(rustdoc::non_autolinks)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `rustdoc::bare_urls`
|
||||
|
||||
error: lint `rustdoc` has been removed: use `rustdoc::all` instead
|
||||
--> $DIR/unknown-renamed-lints.rs:16:9
|
||||
--> $DIR/unknown-renamed-lints.rs:20:9
|
||||
|
|
||||
LL | #![deny(rustdoc)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: unknown lint: `rustdoc::intra_doc_link_resolution_failure`
|
||||
--> $DIR/unknown-renamed-lints.rs:20:9
|
||||
--> $DIR/unknown-renamed-lints.rs:24:9
|
||||
|
|
||||
LL | #![deny(rustdoc::intra_doc_link_resolution_failure)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: Compilation failed, aborting rustdoc
|
||||
|
||||
error: aborting due to 6 previous errors
|
||||
error: aborting due to 8 previous errors
|
||||
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
#![deny(rustdoc::non_autolinks)]
|
||||
|
||||
/// [http://aa.com](http://aa.com)
|
||||
//~^ ERROR unneeded long form for URL
|
||||
/// [http://bb.com]
|
||||
//~^ ERROR unneeded long form for URL
|
||||
///
|
||||
/// [http://bb.com]: http://bb.com
|
||||
///
|
||||
/// [http://c.com][http://c.com]
|
||||
pub fn a() {}
|
||||
#![deny(rustdoc::bare_urls)]
|
||||
|
||||
/// https://somewhere.com
|
||||
//~^ ERROR this URL is not a hyperlink
|
||||
|
@ -54,12 +44,14 @@ pub fn c() {}
|
|||
///
|
||||
/// ```
|
||||
/// This link should not be linted: http://example.com
|
||||
///
|
||||
/// Nor this one: <http://example.com> or this one: [x](http://example.com)
|
||||
/// ```
|
||||
///
|
||||
/// [should_not.lint](should_not.lint)
|
||||
pub fn everything_is_fine_here() {}
|
||||
|
||||
#[allow(rustdoc::non_autolinks)]
|
||||
#[allow(rustdoc::bare_urls)]
|
||||
pub mod foo {
|
||||
/// https://somewhere.com/a?hello=12&bye=11#xyz
|
||||
pub fn bar() {}
|
||||
|
|
|
@ -1,122 +1,110 @@
|
|||
error: unneeded long form for URL
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:3:5
|
||||
|
|
||||
LL | /// [http://aa.com](http://aa.com)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://aa.com>`
|
||||
LL | /// https://somewhere.com
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com>`
|
||||
|
|
||||
note: the lint level is defined here
|
||||
--> $DIR/url-improvements.rs:1:9
|
||||
|
|
||||
LL | #![deny(rustdoc::non_autolinks)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
LL | #![deny(rustdoc::bare_urls)]
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unneeded long form for URL
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:5:5
|
||||
|
|
||||
LL | /// [http://bb.com]
|
||||
| ^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://bb.com>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:13:5
|
||||
|
|
||||
LL | /// https://somewhere.com
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:15:5
|
||||
|
|
||||
LL | /// https://somewhere.com/a
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com/a>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:17:5
|
||||
--> $DIR/url-improvements.rs:7:5
|
||||
|
|
||||
LL | /// https://www.somewhere.com
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://www.somewhere.com>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:19:5
|
||||
--> $DIR/url-improvements.rs:9:5
|
||||
|
|
||||
LL | /// https://www.somewhere.com/a
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://www.somewhere.com/a>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:21:5
|
||||
--> $DIR/url-improvements.rs:11:5
|
||||
|
|
||||
LL | /// https://subdomain.example.com
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://subdomain.example.com>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:23:5
|
||||
--> $DIR/url-improvements.rs:13:5
|
||||
|
|
||||
LL | /// https://somewhere.com?
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com?>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:25:5
|
||||
--> $DIR/url-improvements.rs:15:5
|
||||
|
|
||||
LL | /// https://somewhere.com/a?
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com/a?>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:27:5
|
||||
--> $DIR/url-improvements.rs:17:5
|
||||
|
|
||||
LL | /// https://somewhere.com?hello=12
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com?hello=12>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:29:5
|
||||
--> $DIR/url-improvements.rs:19:5
|
||||
|
|
||||
LL | /// https://somewhere.com/a?hello=12
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com/a?hello=12>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:31:5
|
||||
--> $DIR/url-improvements.rs:21:5
|
||||
|
|
||||
LL | /// https://example.com?hello=12#xyz
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://example.com?hello=12#xyz>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:33:5
|
||||
--> $DIR/url-improvements.rs:23:5
|
||||
|
|
||||
LL | /// https://example.com/a?hello=12#xyz
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://example.com/a?hello=12#xyz>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:35:5
|
||||
--> $DIR/url-improvements.rs:25:5
|
||||
|
|
||||
LL | /// https://example.com#xyz
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://example.com#xyz>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:37:5
|
||||
--> $DIR/url-improvements.rs:27:5
|
||||
|
|
||||
LL | /// https://example.com/a#xyz
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://example.com/a#xyz>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:39:5
|
||||
--> $DIR/url-improvements.rs:29:5
|
||||
|
|
||||
LL | /// https://somewhere.com?hello=12&bye=11
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com?hello=12&bye=11>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:41:5
|
||||
--> $DIR/url-improvements.rs:31:5
|
||||
|
|
||||
LL | /// https://somewhere.com/a?hello=12&bye=11
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com/a?hello=12&bye=11>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:43:5
|
||||
--> $DIR/url-improvements.rs:33:5
|
||||
|
|
||||
LL | /// https://somewhere.com?hello=12&bye=11#xyz
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com?hello=12&bye=11#xyz>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:45:10
|
||||
--> $DIR/url-improvements.rs:35:10
|
||||
|
|
||||
LL | /// hey! https://somewhere.com/a?hello=12&bye=11#xyz
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com/a?hello=12&bye=11#xyz>`
|
||||
|
||||
error: aborting due to 19 previous errors
|
||||
error: aborting due to 17 previous errors
|
||||
|
||||
|
|
|
@ -6,11 +6,146 @@ document.
|
|||
|
||||
## Unreleased / In Rust Nightly
|
||||
|
||||
[3e41797...master](https://github.com/rust-lang/rust-clippy/compare/3e41797...master)
|
||||
[6ed6f1e...master](https://github.com/rust-lang/rust-clippy/compare/6ed6f1e...master)
|
||||
|
||||
## Rust 1.52
|
||||
|
||||
Current beta, release 2021-05-06
|
||||
|
||||
[3e41797...6ed6f1e](https://github.com/rust-lang/rust-clippy/compare/3e41797...6ed6f1e)
|
||||
|
||||
### New Lints
|
||||
|
||||
* [`from_str_radix_10`]
|
||||
[#6717](https://github.com/rust-lang/rust-clippy/pull/6717)
|
||||
* [`implicit_clone`]
|
||||
[#6730](https://github.com/rust-lang/rust-clippy/pull/6730)
|
||||
* [`semicolon_if_nothing_returned`]
|
||||
[#6681](https://github.com/rust-lang/rust-clippy/pull/6681)
|
||||
* [`manual_flatten`]
|
||||
[#6646](https://github.com/rust-lang/rust-clippy/pull/6646)
|
||||
* [`inconsistent_struct_constructor`]
|
||||
[#6769](https://github.com/rust-lang/rust-clippy/pull/6769)
|
||||
* [`iter_count`]
|
||||
[#6791](https://github.com/rust-lang/rust-clippy/pull/6791)
|
||||
* [`default_numeric_fallback`]
|
||||
[#6662](https://github.com/rust-lang/rust-clippy/pull/6662)
|
||||
* [`bytes_nth`]
|
||||
[#6695](https://github.com/rust-lang/rust-clippy/pull/6695)
|
||||
* [`filter_map_identity`]
|
||||
[#6685](https://github.com/rust-lang/rust-clippy/pull/6685)
|
||||
* [`manual_map`]
|
||||
[#6573](https://github.com/rust-lang/rust-clippy/pull/6573)
|
||||
|
||||
### Moves and Deprecations
|
||||
|
||||
* Moved [`upper_case_acronyms`] to `pedantic`
|
||||
[#6775](https://github.com/rust-lang/rust-clippy/pull/6775)
|
||||
* Moved [`manual_map`] to `nursery`
|
||||
[#6796](https://github.com/rust-lang/rust-clippy/pull/6796)
|
||||
* Moved [`unnecessary_wraps`] to `pedantic`
|
||||
[#6765](https://github.com/rust-lang/rust-clippy/pull/6765)
|
||||
* Moved [`trivial_regex`] to `nursery`
|
||||
[#6696](https://github.com/rust-lang/rust-clippy/pull/6696)
|
||||
* Moved [`naive_bytecount`] to `pedantic`
|
||||
[#6825](https://github.com/rust-lang/rust-clippy/pull/6825)
|
||||
* Moved [`upper_case_acronyms`] to `style`
|
||||
[#6788](https://github.com/rust-lang/rust-clippy/pull/6788)
|
||||
* Moved [`manual_map`] to `style`
|
||||
[#6801](https://github.com/rust-lang/rust-clippy/pull/6801)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [`disallowed_method`]: Now supports functions in addition to methods
|
||||
[#6674](https://github.com/rust-lang/rust-clippy/pull/6674)
|
||||
* [`upper_case_acronyms`]: Added a new configuration `upper-case-acronyms-aggressive` to
|
||||
trigger the lint if there is more than one uppercase character next to each other
|
||||
[#6788](https://github.com/rust-lang/rust-clippy/pull/6788)
|
||||
* [`collapsible_match`]: Now supports block comparison with different value names
|
||||
[#6754](https://github.com/rust-lang/rust-clippy/pull/6754)
|
||||
* [`unnecessary_wraps`]: Will now suggest removing unnecessary wrapped return unit type, like `Option<()>`
|
||||
[#6665](https://github.com/rust-lang/rust-clippy/pull/6665)
|
||||
* Improved value usage detection in closures
|
||||
[#6698](https://github.com/rust-lang/rust-clippy/pull/6698)
|
||||
|
||||
### False Positive Fixes
|
||||
|
||||
* [`use_self`]: No longer lints in macros
|
||||
[#6833](https://github.com/rust-lang/rust-clippy/pull/6833)
|
||||
* [`use_self`]: Fixed multiple false positives for: generics, associated types and derive implementations
|
||||
[#6179](https://github.com/rust-lang/rust-clippy/pull/6179)
|
||||
* [`missing_inline_in_public_items`]: No longer lints for procedural macros
|
||||
[#6814](https://github.com/rust-lang/rust-clippy/pull/6814)
|
||||
* [`inherent_to_string`]: No longer lints on functions with function generics
|
||||
[#6771](https://github.com/rust-lang/rust-clippy/pull/6771)
|
||||
* [`doc_markdown`]: Add `OpenDNS` to the default configuration as an allowed identifier
|
||||
[#6783](https://github.com/rust-lang/rust-clippy/pull/6783)
|
||||
* [`missing_panics_doc`]: No longer lints on [`unreachable!`](https://doc.rust-lang.org/std/macro.unreachable.html)
|
||||
[#6700](https://github.com/rust-lang/rust-clippy/pull/6700)
|
||||
* [`collapsible_if`]: No longer lints on if statements with attributes
|
||||
[#6701](https://github.com/rust-lang/rust-clippy/pull/6701)
|
||||
* [`match_same_arms`]: Only considers empty blocks as equal if the tokens contained are the same
|
||||
[#6843](https://github.com/rust-lang/rust-clippy/pull/6843)
|
||||
* [`redundant_closure`]: Now ignores macros
|
||||
[#6871](https://github.com/rust-lang/rust-clippy/pull/6871)
|
||||
* [`manual_map`]: Fixed false positives when control flow statements like `return`, `break` etc. are used
|
||||
[#6801](https://github.com/rust-lang/rust-clippy/pull/6801)
|
||||
* [`vec_init_then_push`]: Fixed false positives for loops and if statements
|
||||
[#6697](https://github.com/rust-lang/rust-clippy/pull/6697)
|
||||
* [`len_without_is_empty`]: Will now consider multiple impl blocks and `#[allow]` on
|
||||
the `len` method as well as the type definition.
|
||||
[#6853](https://github.com/rust-lang/rust-clippy/pull/6853)
|
||||
* [`let_underscore_drop`]: Only lints on types which implement `Drop`
|
||||
[#6682](https://github.com/rust-lang/rust-clippy/pull/6682)
|
||||
* [`unit_arg`]: No longer lints on unit arguments when they come from a path expression.
|
||||
[#6601](https://github.com/rust-lang/rust-clippy/pull/6601)
|
||||
* [`cargo_common_metadata`]: No longer lints if
|
||||
[`publish = false`](https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field)
|
||||
is defined in the manifest
|
||||
[#6650](https://github.com/rust-lang/rust-clippy/pull/6650)
|
||||
|
||||
### Suggestion Fixes/Improvements
|
||||
|
||||
* [`collapsible_match`]: Fixed lint message capitalization
|
||||
[#6766](https://github.com/rust-lang/rust-clippy/pull/6766)
|
||||
* [`or_fun_call`]: Improved suggestions for `or_insert(vec![])`
|
||||
[#6790](https://github.com/rust-lang/rust-clippy/pull/6790)
|
||||
* [`manual_map`]: No longer expands macros in the suggestions
|
||||
[#6801](https://github.com/rust-lang/rust-clippy/pull/6801)
|
||||
* Aligned Clippy's lint messages with the rustc dev guide
|
||||
[#6787](https://github.com/rust-lang/rust-clippy/pull/6787)
|
||||
|
||||
### ICE Fixes
|
||||
|
||||
* [`zero_sized_map_values`]
|
||||
[#6866](https://github.com/rust-lang/rust-clippy/pull/6866)
|
||||
|
||||
### Documentation Improvements
|
||||
|
||||
* [`useless_format`]: Improved the documentation example
|
||||
[#6854](https://github.com/rust-lang/rust-clippy/pull/6854)
|
||||
* Clippy's [`README.md`]: Includes a new subsection on running Clippy as a rustc wrapper
|
||||
[#6782](https://github.com/rust-lang/rust-clippy/pull/6782)
|
||||
|
||||
### Others
|
||||
* Running `cargo clippy` after `cargo check` now works as expected
|
||||
(`cargo clippy` and `cargo check` no longer shares the same build cache)
|
||||
[#6687](https://github.com/rust-lang/rust-clippy/pull/6687)
|
||||
* Cargo now re-runs Clippy if arguments after `--` provided to `cargo clippy` are changed.
|
||||
[#6834](https://github.com/rust-lang/rust-clippy/pull/6834)
|
||||
* Extracted Clippy's `utils` module into the new `clippy_utils` crate
|
||||
[#6756](https://github.com/rust-lang/rust-clippy/pull/6756)
|
||||
* Clippy lintcheck tool improvements
|
||||
[#6800](https://github.com/rust-lang/rust-clippy/pull/6800)
|
||||
[#6735](https://github.com/rust-lang/rust-clippy/pull/6735)
|
||||
[#6764](https://github.com/rust-lang/rust-clippy/pull/6764)
|
||||
[#6708](https://github.com/rust-lang/rust-clippy/pull/6708)
|
||||
[#6780](https://github.com/rust-lang/rust-clippy/pull/6780)
|
||||
[#6686](https://github.com/rust-lang/rust-clippy/pull/6686)
|
||||
|
||||
## Rust 1.51
|
||||
|
||||
Current beta, release 2021-03-25
|
||||
Current stable, released 2021-03-25
|
||||
|
||||
[4911ab1...3e41797](https://github.com/rust-lang/rust-clippy/compare/4911ab1...3e41797)
|
||||
|
||||
|
@ -125,7 +260,7 @@ Current beta, release 2021-03-25
|
|||
|
||||
## Rust 1.50
|
||||
|
||||
Current stable, released 2021-02-11
|
||||
Released 2021-02-11
|
||||
|
||||
[b20d4c1...4bd77a1](https://github.com/rust-lang/rust-clippy/compare/b20d4c1...4bd77a1)
|
||||
|
||||
|
@ -1970,6 +2105,7 @@ Released 2018-09-13
|
|||
[configuration file]: ./rust-clippy#configuration
|
||||
[pull3665]: https://github.com/rust-lang/rust-clippy/pull/3665
|
||||
[adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md
|
||||
[`README.md`]: https://github.com/rust-lang/rust-clippy/blob/master/README.md
|
||||
|
||||
<!-- lint disable no-unused-definitions -->
|
||||
<!-- begin autogenerated links to lint list -->
|
||||
|
@ -1993,6 +2129,7 @@ Released 2018-09-13
|
|||
[`borrowed_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrowed_box
|
||||
[`box_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_vec
|
||||
[`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local
|
||||
[`branches_sharing_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#branches_sharing_code
|
||||
[`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow
|
||||
[`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth
|
||||
[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
|
||||
|
@ -2233,6 +2370,7 @@ Released 2018-09-13
|
|||
[`needless_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_collect
|
||||
[`needless_continue`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_continue
|
||||
[`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main
|
||||
[`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
|
||||
[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
|
||||
[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
|
||||
[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
|
||||
|
@ -2246,6 +2384,7 @@ Released 2018-09-13
|
|||
[`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default
|
||||
[`no_effect`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect
|
||||
[`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal
|
||||
[`non_octal_unix_permissions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_octal_unix_permissions
|
||||
[`nonminimal_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool
|
||||
[`nonsensical_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonsensical_open_options
|
||||
[`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref
|
||||
|
@ -2253,6 +2392,7 @@ Released 2018-09-13
|
|||
[`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
|
||||
[`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref
|
||||
[`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap
|
||||
[`option_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_filter_map
|
||||
[`option_if_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_if_let_else
|
||||
[`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none
|
||||
[`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
|
||||
|
||||
[There are over 400 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
|
||||
[There are over 450 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
|
||||
|
||||
Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
|
||||
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.
|
||||
|
|
|
@ -68,7 +68,7 @@ pub fn run(check: bool, verbose: bool) {
|
|||
continue;
|
||||
}
|
||||
|
||||
success &= rustfmt(context, &path)?;
|
||||
success &= rustfmt(context, path)?;
|
||||
}
|
||||
|
||||
Ok(success)
|
||||
|
|
|
@ -101,7 +101,7 @@ impl Lint {
|
|||
#[must_use]
|
||||
pub fn gen_lint_group_list<'a>(lints: impl Iterator<Item = &'a Lint>) -> Vec<String> {
|
||||
lints
|
||||
.map(|l| format!(" LintId::of(&{}::{}),", l.module, l.name.to_uppercase()))
|
||||
.map(|l| format!(" LintId::of({}::{}),", l.module, l.name.to_uppercase()))
|
||||
.sorted()
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
@ -154,17 +154,17 @@ pub fn gen_register_lint_list<'a>(
|
|||
let header = " store.register_lints(&[".to_string();
|
||||
let footer = " ]);".to_string();
|
||||
let internal_lints = internal_lints
|
||||
.sorted_by_key(|l| format!(" &{}::{},", l.module, l.name.to_uppercase()))
|
||||
.sorted_by_key(|l| format!(" {}::{},", l.module, l.name.to_uppercase()))
|
||||
.map(|l| {
|
||||
format!(
|
||||
" #[cfg(feature = \"internal-lints\")]\n &{}::{},",
|
||||
" #[cfg(feature = \"internal-lints\")]\n {}::{},",
|
||||
l.module,
|
||||
l.name.to_uppercase()
|
||||
)
|
||||
});
|
||||
let other_lints = usable_lints
|
||||
.sorted_by_key(|l| format!(" &{}::{},", l.module, l.name.to_uppercase()))
|
||||
.map(|l| format!(" &{}::{},", l.module, l.name.to_uppercase()))
|
||||
.sorted_by_key(|l| format!(" {}::{},", l.module, l.name.to_uppercase()))
|
||||
.map(|l| format!(" {}::{},", l.module, l.name.to_uppercase()))
|
||||
.sorted();
|
||||
let mut lint_list = vec![header];
|
||||
lint_list.extend(internal_lints);
|
||||
|
@ -550,9 +550,9 @@ fn test_gen_lint_group_list() {
|
|||
Lint::new("internal", "internal_style", "abc", None, "module_name"),
|
||||
];
|
||||
let expected = vec![
|
||||
" LintId::of(&module_name::ABC),".to_string(),
|
||||
" LintId::of(&module_name::INTERNAL),".to_string(),
|
||||
" LintId::of(&module_name::SHOULD_ASSERT_EQ),".to_string(),
|
||||
" LintId::of(module_name::ABC),".to_string(),
|
||||
" LintId::of(module_name::INTERNAL),".to_string(),
|
||||
" LintId::of(module_name::SHOULD_ASSERT_EQ),".to_string(),
|
||||
];
|
||||
assert_eq!(expected, gen_lint_group_list(lints.iter()));
|
||||
}
|
||||
|
|
173
src/tools/clippy/clippy_lints/src/absurd_extreme_comparisons.rs
Normal file
173
src/tools/clippy/clippy_lints/src/absurd_extreme_comparisons.rs
Normal file
|
@ -0,0 +1,173 @@
|
|||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
use crate::consts::{constant, Constant};
|
||||
|
||||
use clippy_utils::comparisons::{normalize_comparison, Rel};
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_isize_or_usize;
|
||||
use clippy_utils::{clip, int_bits, unsext};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for comparisons where one side of the relation is
|
||||
/// either the minimum or maximum value for its type and warns if it involves a
|
||||
/// case that is always true or always false. Only integer and boolean types are
|
||||
/// checked.
|
||||
///
|
||||
/// **Why is this bad?** An expression like `min <= x` may misleadingly imply
|
||||
/// that it is possible for `x` to be less than the minimum. Expressions like
|
||||
/// `max < x` are probably mistakes.
|
||||
///
|
||||
/// **Known problems:** For `usize` the size of the current compile target will
|
||||
/// be assumed (e.g., 64 bits on 64 bit systems). This means code that uses such
|
||||
/// a comparison to detect target pointer width will trigger this lint. One can
|
||||
/// use `mem::sizeof` and compare its value or conditional compilation
|
||||
/// attributes
|
||||
/// like `#[cfg(target_pointer_width = "64")] ..` instead.
|
||||
///
|
||||
/// **Example:**
|
||||
///
|
||||
/// ```rust
|
||||
/// let vec: Vec<isize> = Vec::new();
|
||||
/// if vec.len() <= 0 {}
|
||||
/// if 100 > i32::MAX {}
|
||||
/// ```
|
||||
pub ABSURD_EXTREME_COMPARISONS,
|
||||
correctness,
|
||||
"a comparison with a maximum or minimum value that is always true or false"
|
||||
}
|
||||
|
||||
declare_lint_pass!(AbsurdExtremeComparisons => [ABSURD_EXTREME_COMPARISONS]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for AbsurdExtremeComparisons {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Binary(ref cmp, lhs, rhs) = expr.kind {
|
||||
if let Some((culprit, result)) = detect_absurd_comparison(cx, cmp.node, lhs, rhs) {
|
||||
if !expr.span.from_expansion() {
|
||||
let msg = "this comparison involving the minimum or maximum element for this \
|
||||
type contains a case that is always true or always false";
|
||||
|
||||
let conclusion = match result {
|
||||
AbsurdComparisonResult::AlwaysFalse => "this comparison is always false".to_owned(),
|
||||
AbsurdComparisonResult::AlwaysTrue => "this comparison is always true".to_owned(),
|
||||
AbsurdComparisonResult::InequalityImpossible => format!(
|
||||
"the case where the two sides are not equal never occurs, consider using `{} == {}` \
|
||||
instead",
|
||||
snippet(cx, lhs.span, "lhs"),
|
||||
snippet(cx, rhs.span, "rhs")
|
||||
),
|
||||
};
|
||||
|
||||
let help = format!(
|
||||
"because `{}` is the {} value for this type, {}",
|
||||
snippet(cx, culprit.expr.span, "x"),
|
||||
match culprit.which {
|
||||
ExtremeType::Minimum => "minimum",
|
||||
ExtremeType::Maximum => "maximum",
|
||||
},
|
||||
conclusion
|
||||
);
|
||||
|
||||
span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ExtremeType {
|
||||
Minimum,
|
||||
Maximum,
|
||||
}
|
||||
|
||||
struct ExtremeExpr<'a> {
|
||||
which: ExtremeType,
|
||||
expr: &'a Expr<'a>,
|
||||
}
|
||||
|
||||
enum AbsurdComparisonResult {
|
||||
AlwaysFalse,
|
||||
AlwaysTrue,
|
||||
InequalityImpossible,
|
||||
}
|
||||
|
||||
fn is_cast_between_fixed_and_target<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
|
||||
if let ExprKind::Cast(cast_exp, _) = expr.kind {
|
||||
let precast_ty = cx.typeck_results().expr_ty(cast_exp);
|
||||
let cast_ty = cx.typeck_results().expr_ty(expr);
|
||||
|
||||
return is_isize_or_usize(precast_ty) != is_isize_or_usize(cast_ty);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn detect_absurd_comparison<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
op: BinOpKind,
|
||||
lhs: &'tcx Expr<'_>,
|
||||
rhs: &'tcx Expr<'_>,
|
||||
) -> Option<(ExtremeExpr<'tcx>, AbsurdComparisonResult)> {
|
||||
use AbsurdComparisonResult::{AlwaysFalse, AlwaysTrue, InequalityImpossible};
|
||||
use ExtremeType::{Maximum, Minimum};
|
||||
// absurd comparison only makes sense on primitive types
|
||||
// primitive types don't implement comparison operators with each other
|
||||
if cx.typeck_results().expr_ty(lhs) != cx.typeck_results().expr_ty(rhs) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// comparisons between fix sized types and target sized types are considered unanalyzable
|
||||
if is_cast_between_fixed_and_target(cx, lhs) || is_cast_between_fixed_and_target(cx, rhs) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (rel, normalized_lhs, normalized_rhs) = normalize_comparison(op, lhs, rhs)?;
|
||||
|
||||
let lx = detect_extreme_expr(cx, normalized_lhs);
|
||||
let rx = detect_extreme_expr(cx, normalized_rhs);
|
||||
|
||||
Some(match rel {
|
||||
Rel::Lt => {
|
||||
match (lx, rx) {
|
||||
(Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, AlwaysFalse), // max < x
|
||||
(_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, AlwaysFalse), // x < min
|
||||
_ => return None,
|
||||
}
|
||||
},
|
||||
Rel::Le => {
|
||||
match (lx, rx) {
|
||||
(Some(l @ ExtremeExpr { which: Minimum, .. }), _) => (l, AlwaysTrue), // min <= x
|
||||
(Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, InequalityImpossible), // max <= x
|
||||
(_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, InequalityImpossible), // x <= min
|
||||
(_, Some(r @ ExtremeExpr { which: Maximum, .. })) => (r, AlwaysTrue), // x <= max
|
||||
_ => return None,
|
||||
}
|
||||
},
|
||||
Rel::Ne | Rel::Eq => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn detect_extreme_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<ExtremeExpr<'tcx>> {
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
|
||||
let cv = constant(cx, cx.typeck_results(), expr)?.0;
|
||||
|
||||
let which = match (ty.kind(), cv) {
|
||||
(&ty::Bool, Constant::Bool(false)) | (&ty::Uint(_), Constant::Int(0)) => ExtremeType::Minimum,
|
||||
(&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MIN >> (128 - int_bits(cx.tcx, ity)), ity) => {
|
||||
ExtremeType::Minimum
|
||||
},
|
||||
|
||||
(&ty::Bool, Constant::Bool(true)) => ExtremeType::Maximum,
|
||||
(&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MAX >> (128 - int_bits(cx.tcx, ity)), ity) => {
|
||||
ExtremeType::Maximum
|
||||
},
|
||||
(&ty::Uint(uty), Constant::Int(i)) if clip(cx.tcx, u128::MAX, uty) == i => ExtremeType::Maximum,
|
||||
|
||||
_ => return None,
|
||||
};
|
||||
Some(ExtremeExpr { which, expr })
|
||||
}
|
|
@ -71,7 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
|
|||
return;
|
||||
}
|
||||
if_chain! {
|
||||
if let ExprKind::Unary(_, ref lit) = e.kind;
|
||||
if let ExprKind::Unary(_, lit) = e.kind;
|
||||
if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), lit);
|
||||
if is_true;
|
||||
then {
|
||||
|
@ -82,7 +82,7 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
|
|||
if assert_span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
if let Some(assert_match) = match_assert_with_message(&cx, e) {
|
||||
if let Some(assert_match) = match_assert_with_message(cx, e) {
|
||||
match assert_match {
|
||||
// matched assert but not message
|
||||
AssertKind::WithoutMessage(false) => lint_false_without_message(),
|
||||
|
@ -113,17 +113,17 @@ enum AssertKind {
|
|||
/// where `message` is any expression and `c` is a constant bool.
|
||||
fn match_assert_with_message<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<AssertKind> {
|
||||
if_chain! {
|
||||
if let ExprKind::If(ref cond, ref then, _) = expr.kind;
|
||||
if let ExprKind::Unary(UnOp::Not, ref expr) = cond.kind;
|
||||
if let ExprKind::If(cond, then, _) = expr.kind;
|
||||
if let ExprKind::Unary(UnOp::Not, expr) = cond.kind;
|
||||
// bind the first argument of the `assert!` macro
|
||||
if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), expr);
|
||||
// block
|
||||
if let ExprKind::Block(ref block, _) = then.kind;
|
||||
if let ExprKind::Block(block, _) = then.kind;
|
||||
if block.stmts.is_empty();
|
||||
if let Some(block_expr) = &block.expr;
|
||||
// inner block is optional. unwrap it if it exists, or use the expression as is otherwise.
|
||||
if let Some(begin_panic_call) = match block_expr.kind {
|
||||
ExprKind::Block(ref inner_block, _) => &inner_block.expr,
|
||||
ExprKind::Block(inner_block, _) => &inner_block.expr,
|
||||
_ => &block.expr,
|
||||
};
|
||||
// function call
|
||||
|
|
|
@ -85,7 +85,7 @@ fn match_ordering_def_path(cx: &LateContext<'_>, did: DefId, orderings: &[&str])
|
|||
|
||||
fn check_atomic_load_store(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(ref method_path, _, args, _) = &expr.kind;
|
||||
if let ExprKind::MethodCall(method_path, _, args, _) = &expr.kind;
|
||||
let method = method_path.ident.name.as_str();
|
||||
if type_is_atomic(cx, &args[0]);
|
||||
if method == "load" || method == "store";
|
||||
|
@ -120,7 +120,7 @@ fn check_atomic_load_store(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
|
||||
fn check_memory_fence(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(ref func, ref args) = expr.kind;
|
||||
if let ExprKind::Call(func, args) = expr.kind;
|
||||
if let ExprKind::Path(ref func_qpath) = func.kind;
|
||||
if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
|
||||
if ["fence", "compiler_fence"]
|
||||
|
@ -152,7 +152,7 @@ fn opt_ordering_defid(cx: &LateContext<'_>, ord_arg: &Expr<'_>) -> Option<DefId>
|
|||
|
||||
fn check_atomic_compare_exchange(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(ref method_path, _, args, _) = &expr.kind;
|
||||
if let ExprKind::MethodCall(method_path, _, args, _) = &expr.kind;
|
||||
let method = method_path.ident.name.as_str();
|
||||
if type_is_atomic(cx, &args[0]);
|
||||
if method == "compare_exchange" || method == "compare_exchange_weak" || method == "fetch_update";
|
||||
|
|
|
@ -250,12 +250,8 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|
|||
fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) {
|
||||
if let Some(items) = &attr.meta_item_list() {
|
||||
if let Some(ident) = attr.ident() {
|
||||
let ident = &*ident.as_str();
|
||||
match ident {
|
||||
"allow" | "warn" | "deny" | "forbid" => {
|
||||
check_clippy_lint_names(cx, ident, items);
|
||||
},
|
||||
_ => {},
|
||||
if is_lint_level(ident.name) {
|
||||
check_clippy_lint_names(cx, ident.name, items);
|
||||
}
|
||||
if items.is_empty() || !attr.has_name(sym::deprecated) {
|
||||
return;
|
||||
|
@ -288,9 +284,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|
|||
return;
|
||||
}
|
||||
if let Some(lint_list) = &attr.meta_item_list() {
|
||||
if let Some(ident) = attr.ident() {
|
||||
match &*ident.as_str() {
|
||||
"allow" | "warn" | "deny" | "forbid" => {
|
||||
if attr.ident().map_or(false, |ident| is_lint_level(ident.name)) {
|
||||
// permit `unused_imports`, `deprecated`, `unreachable_pub`,
|
||||
// `clippy::wildcard_imports`, and `clippy::enum_glob_use` for `use` items
|
||||
// and `unused_imports` for `extern crate` items with `macro_use`
|
||||
|
@ -301,8 +295,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|
|||
|| is_word(lint, sym::deprecated)
|
||||
|| is_word(lint, sym!(unreachable_pub))
|
||||
|| is_word(lint, sym!(unused))
|
||||
|| extract_clippy_lint(lint)
|
||||
.map_or(false, |s| s == "wildcard_imports")
|
||||
|| extract_clippy_lint(lint).map_or(false, |s| s == "wildcard_imports")
|
||||
|| extract_clippy_lint(lint).map_or(false, |s| s == "enum_glob_use")
|
||||
{
|
||||
return;
|
||||
|
@ -340,9 +333,6 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|
|||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -371,18 +361,18 @@ fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<SymbolStr> {
|
|||
if meta_item.path.segments.len() > 1;
|
||||
if let tool_name = meta_item.path.segments[0].ident;
|
||||
if tool_name.name == sym::clippy;
|
||||
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
|
||||
then {
|
||||
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
|
||||
return Some(lint_name.as_str());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn check_clippy_lint_names(cx: &LateContext<'_>, ident: &str, items: &[NestedMetaItem]) {
|
||||
fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem]) {
|
||||
for lint in items {
|
||||
if let Some(lint_name) = extract_clippy_lint(lint) {
|
||||
if lint_name == "restriction" && ident != "allow" {
|
||||
if lint_name == "restriction" && name != sym::allow {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
BLANKET_CLIPPY_RESTRICTION_LINTS,
|
||||
|
@ -602,7 +592,7 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
|
|||
if let NestedMetaItem::MetaItem(meta) = item {
|
||||
match &meta.kind {
|
||||
MetaItemKind::List(list) => {
|
||||
mismatched.extend(find_mismatched_target_os(&list));
|
||||
mismatched.extend(find_mismatched_target_os(list));
|
||||
},
|
||||
MetaItemKind::Word => {
|
||||
if_chain! {
|
||||
|
@ -629,7 +619,7 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
|
|||
then {
|
||||
let mess = "operating system used in target family position";
|
||||
|
||||
span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, &mess, |diag| {
|
||||
span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
|
||||
// Avoid showing the unix suggestion multiple times in case
|
||||
// we have more than one mismatch for unix-like systems
|
||||
let mut unix_suggested = false;
|
||||
|
@ -647,3 +637,7 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_lint_level(symbol: Symbol) -> bool {
|
||||
matches!(symbol, sym::allow | sym::warn | sym::deny | sym::forbid)
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ impl LateLintPass<'_> for AwaitHolding {
|
|||
let typeck_results = cx.tcx.typeck_body(body_id);
|
||||
check_interior_types(
|
||||
cx,
|
||||
&typeck_results.generator_interior_types.as_ref().skip_binder(),
|
||||
typeck_results.generator_interior_types.as_ref().skip_binder(),
|
||||
body.value.span,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -38,19 +38,17 @@ declare_lint_pass!(ByteCount => [NAIVE_BYTECOUNT]);
|
|||
impl<'tcx> LateLintPass<'tcx> for ByteCount {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(ref count, _, ref count_args, _) = expr.kind;
|
||||
if let ExprKind::MethodCall(count, _, count_args, _) = expr.kind;
|
||||
if count.ident.name == sym!(count);
|
||||
if count_args.len() == 1;
|
||||
if let ExprKind::MethodCall(ref filter, _, ref filter_args, _) = count_args[0].kind;
|
||||
if let ExprKind::MethodCall(filter, _, filter_args, _) = count_args[0].kind;
|
||||
if filter.ident.name == sym!(filter);
|
||||
if filter_args.len() == 2;
|
||||
if let ExprKind::Closure(_, _, body_id, _, _) = filter_args[1].kind;
|
||||
then {
|
||||
let body = cx.tcx.hir().body(body_id);
|
||||
if_chain! {
|
||||
if body.params.len() == 1;
|
||||
if let Some(argname) = get_pat_name(&body.params[0].pat);
|
||||
if let ExprKind::Binary(ref op, ref l, ref r) = body.value.kind;
|
||||
if let Some(argname) = get_pat_name(body.params[0].pat);
|
||||
if let ExprKind::Binary(ref op, l, r) = body.value.kind;
|
||||
if op.node == BinOpKind::Eq;
|
||||
if match_type(cx,
|
||||
cx.typeck_results().expr_ty(&filter_args[0]).peel_refs(),
|
||||
|
@ -66,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for ByteCount {
|
|||
if ty::Uint(UintTy::U8) != *cx.typeck_results().expr_ty(needle).peel_refs().kind() {
|
||||
return;
|
||||
}
|
||||
let haystack = if let ExprKind::MethodCall(ref path, _, ref args, _) =
|
||||
let haystack = if let ExprKind::MethodCall(path, _, args, _) =
|
||||
filter_args[0].kind {
|
||||
let p = path.ident.name;
|
||||
if (p == sym::iter || p == sym!(iter_mut)) && args.len() == 1 {
|
||||
|
@ -92,8 +90,6 @@ impl<'tcx> LateLintPass<'tcx> for ByteCount {
|
|||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn check_arg(name: Symbol, arg: Symbol, needle: &Expr<'_>) -> bool {
|
||||
|
@ -102,10 +98,10 @@ fn check_arg(name: Symbol, arg: Symbol, needle: &Expr<'_>) -> bool {
|
|||
|
||||
fn get_path_name(expr: &Expr<'_>) -> Option<Symbol> {
|
||||
match expr.kind {
|
||||
ExprKind::Box(ref e) | ExprKind::AddrOf(BorrowKind::Ref, _, ref e) | ExprKind::Unary(UnOp::Deref, ref e) => {
|
||||
ExprKind::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) | ExprKind::Unary(UnOp::Deref, e) => {
|
||||
get_path_name(e)
|
||||
},
|
||||
ExprKind::Block(ref b, _) => {
|
||||
ExprKind::Block(b, _) => {
|
||||
if b.stmts.is_empty() {
|
||||
b.expr.as_ref().and_then(|p| get_path_name(p))
|
||||
} else {
|
||||
|
|
|
@ -20,11 +20,10 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// **Example:**
|
||||
/// ```toml
|
||||
/// # This `Cargo.toml` is missing an authors field:
|
||||
/// # This `Cargo.toml` is missing a description field:
|
||||
/// [package]
|
||||
/// name = "clippy"
|
||||
/// version = "0.0.212"
|
||||
/// description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
/// repository = "https://github.com/rust-lang/rust-clippy"
|
||||
/// readme = "README.md"
|
||||
/// license = "MIT OR Apache-2.0"
|
||||
|
@ -32,14 +31,13 @@ declare_clippy_lint! {
|
|||
/// categories = ["development-tools", "development-tools::cargo-plugins"]
|
||||
/// ```
|
||||
///
|
||||
/// Should include an authors field like:
|
||||
/// Should include a description field like:
|
||||
///
|
||||
/// ```toml
|
||||
/// # This `Cargo.toml` includes all common metadata
|
||||
/// [package]
|
||||
/// name = "clippy"
|
||||
/// version = "0.0.212"
|
||||
/// authors = ["Someone <someone@rust-lang.org>"]
|
||||
/// description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
/// repository = "https://github.com/rust-lang/rust-clippy"
|
||||
/// readme = "README.md"
|
||||
|
@ -97,10 +95,6 @@ impl LateLintPass<'_> for CargoCommonMetadata {
|
|||
// only run the lint if publish is `None` (`publish = true` or skipped entirely)
|
||||
// or if the vector isn't empty (`publish = ["something"]`)
|
||||
if package.publish.as_ref().filter(|publish| publish.is_empty()).is_none() || self.ignore_publish {
|
||||
if is_empty_vec(&package.authors) {
|
||||
missing_warning(cx, &package, "package.authors");
|
||||
}
|
||||
|
||||
if is_empty_str(&package.description) {
|
||||
missing_warning(cx, &package, "package.description");
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use rustc_target::abi::LayoutOf;
|
|||
use super::CAST_PTR_ALIGNMENT;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Cast(ref cast_expr, cast_to) = expr.kind {
|
||||
if let ExprKind::Cast(cast_expr, cast_to) = expr.kind {
|
||||
if is_hir_ty_cfg_dependant(cx, cast_to) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
|
|||
}
|
||||
|
||||
// Don't lint for positive constants.
|
||||
let const_val = constant(cx, &cx.typeck_results(), cast_op);
|
||||
let const_val = constant(cx, cx.typeck_results(), cast_op);
|
||||
if_chain! {
|
||||
if let Some((Constant::Int(n), _)) = const_val;
|
||||
if let ty::Int(ity) = *cast_from.kind();
|
||||
|
@ -41,14 +41,14 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
|
|||
}
|
||||
|
||||
// Don't lint for the result of methods that always return non-negative values.
|
||||
if let ExprKind::MethodCall(ref path, _, _, _) = cast_op.kind {
|
||||
if let ExprKind::MethodCall(path, _, _, _) = cast_op.kind {
|
||||
let mut method_name = path.ident.name.as_str();
|
||||
let allowed_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"];
|
||||
|
||||
if_chain! {
|
||||
if method_name == "unwrap";
|
||||
if let Some(arglist) = method_chain_args(cast_op, &["unwrap"]);
|
||||
if let ExprKind::MethodCall(ref inner_path, _, _, _) = &arglist[0][0].kind;
|
||||
if let ExprKind::MethodCall(inner_path, _, _, _) = &arglist[0][0].kind;
|
||||
then {
|
||||
method_name = inner_path.ident.name.as_str();
|
||||
}
|
||||
|
|
|
@ -372,7 +372,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
|
|||
return;
|
||||
}
|
||||
|
||||
if let ExprKind::Cast(ref cast_expr, cast_to) = expr.kind {
|
||||
if let ExprKind::Cast(cast_expr, cast_to) = expr.kind {
|
||||
if is_hir_ty_cfg_dependant(cx, cast_to) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for CheckedConversions {
|
|||
|
||||
let result = if_chain! {
|
||||
if !in_external_macro(cx.sess(), item.span);
|
||||
if let ExprKind::Binary(op, ref left, ref right) = &item.kind;
|
||||
if let ExprKind::Binary(op, left, right) = &item.kind;
|
||||
|
||||
then {
|
||||
match op.node {
|
||||
|
@ -200,7 +200,7 @@ impl ConversionType {
|
|||
/// Check for `expr <= (to_type::MAX as from_type)`
|
||||
fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(ref op, ref left, ref right) = &expr.kind;
|
||||
if let ExprKind::Binary(ref op, left, right) = &expr.kind;
|
||||
if let Some((candidate, check)) = normalize_le_ge(op, left, right);
|
||||
if let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX");
|
||||
|
||||
|
@ -219,7 +219,7 @@ fn check_lower_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
|
|||
}
|
||||
|
||||
// First of we need a binary containing the expression & the cast
|
||||
if let ExprKind::Binary(ref op, ref left, ref right) = &expr.kind {
|
||||
if let ExprKind::Binary(ref op, left, right) = &expr.kind {
|
||||
normalize_le_ge(op, right, left).and_then(|(l, r)| check_function(l, r))
|
||||
} else {
|
||||
None
|
||||
|
@ -260,7 +260,7 @@ fn get_types_from_cast<'a>(
|
|||
// or `to_type::MAX as from_type`
|
||||
let call_from_cast: Option<(&Expr<'_>, &str)> = if_chain! {
|
||||
// to_type::max_value(), from_type
|
||||
if let ExprKind::Cast(ref limit, ref from_type) = &expr.kind;
|
||||
if let ExprKind::Cast(limit, from_type) = &expr.kind;
|
||||
if let TyKind::Path(ref from_type_path) = &from_type.kind;
|
||||
if let Some(from_sym) = int_ty_to_sym(from_type_path);
|
||||
|
||||
|
@ -275,7 +275,7 @@ fn get_types_from_cast<'a>(
|
|||
let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| {
|
||||
if_chain! {
|
||||
// `from_type::from, to_type::max_value()`
|
||||
if let ExprKind::Call(ref from_func, ref args) = &expr.kind;
|
||||
if let ExprKind::Call(from_func, args) = &expr.kind;
|
||||
// `to_type::max_value()`
|
||||
if args.len() == 1;
|
||||
if let limit = &args[0];
|
||||
|
@ -317,13 +317,12 @@ fn get_types_from_cast<'a>(
|
|||
/// Gets the type which implements the called function
|
||||
fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function: &str) -> Option<&'a str> {
|
||||
if_chain! {
|
||||
if let QPath::TypeRelative(ref ty, ref path) = &path;
|
||||
if let QPath::TypeRelative(ty, path) = &path;
|
||||
if path.ident.name.as_str() == function;
|
||||
if let TyKind::Path(QPath::Resolved(None, ref tp)) = &ty.kind;
|
||||
if let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind;
|
||||
if let [int] = &*tp.segments;
|
||||
let name = &int.ident.name.as_str();
|
||||
|
||||
then {
|
||||
let name = &int.ident.name.as_str();
|
||||
candidates.iter().find(|c| name == *c).cloned()
|
||||
} else {
|
||||
None
|
||||
|
@ -334,11 +333,10 @@ fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function:
|
|||
/// Gets the type as a string, if it is a supported integer
|
||||
fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> {
|
||||
if_chain! {
|
||||
if let QPath::Resolved(_, ref path) = *path;
|
||||
if let QPath::Resolved(_, path) = *path;
|
||||
if let [ty] = &*path.segments;
|
||||
let name = &ty.ident.name.as_str();
|
||||
|
||||
then {
|
||||
let name = &ty.ident.name.as_str();
|
||||
INTS.iter().find(|c| name == *c).cloned()
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -152,7 +152,7 @@ impl<'tcx> Visitor<'tcx> for CcHelper {
|
|||
ExprKind::If(_, _, _) => {
|
||||
self.cc += 1;
|
||||
},
|
||||
ExprKind::Match(_, ref arms, _) => {
|
||||
ExprKind::Match(_, arms, _) => {
|
||||
if arms.len() > 1 {
|
||||
self.cc += 1;
|
||||
}
|
||||
|
|
|
@ -62,8 +62,8 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleMatch {
|
|||
}
|
||||
|
||||
fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext<'tcx>) {
|
||||
if_chain! {
|
||||
let expr = strip_singleton_blocks(arm.body);
|
||||
if_chain! {
|
||||
if let ExprKind::Match(expr_in, arms_inner, _) = expr.kind;
|
||||
// the outer arm pattern and the inner match
|
||||
if expr_in.span.ctxt() == arm.pat.span.ctxt();
|
||||
|
|
|
@ -71,10 +71,8 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain {
|
|||
}
|
||||
|
||||
for cond in conds.windows(2) {
|
||||
if let (
|
||||
&ExprKind::Binary(ref kind1, ref lhs1, ref rhs1),
|
||||
&ExprKind::Binary(ref kind2, ref lhs2, ref rhs2),
|
||||
) = (&cond[0].kind, &cond[1].kind)
|
||||
if let (&ExprKind::Binary(ref kind1, lhs1, rhs1), &ExprKind::Binary(ref kind2, lhs2, rhs2)) =
|
||||
(&cond[0].kind, &cond[1].kind)
|
||||
{
|
||||
if !kind_is_cmp(kind1.node) || !kind_is_cmp(kind2.node) {
|
||||
return;
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_note;
|
||||
use clippy_utils::{eq_expr_value, in_macro, search_same, SpanlessEq, SpanlessHash};
|
||||
use clippy_utils::{get_parent_expr, if_sequence};
|
||||
use rustc_hir::{Block, Expr, ExprKind};
|
||||
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
|
||||
use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt};
|
||||
use clippy_utils::{
|
||||
both, count_eq, eq_expr_value, get_enclosing_block, get_parent_expr, if_sequence, in_macro, parent_node_is_if_expr,
|
||||
run_lints, search_same, ContainsName, SpanlessEq, SpanlessHash,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::{Applicability, DiagnosticBuilder};
|
||||
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
|
||||
use rustc_hir::{Block, Expr, ExprKind, HirId};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::{source_map::Span, symbol::Symbol, BytePos};
|
||||
use std::borrow::Cow;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for consecutive `if`s with the same condition.
|
||||
|
@ -104,14 +114,53 @@ declare_clippy_lint! {
|
|||
"`if` with the same `then` and `else` blocks"
|
||||
}
|
||||
|
||||
declare_lint_pass!(CopyAndPaste => [IFS_SAME_COND, SAME_FUNCTIONS_IN_IF_CONDITION, IF_SAME_THEN_ELSE]);
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks if the `if` and `else` block contain shared code that can be
|
||||
/// moved out of the blocks.
|
||||
///
|
||||
/// **Why is this bad?** Duplicate code is less maintainable.
|
||||
///
|
||||
/// **Known problems:** Hopefully none.
|
||||
///
|
||||
/// **Example:**
|
||||
/// ```ignore
|
||||
/// let foo = if … {
|
||||
/// println!("Hello World");
|
||||
/// 13
|
||||
/// } else {
|
||||
/// println!("Hello World");
|
||||
/// 42
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// Could be written as:
|
||||
/// ```ignore
|
||||
/// println!("Hello World");
|
||||
/// let foo = if … {
|
||||
/// 13
|
||||
/// } else {
|
||||
/// 42
|
||||
/// };
|
||||
/// ```
|
||||
pub BRANCHES_SHARING_CODE,
|
||||
complexity,
|
||||
"`if` statement with shared code in all blocks"
|
||||
}
|
||||
|
||||
declare_lint_pass!(CopyAndPaste => [
|
||||
IFS_SAME_COND,
|
||||
SAME_FUNCTIONS_IN_IF_CONDITION,
|
||||
IF_SAME_THEN_ELSE,
|
||||
BRANCHES_SHARING_CODE
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if !expr.span.from_expansion() {
|
||||
if let ExprKind::If(_, _, _) = expr.kind {
|
||||
// skip ifs directly in else, it will be checked in the parent if
|
||||
if let Some(&Expr {
|
||||
kind: ExprKind::If(_, _, Some(ref else_expr)),
|
||||
kind: ExprKind::If(_, _, Some(else_expr)),
|
||||
..
|
||||
}) = get_parent_expr(cx, expr)
|
||||
{
|
||||
|
@ -121,27 +170,398 @@ impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
|
|||
}
|
||||
|
||||
let (conds, blocks) = if_sequence(expr);
|
||||
lint_same_then_else(cx, &blocks);
|
||||
// Conditions
|
||||
lint_same_cond(cx, &conds);
|
||||
lint_same_fns_in_if_cond(cx, &conds);
|
||||
// Block duplication
|
||||
lint_same_then_else(cx, &blocks, conds.len() == blocks.len(), expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of `IF_SAME_THEN_ELSE`.
|
||||
fn lint_same_then_else(cx: &LateContext<'_>, blocks: &[&Block<'_>]) {
|
||||
let eq: &dyn Fn(&&Block<'_>, &&Block<'_>) -> bool =
|
||||
&|&lhs, &rhs| -> bool { SpanlessEq::new(cx).eq_block(lhs, rhs) };
|
||||
/// Implementation of `BRANCHES_SHARING_CODE` and `IF_SAME_THEN_ELSE` if the blocks are equal.
|
||||
fn lint_same_then_else<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
blocks: &[&Block<'tcx>],
|
||||
has_conditional_else: bool,
|
||||
expr: &'tcx Expr<'_>,
|
||||
) {
|
||||
// We only lint ifs with multiple blocks
|
||||
if blocks.len() < 2 || parent_node_is_if_expr(expr, cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some((i, j)) = search_same_sequenced(blocks, eq) {
|
||||
// Check if each block has shared code
|
||||
let has_expr = blocks[0].expr.is_some();
|
||||
let (start_eq, mut end_eq, expr_eq) = scan_block_for_eq(cx, blocks);
|
||||
|
||||
// BRANCHES_SHARING_CODE prerequisites
|
||||
if has_conditional_else || (start_eq == 0 && end_eq == 0 && (has_expr && !expr_eq)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only the start is the same
|
||||
if start_eq != 0 && end_eq == 0 && (!has_expr || !expr_eq) {
|
||||
let block = blocks[0];
|
||||
let start_stmts = block.stmts.split_at(start_eq).0;
|
||||
|
||||
let mut start_walker = UsedValueFinderVisitor::new(cx);
|
||||
for stmt in start_stmts {
|
||||
intravisit::walk_stmt(&mut start_walker, stmt);
|
||||
}
|
||||
|
||||
emit_branches_sharing_code_lint(
|
||||
cx,
|
||||
start_eq,
|
||||
0,
|
||||
false,
|
||||
check_for_warn_of_moved_symbol(cx, &start_walker.def_symbols, expr),
|
||||
blocks,
|
||||
expr,
|
||||
);
|
||||
} else if end_eq != 0 || (has_expr && expr_eq) {
|
||||
let block = blocks[blocks.len() - 1];
|
||||
let (start_stmts, block_stmts) = block.stmts.split_at(start_eq);
|
||||
let (block_stmts, end_stmts) = block_stmts.split_at(block_stmts.len() - end_eq);
|
||||
|
||||
// Scan start
|
||||
let mut start_walker = UsedValueFinderVisitor::new(cx);
|
||||
for stmt in start_stmts {
|
||||
intravisit::walk_stmt(&mut start_walker, stmt);
|
||||
}
|
||||
let mut moved_syms = start_walker.def_symbols;
|
||||
|
||||
// Scan block
|
||||
let mut block_walker = UsedValueFinderVisitor::new(cx);
|
||||
for stmt in block_stmts {
|
||||
intravisit::walk_stmt(&mut block_walker, stmt);
|
||||
}
|
||||
let mut block_defs = block_walker.defs;
|
||||
|
||||
// Scan moved stmts
|
||||
let mut moved_start: Option<usize> = None;
|
||||
let mut end_walker = UsedValueFinderVisitor::new(cx);
|
||||
for (index, stmt) in end_stmts.iter().enumerate() {
|
||||
intravisit::walk_stmt(&mut end_walker, stmt);
|
||||
|
||||
for value in &end_walker.uses {
|
||||
// Well we can't move this and all prev statements. So reset
|
||||
if block_defs.contains(value) {
|
||||
moved_start = Some(index + 1);
|
||||
end_walker.defs.drain().for_each(|x| {
|
||||
block_defs.insert(x);
|
||||
});
|
||||
|
||||
end_walker.def_symbols.clear();
|
||||
}
|
||||
}
|
||||
|
||||
end_walker.uses.clear();
|
||||
}
|
||||
|
||||
if let Some(moved_start) = moved_start {
|
||||
end_eq -= moved_start;
|
||||
}
|
||||
|
||||
let end_linable = block.expr.map_or_else(
|
||||
|| end_eq != 0,
|
||||
|expr| {
|
||||
intravisit::walk_expr(&mut end_walker, expr);
|
||||
end_walker.uses.iter().any(|x| !block_defs.contains(x))
|
||||
},
|
||||
);
|
||||
|
||||
if end_linable {
|
||||
end_walker.def_symbols.drain().for_each(|x| {
|
||||
moved_syms.insert(x);
|
||||
});
|
||||
}
|
||||
|
||||
emit_branches_sharing_code_lint(
|
||||
cx,
|
||||
start_eq,
|
||||
end_eq,
|
||||
end_linable,
|
||||
check_for_warn_of_moved_symbol(cx, &moved_syms, expr),
|
||||
blocks,
|
||||
expr,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn scan_block_for_eq(cx: &LateContext<'tcx>, blocks: &[&Block<'tcx>]) -> (usize, usize, bool) {
|
||||
let mut start_eq = usize::MAX;
|
||||
let mut end_eq = usize::MAX;
|
||||
let mut expr_eq = true;
|
||||
for win in blocks.windows(2) {
|
||||
let l_stmts = win[0].stmts;
|
||||
let r_stmts = win[1].stmts;
|
||||
|
||||
// `SpanlessEq` now keeps track of the locals and is therefore context sensitive clippy#6752.
|
||||
// The comparison therefore needs to be done in a way that builds the correct context.
|
||||
let mut evaluator = SpanlessEq::new(cx);
|
||||
let mut evaluator = evaluator.inter_expr();
|
||||
|
||||
let current_start_eq = count_eq(&mut l_stmts.iter(), &mut r_stmts.iter(), |l, r| evaluator.eq_stmt(l, r));
|
||||
|
||||
let current_end_eq = {
|
||||
// We skip the middle statements which can't be equal
|
||||
let end_comparison_count = l_stmts.len().min(r_stmts.len()) - current_start_eq;
|
||||
let it1 = l_stmts.iter().skip(l_stmts.len() - end_comparison_count);
|
||||
let it2 = r_stmts.iter().skip(r_stmts.len() - end_comparison_count);
|
||||
it1.zip(it2)
|
||||
.fold(0, |acc, (l, r)| if evaluator.eq_stmt(l, r) { acc + 1 } else { 0 })
|
||||
};
|
||||
let block_expr_eq = both(&win[0].expr, &win[1].expr, |l, r| evaluator.eq_expr(l, r));
|
||||
|
||||
// IF_SAME_THEN_ELSE
|
||||
if_chain! {
|
||||
if block_expr_eq;
|
||||
if l_stmts.len() == r_stmts.len();
|
||||
if l_stmts.len() == current_start_eq;
|
||||
if run_lints(cx, &[IF_SAME_THEN_ELSE], win[0].hir_id);
|
||||
if run_lints(cx, &[IF_SAME_THEN_ELSE], win[1].hir_id);
|
||||
then {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
IF_SAME_THEN_ELSE,
|
||||
j.span,
|
||||
win[0].span,
|
||||
"this `if` has identical blocks",
|
||||
Some(i.span),
|
||||
Some(win[1].span),
|
||||
"same as this",
|
||||
);
|
||||
|
||||
return (0, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
start_eq = start_eq.min(current_start_eq);
|
||||
end_eq = end_eq.min(current_end_eq);
|
||||
expr_eq &= block_expr_eq;
|
||||
}
|
||||
|
||||
let has_expr = blocks[0].expr.is_some();
|
||||
if has_expr && !expr_eq {
|
||||
end_eq = 0;
|
||||
}
|
||||
|
||||
// Check if the regions are overlapping. Set `end_eq` to prevent the overlap
|
||||
let min_block_size = blocks.iter().map(|x| x.stmts.len()).min().unwrap();
|
||||
if (start_eq + end_eq) > min_block_size {
|
||||
end_eq = min_block_size - start_eq;
|
||||
}
|
||||
|
||||
(start_eq, end_eq, expr_eq)
|
||||
}
|
||||
|
||||
fn check_for_warn_of_moved_symbol(
|
||||
cx: &LateContext<'tcx>,
|
||||
symbols: &FxHashSet<Symbol>,
|
||||
if_expr: &'tcx Expr<'_>,
|
||||
) -> bool {
|
||||
get_enclosing_block(cx, if_expr.hir_id).map_or(false, |block| {
|
||||
let ignore_span = block.span.shrink_to_lo().to(if_expr.span);
|
||||
|
||||
symbols
|
||||
.iter()
|
||||
.filter(|sym| !sym.as_str().starts_with('_'))
|
||||
.any(move |sym| {
|
||||
let mut walker = ContainsName {
|
||||
name: *sym,
|
||||
result: false,
|
||||
};
|
||||
|
||||
// Scan block
|
||||
block
|
||||
.stmts
|
||||
.iter()
|
||||
.filter(|stmt| !ignore_span.overlaps(stmt.span))
|
||||
.for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt));
|
||||
|
||||
if let Some(expr) = block.expr {
|
||||
intravisit::walk_expr(&mut walker, expr);
|
||||
}
|
||||
|
||||
walker.result
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn emit_branches_sharing_code_lint(
|
||||
cx: &LateContext<'tcx>,
|
||||
start_stmts: usize,
|
||||
end_stmts: usize,
|
||||
lint_end: bool,
|
||||
warn_about_moved_symbol: bool,
|
||||
blocks: &[&Block<'tcx>],
|
||||
if_expr: &'tcx Expr<'_>,
|
||||
) {
|
||||
if start_stmts == 0 && !lint_end {
|
||||
return;
|
||||
}
|
||||
|
||||
// (help, span, suggestion)
|
||||
let mut suggestions: Vec<(&str, Span, String)> = vec![];
|
||||
let mut add_expr_note = false;
|
||||
|
||||
// Construct suggestions
|
||||
if start_stmts > 0 {
|
||||
let block = blocks[0];
|
||||
let span_start = first_line_of_span(cx, if_expr.span).shrink_to_lo();
|
||||
let span_end = block.stmts[start_stmts - 1].span.source_callsite();
|
||||
|
||||
let cond_span = first_line_of_span(cx, if_expr.span).until(block.span);
|
||||
let cond_snippet = reindent_multiline(snippet(cx, cond_span, "_"), false, None);
|
||||
let cond_indent = indent_of(cx, cond_span);
|
||||
let moved_span = block.stmts[0].span.source_callsite().to(span_end);
|
||||
let moved_snippet = reindent_multiline(snippet(cx, moved_span, "_"), true, None);
|
||||
let suggestion = moved_snippet.to_string() + "\n" + &cond_snippet + "{";
|
||||
let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, cond_indent);
|
||||
|
||||
let span = span_start.to(span_end);
|
||||
suggestions.push(("start", span, suggestion.to_string()));
|
||||
}
|
||||
|
||||
if lint_end {
|
||||
let block = blocks[blocks.len() - 1];
|
||||
let span_end = block.span.shrink_to_hi();
|
||||
|
||||
let moved_start = if end_stmts == 0 && block.expr.is_some() {
|
||||
block.expr.unwrap().span
|
||||
} else {
|
||||
block.stmts[block.stmts.len() - end_stmts].span
|
||||
}
|
||||
.source_callsite();
|
||||
let moved_end = block
|
||||
.expr
|
||||
.map_or_else(|| block.stmts[block.stmts.len() - 1].span, |expr| expr.span)
|
||||
.source_callsite();
|
||||
|
||||
let moved_span = moved_start.to(moved_end);
|
||||
let moved_snipped = reindent_multiline(snippet(cx, moved_span, "_"), true, None);
|
||||
let indent = indent_of(cx, if_expr.span.shrink_to_hi());
|
||||
let suggestion = "}\n".to_string() + &moved_snipped;
|
||||
let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, indent);
|
||||
|
||||
let mut span = moved_start.to(span_end);
|
||||
// Improve formatting if the inner block has indention (i.e. normal Rust formatting)
|
||||
let test_span = Span::new(span.lo() - BytePos(4), span.lo(), span.ctxt());
|
||||
if snippet_opt(cx, test_span)
|
||||
.map(|snip| snip == " ")
|
||||
.unwrap_or_default()
|
||||
{
|
||||
span = span.with_lo(test_span.lo());
|
||||
}
|
||||
|
||||
suggestions.push(("end", span, suggestion.to_string()));
|
||||
add_expr_note = !cx.typeck_results().expr_ty(if_expr).is_unit()
|
||||
}
|
||||
|
||||
let add_optional_msgs = |diag: &mut DiagnosticBuilder<'_>| {
|
||||
if add_expr_note {
|
||||
diag.note("The end suggestion probably needs some adjustments to use the expression result correctly");
|
||||
}
|
||||
|
||||
if warn_about_moved_symbol {
|
||||
diag.warn("Some moved values might need to be renamed to avoid wrong references");
|
||||
}
|
||||
};
|
||||
|
||||
// Emit lint
|
||||
if suggestions.len() == 1 {
|
||||
let (place_str, span, sugg) = suggestions.pop().unwrap();
|
||||
let msg = format!("all if blocks contain the same code at the {}", place_str);
|
||||
let help = format!("consider moving the {} statements out like this", place_str);
|
||||
span_lint_and_then(cx, BRANCHES_SHARING_CODE, span, msg.as_str(), |diag| {
|
||||
diag.span_suggestion(span, help.as_str(), sugg, Applicability::Unspecified);
|
||||
|
||||
add_optional_msgs(diag);
|
||||
});
|
||||
} else if suggestions.len() == 2 {
|
||||
let (_, end_span, end_sugg) = suggestions.pop().unwrap();
|
||||
let (_, start_span, start_sugg) = suggestions.pop().unwrap();
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
BRANCHES_SHARING_CODE,
|
||||
start_span,
|
||||
"all if blocks contain the same code at the start and the end. Here at the start",
|
||||
move |diag| {
|
||||
diag.span_note(end_span, "and here at the end");
|
||||
|
||||
diag.span_suggestion(
|
||||
start_span,
|
||||
"consider moving the start statements out like this",
|
||||
start_sugg,
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
|
||||
diag.span_suggestion(
|
||||
end_span,
|
||||
"and consider moving the end statements out like this",
|
||||
end_sugg,
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
|
||||
add_optional_msgs(diag);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// This visitor collects `HirId`s and Symbols of defined symbols and `HirId`s of used values.
|
||||
struct UsedValueFinderVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
|
||||
/// The `HirId`s of defined values in the scanned statements
|
||||
defs: FxHashSet<HirId>,
|
||||
|
||||
/// The Symbols of the defined symbols in the scanned statements
|
||||
def_symbols: FxHashSet<Symbol>,
|
||||
|
||||
/// The `HirId`s of the used values
|
||||
uses: FxHashSet<HirId>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> UsedValueFinderVisitor<'a, 'tcx> {
|
||||
fn new(cx: &'a LateContext<'tcx>) -> Self {
|
||||
UsedValueFinderVisitor {
|
||||
cx,
|
||||
defs: FxHashSet::default(),
|
||||
def_symbols: FxHashSet::default(),
|
||||
uses: FxHashSet::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for UsedValueFinderVisitor<'a, 'tcx> {
|
||||
type Map = Map<'tcx>;
|
||||
|
||||
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||
NestedVisitorMap::All(self.cx.tcx.hir())
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, l: &'tcx rustc_hir::Local<'tcx>) {
|
||||
let local_id = l.pat.hir_id;
|
||||
self.defs.insert(local_id);
|
||||
|
||||
if let Some(sym) = l.pat.simple_ident() {
|
||||
self.def_symbols.insert(sym.name);
|
||||
}
|
||||
|
||||
if let Some(expr) = l.init {
|
||||
intravisit::walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_qpath(&mut self, qpath: &'tcx rustc_hir::QPath<'tcx>, id: HirId, _span: rustc_span::Span) {
|
||||
if let rustc_hir::QPath::Resolved(_, path) = *qpath {
|
||||
if path.segments.len() == 1 {
|
||||
if let rustc_hir::def::Res::Local(var) = self.cx.qpath_res(qpath, id) {
|
||||
self.uses.insert(var);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,15 +618,3 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn search_same_sequenced<T, Eq>(exprs: &[T], eq: Eq) -> Option<(&T, &T)>
|
||||
where
|
||||
Eq: Fn(&T, &T) -> bool,
|
||||
{
|
||||
for win in exprs.windows(2) {
|
||||
if eq(&win[0], &win[1]) {
|
||||
return Some((&win[0], &win[1]));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ declare_lint_pass!(CreateDir => [CREATE_DIR]);
|
|||
impl LateLintPass<'_> for CreateDir {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(ref func, ref args) = expr.kind;
|
||||
if let ExprKind::Call(func, args) = expr.kind;
|
||||
if let ExprKind::Path(ref path) = func.kind;
|
||||
if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id();
|
||||
if match_def_path(cx, def_id, &paths::STD_FS_CREATE_DIR);
|
||||
|
|
|
@ -77,16 +77,16 @@ impl LateLintPass<'_> for Default {
|
|||
if_chain! {
|
||||
// Avoid cases already linted by `field_reassign_with_default`
|
||||
if !self.reassigned_linted.contains(&expr.span);
|
||||
if let ExprKind::Call(ref path, ..) = expr.kind;
|
||||
if let ExprKind::Call(path, ..) = expr.kind;
|
||||
if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
|
||||
if let ExprKind::Path(ref qpath) = path.kind;
|
||||
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
|
||||
if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD);
|
||||
// Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
|
||||
if let QPath::Resolved(None, _path) = qpath;
|
||||
then {
|
||||
let expr_ty = cx.typeck_results().expr_ty(expr);
|
||||
if let ty::Adt(def, ..) = expr_ty.kind() {
|
||||
if let ty::Adt(def, ..) = expr_ty.kind();
|
||||
then {
|
||||
// TODO: Work out a way to put "whatever the imported way of referencing
|
||||
// this type in this file" rather than a fully-qualified type.
|
||||
let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did));
|
||||
|
@ -102,7 +102,6 @@ impl LateLintPass<'_> for Default {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
|
||||
|
@ -202,6 +201,7 @@ impl LateLintPass<'_> for Default {
|
|||
let binding_type = if_chain! {
|
||||
if let ty::Adt(adt_def, substs) = binding_type.kind();
|
||||
if !substs.is_empty();
|
||||
then {
|
||||
let adt_def_ty_name = cx.tcx.item_name(adt_def.did);
|
||||
let generic_args = substs.iter().collect::<Vec<_>>();
|
||||
let tys_str = generic_args
|
||||
|
@ -209,7 +209,6 @@ impl LateLintPass<'_> for Default {
|
|||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
then {
|
||||
format!("{}::<{}>", adt_def_ty_name, &tys_str)
|
||||
} else {
|
||||
binding_type.to_string()
|
||||
|
@ -247,7 +246,7 @@ impl LateLintPass<'_> for Default {
|
|||
/// Checks if the given expression is the `default` method belonging to the `Default` trait.
|
||||
fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(ref fn_expr, _) = &expr.kind;
|
||||
if let ExprKind::Call(fn_expr, _) = &expr.kind;
|
||||
if let ExprKind::Path(qpath) = &fn_expr.kind;
|
||||
if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id);
|
||||
then {
|
||||
|
@ -263,11 +262,11 @@ fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool
|
|||
fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> {
|
||||
if_chain! {
|
||||
// only take assignments
|
||||
if let StmtKind::Semi(ref later_expr) = this.kind;
|
||||
if let ExprKind::Assign(ref assign_lhs, ref assign_rhs, _) = later_expr.kind;
|
||||
if let StmtKind::Semi(later_expr) = this.kind;
|
||||
if let ExprKind::Assign(assign_lhs, assign_rhs, _) = later_expr.kind;
|
||||
// only take assignments to fields where the left-hand side field is a field of
|
||||
// the same binding as the previous statement
|
||||
if let ExprKind::Field(ref binding, field_ident) = assign_lhs.kind;
|
||||
if let ExprKind::Field(binding, field_ident) = assign_lhs.kind;
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind;
|
||||
if let Some(second_binding_name) = path.segments.last();
|
||||
if second_binding_name.ident.name == binding_name;
|
||||
|
|
|
@ -131,8 +131,8 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
|
|||
},
|
||||
|
||||
ExprKind::Struct(_, fields, base) => {
|
||||
if_chain! {
|
||||
let ty = self.cx.typeck_results().expr_ty(expr);
|
||||
if_chain! {
|
||||
if let Some(adt_def) = ty.ty_adt_def();
|
||||
if adt_def.is_struct();
|
||||
if let Some(variant) = adt_def.variants.iter().next();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_then};
|
||||
use clippy_utils::paths;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use clippy_utils::ty::{implements_trait, is_copy};
|
||||
use clippy_utils::{get_trait_def_id, is_allowed, is_automatically_derived, match_def_path};
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::def_id::DefId;
|
||||
|
@ -12,7 +12,7 @@ use rustc_lint::{LateContext, LateLintPass};
|
|||
use rustc_middle::hir::map::Map;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::source_map::Span;
|
||||
use rustc_span::{def_id::LOCAL_CRATE, source_map::Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for deriving `Hash` but implementing `PartialEq`
|
||||
|
@ -199,7 +199,7 @@ fn check_hash_peq<'tcx>(
|
|||
then {
|
||||
// Look for the PartialEq implementations for `ty`
|
||||
cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
|
||||
let peq_is_automatically_derived = is_automatically_derived(&cx.tcx.get_attrs(impl_id));
|
||||
let peq_is_automatically_derived = is_automatically_derived(cx.tcx.get_attrs(impl_id));
|
||||
|
||||
if peq_is_automatically_derived == hash_is_automatically_derived {
|
||||
return;
|
||||
|
@ -253,7 +253,7 @@ fn check_ord_partial_ord<'tcx>(
|
|||
then {
|
||||
// Look for the PartialOrd implementations for `ty`
|
||||
cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
|
||||
let partial_ord_is_automatically_derived = is_automatically_derived(&cx.tcx.get_attrs(impl_id));
|
||||
let partial_ord_is_automatically_derived = is_automatically_derived(cx.tcx.get_attrs(impl_id));
|
||||
|
||||
if partial_ord_is_automatically_derived == ord_is_automatically_derived {
|
||||
return;
|
||||
|
@ -293,38 +293,44 @@ fn check_ord_partial_ord<'tcx>(
|
|||
|
||||
/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
|
||||
fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
|
||||
if cx
|
||||
.tcx
|
||||
.lang_items()
|
||||
.clone_trait()
|
||||
.map_or(false, |id| Some(id) == trait_ref.trait_def_id())
|
||||
{
|
||||
let clone_id = match cx.tcx.lang_items().clone_trait() {
|
||||
Some(id) if trait_ref.trait_def_id() == Some(id) => id,
|
||||
_ => return,
|
||||
};
|
||||
let copy_id = match cx.tcx.lang_items().copy_trait() {
|
||||
Some(id) => id,
|
||||
None => return,
|
||||
};
|
||||
let (ty_adt, ty_subs) = match *ty.kind() {
|
||||
// Unions can't derive clone.
|
||||
ty::Adt(adt, subs) if !adt.is_union() => (adt, subs),
|
||||
_ => return,
|
||||
};
|
||||
// If the current self type doesn't implement Copy (due to generic constraints), search to see if
|
||||
// there's a Copy impl for any instance of the adt.
|
||||
if !is_copy(cx, ty) {
|
||||
if ty_subs.non_erasable_generics().next().is_some() {
|
||||
let has_copy_impl = cx
|
||||
.tcx
|
||||
.all_local_trait_impls(LOCAL_CRATE)
|
||||
.get(©_id)
|
||||
.map_or(false, |impls| {
|
||||
impls
|
||||
.iter()
|
||||
.any(|&id| matches!(cx.tcx.type_of(id).kind(), ty::Adt(adt, _) if ty_adt.did == adt.did))
|
||||
});
|
||||
if !has_copy_impl {
|
||||
return;
|
||||
}
|
||||
|
||||
match *ty.kind() {
|
||||
ty::Adt(def, _) if def.is_union() => return,
|
||||
|
||||
// Some types are not Clone by default but could be cloned “by hand” if necessary
|
||||
ty::Adt(def, substs) => {
|
||||
for variant in &def.variants {
|
||||
for field in &variant.fields {
|
||||
if let ty::FnDef(..) = field.ty(cx.tcx, substs).kind() {
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for subst in substs {
|
||||
if let ty::subst::GenericArgKind::Type(subst) = subst.unpack() {
|
||||
if let ty::Param(_) = subst.kind() {
|
||||
// Derive constrains all generic types to requiring Clone. Check if any type is not constrained for
|
||||
// this impl.
|
||||
if ty_subs.types().any(|ty| !implements_trait(cx, ty, clone_id, &[])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
|
@ -334,7 +340,6 @@ fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &T
|
|||
Some(item.span),
|
||||
"consider deriving `Clone` or removing `Copy`",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint.
|
||||
|
|
|
@ -11,7 +11,7 @@ use rustc_errors::emitter::EmitterWriter;
|
|||
use rustc_errors::Handler;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
|
||||
use rustc_hir::{Expr, ExprKind, QPath};
|
||||
use rustc_hir::{AnonConst, Expr, ExprKind, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
|
@ -710,16 +710,22 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
|
|||
|
||||
// check for `begin_panic`
|
||||
if_chain! {
|
||||
if let ExprKind::Call(ref func_expr, _) = expr.kind;
|
||||
if let ExprKind::Path(QPath::Resolved(_, ref path)) = func_expr.kind;
|
||||
if let ExprKind::Call(func_expr, _) = expr.kind;
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = func_expr.kind;
|
||||
if let Some(path_def_id) = path.res.opt_def_id();
|
||||
if match_panic_def_id(self.cx, path_def_id);
|
||||
if is_expn_of(expr.span, "unreachable").is_none();
|
||||
if !is_expn_of_debug_assertions(expr.span);
|
||||
then {
|
||||
self.panic_span = Some(expr.span);
|
||||
}
|
||||
}
|
||||
|
||||
// check for `assert_eq` or `assert_ne`
|
||||
if is_expn_of(expr.span, "assert_eq").is_some() || is_expn_of(expr.span, "assert_ne").is_some() {
|
||||
self.panic_span = Some(expr.span);
|
||||
}
|
||||
|
||||
// check for `unwrap`
|
||||
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
|
||||
let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
|
||||
|
@ -734,7 +740,15 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
|
|||
intravisit::walk_expr(self, expr);
|
||||
}
|
||||
|
||||
// Panics in const blocks will cause compilation to fail.
|
||||
fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
|
||||
|
||||
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
|
||||
}
|
||||
}
|
||||
|
||||
fn is_expn_of_debug_assertions(span: Span) -> bool {
|
||||
const MACRO_NAMES: &[&str] = &["debug_assert", "debug_assert_eq", "debug_assert_ne"];
|
||||
MACRO_NAMES.iter().any(|name| is_expn_of(span, name).is_some())
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ impl<'tcx> DoubleComparisons {
|
|||
},
|
||||
_ => return,
|
||||
};
|
||||
if !(eq_expr_value(cx, &llhs, &rlhs) && eq_expr_value(cx, &lrhs, &rrhs)) {
|
||||
if !(eq_expr_value(cx, llhs, rlhs) && eq_expr_value(cx, lrhs, rrhs)) {
|
||||
return;
|
||||
}
|
||||
macro_rules! lint_double_comparison {
|
||||
|
@ -88,7 +88,7 @@ impl<'tcx> DoubleComparisons {
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for DoubleComparisons {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = expr.kind {
|
||||
if let ExprKind::Binary(ref kind, lhs, rhs) = expr.kind {
|
||||
Self::check_binop(cx, kind.node, lhs, rhs, expr.span);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ impl EarlyLintPass for DoubleParens {
|
|||
match expr.kind {
|
||||
ExprKind::Paren(ref in_paren) => match in_paren.kind {
|
||||
ExprKind::Paren(_) | ExprKind::Tup(_) => {
|
||||
span_lint(cx, DOUBLE_PARENS, expr.span, &msg);
|
||||
span_lint(cx, DOUBLE_PARENS, expr.span, msg);
|
||||
},
|
||||
_ => {},
|
||||
},
|
||||
|
@ -58,7 +58,7 @@ impl EarlyLintPass for DoubleParens {
|
|||
if params.len() == 1 {
|
||||
let param = ¶ms[0];
|
||||
if let ExprKind::Paren(_) = param.kind {
|
||||
span_lint(cx, DOUBLE_PARENS, param.span, &msg);
|
||||
span_lint(cx, DOUBLE_PARENS, param.span, msg);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -66,7 +66,7 @@ impl EarlyLintPass for DoubleParens {
|
|||
if params.len() == 2 {
|
||||
let param = ¶ms[1];
|
||||
if let ExprKind::Paren(_) = param.kind {
|
||||
span_lint(cx, DOUBLE_PARENS, param.span, &msg);
|
||||
span_lint(cx, DOUBLE_PARENS, param.span, msg);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -113,7 +113,7 @@ declare_lint_pass!(DropForgetRef => [DROP_REF, FORGET_REF, DROP_COPY, FORGET_COP
|
|||
impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(ref path, ref args) = expr.kind;
|
||||
if let ExprKind::Call(path, args) = expr.kind;
|
||||
if let ExprKind::Path(ref qpath) = path.kind;
|
||||
if args.len() == 1;
|
||||
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
|
||||
|
|
|
@ -43,8 +43,8 @@ declare_lint_pass!(DurationSubsec => [DURATION_SUBSEC]);
|
|||
impl<'tcx> LateLintPass<'tcx> for DurationSubsec {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Div, .. }, ref left, ref right) = expr.kind;
|
||||
if let ExprKind::MethodCall(ref method_path, _ , ref args, _) = left.kind;
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Div, .. }, left, right) = expr.kind;
|
||||
if let ExprKind::MethodCall(method_path, _ , args, _) = left.kind;
|
||||
if match_type(cx, cx.typeck_results().expr_ty(&args[0]).peel_refs(), &paths::DURATION);
|
||||
if let Some((Constant::Int(divisor), _)) = constant(cx, cx.typeck_results(), right);
|
||||
then {
|
||||
|
|
|
@ -57,14 +57,14 @@ declare_lint_pass!(HashMapPass => [MAP_ENTRY]);
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for HashMapPass {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::If(ref check, ref then_block, ref else_block) = expr.kind {
|
||||
if let ExprKind::Unary(UnOp::Not, ref check) = check.kind {
|
||||
if let ExprKind::If(check, then_block, ref else_block) = expr.kind {
|
||||
if let ExprKind::Unary(UnOp::Not, check) = check.kind {
|
||||
if let Some((ty, map, key)) = check_cond(cx, check) {
|
||||
// in case of `if !m.contains_key(&k) { m.insert(k, v); }`
|
||||
// we can give a better error message
|
||||
let sole_expr = {
|
||||
else_block.is_none()
|
||||
&& if let ExprKind::Block(ref then_block, _) = then_block.kind {
|
||||
&& if let ExprKind::Block(then_block, _) = then_block.kind {
|
||||
(then_block.expr.is_some() as usize) + then_block.stmts.len() == 1
|
||||
} else {
|
||||
true
|
||||
|
@ -81,9 +81,9 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
|
|||
sole_expr,
|
||||
};
|
||||
|
||||
walk_expr(&mut visitor, &**then_block);
|
||||
walk_expr(&mut visitor, then_block);
|
||||
}
|
||||
} else if let Some(ref else_block) = *else_block {
|
||||
} else if let Some(else_block) = *else_block {
|
||||
if let Some((ty, map, key)) = check_cond(cx, check) {
|
||||
let mut visitor = InsertVisitor {
|
||||
cx,
|
||||
|
@ -103,10 +103,10 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
|
|||
|
||||
fn check_cond<'a>(cx: &LateContext<'_>, check: &'a Expr<'a>) -> Option<(&'static str, &'a Expr<'a>, &'a Expr<'a>)> {
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(ref path, _, ref params, _) = check.kind;
|
||||
if let ExprKind::MethodCall(path, _, params, _) = check.kind;
|
||||
if params.len() >= 2;
|
||||
if path.ident.name == sym!(contains_key);
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref key) = params[1].kind;
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, key) = params[1].kind;
|
||||
then {
|
||||
let map = ¶ms[0];
|
||||
let obj_ty = cx.typeck_results().expr_ty(map).peel_refs();
|
||||
|
@ -140,7 +140,7 @@ impl<'a, 'tcx, 'b> Visitor<'tcx> for InsertVisitor<'a, 'tcx, 'b> {
|
|||
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(ref path, _, ref params, _) = expr.kind;
|
||||
if let ExprKind::MethodCall(path, _, params, _) = expr.kind;
|
||||
if params.len() == 3;
|
||||
if path.ident.name == sym!(insert);
|
||||
if get_item_name(self.cx, self.map) == get_item_name(self.cx, ¶ms[0]);
|
||||
|
|
|
@ -65,12 +65,12 @@ const ASSERT_MACRO_NAMES: [&str; 4] = ["assert_eq", "assert_ne", "debug_assert_e
|
|||
impl<'tcx> LateLintPass<'tcx> for EqOp {
|
||||
#[allow(clippy::similar_names, clippy::too_many_lines)]
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Block(ref block, _) = e.kind {
|
||||
if let ExprKind::Block(block, _) = e.kind {
|
||||
for stmt in block.stmts {
|
||||
for amn in &ASSERT_MACRO_NAMES {
|
||||
if_chain! {
|
||||
if is_expn_of(stmt.span, amn).is_some();
|
||||
if let StmtKind::Semi(ref matchexpr) = stmt.kind;
|
||||
if let StmtKind::Semi(matchexpr) = stmt.kind;
|
||||
if let Some(macro_args) = higher::extract_assert_macro_args(matchexpr);
|
||||
if macro_args.len() == 2;
|
||||
let (lhs, rhs) = (macro_args[0], macro_args[1]);
|
||||
|
@ -88,12 +88,12 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
|
|||
}
|
||||
}
|
||||
}
|
||||
if let ExprKind::Binary(op, ref left, ref right) = e.kind {
|
||||
if let ExprKind::Binary(op, left, right) = e.kind {
|
||||
if e.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
let macro_with_not_op = |expr_kind: &ExprKind<'_>| {
|
||||
if let ExprKind::Unary(_, ref expr) = *expr_kind {
|
||||
if let ExprKind::Unary(_, expr) = *expr_kind {
|
||||
in_macro(expr.span)
|
||||
} else {
|
||||
false
|
||||
|
@ -135,7 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
|
|||
// do not suggest to dereference literals
|
||||
(&ExprKind::Lit(..), _) | (_, &ExprKind::Lit(..)) => {},
|
||||
// &foo == &bar
|
||||
(&ExprKind::AddrOf(BorrowKind::Ref, _, ref l), &ExprKind::AddrOf(BorrowKind::Ref, _, ref r)) => {
|
||||
(&ExprKind::AddrOf(BorrowKind::Ref, _, l), &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => {
|
||||
let lty = cx.typeck_results().expr_ty(l);
|
||||
let rty = cx.typeck_results().expr_ty(r);
|
||||
let lcpy = is_copy(cx, lty);
|
||||
|
@ -198,7 +198,7 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
|
|||
}
|
||||
},
|
||||
// &foo == bar
|
||||
(&ExprKind::AddrOf(BorrowKind::Ref, _, ref l), _) => {
|
||||
(&ExprKind::AddrOf(BorrowKind::Ref, _, l), _) => {
|
||||
let lty = cx.typeck_results().expr_ty(l);
|
||||
let lcpy = is_copy(cx, lty);
|
||||
if (requires_ref || lcpy)
|
||||
|
@ -222,7 +222,7 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
|
|||
}
|
||||
},
|
||||
// foo == &bar
|
||||
(_, &ExprKind::AddrOf(BorrowKind::Ref, _, ref r)) => {
|
||||
(_, &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => {
|
||||
let rty = cx.typeck_results().expr_ty(r);
|
||||
let rcpy = is_copy(cx, rty);
|
||||
if (requires_ref || rcpy)
|
||||
|
|
|
@ -34,7 +34,7 @@ impl<'tcx> LateLintPass<'tcx> for ErasingOp {
|
|||
if e.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
if let ExprKind::Binary(ref cmp, ref left, ref right) = e.kind {
|
||||
if let ExprKind::Binary(ref cmp, left, right) = e.kind {
|
||||
match cmp.node {
|
||||
BinOpKind::Mul | BinOpKind::BitAnd => {
|
||||
check(cx, left, e.span);
|
||||
|
|
|
@ -87,7 +87,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
|
|||
}
|
||||
|
||||
fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if let ExprKind::Closure(_, ref decl, eid, _, _) = expr.kind {
|
||||
if let ExprKind::Closure(_, decl, eid, _, _) = expr.kind {
|
||||
let body = cx.tcx.hir().body(eid);
|
||||
let ex = &body.value;
|
||||
|
||||
|
@ -109,7 +109,7 @@ fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
}
|
||||
|
||||
if_chain!(
|
||||
if let ExprKind::Call(ref caller, ref args) = ex.kind;
|
||||
if let ExprKind::Call(caller, args) = ex.kind;
|
||||
|
||||
if let ExprKind::Path(_) = caller.kind;
|
||||
|
||||
|
@ -142,7 +142,7 @@ fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
);
|
||||
|
||||
if_chain!(
|
||||
if let ExprKind::MethodCall(ref path, _, ref args, _) = ex.kind;
|
||||
if let ExprKind::MethodCall(path, _, args, _) = ex.kind;
|
||||
|
||||
// Not the same number of arguments, there is no way the closure is the same as the function return;
|
||||
if args.len() == decl.inputs.len();
|
||||
|
@ -178,7 +178,7 @@ fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: def_id::DefId, self_a
|
|||
let actual_type_of_self = &cx.typeck_results().node_type(self_arg.hir_id);
|
||||
|
||||
if let Some(trait_id) = cx.tcx.trait_of_item(method_def_id) {
|
||||
if match_borrow_depth(expected_type_of_self, &actual_type_of_self)
|
||||
if match_borrow_depth(expected_type_of_self, actual_type_of_self)
|
||||
&& implements_trait(cx, actual_type_of_self, trait_id, &[])
|
||||
{
|
||||
return Some(cx.tcx.def_path_str(trait_id));
|
||||
|
@ -187,8 +187,8 @@ fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: def_id::DefId, self_a
|
|||
|
||||
cx.tcx.impl_of_method(method_def_id).and_then(|_| {
|
||||
//a type may implicitly implement other type's methods (e.g. Deref)
|
||||
if match_types(expected_type_of_self, &actual_type_of_self) {
|
||||
return Some(get_type_name(cx, &actual_type_of_self));
|
||||
if match_types(expected_type_of_self, actual_type_of_self) {
|
||||
return Some(get_type_name(cx, actual_type_of_self));
|
||||
}
|
||||
None
|
||||
})
|
||||
|
@ -196,7 +196,7 @@ fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: def_id::DefId, self_a
|
|||
|
||||
fn match_borrow_depth(lhs: Ty<'_>, rhs: Ty<'_>) -> bool {
|
||||
match (&lhs.kind(), &rhs.kind()) {
|
||||
(ty::Ref(_, t1, mut1), ty::Ref(_, t2, mut2)) => mut1 == mut2 && match_borrow_depth(&t1, &t2),
|
||||
(ty::Ref(_, t1, mut1), ty::Ref(_, t2, mut2)) => mut1 == mut2 && match_borrow_depth(t1, t2),
|
||||
(l, r) => !matches!((l, r), (ty::Ref(_, _, _), _) | (_, ty::Ref(_, _, _))),
|
||||
}
|
||||
}
|
||||
|
@ -218,7 +218,7 @@ fn match_types(lhs: Ty<'_>, rhs: Ty<'_>) -> bool {
|
|||
fn get_type_name(cx: &LateContext<'_>, ty: Ty<'_>) -> String {
|
||||
match ty.kind() {
|
||||
ty::Adt(t, _) => cx.tcx.def_path_str(t.did),
|
||||
ty::Ref(_, r, _) => get_type_name(cx, &r),
|
||||
ty::Ref(_, r, _) => get_type_name(cx, r),
|
||||
_ => ty.to_string(),
|
||||
}
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ fn compare_inputs(
|
|||
for (closure_input, function_arg) in closure_inputs.zip(call_args) {
|
||||
if let PatKind::Binding(_, _, ident, _) = closure_input.pat.kind {
|
||||
// XXXManishearth Should I be checking the binding mode here?
|
||||
if let ExprKind::Path(QPath::Resolved(None, ref p)) = function_arg.kind {
|
||||
if let ExprKind::Path(QPath::Resolved(None, p)) = function_arg.kind {
|
||||
if p.segments.len() != 1 {
|
||||
// If it's a proper path, it can't be a local variable
|
||||
return false;
|
||||
|
|
|
@ -71,7 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for EvalOrderDependence {
|
|||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
// Find a write to a local variable.
|
||||
match expr.kind {
|
||||
ExprKind::Assign(ref lhs, ..) | ExprKind::AssignOp(_, ref lhs, _) => {
|
||||
ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) => {
|
||||
if let Some(var) = path_to_local(lhs) {
|
||||
let mut visitor = ReadVisitor {
|
||||
cx,
|
||||
|
@ -87,12 +87,12 @@ impl<'tcx> LateLintPass<'tcx> for EvalOrderDependence {
|
|||
}
|
||||
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
|
||||
match stmt.kind {
|
||||
StmtKind::Local(ref local) => {
|
||||
if let Local { init: Some(ref e), .. } = **local {
|
||||
StmtKind::Local(local) => {
|
||||
if let Local { init: Some(e), .. } = local {
|
||||
DivergenceVisitor { cx }.visit_expr(e);
|
||||
}
|
||||
},
|
||||
StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => DivergenceVisitor { cx }.maybe_walk_expr(e),
|
||||
StmtKind::Expr(e) | StmtKind::Semi(e) => DivergenceVisitor { cx }.maybe_walk_expr(e),
|
||||
StmtKind::Item(..) => {},
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> {
|
|||
fn maybe_walk_expr(&mut self, e: &'tcx Expr<'_>) {
|
||||
match e.kind {
|
||||
ExprKind::Closure(..) => {},
|
||||
ExprKind::Match(ref e, arms, _) => {
|
||||
ExprKind::Match(e, arms, _) => {
|
||||
self.visit_expr(e);
|
||||
for arm in arms {
|
||||
if let Some(Guard::If(if_expr)) = arm.guard {
|
||||
|
@ -130,7 +130,7 @@ impl<'a, 'tcx> Visitor<'tcx> for DivergenceVisitor<'a, 'tcx> {
|
|||
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
|
||||
match e.kind {
|
||||
ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => self.report_diverging_sub_expr(e),
|
||||
ExprKind::Call(ref func, _) => {
|
||||
ExprKind::Call(func, _) => {
|
||||
let typ = self.cx.typeck_results().expr_ty(func);
|
||||
match typ.kind() {
|
||||
ty::FnDef(..) | ty::FnPtr(_) => {
|
||||
|
@ -266,10 +266,10 @@ fn check_expr<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, expr: &'tcx Expr<'_>) -
|
|||
|
||||
fn check_stmt<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, stmt: &'tcx Stmt<'_>) -> StopEarly {
|
||||
match stmt.kind {
|
||||
StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => check_expr(vis, expr),
|
||||
StmtKind::Expr(expr) | StmtKind::Semi(expr) => check_expr(vis, expr),
|
||||
// If the declaration is of a local variable, check its initializer
|
||||
// expression if it has one. Otherwise, keep going.
|
||||
StmtKind::Local(ref local) => local
|
||||
StmtKind::Local(local) => local
|
||||
.init
|
||||
.as_ref()
|
||||
.map_or(StopEarly::KeepGoing, |expr| check_expr(vis, expr)),
|
||||
|
@ -343,7 +343,7 @@ impl<'a, 'tcx> Visitor<'tcx> for ReadVisitor<'a, 'tcx> {
|
|||
/// Returns `true` if `expr` is the LHS of an assignment, like `expr = ...`.
|
||||
fn is_in_assignment_position(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
if let Some(parent) = get_parent_expr(cx, expr) {
|
||||
if let ExprKind::Assign(ref lhs, ..) = parent.kind {
|
||||
if let ExprKind::Assign(lhs, ..) = parent.kind {
|
||||
return lhs.hir_id == expr.hir_id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::{attr_by_name, in_macro, match_path_ast};
|
||||
use clippy_utils::{in_macro, match_path_ast};
|
||||
use rustc_ast::ast::{AssocItemKind, Extern, FnKind, FnSig, ImplKind, Item, ItemKind, TraitKind, Ty, TyKind};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::Span;
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
|
@ -138,7 +138,7 @@ impl EarlyLintPass for ExcessiveBools {
|
|||
}
|
||||
match &item.kind {
|
||||
ItemKind::Struct(variant_data, _) => {
|
||||
if attr_by_name(&item.attrs, "repr").is_some() {
|
||||
if item.attrs.iter().any(|attr| attr.has_name(sym::repr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,21 +28,19 @@ declare_lint_pass!(Exit => [EXIT]);
|
|||
impl<'tcx> LateLintPass<'tcx> for Exit {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(ref path_expr, ref _args) = e.kind;
|
||||
if let ExprKind::Call(path_expr, _args) = e.kind;
|
||||
if let ExprKind::Path(ref path) = path_expr.kind;
|
||||
if let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id();
|
||||
if match_def_path(cx, def_id, &paths::EXIT);
|
||||
then {
|
||||
let parent = cx.tcx.hir().get_parent_item(e.hir_id);
|
||||
if let Some(Node::Item(Item{kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find(parent) {
|
||||
if let Some(Node::Item(Item{kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find(parent);
|
||||
// If the next item up is a function we check if it is an entry point
|
||||
// and only then emit a linter warning
|
||||
let def_id = cx.tcx.hir().local_def_id(parent);
|
||||
if !is_entrypoint_fn(cx, def_id.to_def_id()) {
|
||||
if !is_entrypoint_fn(cx, def_id.to_def_id());
|
||||
then {
|
||||
span_lint(cx, EXIT, e.span, "usage of `process::exit`");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,11 +34,11 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
|
|||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
// match call to unwrap
|
||||
if let ExprKind::MethodCall(ref unwrap_fun, _, ref unwrap_args, _) = expr.kind;
|
||||
if let ExprKind::MethodCall(unwrap_fun, _, unwrap_args, _) = expr.kind;
|
||||
if unwrap_fun.ident.name == sym::unwrap;
|
||||
// match call to write_fmt
|
||||
if !unwrap_args.is_empty();
|
||||
if let ExprKind::MethodCall(ref write_fun, _, write_args, _) =
|
||||
if let ExprKind::MethodCall(write_fun, _, write_args, _) =
|
||||
unwrap_args[0].kind;
|
||||
if write_fun.ident.name == sym!(write_fmt);
|
||||
// match calls to std::io::stdout() / std::io::stderr ()
|
||||
|
@ -135,10 +135,10 @@ fn write_output_string(write_args: &[Expr<'_>]) -> Option<String> {
|
|||
if_chain! {
|
||||
// Obtain the string that should be printed
|
||||
if write_args.len() > 1;
|
||||
if let ExprKind::Call(_, ref output_args) = write_args[1].kind;
|
||||
if let ExprKind::Call(_, output_args) = write_args[1].kind;
|
||||
if !output_args.is_empty();
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref output_string_expr) = output_args[0].kind;
|
||||
if let ExprKind::Array(ref string_exprs) = output_string_expr.kind;
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, output_string_expr) = output_args[0].kind;
|
||||
if let ExprKind::Array(string_exprs) = output_string_expr.kind;
|
||||
// we only want to provide an automatic suggestion for simple (non-format) strings
|
||||
if string_exprs.len() == 1;
|
||||
if let ExprKind::Lit(ref lit) = string_exprs[0].kind;
|
||||
|
|
|
@ -81,8 +81,8 @@ fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_items: &[h
|
|||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
// check for `begin_panic`
|
||||
if_chain! {
|
||||
if let ExprKind::Call(ref func_expr, _) = expr.kind;
|
||||
if let ExprKind::Path(QPath::Resolved(_, ref path)) = func_expr.kind;
|
||||
if let ExprKind::Call(func_expr, _) = expr.kind;
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = func_expr.kind;
|
||||
if let Some(path_def_id) = path.res.opt_def_id();
|
||||
if match_panic_def_id(self.lcx, path_def_id);
|
||||
if is_expn_of(expr.span, "unreachable").is_none();
|
||||
|
|
|
@ -48,7 +48,7 @@ impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs {
|
|||
let rhs;
|
||||
|
||||
// check if expr is a binary expression with a lt or gt operator
|
||||
if let ExprKind::Binary(op, ref left, ref right) = expr.kind {
|
||||
if let ExprKind::Binary(op, left, right) = expr.kind {
|
||||
match op.node {
|
||||
BinOpKind::Lt => {
|
||||
lhs = left;
|
||||
|
@ -88,8 +88,8 @@ impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs {
|
|||
if let ty::Float(_) = t_val_r.kind();
|
||||
|
||||
then {
|
||||
let sug_l = sugg::Sugg::hir(cx, &val_l, "..");
|
||||
let sug_r = sugg::Sugg::hir(cx, &val_r, "..");
|
||||
let sug_l = sugg::Sugg::hir(cx, val_l, "..");
|
||||
let sug_r = sugg::Sugg::hir(cx, val_r, "..");
|
||||
// format the suggestion
|
||||
let suggestion = format!("{}.abs()", sugg::make_assoc(AssocOp::Subtract, &sug_l, &sug_r).maybe_par());
|
||||
// spans the lint
|
||||
|
|
|
@ -61,8 +61,8 @@ declare_lint_pass!(FloatLiteral => [EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL]);
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
|
||||
if_chain! {
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
if_chain! {
|
||||
if let ty::Float(fty) = *ty.kind();
|
||||
if let hir::ExprKind::Lit(ref lit) = expr.kind;
|
||||
if let LitKind::Float(sym, lit_float_ty) = lit.node;
|
||||
|
|
|
@ -131,7 +131,7 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su
|
|||
let mut suggestion = Sugg::hir(cx, expr, "..");
|
||||
|
||||
if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind {
|
||||
expr = &inner_expr;
|
||||
expr = inner_expr;
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
|
@ -313,8 +313,8 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
|
|||
Spanned {
|
||||
node: BinOpKind::Add, ..
|
||||
},
|
||||
ref lhs,
|
||||
ref rhs,
|
||||
lhs,
|
||||
rhs,
|
||||
) = parent.kind
|
||||
{
|
||||
let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs };
|
||||
|
@ -329,7 +329,7 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
|
|||
"{}.mul_add({}, {})",
|
||||
Sugg::hir(cx, &args[0], ".."),
|
||||
Sugg::hir(cx, &args[0], ".."),
|
||||
Sugg::hir(cx, &other_addend, ".."),
|
||||
Sugg::hir(cx, other_addend, ".."),
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
|
@ -356,18 +356,18 @@ fn detect_hypot(cx: &LateContext<'_>, args: &[Expr<'_>]) -> Option<String> {
|
|||
Spanned {
|
||||
node: BinOpKind::Add, ..
|
||||
},
|
||||
ref add_lhs,
|
||||
ref add_rhs,
|
||||
add_lhs,
|
||||
add_rhs,
|
||||
) = args[0].kind
|
||||
{
|
||||
// check if expression of the form x * x + y * y
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref lmul_lhs, ref lmul_rhs) = add_lhs.kind;
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref rmul_lhs, ref rmul_rhs) = add_rhs.kind;
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lmul_lhs, lmul_rhs) = add_lhs.kind;
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, rmul_lhs, rmul_rhs) = add_rhs.kind;
|
||||
if eq_expr_value(cx, lmul_lhs, lmul_rhs);
|
||||
if eq_expr_value(cx, rmul_lhs, rmul_rhs);
|
||||
then {
|
||||
return Some(format!("{}.hypot({})", Sugg::hir(cx, &lmul_lhs, ".."), Sugg::hir(cx, &rmul_lhs, "..")));
|
||||
return Some(format!("{}.hypot({})", Sugg::hir(cx, lmul_lhs, ".."), Sugg::hir(cx, rmul_lhs, "..")));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -376,13 +376,13 @@ fn detect_hypot(cx: &LateContext<'_>, args: &[Expr<'_>]) -> Option<String> {
|
|||
if let ExprKind::MethodCall(
|
||||
PathSegment { ident: lmethod_name, .. },
|
||||
ref _lspan,
|
||||
ref largs,
|
||||
largs,
|
||||
_
|
||||
) = add_lhs.kind;
|
||||
if let ExprKind::MethodCall(
|
||||
PathSegment { ident: rmethod_name, .. },
|
||||
ref _rspan,
|
||||
ref rargs,
|
||||
rargs,
|
||||
_
|
||||
) = add_rhs.kind;
|
||||
if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi";
|
||||
|
@ -416,11 +416,11 @@ fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
|
|||
// and suggest usage of `x.exp_m1() - (y - 1)` instead
|
||||
fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, ref lhs, ref rhs) = expr.kind;
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, lhs, rhs) = expr.kind;
|
||||
if cx.typeck_results().expr_ty(lhs).is_floating_point();
|
||||
if let Some((value, _)) = constant(cx, cx.typeck_results(), rhs);
|
||||
if F32(1.0) == value || F64(1.0) == value;
|
||||
if let ExprKind::MethodCall(ref path, _, ref method_args, _) = lhs.kind;
|
||||
if let ExprKind::MethodCall(path, _, method_args, _) = lhs.kind;
|
||||
if cx.typeck_results().expr_ty(&method_args[0]).is_floating_point();
|
||||
if path.ident.name.as_str() == "exp";
|
||||
then {
|
||||
|
@ -442,7 +442,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
|
||||
fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref lhs, ref rhs) = &expr.kind;
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lhs, rhs) = &expr.kind;
|
||||
if cx.typeck_results().expr_ty(lhs).is_floating_point();
|
||||
if cx.typeck_results().expr_ty(rhs).is_floating_point();
|
||||
then {
|
||||
|
@ -604,8 +604,8 @@ fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
|
||||
fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(PathSegment { ident: method_name_a, .. }, _, ref args_a, _) = expr_a.kind;
|
||||
if let ExprKind::MethodCall(PathSegment { ident: method_name_b, .. }, _, ref args_b, _) = expr_b.kind;
|
||||
if let ExprKind::MethodCall(PathSegment { ident: method_name_a, .. }, _, args_a, _) = expr_a.kind;
|
||||
if let ExprKind::MethodCall(PathSegment { ident: method_name_b, .. }, _, args_b, _) = expr_b.kind;
|
||||
then {
|
||||
return method_name_a.as_str() == method_name_b.as_str() &&
|
||||
args_a.len() == args_b.len() &&
|
||||
|
@ -630,8 +630,8 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
rhs,
|
||||
) = &expr.kind;
|
||||
if are_same_base_logs(cx, lhs, rhs);
|
||||
if let ExprKind::MethodCall(_, _, ref largs, _) = lhs.kind;
|
||||
if let ExprKind::MethodCall(_, _, ref rargs, _) = rhs.kind;
|
||||
if let ExprKind::MethodCall(_, _, largs, _) = lhs.kind;
|
||||
if let ExprKind::MethodCall(_, _, rargs, _) = rhs.kind;
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
@ -675,7 +675,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
expr.span,
|
||||
"conversion to degrees can be done more accurately",
|
||||
"consider using",
|
||||
format!("{}.to_degrees()", Sugg::hir(cx, &mul_lhs, "..")),
|
||||
format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..")),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else if
|
||||
|
@ -688,7 +688,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
expr.span,
|
||||
"conversion to radians can be done more accurately",
|
||||
"consider using",
|
||||
format!("{}.to_radians()", Sugg::hir(cx, &mul_lhs, "..")),
|
||||
format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..")),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
@ -698,7 +698,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::MethodCall(ref path, _, args, _) = &expr.kind {
|
||||
if let ExprKind::MethodCall(path, _, args, _) = &expr.kind {
|
||||
let recv_ty = cx.typeck_results().expr_ty(&args[0]);
|
||||
|
||||
if recv_ty.is_floating_point() {
|
||||
|
|
|
@ -78,8 +78,8 @@ fn span_useless_format<T: LintContext>(cx: &T, span: Span, help: &str, mut sugg:
|
|||
|
||||
fn on_argumentv1_new<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) -> Option<String> {
|
||||
if_chain! {
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref format_args) = expr.kind;
|
||||
if let ExprKind::Array(ref elems) = arms[0].body.kind;
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, format_args) = expr.kind;
|
||||
if let ExprKind::Array(elems) = arms[0].body.kind;
|
||||
if elems.len() == 1;
|
||||
if let Some(args) = match_function_call(cx, &elems[0], &paths::FMT_ARGUMENTV1_NEW);
|
||||
// matches `core::fmt::Display::fmt`
|
||||
|
@ -88,10 +88,10 @@ fn on_argumentv1_new<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &
|
|||
if let Some(did) = cx.qpath_res(qpath, args[1].hir_id).opt_def_id();
|
||||
if match_def_path(cx, did, &paths::DISPLAY_FMT_METHOD);
|
||||
// check `(arg0,)` in match block
|
||||
if let PatKind::Tuple(ref pats, None) = arms[0].pat.kind;
|
||||
if let PatKind::Tuple(pats, None) = arms[0].pat.kind;
|
||||
if pats.len() == 1;
|
||||
then {
|
||||
let ty = cx.typeck_results().pat_ty(&pats[0]).peel_refs();
|
||||
let ty = cx.typeck_results().pat_ty(pats[0]).peel_refs();
|
||||
if *ty.kind() != rustc_middle::ty::Str && !is_type_diagnostic_item(cx, ty, sym::string_type) {
|
||||
return None;
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ fn on_argumentv1_new<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &
|
|||
}
|
||||
} else {
|
||||
let snip = snippet(cx, format_args.span, "<arg>");
|
||||
if let ExprKind::MethodCall(ref path, _, _, _) = format_args.kind {
|
||||
if let ExprKind::MethodCall(path, _, _, _) = format_args.kind {
|
||||
if path.ident.name == sym!(to_string) {
|
||||
return Some(format!("{}", snip));
|
||||
}
|
||||
|
@ -120,16 +120,16 @@ fn on_new_v1<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Strin
|
|||
if let Some(args) = match_function_call(cx, expr, &paths::FMT_ARGUMENTS_NEW_V1);
|
||||
if args.len() == 2;
|
||||
// Argument 1 in `new_v1()`
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arr) = args[0].kind;
|
||||
if let ExprKind::Array(ref pieces) = arr.kind;
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, arr) = args[0].kind;
|
||||
if let ExprKind::Array(pieces) = arr.kind;
|
||||
if pieces.len() == 1;
|
||||
if let ExprKind::Lit(ref lit) = pieces[0].kind;
|
||||
if let LitKind::Str(ref s, _) = lit.node;
|
||||
// Argument 2 in `new_v1()`
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg1) = args[1].kind;
|
||||
if let ExprKind::Match(ref matchee, ref arms, MatchSource::Normal) = arg1.kind;
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, arg1) = args[1].kind;
|
||||
if let ExprKind::Match(matchee, arms, MatchSource::Normal) = arg1.kind;
|
||||
if arms.len() == 1;
|
||||
if let ExprKind::Tup(ref tup) = matchee.kind;
|
||||
if let ExprKind::Tup(tup) = matchee.kind;
|
||||
then {
|
||||
// `format!("foo")` expansion contains `match () { () => [], }`
|
||||
if tup.is_empty() {
|
||||
|
@ -152,16 +152,16 @@ fn on_new_v1_fmt<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<S
|
|||
if args.len() == 3;
|
||||
if check_unformatted(&args[2]);
|
||||
// Argument 1 in `new_v1_formatted()`
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arr) = args[0].kind;
|
||||
if let ExprKind::Array(ref pieces) = arr.kind;
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, arr) = args[0].kind;
|
||||
if let ExprKind::Array(pieces) = arr.kind;
|
||||
if pieces.len() == 1;
|
||||
if let ExprKind::Lit(ref lit) = pieces[0].kind;
|
||||
if let LitKind::Str(..) = lit.node;
|
||||
// Argument 2 in `new_v1_formatted()`
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg1) = args[1].kind;
|
||||
if let ExprKind::Match(ref matchee, ref arms, MatchSource::Normal) = arg1.kind;
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, arg1) = args[1].kind;
|
||||
if let ExprKind::Match(matchee, arms, MatchSource::Normal) = arg1.kind;
|
||||
if arms.len() == 1;
|
||||
if let ExprKind::Tup(ref tup) = matchee.kind;
|
||||
if let ExprKind::Tup(tup) = matchee.kind;
|
||||
then {
|
||||
return on_argumentv1_new(cx, &tup[0], arms);
|
||||
}
|
||||
|
@ -182,14 +182,14 @@ fn on_new_v1_fmt<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<S
|
|||
/// ```
|
||||
fn check_unformatted(expr: &Expr<'_>) -> bool {
|
||||
if_chain! {
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) = expr.kind;
|
||||
if let ExprKind::Array(ref exprs) = expr.kind;
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
|
||||
if let ExprKind::Array(exprs) = expr.kind;
|
||||
if exprs.len() == 1;
|
||||
// struct `core::fmt::rt::v1::Argument`
|
||||
if let ExprKind::Struct(_, ref fields, _) = exprs[0].kind;
|
||||
if let ExprKind::Struct(_, fields, _) = exprs[0].kind;
|
||||
if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format);
|
||||
// struct `core::fmt::rt::v1::FormatSpec`
|
||||
if let ExprKind::Struct(_, ref fields, _) = format_field.expr.kind;
|
||||
if let ExprKind::Struct(_, fields, _) = format_field.expr.kind;
|
||||
if let Some(precision_field) = fields.iter().find(|f| f.ident.name == sym::precision);
|
||||
if let ExprKind::Path(ref precision_path) = precision_field.expr.kind;
|
||||
if last_path_segment(precision_path).ident.name == sym::Implied;
|
||||
|
|
|
@ -217,9 +217,8 @@ fn check_else(cx: &EarlyContext<'_>, expr: &Expr) {
|
|||
if let Some(else_snippet) = snippet_opt(cx, else_span);
|
||||
if let Some(else_pos) = else_snippet.find("else");
|
||||
if else_snippet[else_pos..].contains('\n');
|
||||
let else_desc = if is_if(else_) { "if" } else { "{..}" };
|
||||
|
||||
then {
|
||||
let else_desc = if is_if(else_) { "if" } else { "{..}" };
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
SUSPICIOUS_ELSE_FORMATTING,
|
||||
|
|
|
@ -1,738 +0,0 @@
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};
|
||||
use clippy_utils::source::{snippet, snippet_opt};
|
||||
use clippy_utils::ty::{is_must_use_ty, is_type_diagnostic_item, type_is_unsafe_function};
|
||||
use clippy_utils::{
|
||||
attr_by_name, attrs::is_proc_macro, is_trait_impl_item, iter_input_pats, match_def_path, must_use_attr,
|
||||
path_to_local, return_ty, trait_ref_of_method,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::Attribute;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit;
|
||||
use rustc_hir::{def::Res, def_id::DefId, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::source_map::Span;
|
||||
use rustc_span::sym;
|
||||
use rustc_target::spec::abi::Abi;
|
||||
use rustc_typeck::hir_ty_to_ty;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for functions with too many parameters.
|
||||
///
|
||||
/// **Why is this bad?** Functions with lots of parameters are considered bad
|
||||
/// style and reduce readability (“what does the 5th parameter mean?”). Consider
|
||||
/// grouping some parameters into a new type.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Example:**
|
||||
/// ```rust
|
||||
/// # struct Color;
|
||||
/// fn foo(x: u32, y: u32, name: &str, c: Color, w: f32, h: f32, a: f32, b: f32) {
|
||||
/// // ..
|
||||
/// }
|
||||
/// ```
|
||||
pub TOO_MANY_ARGUMENTS,
|
||||
complexity,
|
||||
"functions with too many arguments"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for functions with a large amount of lines.
|
||||
///
|
||||
/// **Why is this bad?** Functions with a lot of lines are harder to understand
|
||||
/// due to having to look at a larger amount of code to understand what the
|
||||
/// function is doing. Consider splitting the body of the function into
|
||||
/// multiple functions.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Example:**
|
||||
/// ```rust
|
||||
/// fn im_too_long() {
|
||||
/// println!("");
|
||||
/// // ... 100 more LoC
|
||||
/// println!("");
|
||||
/// }
|
||||
/// ```
|
||||
pub TOO_MANY_LINES,
|
||||
pedantic,
|
||||
"functions with too many lines"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for public functions that dereference raw pointer
|
||||
/// arguments but are not marked unsafe.
|
||||
///
|
||||
/// **Why is this bad?** The function should probably be marked `unsafe`, since
|
||||
/// for an arbitrary raw pointer, there is no way of telling for sure if it is
|
||||
/// valid.
|
||||
///
|
||||
/// **Known problems:**
|
||||
///
|
||||
/// * It does not check functions recursively so if the pointer is passed to a
|
||||
/// private non-`unsafe` function which does the dereferencing, the lint won't
|
||||
/// trigger.
|
||||
/// * It only checks for arguments whose type are raw pointers, not raw pointers
|
||||
/// got from an argument in some other way (`fn foo(bar: &[*const u8])` or
|
||||
/// `some_argument.get_raw_ptr()`).
|
||||
///
|
||||
/// **Example:**
|
||||
/// ```rust,ignore
|
||||
/// // Bad
|
||||
/// pub fn foo(x: *const u8) {
|
||||
/// println!("{}", unsafe { *x });
|
||||
/// }
|
||||
///
|
||||
/// // Good
|
||||
/// pub unsafe fn foo(x: *const u8) {
|
||||
/// println!("{}", unsafe { *x });
|
||||
/// }
|
||||
/// ```
|
||||
pub NOT_UNSAFE_PTR_ARG_DEREF,
|
||||
correctness,
|
||||
"public functions dereferencing raw pointer arguments but not marked `unsafe`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for a [`#[must_use]`] attribute on
|
||||
/// unit-returning functions and methods.
|
||||
///
|
||||
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
|
||||
///
|
||||
/// **Why is this bad?** Unit values are useless. The attribute is likely
|
||||
/// a remnant of a refactoring that removed the return type.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Examples:**
|
||||
/// ```rust
|
||||
/// #[must_use]
|
||||
/// fn useless() { }
|
||||
/// ```
|
||||
pub MUST_USE_UNIT,
|
||||
style,
|
||||
"`#[must_use]` attribute on a unit-returning function / method"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for a [`#[must_use]`] attribute without
|
||||
/// further information on functions and methods that return a type already
|
||||
/// marked as `#[must_use]`.
|
||||
///
|
||||
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
|
||||
///
|
||||
/// **Why is this bad?** The attribute isn't needed. Not using the result
|
||||
/// will already be reported. Alternatively, one can add some text to the
|
||||
/// attribute to improve the lint message.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Examples:**
|
||||
/// ```rust
|
||||
/// #[must_use]
|
||||
/// fn double_must_use() -> Result<(), ()> {
|
||||
/// unimplemented!();
|
||||
/// }
|
||||
/// ```
|
||||
pub DOUBLE_MUST_USE,
|
||||
style,
|
||||
"`#[must_use]` attribute on a `#[must_use]`-returning function / method"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for public functions that have no
|
||||
/// [`#[must_use]`] attribute, but return something not already marked
|
||||
/// must-use, have no mutable arg and mutate no statics.
|
||||
///
|
||||
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
|
||||
///
|
||||
/// **Why is this bad?** Not bad at all, this lint just shows places where
|
||||
/// you could add the attribute.
|
||||
///
|
||||
/// **Known problems:** The lint only checks the arguments for mutable
|
||||
/// types without looking if they are actually changed. On the other hand,
|
||||
/// it also ignores a broad range of potentially interesting side effects,
|
||||
/// because we cannot decide whether the programmer intends the function to
|
||||
/// be called for the side effect or the result. Expect many false
|
||||
/// positives. At least we don't lint if the result type is unit or already
|
||||
/// `#[must_use]`.
|
||||
///
|
||||
/// **Examples:**
|
||||
/// ```rust
|
||||
/// // this could be annotated with `#[must_use]`.
|
||||
/// fn id<T>(t: T) -> T { t }
|
||||
/// ```
|
||||
pub MUST_USE_CANDIDATE,
|
||||
pedantic,
|
||||
"function or method that could take a `#[must_use]` attribute"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for public functions that return a `Result`
|
||||
/// with an `Err` type of `()`. It suggests using a custom type that
|
||||
/// implements [`std::error::Error`].
|
||||
///
|
||||
/// **Why is this bad?** Unit does not implement `Error` and carries no
|
||||
/// further information about what went wrong.
|
||||
///
|
||||
/// **Known problems:** Of course, this lint assumes that `Result` is used
|
||||
/// for a fallible operation (which is after all the intended use). However
|
||||
/// code may opt to (mis)use it as a basic two-variant-enum. In that case,
|
||||
/// the suggestion is misguided, and the code should use a custom enum
|
||||
/// instead.
|
||||
///
|
||||
/// **Examples:**
|
||||
/// ```rust
|
||||
/// pub fn read_u8() -> Result<u8, ()> { Err(()) }
|
||||
/// ```
|
||||
/// should become
|
||||
/// ```rust,should_panic
|
||||
/// use std::fmt;
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// pub struct EndOfStream;
|
||||
///
|
||||
/// impl fmt::Display for EndOfStream {
|
||||
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// write!(f, "End of Stream")
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl std::error::Error for EndOfStream { }
|
||||
///
|
||||
/// pub fn read_u8() -> Result<u8, EndOfStream> { Err(EndOfStream) }
|
||||
///# fn main() {
|
||||
///# read_u8().unwrap();
|
||||
///# }
|
||||
/// ```
|
||||
///
|
||||
/// Note that there are crates that simplify creating the error type, e.g.
|
||||
/// [`thiserror`](https://docs.rs/thiserror).
|
||||
pub RESULT_UNIT_ERR,
|
||||
style,
|
||||
"public function returning `Result` with an `Err` type of `()`"
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Functions {
|
||||
threshold: u64,
|
||||
max_lines: u64,
|
||||
}
|
||||
|
||||
impl Functions {
|
||||
pub fn new(threshold: u64, max_lines: u64) -> Self {
|
||||
Self { threshold, max_lines }
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(Functions => [
|
||||
TOO_MANY_ARGUMENTS,
|
||||
TOO_MANY_LINES,
|
||||
NOT_UNSAFE_PTR_ARG_DEREF,
|
||||
MUST_USE_UNIT,
|
||||
DOUBLE_MUST_USE,
|
||||
MUST_USE_CANDIDATE,
|
||||
RESULT_UNIT_ERR,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Functions {
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'tcx>,
|
||||
kind: intravisit::FnKind<'tcx>,
|
||||
decl: &'tcx hir::FnDecl<'_>,
|
||||
body: &'tcx hir::Body<'_>,
|
||||
span: Span,
|
||||
hir_id: hir::HirId,
|
||||
) {
|
||||
let unsafety = match kind {
|
||||
intravisit::FnKind::ItemFn(_, _, hir::FnHeader { unsafety, .. }, _) => unsafety,
|
||||
intravisit::FnKind::Method(_, sig, _) => sig.header.unsafety,
|
||||
intravisit::FnKind::Closure => return,
|
||||
};
|
||||
|
||||
// don't warn for implementations, it's not their fault
|
||||
if !is_trait_impl_item(cx, hir_id) {
|
||||
// don't lint extern functions decls, it's not their fault either
|
||||
match kind {
|
||||
intravisit::FnKind::Method(
|
||||
_,
|
||||
&hir::FnSig {
|
||||
header: hir::FnHeader { abi: Abi::Rust, .. },
|
||||
..
|
||||
},
|
||||
_,
|
||||
)
|
||||
| intravisit::FnKind::ItemFn(_, _, hir::FnHeader { abi: Abi::Rust, .. }, _) => {
|
||||
self.check_arg_number(cx, decl, span.with_hi(decl.output.span().hi()))
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
Self::check_raw_ptr(cx, unsafety, decl, body, hir_id);
|
||||
self.check_line_number(cx, span, body);
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
let attr = must_use_attr(attrs);
|
||||
if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind {
|
||||
let is_public = cx.access_levels.is_exported(item.hir_id());
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
if is_public {
|
||||
check_result_unit_err(cx, &sig.decl, item.span, fn_header_span);
|
||||
}
|
||||
if let Some(attr) = attr {
|
||||
check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr);
|
||||
return;
|
||||
}
|
||||
if is_public && !is_proc_macro(cx.sess(), attrs) && attr_by_name(attrs, "no_mangle").is_none() {
|
||||
check_must_use_candidate(
|
||||
cx,
|
||||
&sig.decl,
|
||||
cx.tcx.hir().body(*body_id),
|
||||
item.span,
|
||||
item.hir_id(),
|
||||
item.span.with_hi(sig.decl.output.span().hi()),
|
||||
"this function could have a `#[must_use]` attribute",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
|
||||
if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind {
|
||||
let is_public = cx.access_levels.is_exported(item.hir_id());
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
if is_public && trait_ref_of_method(cx, item.hir_id()).is_none() {
|
||||
check_result_unit_err(cx, &sig.decl, item.span, fn_header_span);
|
||||
}
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
let attr = must_use_attr(attrs);
|
||||
if let Some(attr) = attr {
|
||||
check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr);
|
||||
} else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.hir_id()).is_none()
|
||||
{
|
||||
check_must_use_candidate(
|
||||
cx,
|
||||
&sig.decl,
|
||||
cx.tcx.hir().body(*body_id),
|
||||
item.span,
|
||||
item.hir_id(),
|
||||
item.span.with_hi(sig.decl.output.span().hi()),
|
||||
"this method could have a `#[must_use]` attribute",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
|
||||
if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind {
|
||||
// don't lint extern functions decls, it's not their fault
|
||||
if sig.header.abi == Abi::Rust {
|
||||
self.check_arg_number(cx, &sig.decl, item.span.with_hi(sig.decl.output.span().hi()));
|
||||
}
|
||||
let is_public = cx.access_levels.is_exported(item.hir_id());
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
if is_public {
|
||||
check_result_unit_err(cx, &sig.decl, item.span, fn_header_span);
|
||||
}
|
||||
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
let attr = must_use_attr(attrs);
|
||||
if let Some(attr) = attr {
|
||||
check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr);
|
||||
}
|
||||
if let hir::TraitFn::Provided(eid) = *eid {
|
||||
let body = cx.tcx.hir().body(eid);
|
||||
Self::check_raw_ptr(cx, sig.header.unsafety, &sig.decl, body, item.hir_id());
|
||||
|
||||
if attr.is_none() && is_public && !is_proc_macro(cx.sess(), attrs) {
|
||||
check_must_use_candidate(
|
||||
cx,
|
||||
&sig.decl,
|
||||
body,
|
||||
item.span,
|
||||
item.hir_id(),
|
||||
item.span.with_hi(sig.decl.output.span().hi()),
|
||||
"this method could have a `#[must_use]` attribute",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> Functions {
|
||||
fn check_arg_number(self, cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, fn_span: Span) {
|
||||
let args = decl.inputs.len() as u64;
|
||||
if args > self.threshold {
|
||||
span_lint(
|
||||
cx,
|
||||
TOO_MANY_ARGUMENTS,
|
||||
fn_span,
|
||||
&format!("this function has too many arguments ({}/{})", args, self.threshold),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_line_number(self, cx: &LateContext<'_>, span: Span, body: &'tcx hir::Body<'_>) {
|
||||
if in_external_macro(cx.sess(), span) {
|
||||
return;
|
||||
}
|
||||
|
||||
let code_snippet = snippet(cx, body.value.span, "..");
|
||||
let mut line_count: u64 = 0;
|
||||
let mut in_comment = false;
|
||||
let mut code_in_line;
|
||||
|
||||
// Skip the surrounding function decl.
|
||||
let start_brace_idx = code_snippet.find('{').map_or(0, |i| i + 1);
|
||||
let end_brace_idx = code_snippet.rfind('}').unwrap_or_else(|| code_snippet.len());
|
||||
let function_lines = code_snippet[start_brace_idx..end_brace_idx].lines();
|
||||
|
||||
for mut line in function_lines {
|
||||
code_in_line = false;
|
||||
loop {
|
||||
line = line.trim_start();
|
||||
if line.is_empty() {
|
||||
break;
|
||||
}
|
||||
if in_comment {
|
||||
if let Some(i) = line.find("*/") {
|
||||
line = &line[i + 2..];
|
||||
in_comment = false;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
let multi_idx = line.find("/*").unwrap_or_else(|| line.len());
|
||||
let single_idx = line.find("//").unwrap_or_else(|| line.len());
|
||||
code_in_line |= multi_idx > 0 && single_idx > 0;
|
||||
// Implies multi_idx is below line.len()
|
||||
if multi_idx < single_idx {
|
||||
line = &line[multi_idx + 2..];
|
||||
in_comment = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if code_in_line {
|
||||
line_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if line_count > self.max_lines {
|
||||
span_lint(
|
||||
cx,
|
||||
TOO_MANY_LINES,
|
||||
span,
|
||||
&format!("this function has too many lines ({}/{})", line_count, self.max_lines),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn check_raw_ptr(
|
||||
cx: &LateContext<'tcx>,
|
||||
unsafety: hir::Unsafety,
|
||||
decl: &'tcx hir::FnDecl<'_>,
|
||||
body: &'tcx hir::Body<'_>,
|
||||
hir_id: hir::HirId,
|
||||
) {
|
||||
let expr = &body.value;
|
||||
if unsafety == hir::Unsafety::Normal && cx.access_levels.is_exported(hir_id) {
|
||||
let raw_ptrs = iter_input_pats(decl, body)
|
||||
.zip(decl.inputs.iter())
|
||||
.filter_map(|(arg, ty)| raw_ptr_arg(arg, ty))
|
||||
.collect::<FxHashSet<_>>();
|
||||
|
||||
if !raw_ptrs.is_empty() {
|
||||
let typeck_results = cx.tcx.typeck_body(body.id());
|
||||
let mut v = DerefVisitor {
|
||||
cx,
|
||||
ptrs: raw_ptrs,
|
||||
typeck_results,
|
||||
};
|
||||
|
||||
intravisit::walk_expr(&mut v, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_result_unit_err(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, item_span: Span, fn_header_span: Span) {
|
||||
if_chain! {
|
||||
if !in_external_macro(cx.sess(), item_span);
|
||||
if let hir::FnRetTy::Return(ref ty) = decl.output;
|
||||
let ty = hir_ty_to_ty(cx.tcx, ty);
|
||||
if is_type_diagnostic_item(cx, ty, sym::result_type);
|
||||
if let ty::Adt(_, substs) = ty.kind();
|
||||
let err_ty = substs.type_at(1);
|
||||
if err_ty.is_unit();
|
||||
then {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
RESULT_UNIT_ERR,
|
||||
fn_header_span,
|
||||
"this returns a `Result<_, ()>",
|
||||
None,
|
||||
"use a custom Error type instead",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_needless_must_use(
|
||||
cx: &LateContext<'_>,
|
||||
decl: &hir::FnDecl<'_>,
|
||||
item_id: hir::HirId,
|
||||
item_span: Span,
|
||||
fn_header_span: Span,
|
||||
attr: &Attribute,
|
||||
) {
|
||||
if in_external_macro(cx.sess(), item_span) {
|
||||
return;
|
||||
}
|
||||
if returns_unit(decl) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MUST_USE_UNIT,
|
||||
fn_header_span,
|
||||
"this unit-returning function has a `#[must_use]` attribute",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
attr.span,
|
||||
"remove the attribute",
|
||||
"".into(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else if !attr.value_str().is_some() && is_must_use_ty(cx, return_ty(cx, item_id)) {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
DOUBLE_MUST_USE,
|
||||
fn_header_span,
|
||||
"this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`",
|
||||
None,
|
||||
"either add some descriptive text or remove the attribute",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_must_use_candidate<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
decl: &'tcx hir::FnDecl<'_>,
|
||||
body: &'tcx hir::Body<'_>,
|
||||
item_span: Span,
|
||||
item_id: hir::HirId,
|
||||
fn_span: Span,
|
||||
msg: &str,
|
||||
) {
|
||||
if has_mutable_arg(cx, body)
|
||||
|| mutates_static(cx, body)
|
||||
|| in_external_macro(cx.sess(), item_span)
|
||||
|| returns_unit(decl)
|
||||
|| !cx.access_levels.is_exported(item_id)
|
||||
|| is_must_use_ty(cx, return_ty(cx, item_id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |diag| {
|
||||
if let Some(snippet) = snippet_opt(cx, fn_span) {
|
||||
diag.span_suggestion(
|
||||
fn_span,
|
||||
"add the attribute",
|
||||
format!("#[must_use] {}", snippet),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn returns_unit(decl: &hir::FnDecl<'_>) -> bool {
|
||||
match decl.output {
|
||||
hir::FnRetTy::DefaultReturn(_) => true,
|
||||
hir::FnRetTy::Return(ref ty) => match ty.kind {
|
||||
hir::TyKind::Tup(ref tys) => tys.is_empty(),
|
||||
hir::TyKind::Never => true,
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn has_mutable_arg(cx: &LateContext<'_>, body: &hir::Body<'_>) -> bool {
|
||||
let mut tys = FxHashSet::default();
|
||||
body.params.iter().any(|param| is_mutable_pat(cx, ¶m.pat, &mut tys))
|
||||
}
|
||||
|
||||
fn is_mutable_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, tys: &mut FxHashSet<DefId>) -> bool {
|
||||
if let hir::PatKind::Wild = pat.kind {
|
||||
return false; // ignore `_` patterns
|
||||
}
|
||||
if cx.tcx.has_typeck_results(pat.hir_id.owner.to_def_id()) {
|
||||
is_mutable_ty(cx, &cx.tcx.typeck(pat.hir_id.owner).pat_ty(pat), pat.span, tys)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]];
|
||||
|
||||
fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &mut FxHashSet<DefId>) -> bool {
|
||||
match *ty.kind() {
|
||||
// primitive types are never mutable
|
||||
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false,
|
||||
ty::Adt(ref adt, ref substs) => {
|
||||
tys.insert(adt.did) && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
|
||||
|| KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path))
|
||||
&& substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys))
|
||||
},
|
||||
ty::Tuple(ref substs) => substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)),
|
||||
ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys),
|
||||
ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => {
|
||||
mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys)
|
||||
},
|
||||
// calling something constitutes a side effect, so return true on all callables
|
||||
// also never calls need not be used, so return true for them, too
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_ptr_arg(arg: &hir::Param<'_>, ty: &hir::Ty<'_>) -> Option<hir::HirId> {
|
||||
if let (&hir::PatKind::Binding(_, id, _, _), &hir::TyKind::Ptr(_)) = (&arg.pat.kind, &ty.kind) {
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
struct DerefVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
ptrs: FxHashSet<hir::HirId>,
|
||||
typeck_results: &'a ty::TypeckResults<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> intravisit::Visitor<'tcx> for DerefVisitor<'a, 'tcx> {
|
||||
type Map = Map<'tcx>;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
|
||||
match expr.kind {
|
||||
hir::ExprKind::Call(ref f, args) => {
|
||||
let ty = self.typeck_results.expr_ty(f);
|
||||
|
||||
if type_is_unsafe_function(self.cx, ty) {
|
||||
for arg in args {
|
||||
self.check_arg(arg);
|
||||
}
|
||||
}
|
||||
},
|
||||
hir::ExprKind::MethodCall(_, _, args, _) => {
|
||||
let def_id = self.typeck_results.type_dependent_def_id(expr.hir_id).unwrap();
|
||||
let base_type = self.cx.tcx.type_of(def_id);
|
||||
|
||||
if type_is_unsafe_function(self.cx, base_type) {
|
||||
for arg in args {
|
||||
self.check_arg(arg);
|
||||
}
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Unary(hir::UnOp::Deref, ref ptr) => self.check_arg(ptr),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
intravisit::walk_expr(self, expr);
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
|
||||
intravisit::NestedVisitorMap::None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> DerefVisitor<'a, 'tcx> {
|
||||
fn check_arg(&self, ptr: &hir::Expr<'_>) {
|
||||
if let Some(id) = path_to_local(ptr) {
|
||||
if self.ptrs.contains(&id) {
|
||||
span_lint(
|
||||
self.cx,
|
||||
NOT_UNSAFE_PTR_ARG_DEREF,
|
||||
ptr.span,
|
||||
"this public function dereferences a raw pointer but is not marked `unsafe`",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StaticMutVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
mutates_static: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> {
|
||||
type Map = Map<'tcx>;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
|
||||
use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall};
|
||||
|
||||
if self.mutates_static {
|
||||
return;
|
||||
}
|
||||
match expr.kind {
|
||||
Call(_, args) | MethodCall(_, _, args, _) => {
|
||||
let mut tys = FxHashSet::default();
|
||||
for arg in args {
|
||||
if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
|
||||
&& is_mutable_ty(
|
||||
self.cx,
|
||||
self.cx.tcx.typeck(arg.hir_id.owner).expr_ty(arg),
|
||||
arg.span,
|
||||
&mut tys,
|
||||
)
|
||||
&& is_mutated_static(arg)
|
||||
{
|
||||
self.mutates_static = true;
|
||||
return;
|
||||
}
|
||||
tys.clear();
|
||||
}
|
||||
},
|
||||
Assign(ref target, ..) | AssignOp(_, ref target, _) | AddrOf(_, hir::Mutability::Mut, ref target) => {
|
||||
self.mutates_static |= is_mutated_static(target)
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
|
||||
intravisit::NestedVisitorMap::None
|
||||
}
|
||||
}
|
||||
|
||||
fn is_mutated_static(e: &hir::Expr<'_>) -> bool {
|
||||
use hir::ExprKind::{Field, Index, Path};
|
||||
|
||||
match e.kind {
|
||||
Path(QPath::Resolved(_, path)) => !matches!(path.res, Res::Local(_)),
|
||||
Path(_) => true,
|
||||
Field(ref inner, _) | Index(ref inner, _) => is_mutated_static(inner),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool {
|
||||
let mut v = StaticMutVisitor {
|
||||
cx,
|
||||
mutates_static: false,
|
||||
};
|
||||
intravisit::walk_expr(&mut v, &body.value);
|
||||
v.mutates_static
|
||||
}
|
267
src/tools/clippy/clippy_lints/src/functions/mod.rs
Normal file
267
src/tools/clippy/clippy_lints/src/functions/mod.rs
Normal file
|
@ -0,0 +1,267 @@
|
|||
mod must_use;
|
||||
mod not_unsafe_ptr_arg_deref;
|
||||
mod result_unit_err;
|
||||
mod too_many_arguments;
|
||||
mod too_many_lines;
|
||||
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for functions with too many parameters.
|
||||
///
|
||||
/// **Why is this bad?** Functions with lots of parameters are considered bad
|
||||
/// style and reduce readability (“what does the 5th parameter mean?”). Consider
|
||||
/// grouping some parameters into a new type.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Example:**
|
||||
/// ```rust
|
||||
/// # struct Color;
|
||||
/// fn foo(x: u32, y: u32, name: &str, c: Color, w: f32, h: f32, a: f32, b: f32) {
|
||||
/// // ..
|
||||
/// }
|
||||
/// ```
|
||||
pub TOO_MANY_ARGUMENTS,
|
||||
complexity,
|
||||
"functions with too many arguments"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for functions with a large amount of lines.
|
||||
///
|
||||
/// **Why is this bad?** Functions with a lot of lines are harder to understand
|
||||
/// due to having to look at a larger amount of code to understand what the
|
||||
/// function is doing. Consider splitting the body of the function into
|
||||
/// multiple functions.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Example:**
|
||||
/// ```rust
|
||||
/// fn im_too_long() {
|
||||
/// println!("");
|
||||
/// // ... 100 more LoC
|
||||
/// println!("");
|
||||
/// }
|
||||
/// ```
|
||||
pub TOO_MANY_LINES,
|
||||
pedantic,
|
||||
"functions with too many lines"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for public functions that dereference raw pointer
|
||||
/// arguments but are not marked `unsafe`.
|
||||
///
|
||||
/// **Why is this bad?** The function should probably be marked `unsafe`, since
|
||||
/// for an arbitrary raw pointer, there is no way of telling for sure if it is
|
||||
/// valid.
|
||||
///
|
||||
/// **Known problems:**
|
||||
///
|
||||
/// * It does not check functions recursively so if the pointer is passed to a
|
||||
/// private non-`unsafe` function which does the dereferencing, the lint won't
|
||||
/// trigger.
|
||||
/// * It only checks for arguments whose type are raw pointers, not raw pointers
|
||||
/// got from an argument in some other way (`fn foo(bar: &[*const u8])` or
|
||||
/// `some_argument.get_raw_ptr()`).
|
||||
///
|
||||
/// **Example:**
|
||||
/// ```rust,ignore
|
||||
/// // Bad
|
||||
/// pub fn foo(x: *const u8) {
|
||||
/// println!("{}", unsafe { *x });
|
||||
/// }
|
||||
///
|
||||
/// // Good
|
||||
/// pub unsafe fn foo(x: *const u8) {
|
||||
/// println!("{}", unsafe { *x });
|
||||
/// }
|
||||
/// ```
|
||||
pub NOT_UNSAFE_PTR_ARG_DEREF,
|
||||
correctness,
|
||||
"public functions dereferencing raw pointer arguments but not marked `unsafe`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for a [`#[must_use]`] attribute on
|
||||
/// unit-returning functions and methods.
|
||||
///
|
||||
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
|
||||
///
|
||||
/// **Why is this bad?** Unit values are useless. The attribute is likely
|
||||
/// a remnant of a refactoring that removed the return type.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Examples:**
|
||||
/// ```rust
|
||||
/// #[must_use]
|
||||
/// fn useless() { }
|
||||
/// ```
|
||||
pub MUST_USE_UNIT,
|
||||
style,
|
||||
"`#[must_use]` attribute on a unit-returning function / method"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for a [`#[must_use]`] attribute without
|
||||
/// further information on functions and methods that return a type already
|
||||
/// marked as `#[must_use]`.
|
||||
///
|
||||
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
|
||||
///
|
||||
/// **Why is this bad?** The attribute isn't needed. Not using the result
|
||||
/// will already be reported. Alternatively, one can add some text to the
|
||||
/// attribute to improve the lint message.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Examples:**
|
||||
/// ```rust
|
||||
/// #[must_use]
|
||||
/// fn double_must_use() -> Result<(), ()> {
|
||||
/// unimplemented!();
|
||||
/// }
|
||||
/// ```
|
||||
pub DOUBLE_MUST_USE,
|
||||
style,
|
||||
"`#[must_use]` attribute on a `#[must_use]`-returning function / method"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for public functions that have no
|
||||
/// [`#[must_use]`] attribute, but return something not already marked
|
||||
/// must-use, have no mutable arg and mutate no statics.
|
||||
///
|
||||
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
|
||||
///
|
||||
/// **Why is this bad?** Not bad at all, this lint just shows places where
|
||||
/// you could add the attribute.
|
||||
///
|
||||
/// **Known problems:** The lint only checks the arguments for mutable
|
||||
/// types without looking if they are actually changed. On the other hand,
|
||||
/// it also ignores a broad range of potentially interesting side effects,
|
||||
/// because we cannot decide whether the programmer intends the function to
|
||||
/// be called for the side effect or the result. Expect many false
|
||||
/// positives. At least we don't lint if the result type is unit or already
|
||||
/// `#[must_use]`.
|
||||
///
|
||||
/// **Examples:**
|
||||
/// ```rust
|
||||
/// // this could be annotated with `#[must_use]`.
|
||||
/// fn id<T>(t: T) -> T { t }
|
||||
/// ```
|
||||
pub MUST_USE_CANDIDATE,
|
||||
pedantic,
|
||||
"function or method that could take a `#[must_use]` attribute"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for public functions that return a `Result`
|
||||
/// with an `Err` type of `()`. It suggests using a custom type that
|
||||
/// implements `std::error::Error`.
|
||||
///
|
||||
/// **Why is this bad?** Unit does not implement `Error` and carries no
|
||||
/// further information about what went wrong.
|
||||
///
|
||||
/// **Known problems:** Of course, this lint assumes that `Result` is used
|
||||
/// for a fallible operation (which is after all the intended use). However
|
||||
/// code may opt to (mis)use it as a basic two-variant-enum. In that case,
|
||||
/// the suggestion is misguided, and the code should use a custom enum
|
||||
/// instead.
|
||||
///
|
||||
/// **Examples:**
|
||||
/// ```rust
|
||||
/// pub fn read_u8() -> Result<u8, ()> { Err(()) }
|
||||
/// ```
|
||||
/// should become
|
||||
/// ```rust,should_panic
|
||||
/// use std::fmt;
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// pub struct EndOfStream;
|
||||
///
|
||||
/// impl fmt::Display for EndOfStream {
|
||||
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// write!(f, "End of Stream")
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl std::error::Error for EndOfStream { }
|
||||
///
|
||||
/// pub fn read_u8() -> Result<u8, EndOfStream> { Err(EndOfStream) }
|
||||
///# fn main() {
|
||||
///# read_u8().unwrap();
|
||||
///# }
|
||||
/// ```
|
||||
///
|
||||
/// Note that there are crates that simplify creating the error type, e.g.
|
||||
/// [`thiserror`](https://docs.rs/thiserror).
|
||||
pub RESULT_UNIT_ERR,
|
||||
style,
|
||||
"public function returning `Result` with an `Err` type of `()`"
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Functions {
|
||||
too_many_arguments_threshold: u64,
|
||||
too_many_lines_threshold: u64,
|
||||
}
|
||||
|
||||
impl Functions {
|
||||
pub fn new(too_many_arguments_threshold: u64, too_many_lines_threshold: u64) -> Self {
|
||||
Self {
|
||||
too_many_arguments_threshold,
|
||||
too_many_lines_threshold,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(Functions => [
|
||||
TOO_MANY_ARGUMENTS,
|
||||
TOO_MANY_LINES,
|
||||
NOT_UNSAFE_PTR_ARG_DEREF,
|
||||
MUST_USE_UNIT,
|
||||
DOUBLE_MUST_USE,
|
||||
MUST_USE_CANDIDATE,
|
||||
RESULT_UNIT_ERR,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Functions {
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'tcx>,
|
||||
kind: intravisit::FnKind<'tcx>,
|
||||
decl: &'tcx hir::FnDecl<'_>,
|
||||
body: &'tcx hir::Body<'_>,
|
||||
span: Span,
|
||||
hir_id: hir::HirId,
|
||||
) {
|
||||
too_many_arguments::check_fn(cx, kind, decl, span, hir_id, self.too_many_arguments_threshold);
|
||||
too_many_lines::check_fn(cx, span, body, self.too_many_lines_threshold);
|
||||
not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, hir_id);
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
|
||||
must_use::check_item(cx, item);
|
||||
result_unit_err::check_item(cx, item);
|
||||
}
|
||||
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
|
||||
must_use::check_impl_item(cx, item);
|
||||
result_unit_err::check_impl_item(cx, item);
|
||||
}
|
||||
|
||||
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
|
||||
too_many_arguments::check_trait_item(cx, item, self.too_many_arguments_threshold);
|
||||
not_unsafe_ptr_arg_deref::check_trait_item(cx, item);
|
||||
must_use::check_trait_item(cx, item);
|
||||
result_unit_err::check_trait_item(cx, item);
|
||||
}
|
||||
}
|
272
src/tools/clippy/clippy_lints/src/functions/must_use.rs
Normal file
272
src/tools/clippy/clippy_lints/src/functions/must_use.rs
Normal file
|
@ -0,0 +1,272 @@
|
|||
use rustc_ast::ast::Attribute;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefIdSet;
|
||||
use rustc_hir::{self as hir, def::Res, intravisit, QPath};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::{
|
||||
hir::map::Map,
|
||||
lint::in_external_macro,
|
||||
ty::{self, Ty},
|
||||
};
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
use clippy_utils::attrs::is_proc_macro;
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::is_must_use_ty;
|
||||
use clippy_utils::{match_def_path, must_use_attr, return_ty, trait_ref_of_method};
|
||||
|
||||
use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT};
|
||||
|
||||
pub(super) fn check_item(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
let attr = must_use_attr(attrs);
|
||||
if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind {
|
||||
let is_public = cx.access_levels.is_exported(item.hir_id());
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
if let Some(attr) = attr {
|
||||
check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
|
||||
return;
|
||||
} else if is_public && !is_proc_macro(cx.sess(), attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) {
|
||||
check_must_use_candidate(
|
||||
cx,
|
||||
sig.decl,
|
||||
cx.tcx.hir().body(*body_id),
|
||||
item.span,
|
||||
item.hir_id(),
|
||||
item.span.with_hi(sig.decl.output.span().hi()),
|
||||
"this function could have a `#[must_use]` attribute",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_impl_item(cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
|
||||
if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind {
|
||||
let is_public = cx.access_levels.is_exported(item.hir_id());
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
let attr = must_use_attr(attrs);
|
||||
if let Some(attr) = attr {
|
||||
check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
|
||||
} else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.hir_id()).is_none() {
|
||||
check_must_use_candidate(
|
||||
cx,
|
||||
sig.decl,
|
||||
cx.tcx.hir().body(*body_id),
|
||||
item.span,
|
||||
item.hir_id(),
|
||||
item.span.with_hi(sig.decl.output.span().hi()),
|
||||
"this method could have a `#[must_use]` attribute",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_trait_item(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
|
||||
if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind {
|
||||
let is_public = cx.access_levels.is_exported(item.hir_id());
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
let attr = must_use_attr(attrs);
|
||||
if let Some(attr) = attr {
|
||||
check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
|
||||
} else if let hir::TraitFn::Provided(eid) = *eid {
|
||||
let body = cx.tcx.hir().body(eid);
|
||||
if attr.is_none() && is_public && !is_proc_macro(cx.sess(), attrs) {
|
||||
check_must_use_candidate(
|
||||
cx,
|
||||
sig.decl,
|
||||
body,
|
||||
item.span,
|
||||
item.hir_id(),
|
||||
item.span.with_hi(sig.decl.output.span().hi()),
|
||||
"this method could have a `#[must_use]` attribute",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_needless_must_use(
|
||||
cx: &LateContext<'_>,
|
||||
decl: &hir::FnDecl<'_>,
|
||||
item_id: hir::HirId,
|
||||
item_span: Span,
|
||||
fn_header_span: Span,
|
||||
attr: &Attribute,
|
||||
) {
|
||||
if in_external_macro(cx.sess(), item_span) {
|
||||
return;
|
||||
}
|
||||
if returns_unit(decl) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MUST_USE_UNIT,
|
||||
fn_header_span,
|
||||
"this unit-returning function has a `#[must_use]` attribute",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
attr.span,
|
||||
"remove the attribute",
|
||||
"".into(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else if attr.value_str().is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
DOUBLE_MUST_USE,
|
||||
fn_header_span,
|
||||
"this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`",
|
||||
None,
|
||||
"either add some descriptive text or remove the attribute",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_must_use_candidate<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
decl: &'tcx hir::FnDecl<'_>,
|
||||
body: &'tcx hir::Body<'_>,
|
||||
item_span: Span,
|
||||
item_id: hir::HirId,
|
||||
fn_span: Span,
|
||||
msg: &str,
|
||||
) {
|
||||
if has_mutable_arg(cx, body)
|
||||
|| mutates_static(cx, body)
|
||||
|| in_external_macro(cx.sess(), item_span)
|
||||
|| returns_unit(decl)
|
||||
|| !cx.access_levels.is_exported(item_id)
|
||||
|| is_must_use_ty(cx, return_ty(cx, item_id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |diag| {
|
||||
if let Some(snippet) = snippet_opt(cx, fn_span) {
|
||||
diag.span_suggestion(
|
||||
fn_span,
|
||||
"add the attribute",
|
||||
format!("#[must_use] {}", snippet),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn returns_unit(decl: &hir::FnDecl<'_>) -> bool {
|
||||
match decl.output {
|
||||
hir::FnRetTy::DefaultReturn(_) => true,
|
||||
hir::FnRetTy::Return(ty) => match ty.kind {
|
||||
hir::TyKind::Tup(tys) => tys.is_empty(),
|
||||
hir::TyKind::Never => true,
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn has_mutable_arg(cx: &LateContext<'_>, body: &hir::Body<'_>) -> bool {
|
||||
let mut tys = DefIdSet::default();
|
||||
body.params.iter().any(|param| is_mutable_pat(cx, param.pat, &mut tys))
|
||||
}
|
||||
|
||||
fn is_mutable_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, tys: &mut DefIdSet) -> bool {
|
||||
if let hir::PatKind::Wild = pat.kind {
|
||||
return false; // ignore `_` patterns
|
||||
}
|
||||
if cx.tcx.has_typeck_results(pat.hir_id.owner.to_def_id()) {
|
||||
is_mutable_ty(cx, cx.tcx.typeck(pat.hir_id.owner).pat_ty(pat), pat.span, tys)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]];
|
||||
|
||||
fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &mut DefIdSet) -> bool {
|
||||
match *ty.kind() {
|
||||
// primitive types are never mutable
|
||||
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false,
|
||||
ty::Adt(adt, substs) => {
|
||||
tys.insert(adt.did) && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
|
||||
|| KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path))
|
||||
&& substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys))
|
||||
},
|
||||
ty::Tuple(substs) => substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)),
|
||||
ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys),
|
||||
ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => {
|
||||
mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys)
|
||||
},
|
||||
// calling something constitutes a side effect, so return true on all callables
|
||||
// also never calls need not be used, so return true for them, too
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
struct StaticMutVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
mutates_static: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> {
|
||||
type Map = Map<'tcx>;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
|
||||
use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall};
|
||||
|
||||
if self.mutates_static {
|
||||
return;
|
||||
}
|
||||
match expr.kind {
|
||||
Call(_, args) | MethodCall(_, _, args, _) => {
|
||||
let mut tys = DefIdSet::default();
|
||||
for arg in args {
|
||||
if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
|
||||
&& is_mutable_ty(
|
||||
self.cx,
|
||||
self.cx.tcx.typeck(arg.hir_id.owner).expr_ty(arg),
|
||||
arg.span,
|
||||
&mut tys,
|
||||
)
|
||||
&& is_mutated_static(arg)
|
||||
{
|
||||
self.mutates_static = true;
|
||||
return;
|
||||
}
|
||||
tys.clear();
|
||||
}
|
||||
},
|
||||
Assign(target, ..) | AssignOp(_, target, _) | AddrOf(_, hir::Mutability::Mut, target) => {
|
||||
self.mutates_static |= is_mutated_static(target)
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
|
||||
intravisit::NestedVisitorMap::None
|
||||
}
|
||||
}
|
||||
|
||||
fn is_mutated_static(e: &hir::Expr<'_>) -> bool {
|
||||
use hir::ExprKind::{Field, Index, Path};
|
||||
|
||||
match e.kind {
|
||||
Path(QPath::Resolved(_, path)) => !matches!(path.res, Res::Local(_)),
|
||||
Path(_) => true,
|
||||
Field(inner, _) | Index(inner, _) => is_mutated_static(inner),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool {
|
||||
let mut v = StaticMutVisitor {
|
||||
cx,
|
||||
mutates_static: false,
|
||||
};
|
||||
intravisit::walk_expr(&mut v, &body.value);
|
||||
v.mutates_static
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
use rustc_hir::{self as hir, intravisit, HirIdSet};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::{hir::map::Map, ty};
|
||||
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::ty::type_is_unsafe_function;
|
||||
use clippy_utils::{iter_input_pats, path_to_local};
|
||||
|
||||
use super::NOT_UNSAFE_PTR_ARG_DEREF;
|
||||
|
||||
pub(super) fn check_fn(
|
||||
cx: &LateContext<'tcx>,
|
||||
kind: intravisit::FnKind<'tcx>,
|
||||
decl: &'tcx hir::FnDecl<'tcx>,
|
||||
body: &'tcx hir::Body<'tcx>,
|
||||
hir_id: hir::HirId,
|
||||
) {
|
||||
let unsafety = match kind {
|
||||
intravisit::FnKind::ItemFn(_, _, hir::FnHeader { unsafety, .. }, _) => unsafety,
|
||||
intravisit::FnKind::Method(_, sig, _) => sig.header.unsafety,
|
||||
intravisit::FnKind::Closure => return,
|
||||
};
|
||||
|
||||
check_raw_ptr(cx, unsafety, decl, body, hir_id);
|
||||
}
|
||||
|
||||
pub(super) fn check_trait_item(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
|
||||
if let hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(eid)) = item.kind {
|
||||
let body = cx.tcx.hir().body(eid);
|
||||
check_raw_ptr(cx, sig.header.unsafety, sig.decl, body, item.hir_id());
|
||||
}
|
||||
}
|
||||
|
||||
fn check_raw_ptr(
|
||||
cx: &LateContext<'tcx>,
|
||||
unsafety: hir::Unsafety,
|
||||
decl: &'tcx hir::FnDecl<'tcx>,
|
||||
body: &'tcx hir::Body<'tcx>,
|
||||
hir_id: hir::HirId,
|
||||
) {
|
||||
let expr = &body.value;
|
||||
if unsafety == hir::Unsafety::Normal && cx.access_levels.is_exported(hir_id) {
|
||||
let raw_ptrs = iter_input_pats(decl, body)
|
||||
.zip(decl.inputs.iter())
|
||||
.filter_map(|(arg, ty)| raw_ptr_arg(arg, ty))
|
||||
.collect::<HirIdSet>();
|
||||
|
||||
if !raw_ptrs.is_empty() {
|
||||
let typeck_results = cx.tcx.typeck_body(body.id());
|
||||
let mut v = DerefVisitor {
|
||||
cx,
|
||||
ptrs: raw_ptrs,
|
||||
typeck_results,
|
||||
};
|
||||
|
||||
intravisit::walk_expr(&mut v, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_ptr_arg(arg: &hir::Param<'_>, ty: &hir::Ty<'_>) -> Option<hir::HirId> {
|
||||
if let (&hir::PatKind::Binding(_, id, _, _), &hir::TyKind::Ptr(_)) = (&arg.pat.kind, &ty.kind) {
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
struct DerefVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
ptrs: HirIdSet,
|
||||
typeck_results: &'a ty::TypeckResults<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> intravisit::Visitor<'tcx> for DerefVisitor<'a, 'tcx> {
|
||||
type Map = Map<'tcx>;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
|
||||
match expr.kind {
|
||||
hir::ExprKind::Call(f, args) => {
|
||||
let ty = self.typeck_results.expr_ty(f);
|
||||
|
||||
if type_is_unsafe_function(self.cx, ty) {
|
||||
for arg in args {
|
||||
self.check_arg(arg);
|
||||
}
|
||||
}
|
||||
},
|
||||
hir::ExprKind::MethodCall(_, _, args, _) => {
|
||||
let def_id = self.typeck_results.type_dependent_def_id(expr.hir_id).unwrap();
|
||||
let base_type = self.cx.tcx.type_of(def_id);
|
||||
|
||||
if type_is_unsafe_function(self.cx, base_type) {
|
||||
for arg in args {
|
||||
self.check_arg(arg);
|
||||
}
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Unary(hir::UnOp::Deref, ptr) => self.check_arg(ptr),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
intravisit::walk_expr(self, expr);
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
|
||||
intravisit::NestedVisitorMap::None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> DerefVisitor<'a, 'tcx> {
|
||||
fn check_arg(&self, ptr: &hir::Expr<'_>) {
|
||||
if let Some(id) = path_to_local(ptr) {
|
||||
if self.ptrs.contains(&id) {
|
||||
span_lint(
|
||||
self.cx,
|
||||
NOT_UNSAFE_PTR_ARG_DEREF,
|
||||
ptr.span,
|
||||
"this public function dereferences a raw pointer but is not marked `unsafe`",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::{sym, Span};
|
||||
use rustc_typeck::hir_ty_to_ty;
|
||||
|
||||
use if_chain::if_chain;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::trait_ref_of_method;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
|
||||
use super::RESULT_UNIT_ERR;
|
||||
|
||||
pub(super) fn check_item(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
|
||||
if let hir::ItemKind::Fn(ref sig, ref _generics, _) = item.kind {
|
||||
let is_public = cx.access_levels.is_exported(item.hir_id());
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
if is_public {
|
||||
check_result_unit_err(cx, sig.decl, item.span, fn_header_span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_impl_item(cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
|
||||
if let hir::ImplItemKind::Fn(ref sig, _) = item.kind {
|
||||
let is_public = cx.access_levels.is_exported(item.hir_id());
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
if is_public && trait_ref_of_method(cx, item.hir_id()).is_none() {
|
||||
check_result_unit_err(cx, sig.decl, item.span, fn_header_span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_trait_item(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
|
||||
if let hir::TraitItemKind::Fn(ref sig, _) = item.kind {
|
||||
let is_public = cx.access_levels.is_exported(item.hir_id());
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
if is_public {
|
||||
check_result_unit_err(cx, sig.decl, item.span, fn_header_span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_result_unit_err(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, item_span: Span, fn_header_span: Span) {
|
||||
if_chain! {
|
||||
if !in_external_macro(cx.sess(), item_span);
|
||||
if let hir::FnRetTy::Return(ty) = decl.output;
|
||||
let ty = hir_ty_to_ty(cx.tcx, ty);
|
||||
if is_type_diagnostic_item(cx, ty, sym::result_type);
|
||||
if let ty::Adt(_, substs) = ty.kind();
|
||||
let err_ty = substs.type_at(1);
|
||||
if err_ty.is_unit();
|
||||
then {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
RESULT_UNIT_ERR,
|
||||
fn_header_span,
|
||||
"this returns a `Result<_, ()>`",
|
||||
None,
|
||||
"use a custom `Error` type instead",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
use rustc_hir::{self as hir, intravisit};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::Span;
|
||||
use rustc_target::spec::abi::Abi;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::is_trait_impl_item;
|
||||
|
||||
use super::TOO_MANY_ARGUMENTS;
|
||||
|
||||
pub(super) fn check_fn(
|
||||
cx: &LateContext<'tcx>,
|
||||
kind: intravisit::FnKind<'tcx>,
|
||||
decl: &'tcx hir::FnDecl<'_>,
|
||||
span: Span,
|
||||
hir_id: hir::HirId,
|
||||
too_many_arguments_threshold: u64,
|
||||
) {
|
||||
// don't warn for implementations, it's not their fault
|
||||
if !is_trait_impl_item(cx, hir_id) {
|
||||
// don't lint extern functions decls, it's not their fault either
|
||||
match kind {
|
||||
intravisit::FnKind::Method(
|
||||
_,
|
||||
&hir::FnSig {
|
||||
header: hir::FnHeader { abi: Abi::Rust, .. },
|
||||
..
|
||||
},
|
||||
_,
|
||||
)
|
||||
| intravisit::FnKind::ItemFn(_, _, hir::FnHeader { abi: Abi::Rust, .. }, _) => check_arg_number(
|
||||
cx,
|
||||
decl,
|
||||
span.with_hi(decl.output.span().hi()),
|
||||
too_many_arguments_threshold,
|
||||
),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_trait_item(
|
||||
cx: &LateContext<'tcx>,
|
||||
item: &'tcx hir::TraitItem<'_>,
|
||||
too_many_arguments_threshold: u64,
|
||||
) {
|
||||
if let hir::TraitItemKind::Fn(ref sig, _) = item.kind {
|
||||
// don't lint extern functions decls, it's not their fault
|
||||
if sig.header.abi == Abi::Rust {
|
||||
check_arg_number(
|
||||
cx,
|
||||
sig.decl,
|
||||
item.span.with_hi(sig.decl.output.span().hi()),
|
||||
too_many_arguments_threshold,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_arg_number(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, fn_span: Span, too_many_arguments_threshold: u64) {
|
||||
let args = decl.inputs.len() as u64;
|
||||
if args > too_many_arguments_threshold {
|
||||
span_lint(
|
||||
cx,
|
||||
TOO_MANY_ARGUMENTS,
|
||||
fn_span,
|
||||
&format!(
|
||||
"this function has too many arguments ({}/{})",
|
||||
args, too_many_arguments_threshold
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_span::Span;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::source::snippet;
|
||||
|
||||
use super::TOO_MANY_LINES;
|
||||
|
||||
pub(super) fn check_fn(cx: &LateContext<'_>, span: Span, body: &'tcx hir::Body<'_>, too_many_lines_threshold: u64) {
|
||||
if in_external_macro(cx.sess(), span) {
|
||||
return;
|
||||
}
|
||||
|
||||
let code_snippet = snippet(cx, body.value.span, "..");
|
||||
let mut line_count: u64 = 0;
|
||||
let mut in_comment = false;
|
||||
let mut code_in_line;
|
||||
|
||||
// Skip the surrounding function decl.
|
||||
let start_brace_idx = code_snippet.find('{').map_or(0, |i| i + 1);
|
||||
let end_brace_idx = code_snippet.rfind('}').unwrap_or_else(|| code_snippet.len());
|
||||
let function_lines = code_snippet[start_brace_idx..end_brace_idx].lines();
|
||||
|
||||
for mut line in function_lines {
|
||||
code_in_line = false;
|
||||
loop {
|
||||
line = line.trim_start();
|
||||
if line.is_empty() {
|
||||
break;
|
||||
}
|
||||
if in_comment {
|
||||
if let Some(i) = line.find("*/") {
|
||||
line = &line[i + 2..];
|
||||
in_comment = false;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
let multi_idx = line.find("/*").unwrap_or_else(|| line.len());
|
||||
let single_idx = line.find("//").unwrap_or_else(|| line.len());
|
||||
code_in_line |= multi_idx > 0 && single_idx > 0;
|
||||
// Implies multi_idx is below line.len()
|
||||
if multi_idx < single_idx {
|
||||
line = &line[multi_idx + 2..];
|
||||
in_comment = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if code_in_line {
|
||||
line_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if line_count > too_many_lines_threshold {
|
||||
span_lint(
|
||||
cx,
|
||||
TOO_MANY_LINES,
|
||||
span,
|
||||
&format!(
|
||||
"this function has too many lines ({}/{})",
|
||||
line_count, too_many_lines_threshold
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -51,7 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for GetLastWithLen {
|
|||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
// Is a method call
|
||||
if let ExprKind::MethodCall(ref path, _, ref args, _) = expr.kind;
|
||||
if let ExprKind::MethodCall(path, _, args, _) = expr.kind;
|
||||
|
||||
// Method name is "get"
|
||||
if path.ident.name == sym!(get);
|
||||
|
|
|
@ -35,7 +35,7 @@ impl<'tcx> LateLintPass<'tcx> for IdentityOp {
|
|||
if e.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
if let ExprKind::Binary(cmp, ref left, ref right) = e.kind {
|
||||
if let ExprKind::Binary(cmp, left, right) = e.kind {
|
||||
if is_allowed(cx, cmp, left, right) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -55,8 +55,8 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex {
|
|||
cx,
|
||||
};
|
||||
if let ExprKind::Match(
|
||||
ref op,
|
||||
ref arms,
|
||||
op,
|
||||
arms,
|
||||
MatchSource::IfLetDesugar {
|
||||
contains_else_clause: true,
|
||||
},
|
||||
|
@ -64,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex {
|
|||
{
|
||||
op_visit.visit_expr(op);
|
||||
if op_visit.mutex_lock_called {
|
||||
for arm in *arms {
|
||||
for arm in arms {
|
||||
arm_visit.visit_arm(arm);
|
||||
}
|
||||
|
||||
|
@ -94,14 +94,11 @@ impl<'tcx> Visitor<'tcx> for OppVisitor<'_, 'tcx> {
|
|||
type Map = Map<'tcx>;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let Some(mutex) = is_mutex_lock_call(self.cx, expr);
|
||||
then {
|
||||
if let Some(mutex) = is_mutex_lock_call(self.cx, expr) {
|
||||
self.found_mutex = Some(mutex);
|
||||
self.mutex_lock_called = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
visit::walk_expr(self, expr);
|
||||
}
|
||||
|
||||
|
@ -121,14 +118,11 @@ impl<'tcx> Visitor<'tcx> for ArmVisitor<'_, 'tcx> {
|
|||
type Map = Map<'tcx>;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
|
||||
if_chain! {
|
||||
if let Some(mutex) = is_mutex_lock_call(self.cx, expr);
|
||||
then {
|
||||
if let Some(mutex) = is_mutex_lock_call(self.cx, expr) {
|
||||
self.found_mutex = Some(mutex);
|
||||
self.mutex_lock_called = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
visit::walk_expr(self, expr);
|
||||
}
|
||||
|
||||
|
|
|
@ -44,9 +44,9 @@ declare_lint_pass!(OkIfLet => [IF_LET_SOME_RESULT]);
|
|||
impl<'tcx> LateLintPass<'tcx> for OkIfLet {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if_chain! { //begin checking variables
|
||||
if let ExprKind::Match(ref op, ref body, MatchSource::IfLetDesugar { .. }) = expr.kind; //test if expr is if let
|
||||
if let ExprKind::MethodCall(_, ok_span, ref result_types, _) = op.kind; //check is expr.ok() has type Result<T,E>.ok(, _)
|
||||
if let PatKind::TupleStruct(QPath::Resolved(_, ref x), ref y, _) = body[0].pat.kind; //get operation
|
||||
if let ExprKind::Match(op, body, MatchSource::IfLetDesugar { .. }) = expr.kind; //test if expr is if let
|
||||
if let ExprKind::MethodCall(_, ok_span, result_types, _) = op.kind; //check is expr.ok() has type Result<T,E>.ok(, _)
|
||||
if let PatKind::TupleStruct(QPath::Resolved(_, x), y, _) = body[0].pat.kind; //get operation
|
||||
if method_chain_args(op, &["ok"]).is_some(); //test to see if using ok() methoduse std::marker::Sized;
|
||||
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&result_types[0]), sym::result_type);
|
||||
if rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(x, false)) == "Some";
|
||||
|
|
|
@ -72,15 +72,15 @@ impl LateLintPass<'_> for IfThenSomeElseNone {
|
|||
}
|
||||
|
||||
if_chain! {
|
||||
if let ExprKind::If(ref cond, ref then, Some(ref els)) = expr.kind;
|
||||
if let ExprKind::Block(ref then_block, _) = then.kind;
|
||||
if let Some(ref then_expr) = then_block.expr;
|
||||
if let ExprKind::Call(ref then_call, [then_arg]) = then_expr.kind;
|
||||
if let ExprKind::If(cond, then, Some(els)) = expr.kind;
|
||||
if let ExprKind::Block(then_block, _) = then.kind;
|
||||
if let Some(then_expr) = then_block.expr;
|
||||
if let ExprKind::Call(then_call, [then_arg]) = then_expr.kind;
|
||||
if let ExprKind::Path(ref then_call_qpath) = then_call.kind;
|
||||
if match_qpath(then_call_qpath, &clippy_utils::paths::OPTION_SOME);
|
||||
if let ExprKind::Block(ref els_block, _) = els.kind;
|
||||
if let ExprKind::Block(els_block, _) = els.kind;
|
||||
if els_block.stmts.is_empty();
|
||||
if let Some(ref els_expr) = els_block.expr;
|
||||
if let Some(els_expr) = els_block.expr;
|
||||
if let ExprKind::Path(ref els_call_qpath) = els_expr.kind;
|
||||
if match_qpath(els_call_qpath, &clippy_utils::paths::OPTION_NONE);
|
||||
then {
|
||||
|
|
377
src/tools/clippy/clippy_lints/src/implicit_hasher.rs
Normal file
377
src/tools/clippy/clippy_lints/src/implicit_hasher.rs
Normal file
|
@ -0,0 +1,377 @@
|
|||
#![allow(rustc::default_hash_types)]
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use rustc_errors::DiagnosticBuilder;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::{walk_body, walk_expr, walk_ty, NestedVisitorMap, Visitor};
|
||||
use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::{Ty, TyS, TypeckResults};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::source_map::Span;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_typeck::hir_ty_to_ty;
|
||||
|
||||
use if_chain::if_chain;
|
||||
|
||||
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
|
||||
use clippy_utils::paths;
|
||||
use clippy_utils::source::{snippet, snippet_opt};
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{differing_macro_contexts, match_path};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for public `impl` or `fn` missing generalization
|
||||
/// over different hashers and implicitly defaulting to the default hashing
|
||||
/// algorithm (`SipHash`).
|
||||
///
|
||||
/// **Why is this bad?** `HashMap` or `HashSet` with custom hashers cannot be
|
||||
/// used with them.
|
||||
///
|
||||
/// **Known problems:** Suggestions for replacing constructors can contain
|
||||
/// false-positives. Also applying suggestions can require modification of other
|
||||
/// pieces of code, possibly including external crates.
|
||||
///
|
||||
/// **Example:**
|
||||
/// ```rust
|
||||
/// # use std::collections::HashMap;
|
||||
/// # use std::hash::{Hash, BuildHasher};
|
||||
/// # trait Serialize {};
|
||||
/// impl<K: Hash + Eq, V> Serialize for HashMap<K, V> { }
|
||||
///
|
||||
/// pub fn foo(map: &mut HashMap<i32, i32>) { }
|
||||
/// ```
|
||||
/// could be rewritten as
|
||||
/// ```rust
|
||||
/// # use std::collections::HashMap;
|
||||
/// # use std::hash::{Hash, BuildHasher};
|
||||
/// # trait Serialize {};
|
||||
/// impl<K: Hash + Eq, V, S: BuildHasher> Serialize for HashMap<K, V, S> { }
|
||||
///
|
||||
/// pub fn foo<S: BuildHasher>(map: &mut HashMap<i32, i32, S>) { }
|
||||
/// ```
|
||||
pub IMPLICIT_HASHER,
|
||||
pedantic,
|
||||
"missing generalization over different hashers"
|
||||
}
|
||||
|
||||
declare_lint_pass!(ImplicitHasher => [IMPLICIT_HASHER]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
|
||||
#[allow(clippy::cast_possible_truncation, clippy::too_many_lines)]
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
use rustc_span::BytePos;
|
||||
|
||||
fn suggestion<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
diag: &mut DiagnosticBuilder<'_>,
|
||||
generics_span: Span,
|
||||
generics_suggestion_span: Span,
|
||||
target: &ImplicitHasherType<'_>,
|
||||
vis: ImplicitHasherConstructorVisitor<'_, '_, '_>,
|
||||
) {
|
||||
let generics_snip = snippet(cx, generics_span, "");
|
||||
// trim `<` `>`
|
||||
let generics_snip = if generics_snip.is_empty() {
|
||||
""
|
||||
} else {
|
||||
&generics_snip[1..generics_snip.len() - 1]
|
||||
};
|
||||
|
||||
multispan_sugg(
|
||||
diag,
|
||||
"consider adding a type parameter",
|
||||
vec![
|
||||
(
|
||||
generics_suggestion_span,
|
||||
format!(
|
||||
"<{}{}S: ::std::hash::BuildHasher{}>",
|
||||
generics_snip,
|
||||
if generics_snip.is_empty() { "" } else { ", " },
|
||||
if vis.suggestions.is_empty() {
|
||||
""
|
||||
} else {
|
||||
// request users to add `Default` bound so that generic constructors can be used
|
||||
" + Default"
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
target.span(),
|
||||
format!("{}<{}, S>", target.type_name(), target.type_arguments(),),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
if !vis.suggestions.is_empty() {
|
||||
multispan_sugg(diag, "...and use generic constructor", vis.suggestions);
|
||||
}
|
||||
}
|
||||
|
||||
if !cx.access_levels.is_exported(item.hir_id()) {
|
||||
return;
|
||||
}
|
||||
|
||||
match item.kind {
|
||||
ItemKind::Impl(ref impl_) => {
|
||||
let mut vis = ImplicitHasherTypeVisitor::new(cx);
|
||||
vis.visit_ty(impl_.self_ty);
|
||||
|
||||
for target in &vis.found {
|
||||
if differing_macro_contexts(item.span, target.span()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let generics_suggestion_span = impl_.generics.span.substitute_dummy({
|
||||
let pos = snippet_opt(cx, item.span.until(target.span()))
|
||||
.and_then(|snip| Some(item.span.lo() + BytePos(snip.find("impl")? as u32 + 4)));
|
||||
if let Some(pos) = pos {
|
||||
Span::new(pos, pos, item.span.data().ctxt)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
|
||||
for item in impl_.items.iter().map(|item| cx.tcx.hir().impl_item(item.id)) {
|
||||
ctr_vis.visit_impl_item(item);
|
||||
}
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
IMPLICIT_HASHER,
|
||||
target.span(),
|
||||
&format!(
|
||||
"impl for `{}` should be generalized over different hashers",
|
||||
target.type_name()
|
||||
),
|
||||
move |diag| {
|
||||
suggestion(cx, diag, impl_.generics.span, generics_suggestion_span, target, ctr_vis);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
ItemKind::Fn(ref sig, ref generics, body_id) => {
|
||||
let body = cx.tcx.hir().body(body_id);
|
||||
|
||||
for ty in sig.decl.inputs {
|
||||
let mut vis = ImplicitHasherTypeVisitor::new(cx);
|
||||
vis.visit_ty(ty);
|
||||
|
||||
for target in &vis.found {
|
||||
if in_external_macro(cx.sess(), generics.span) {
|
||||
continue;
|
||||
}
|
||||
let generics_suggestion_span = generics.span.substitute_dummy({
|
||||
let pos = snippet_opt(cx, item.span.until(body.params[0].pat.span))
|
||||
.and_then(|snip| {
|
||||
let i = snip.find("fn")?;
|
||||
Some(item.span.lo() + BytePos((i + (&snip[i..]).find('(')?) as u32))
|
||||
})
|
||||
.expect("failed to create span for type parameters");
|
||||
Span::new(pos, pos, item.span.data().ctxt)
|
||||
});
|
||||
|
||||
let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
|
||||
ctr_vis.visit_body(body);
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
IMPLICIT_HASHER,
|
||||
target.span(),
|
||||
&format!(
|
||||
"parameter of type `{}` should be generalized over different hashers",
|
||||
target.type_name()
|
||||
),
|
||||
move |diag| {
|
||||
suggestion(cx, diag, generics.span, generics_suggestion_span, target, ctr_vis);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ImplicitHasherType<'tcx> {
|
||||
HashMap(Span, Ty<'tcx>, Cow<'static, str>, Cow<'static, str>),
|
||||
HashSet(Span, Ty<'tcx>, Cow<'static, str>),
|
||||
}
|
||||
|
||||
impl<'tcx> ImplicitHasherType<'tcx> {
|
||||
/// Checks that `ty` is a target type without a `BuildHasher`.
|
||||
fn new(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> Option<Self> {
|
||||
if let TyKind::Path(QPath::Resolved(None, path)) = hir_ty.kind {
|
||||
let params: Vec<_> = path
|
||||
.segments
|
||||
.last()
|
||||
.as_ref()?
|
||||
.args
|
||||
.as_ref()?
|
||||
.args
|
||||
.iter()
|
||||
.filter_map(|arg| match arg {
|
||||
GenericArg::Type(ty) => Some(ty),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let params_len = params.len();
|
||||
|
||||
let ty = hir_ty_to_ty(cx.tcx, hir_ty);
|
||||
|
||||
if is_type_diagnostic_item(cx, ty, sym::hashmap_type) && params_len == 2 {
|
||||
Some(ImplicitHasherType::HashMap(
|
||||
hir_ty.span,
|
||||
ty,
|
||||
snippet(cx, params[0].span, "K"),
|
||||
snippet(cx, params[1].span, "V"),
|
||||
))
|
||||
} else if is_type_diagnostic_item(cx, ty, sym::hashset_type) && params_len == 1 {
|
||||
Some(ImplicitHasherType::HashSet(
|
||||
hir_ty.span,
|
||||
ty,
|
||||
snippet(cx, params[0].span, "T"),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
match *self {
|
||||
ImplicitHasherType::HashMap(..) => "HashMap",
|
||||
ImplicitHasherType::HashSet(..) => "HashSet",
|
||||
}
|
||||
}
|
||||
|
||||
fn type_arguments(&self) -> String {
|
||||
match *self {
|
||||
ImplicitHasherType::HashMap(.., ref k, ref v) => format!("{}, {}", k, v),
|
||||
ImplicitHasherType::HashSet(.., ref t) => format!("{}", t),
|
||||
}
|
||||
}
|
||||
|
||||
fn ty(&self) -> Ty<'tcx> {
|
||||
match *self {
|
||||
ImplicitHasherType::HashMap(_, ty, ..) | ImplicitHasherType::HashSet(_, ty, ..) => ty,
|
||||
}
|
||||
}
|
||||
|
||||
fn span(&self) -> Span {
|
||||
match *self {
|
||||
ImplicitHasherType::HashMap(span, ..) | ImplicitHasherType::HashSet(span, ..) => span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ImplicitHasherTypeVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
found: Vec<ImplicitHasherType<'tcx>>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> ImplicitHasherTypeVisitor<'a, 'tcx> {
|
||||
fn new(cx: &'a LateContext<'tcx>) -> Self {
|
||||
Self { cx, found: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for ImplicitHasherTypeVisitor<'a, 'tcx> {
|
||||
type Map = Map<'tcx>;
|
||||
|
||||
fn visit_ty(&mut self, t: &'tcx hir::Ty<'_>) {
|
||||
if let Some(target) = ImplicitHasherType::new(self.cx, t) {
|
||||
self.found.push(target);
|
||||
}
|
||||
|
||||
walk_ty(self, t);
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
}
|
||||
|
||||
/// Looks for default-hasher-dependent constructors like `HashMap::new`.
|
||||
struct ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
|
||||
target: &'b ImplicitHasherType<'tcx>,
|
||||
suggestions: BTreeMap<Span, String>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'tcx> ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
|
||||
fn new(cx: &'a LateContext<'tcx>, target: &'b ImplicitHasherType<'tcx>) -> Self {
|
||||
Self {
|
||||
cx,
|
||||
maybe_typeck_results: cx.maybe_typeck_results(),
|
||||
target,
|
||||
suggestions: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
|
||||
type Map = Map<'tcx>;
|
||||
|
||||
fn visit_body(&mut self, body: &'tcx Body<'_>) {
|
||||
let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body.id()));
|
||||
walk_body(self, body);
|
||||
self.maybe_typeck_results = old_maybe_typeck_results;
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(fun, args) = e.kind;
|
||||
if let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind;
|
||||
if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind;
|
||||
then {
|
||||
if !TyS::same_type(self.target.ty(), self.maybe_typeck_results.unwrap().expr_ty(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if match_path(ty_path, &paths::HASHMAP) {
|
||||
if method.ident.name == sym::new {
|
||||
self.suggestions
|
||||
.insert(e.span, "HashMap::default()".to_string());
|
||||
} else if method.ident.name == sym!(with_capacity) {
|
||||
self.suggestions.insert(
|
||||
e.span,
|
||||
format!(
|
||||
"HashMap::with_capacity_and_hasher({}, Default::default())",
|
||||
snippet(self.cx, args[0].span, "capacity"),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else if match_path(ty_path, &paths::HASHSET) {
|
||||
if method.ident.name == sym::new {
|
||||
self.suggestions
|
||||
.insert(e.span, "HashSet::default()".to_string());
|
||||
} else if method.ident.name == sym!(with_capacity) {
|
||||
self.suggestions.insert(
|
||||
e.span,
|
||||
format!(
|
||||
"HashSet::with_capacity_and_hasher({}, Default::default())",
|
||||
snippet(self.cx, args[0].span, "capacity"),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walk_expr(self, e);
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
|
||||
}
|
||||
}
|
|
@ -100,10 +100,10 @@ fn expr_match(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
|
||||
if check_all_arms {
|
||||
for arm in arms {
|
||||
expr_match(cx, &arm.body);
|
||||
expr_match(cx, arm.body);
|
||||
}
|
||||
} else {
|
||||
expr_match(cx, &arms.first().expect("`if let` doesn't have a single arm").body);
|
||||
expr_match(cx, arms.first().expect("`if let` doesn't have a single arm").body);
|
||||
}
|
||||
},
|
||||
// skip if it already has a return statement
|
||||
|
|
|
@ -46,21 +46,21 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
|
|||
if let ExprKind::If(cond, then, None) = &expr.kind;
|
||||
|
||||
// Check if the conditional expression is a binary operation
|
||||
if let ExprKind::Binary(ref cond_op, ref cond_left, ref cond_right) = cond.kind;
|
||||
if let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind;
|
||||
|
||||
// Ensure that the binary operator is >, != and <
|
||||
if BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node;
|
||||
|
||||
// Check if the true condition block has only one statement
|
||||
if let ExprKind::Block(ref block, _) = then.kind;
|
||||
if let ExprKind::Block(block, _) = then.kind;
|
||||
if block.stmts.len() == 1 && block.expr.is_none();
|
||||
|
||||
// Check if assign operation is done
|
||||
if let StmtKind::Semi(ref e) = block.stmts[0].kind;
|
||||
if let StmtKind::Semi(e) = block.stmts[0].kind;
|
||||
if let Some(target) = subtracts_one(cx, e);
|
||||
|
||||
// Extracting out the variable name
|
||||
if let ExprKind::Path(QPath::Resolved(_, ref ares_path)) = target.kind;
|
||||
if let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind;
|
||||
|
||||
then {
|
||||
// Handle symmetric conditions in the if statement
|
||||
|
@ -104,7 +104,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
|
|||
print_lint_and_sugg(cx, &var_name, expr);
|
||||
};
|
||||
},
|
||||
ExprKind::Call(ref func, _) => {
|
||||
ExprKind::Call(func, _) => {
|
||||
if let ExprKind::Path(ref cond_num_path) = func.kind {
|
||||
if INT_TYPES.iter().any(|int_type| match_qpath(cond_num_path, &[int_type, "min_value"])) {
|
||||
print_lint_and_sugg(cx, &var_name, expr);
|
||||
|
@ -120,7 +120,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
|
|||
|
||||
fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &Expr<'a>) -> Option<&'a Expr<'a>> {
|
||||
match expr.kind {
|
||||
ExprKind::AssignOp(ref op1, ref target, ref value) => {
|
||||
ExprKind::AssignOp(ref op1, target, value) => {
|
||||
if_chain! {
|
||||
if BinOpKind::Sub == op1.node;
|
||||
// Check if literal being subtracted is one
|
||||
|
@ -133,9 +133,9 @@ fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &Expr<'a>) -> Option<&'a Expr<'
|
|||
}
|
||||
}
|
||||
},
|
||||
ExprKind::Assign(ref target, ref value, _) => {
|
||||
ExprKind::Assign(target, value, _) => {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(ref op1, ref left1, ref right1) = value.kind;
|
||||
if let ExprKind::Binary(ref op1, left1, right1) = value.kind;
|
||||
if BinOpKind::Sub == op1.node;
|
||||
|
||||
if SpanlessEq::new(cx).eq_expr(left1, target);
|
||||
|
|
|
@ -88,7 +88,7 @@ declare_lint_pass!(IndexingSlicing => [INDEXING_SLICING, OUT_OF_BOUNDS_INDEXING]
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Index(ref array, ref index) = &expr.kind {
|
||||
if let ExprKind::Index(array, index) = &expr.kind {
|
||||
let ty = cx.typeck_results().expr_ty(array).peel_refs();
|
||||
if let Some(range) = higher::range(index) {
|
||||
// Ranged indexes, i.e., &x[n..m], &x[n..], &x[..n] and &x[..]
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue