1
Fork 0

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:
bors 2021-04-08 19:38:54 +00:00
commit 2e495d2e84
391 changed files with 10850 additions and 6311 deletions

View file

@ -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; pub mod allocator;

View file

@ -59,7 +59,7 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
/// Requirements for a `StableHashingContext` to be used in this crate. /// Requirements for a `StableHashingContext` to be used in this crate.
/// This is a hack to allow using the `HashStable_Generic` derive macro /// 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 { pub trait HashStableContext: rustc_span::HashStableContext {
fn hash_attr(&mut self, _: &ast::Attribute, hasher: &mut StableHasher); fn hash_attr(&mut self, _: &ast::Attribute, hasher: &mut StableHasher);
} }

View file

@ -93,7 +93,7 @@ struct LoweringContext<'a, 'hir: 'a> {
/// HACK(Centril): there is a cyclic dependency between the parser and lowering /// 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 /// 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, nt_to_tokenstream: NtToTokenstream,
/// Used to allocate HIR nodes. /// Used to allocate HIR nodes.

View file

@ -1,6 +1,6 @@
//! Codegen vtables and vtable accesses. //! 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 // FIXME dedup this logic between miri, cg_llvm and cg_clif
use crate::prelude::*; use crate::prelude::*;

View file

@ -19,7 +19,7 @@ pub struct Svh {
impl Svh { impl Svh {
/// Creates a new `Svh` given the hash. If you actually want to /// Creates a new `Svh` given the hash. If you actually want to
/// compute the SVH from some HIR, you want the `calculate_svh` /// 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 { pub fn new(hash: u64) -> Svh {
Svh { hash } Svh { hash }
} }

View file

@ -1,7 +1,7 @@
//! # Feature gates //! # Feature gates
//! //!
//! This crate declares the set of past and present unstable features in the compiler. //! 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. //! at the moment.
//! //!
//! Features are enabled in programs via the crate-level attributes of //! Features are enabled in programs via the crate-level attributes of

View file

@ -9,7 +9,7 @@ use rustc_span::def_id::{DefPathHash, LocalDefId};
/// Requirements for a `StableHashingContext` to be used in this crate. /// Requirements for a `StableHashingContext` to be used in this crate.
/// This is a hack to allow using the `HashStable_Generic` derive macro /// 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: pub trait HashStableContext:
rustc_ast::HashStableContext + rustc_target::HashStableContext rustc_ast::HashStableContext + rustc_target::HashStableContext
{ {

View file

@ -18,8 +18,8 @@ pub static WEAK_ITEMS_REFS: SyncLazy<StableMap<Symbol, LangItem>> = SyncLazy::ne
map map
}); });
/// The `check_name` argument avoids the need for `librustc_hir` to depend on /// The `check_name` argument avoids the need for `rustc_hir` to depend on
/// `librustc_session`. /// `rustc_session`.
pub fn link_name<'a, F>(check_name: F, attrs: &'a [ast::Attribute]) -> Option<Symbol> pub fn link_name<'a, F>(check_name: F, attrs: &'a [ast::Attribute]) -> Option<Symbol>
where where
F: Fn(&'a ast::Attribute, Symbol) -> bool F: Fn(&'a ast::Attribute, Symbol) -> bool

View file

@ -2,7 +2,7 @@
//! //!
//! - **Type inference.** The type inference code can be found in the `infer` module; //! - **Type inference.** The type inference code can be found in the `infer` module;
//! this code handles low-level equality and subtyping operations. The //! 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]. //! For more information about how rustc works, see the [rustc dev guide].
//! //!

View file

@ -1,13 +1,13 @@
//! Low-level Rust lexer. //! 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, //! by separating out pure lexing and rustc-specific concerns, like spans,
//! error reporting, and interning. So, rustc_lexer operates directly on `&str`, //! 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, //! 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. //! 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. //! 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. //! into wide tokens used by actual parser.
//! //!
//! The purpose of this crate is to convert raw sources into a labeled sequence //! 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 //! The main entity of this crate is the [`TokenKind`] enum which represents common
//! lexeme types. //! 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 // We want to be able to build this crate with a stable compiler, so no
// `#![feature]` attributes should be added. // `#![feature]` attributes should be added.

View file

@ -2280,7 +2280,7 @@ declare_lint! {
} }
declare_lint_pass!( 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] IncompleteFeatures => [INCOMPLETE_FEATURES]
); );

View file

@ -464,15 +464,16 @@ impl<'s> LintLevelsBuilder<'s> {
// we don't warn about the name change. // we don't warn about the name change.
if let CheckLintNameResult::Warning(_, Some(new_name)) = lint_result { if let CheckLintNameResult::Warning(_, Some(new_name)) = lint_result {
// Ignore any errors or warnings that happen because the new name is inaccurate // Ignore any errors or warnings that happen because the new name is inaccurate
if let CheckLintNameResult::Ok(ids) = // NOTE: `new_name` already includes the tool name, so we don't have to add it again.
store.check_lint_name(&new_name, tool_name) if let CheckLintNameResult::Ok(ids) = store.check_lint_name(&new_name, None) {
{
let src = let src =
LintLevelSource::Node(Symbol::intern(&new_name), li.span(), reason); LintLevelSource::Node(Symbol::intern(&new_name), li.span(), reason);
for &id in ids { for &id in ids {
self.check_gated_lint(id, attr.span); self.check_gated_lint(id, attr.span);
self.insert_spec(&mut specs, id, (level, src)); self.insert_spec(&mut specs, id, (level, src));
} }
} else {
panic!("renamed lint does not exist: {}", new_name);
} }
} }
} }

View file

@ -1,5 +1,5 @@
//! This module contains `HashStable` implementations for various data types //! 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; use crate::ich::StableHashingContext;

View file

@ -46,7 +46,7 @@ static_assert_size!(InterpErrorInfo<'_>, 8);
/// Packages the kind of error we got from the const code interpreter /// Packages the kind of error we got from the const code interpreter
/// up with a Rust-level backtrace of where the error occurred. /// up with a Rust-level backtrace of where the error occurred.
/// Thsese should always be constructed by calling `.into()` on /// 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. /// macros for this.
#[derive(Debug)] #[derive(Debug)]
pub struct InterpErrorInfo<'tcx>(Box<InterpErrorInfoInner<'tcx>>); pub struct InterpErrorInfo<'tcx>(Box<InterpErrorInfoInner<'tcx>>);

View file

@ -3,7 +3,7 @@
//! which makes a canonical query by replacing unbound inference //! which makes a canonical query by replacing unbound inference
//! variables and regions, so that results can be reused more broadly. //! variables and regions, so that results can be reused more broadly.
//! The providers for the queries defined here can be found in //! The providers for the queries defined here can be found in
//! `librustc_traits`. //! `rustc_traits`.
use crate::ich::StableHashingContext; use crate::ich::StableHashingContext;
use crate::infer::canonical::{Canonical, QueryResponse}; use crate::infer::canonical::{Canonical, QueryResponse};

View file

@ -44,7 +44,7 @@ use std::ops::ControlFlow;
/// This trait is implemented for every type that can be folded. /// This trait is implemented for every type that can be folded.
/// Basically, every type that has a corresponding method in `TypeFolder`. /// 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 { pub trait TypeFoldable<'tcx>: fmt::Debug + Clone {
fn super_fold_with<F: TypeFolder<'tcx>>(self, folder: &mut F) -> Self; fn super_fold_with<F: TypeFolder<'tcx>>(self, folder: &mut F) -> Self;
fn fold_with<F: TypeFolder<'tcx>>(self, folder: &mut F) -> Self { fn fold_with<F: TypeFolder<'tcx>>(self, folder: &mut F) -> Self {

View file

@ -80,7 +80,7 @@ impl BoundRegionKind {
/// Defines the kinds of types. /// Defines the kinds of types.
/// ///
/// N.B., if you change this, you'll probably want to change the corresponding /// 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(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, TyEncodable, TyDecodable, Debug)]
#[derive(HashStable)] #[derive(HashStable)]
#[rustc_diagnostic_item = "TyKind"] #[rustc_diagnostic_item = "TyKind"]
@ -2116,7 +2116,7 @@ impl<'tcx> TyS<'tcx> {
/// ///
/// Note that during type checking, we use an inference variable /// Note that during type checking, we use an inference variable
/// to represent the closure kind, because it has not yet been /// 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. /// is complete, that type variable will be unified.
pub fn to_opt_closure_kind(&self) -> Option<ty::ClosureKind> { pub fn to_opt_closure_kind(&self) -> Option<ty::ClosureKind> {
match self.kind() { match self.kind() {

View file

@ -1,5 +1,5 @@
//! This module provides linkage between RegionInferenceContext and //! 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. //! data to rendered labels.
use std::borrow::Cow; use std::borrow::Cow;

View file

@ -128,7 +128,7 @@ impl<'a> StringReader<'a> {
} }
/// Turns simple `rustc_lexer::TokenKind` enum into a rich /// 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. /// symbols and runs additional validation.
fn cook_lexer_token(&self, token: rustc_lexer::TokenKind, start: BytePos) -> Option<TokenKind> { fn cook_lexer_token(&self, token: rustc_lexer::TokenKind, start: BytePos) -> Option<TokenKind> {
Some(match token { Some(match token {

View file

@ -26,7 +26,7 @@
//! could not be instantiated because the current compilation session //! could not be instantiated because the current compilation session
//! contained no `DefId` for thing that had been removed. //! 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 //! 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 //! `DepConstructor` enum links a `DepKind` to the parameters that are needed at runtime in order
//! to construct a valid `DepNode` fingerprint. //! to construct a valid `DepNode` fingerprint.

View file

@ -6,7 +6,7 @@
//! Paths in macros, imports, expressions, types, patterns are resolved here. //! Paths in macros, imports, expressions, types, patterns are resolved here.
//! Label and lifetime names are resolved here as well. //! 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/")] #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
#![feature(box_patterns)] #![feature(box_patterns)]

View file

@ -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. specific to different compilation targets and so forth.
For more information about how rustc works, see the [rustc dev guide]. For more information about how rustc works, see the [rustc dev guide].

View file

@ -28,5 +28,5 @@ pub mod spec;
/// Requirements for a `StableHashingContext` to be used in this crate. /// Requirements for a `StableHashingContext` to be used in this crate.
/// This is a hack to allow using the `HashStable_Generic` derive macro /// 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 {} pub trait HashStableContext {}

View file

@ -3,7 +3,7 @@
//! which makes a canonical query by replacing unbound inference //! which makes a canonical query by replacing unbound inference
//! variables and regions, so that results can be reused more broadly. //! variables and regions, so that results can be reused more broadly.
//! The providers for the queries defined here can be found in //! The providers for the queries defined here can be found in
//! `librustc_traits`. //! `rustc_traits`.
pub mod dropck_outlives; pub mod dropck_outlives;
pub mod evaluate_obligation; pub mod evaluate_obligation;

View file

@ -233,7 +233,6 @@ impl<I: Iterator> Peekable<I> {
/// Basic usage: /// Basic usage:
/// ///
/// ``` /// ```
/// #![feature(peekable_peek_mut)]
/// let mut iter = [1, 2, 3].iter().peekable(); /// let mut iter = [1, 2, 3].iter().peekable();
/// ///
/// // Like with `peek()`, we can see into the future without advancing the iterator. /// // 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]); /// assert_eq!(iter.collect::<Vec<_>>(), vec![&5, &3]);
/// ``` /// ```
#[inline] #[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> { pub fn peek_mut(&mut self) -> Option<&mut I::Item> {
let iter = &mut self.iter; let iter = &mut self.iter;
self.peeked.get_or_insert_with(|| iter.next()).as_mut() self.peeked.get_or_insert_with(|| iter.next()).as_mut()

View file

@ -937,20 +937,16 @@ pub trait Iterator {
Enumerate::new(self) Enumerate::new(self)
} }
/// Creates an iterator which can use [`peek`] to look at the next element of /// Creates an iterator which can use the [`peek`] and [`peek_mut`] methods
/// the iterator without consuming it. /// 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 /// Note that the underlying iterator is still advanced when [`peek`] or
/// more information. /// [`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 /// # Examples
/// ///
@ -977,6 +973,32 @@ pub trait Iterator {
/// assert_eq!(iter.peek(), None); /// assert_eq!(iter.peek(), None);
/// assert_eq!(iter.next(), 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] #[inline]
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
fn peekable(self) -> Peekable<Self> fn peekable(self) -> Peekable<Self>

View file

@ -298,7 +298,8 @@ pub mod primitive;
unused_imports, unused_imports,
unsafe_op_in_unsafe_fn 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 // 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. // merged. It currently cannot because bootstrap fails as the lint hasn't been defined yet.
#[allow(clashing_extern_declarations)] #[allow(clashing_extern_declarations)]

View file

@ -6,7 +6,7 @@
//! as moving an object with pointers to itself will invalidate them, which could cause undefined //! as moving an object with pointers to itself will invalidate them, which could cause undefined
//! behavior. //! 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 //! `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 //! 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 //! pointee is "pinned". Things get more subtle when discussing types that
@ -14,13 +14,15 @@
//! for more details. //! for more details.
//! //!
//! By default, all types in Rust are movable. Rust allows passing all types by-value, //! 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 //! and common smart-pointer types such as <code>[Box]\<T></code> and <code>[&mut] T</code> allow
//! moving the values they contain: you can move out of a [`Box<T>`], or you can use [`mem::swap`]. //! replacing and moving the values they contain: you can move out of a <code>[Box]\<T></code>,
//! [`Pin<P>`] wraps a pointer type `P`, so [`Pin`]`<`[`Box`]`<T>>` functions much like a regular //! or you can use [`mem::swap`]. <code>[Pin]\<P></code> wraps a pointer type `P`, so
//! [`Box<T>`]: when a [`Pin`]`<`[`Box`]`<T>>` gets dropped, so do its contents, and the memory gets //! <code>[Pin]<[Box]\<T>></code> functions much like a regular <code>[Box]\<T></code>:
//! deallocated. Similarly, [`Pin`]`<&mut T>` is a lot like `&mut T`. However, [`Pin<P>`] does //! when a <code>[Pin]<[Box]\<T>></code> gets dropped, so do its contents, and the memory gets
//! not let clients actually obtain a [`Box<T>`] or `&mut T` to pinned data, which implies that you //! deallocated. Similarly, <code>[Pin]<[&mut] T></code> is a lot like <code>[&mut] T</code>.
//! cannot use operations such as [`mem::swap`]: //! 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; //! use std::pin::Pin;
@ -32,18 +34,18 @@
//! } //! }
//! ``` //! ```
//! //!
//! It is worth reiterating that [`Pin<P>`] does *not* change the fact that a Rust compiler //! It is worth reiterating that <code>[Pin]\<P></code> does *not* change the fact that a Rust
//! considers all types movable. [`mem::swap`] remains callable for any `T`. Instead, [`Pin<P>`] //! compiler considers all types movable. [`mem::swap`] remains callable for any `T`. Instead,
//! prevents certain *values* (pointed to by pointers wrapped in [`Pin<P>`]) from being //! <code>[Pin]\<P></code> prevents certain *values* (pointed to by pointers wrapped in
//! moved by making it impossible to call methods that require `&mut T` on them //! <code>[Pin]\<P></code>) from being moved by making it impossible to call methods that require
//! (like [`mem::swap`]). //! <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 //! <code>[Pin]\<P></code> 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 //! [`Deref`] and [`DerefMut`]. A <code>[Pin]\<P></code> where <code>P: [Deref]</code> should be
//! as a "`P`-style pointer" to a pinned `P::Target` -- so, a [`Pin`]`<`[`Box`]`<T>>` is //! considered as a "`P`-style pointer" to a pinned <code>P::[Target]</code> so, a
//! an owned pointer to a pinned `T`, and a [`Pin`]`<`[`Rc`]`<T>>` is a reference-counted //! <code>[Pin]<[Box]\<T>></code> is an owned pointer to a pinned `T`, and a
//! pointer to a pinned `T`. //! <code>[Pin]<[Rc]\<T>></code> is a reference-counted pointer to a pinned `T`.
//! For correctness, [`Pin<P>`] relies on the implementations of [`Deref`] and //! 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 //! [`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. //! 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 //! 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 //! [`bool`], [`i32`], and references) as well as types consisting solely of these
//! types. Types that do not care about pinning implement the [`Unpin`] //! types. Types that do not care about pinning implement the [`Unpin`]
//! auto-trait, which cancels the effect of [`Pin<P>`]. For `T: Unpin`, //! auto-trait, which cancels the effect of <code>[Pin]\<P></code>. For <code>T: [Unpin]</code>,
//! [`Pin`]`<`[`Box`]`<T>>` and [`Box<T>`] function identically, as do [`Pin`]`<&mut T>` and //! <code>[Pin]<[Box]\<T>></code> and <code>[Box]\<T></code> function identically, as do
//! `&mut T`. //! <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 //! Note that pinning and [`Unpin`] only affect the pointed-to type <code>P::[Target]</code>,
//! type `P` itself that got wrapped in [`Pin<P>`]. For example, whether or not [`Box<T>`] is //! not the pointer type `P` itself that got wrapped in <code>[Pin]\<P></code>. For example,
//! [`Unpin`] has no effect on the behavior of [`Pin`]`<`[`Box`]`<T>>` (here, `T` is the //! whether or not <code>[Box]\<T></code> is [`Unpin`] has no effect on the behavior of
//! pointed-to type). //! <code>[Pin]<[Box]\<T>></code> (here, `T` is the pointed-to type).
//! //!
//! # Example: self-referential struct //! # Example: self-referential struct
//! //!
//! Before we go into more details to explain the guarantees and choices //! 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). //! Feel free to [skip to where the theoretical discussion continues](#drop-guarantee).
//! //!
//! ```rust //! ```rust
@ -129,7 +131,7 @@
//! //!
//! To make this work, every element has pointers to its predecessor and successor in //! 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 //! 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 //! list element will patch the pointers of its predecessor and successor to remove itself
//! from the list. //! from the list.
//! //!
@ -149,8 +151,8 @@
//! when [`drop`] is called*. Only once [`drop`] returns or panics, the memory may be reused. //! when [`drop`] is called*. Only once [`drop`] returns or panics, the memory may be reused.
//! //!
//! Memory can be "invalidated" by deallocation, but also by //! Memory can be "invalidated" by deallocation, but also by
//! replacing a [`Some(v)`] by [`None`], or calling [`Vec::set_len`] to "kill" some elements //! replacing a <code>[Some]\(v)</code> by [`None`], or calling [`Vec::set_len`] to "kill" some
//! off of a vector. It can be repurposed by using [`ptr::write`] to overwrite it without //! 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`]. //! 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 //! 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 //! 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 //! 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 //! list, that element would just stay in the list. However you may not free or reuse the storage
//! *without calling [`drop`]*. //! *without calling [`drop`]*.
//! //!
//! # `Drop` implementation //! # `Drop` implementation
//! //!
//! If your type uses pinning (such as the two examples above), you have to be careful //! 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 //! is called *even if your type was previously pinned*! It is as if the
//! compiler automatically called [`Pin::get_unchecked_mut`]. //! compiler automatically called [`Pin::get_unchecked_mut`].
//! //!
//! This can never cause a problem in safe code because implementing a type that //! 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 //! 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 //! use of pinning in your type (for example by implementing some operation on
//! [`Pin`]`<&Self>` or [`Pin`]`<&mut Self>`) has consequences for your [`Drop`] //! <code>[Pin]<[&]Self></code> or <code>[Pin]<[&mut] Self></code>) has consequences for your
//! implementation as well: if an element of your type could have been pinned, //! [`Drop`][Drop]implementation as well: if an element of your type could have been pinned,
//! you must treat [`Drop`] as implicitly taking [`Pin`]`<&mut Self>`. //! 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 //! ```rust,no_run
//! # use std::pin::Pin; //! # use std::pin::Pin;
@ -204,18 +206,18 @@
//! # Projections and Structural Pinning //! # Projections and Structural Pinning
//! //!
//! When working with pinned structs, the question arises how one can access the //! 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*) //! The usual approach is to write helper methods (so called *projections*)
//! that turn [`Pin`]`<&mut Struct>` into a reference to the field, but what //! that turn <code>[Pin]<[&mut] Struct></code> into a reference to the field, but what type should
//! type should that reference have? Is it [`Pin`]`<&mut Field>` or `&mut Field`? //! 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 //! 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>`]. //! container/wrapper types such as <code>[Vec]\<T></code>, <code>[Box]\<T></code>,
//! (This question applies to both mutable and shared references, we just //! or <code>[RefCell]\<T></code>. (This question applies to both mutable and shared references,
//! use the more common case of mutable references here for illustration.) //! 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 //! It turns out that it is actually up to the author of the data structure to decide whether
//! to decide whether the pinned projection for a particular field turns //! the pinned projection for a particular field turns <code>[Pin]<[&mut] Struct></code>
//! [`Pin`]`<&mut Struct>` into [`Pin`]`<&mut Field>` or `&mut Field`. There are some //! into <code>[Pin]<[&mut] Field></code> or <code>[&mut] Field</code>. There are some
//! constraints though, and the most important constraint is *consistency*: //! constraints though, and the most important constraint is *consistency*:
//! every field can be *either* projected to a pinned reference, *or* have //! 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, //! 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` //! ## Pinning *is not* structural for `field`
//! //!
//! It may seem counter-intuitive that the field of a pinned struct might not be pinned, //! 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, //! 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. //! 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 //! 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 //! ```rust,no_run
//! # use std::pin::Pin; //! # 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 //! 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` //! ## Pinning *is* structural for `field`
//! //!
//! The other option is to decide that 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. //! 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: //! witnessing that the field is pinned:
//! //!
//! ```rust,no_run //! ```rust,no_run
@ -278,34 +280,36 @@
//! 1. The struct must only be [`Unpin`] if all the structural fields are //! 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 //! [`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 //! 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 //! 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 //! 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 //! 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. //! <code>[&mut] self</code>, but the struct (and hence its fields) might have been pinned
//! You have to guarantee that you do not move a field inside your [`Drop`] implementation. //! before. You have to guarantee that you do not move a field inside your [`Drop`][Drop]
//! In particular, as explained previously, this means that your struct must *not* //! implementation. In particular, as explained previously, this means that your struct
//! be `#[repr(packed)]`. //! must *not* be `#[repr(packed)]`.
//! See that section for how to write [`drop`] in a way that the compiler can help you //! See that section for how to write [`drop`] in a way that the compiler can help you
//! not accidentally break pinning. //! not accidentally break pinning.
//! 3. You must make sure that you uphold the [`Drop` guarantee][drop-guarantee]: //! 3. You must make sure that you uphold the [`Drop` guarantee][drop-guarantee]:
//! once your struct is pinned, the memory that contains the //! once your struct is pinned, the memory that contains the
//! content is not overwritten or deallocated without calling the content's destructors. //! 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>`] //! This can be tricky, as witnessed by <code>[VecDeque]\<T></code>: the destructor of
//! can fail to call [`drop`] on all elements if one of the destructors panics. This violates //! <code>[VecDeque]\<T></code> can fail to call [`drop`] on all elements if one of the
//! the [`Drop`] guarantee, because it can lead to elements being deallocated without //! destructors panics. This violates the [`Drop`][Drop] guarantee, because it can lead to
//! their destructor being called. ([`VecDeque<T>`] has no pinning projections, so this //! elements being deallocated without their destructor being called.
//! (<code>[VecDeque]\<T></code> has no pinning projections, so this
//! does not cause unsoundness.) //! does not cause unsoundness.)
//! 4. You must not offer any other operations that could lead to data being moved out of //! 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 //! 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 //! <code>[Option]\<T></code> and there is a [`take`][Option::take]-like operation with type
//! `fn(Pin<&mut Struct<T>>) -> Option<T>`, //! <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 //! 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. //! 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>`] //! For a more complex example of moving data out of a pinned type,
//! had a method `fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T>`. //! 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: //! Then we could do the following:
//! ```compile_fail //! ```compile_fail
//! fn exploit_ref_cell<T>(rc: Pin<&mut RefCell<T>>) { //! 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. //! 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>`] //! This is catastrophic, it means we can first pin the content of the
//! (using `RefCell::get_pin_mut`) and then move that content using the mutable //! <code>[RefCell]\<T></code> (using <code>[RefCell]::get_pin_mut</code>) and then move that
//! reference we got later. //! content using the mutable reference we got later.
//! //!
//! ## Examples //! ## Examples
//! //!
//! For a type like [`Vec<T>`], both possibilities (structural pinning or not) make sense. //! For a type like <code>[Vec]\<T></code>, both possibilities (structural pinning or not) make
//! A [`Vec<T>`] with structural pinning could have `get_pin`/`get_pin_mut` methods to get //! sense. A <code>[Vec]\<T></code> with structural pinning could have `get_pin`/`get_pin_mut`
//! pinned references to elements. However, it could *not* allow calling //! 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) //! [`pop`][Vec::pop] on a pinned <code>[Vec]\<T></code> because that would move the (structurally
//! contents! Nor could it allow [`push`][Vec::push], which might reallocate and thus also move the //! pinned) contents! Nor could it allow [`push`][Vec::push], which might reallocate and thus also
//! contents. //! move the contents.
//! //!
//! A [`Vec<T>`] without structural pinning could `impl<T> Unpin for Vec<T>`, because the contents //! A <code>[Vec]\<T></code> without structural pinning could
//! are never pinned and the [`Vec<T>`] itself is fine with being moved as well. //! <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. //! 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, //! 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`. //! and thus they do not offer pinning projections. This is why <code>[Box]\<T>: [Unpin]</code>
//! It makes sense to do this for pointer types, because moving the `Box<T>` //! holds for all `T`. It makes sense to do this for pointer types, because moving the
//! does not actually move the `T`: the [`Box<T>`] can be freely movable (aka `Unpin`) even if //! <code>[Box]\<T></code> does not actually move the `T`: the <code>[Box]\<T></code> can be freely
//! the `T` is not. In fact, even [`Pin`]`<`[`Box`]`<T>>` and [`Pin`]`<&mut T>` are always //! movable (aka [`Unpin`]) even if the `T` is not. In fact, even <code>[Pin]<[Box]\<T>></code> and
//! [`Unpin`] themselves, for the same reason: their contents (the `T`) are pinned, but the //! <code>[Pin]<[&mut] T></code> are always [`Unpin`] themselves, for the same reason:
//! pointers themselves can be moved without moving the pinned data. For both [`Box<T>`] and //! their contents (the `T`) are pinned, but the pointers themselves can be moved without moving
//! [`Pin`]`<`[`Box`]`<T>>`, whether the content is pinned is entirely independent of whether the //! 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. //! pointer is pinned, meaning pinning is *not* structural.
//! //!
//! When implementing a [`Future`] combinator, you will usually need structural pinning //! 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`]. //! 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, //! 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 //! 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). //! [`poll`] implementation).
//! //!
//! [`Deref`]: crate::ops::Deref //! [Deref]: crate::ops::Deref "ops::Deref"
//! [`DerefMut`]: crate::ops::DerefMut //! [`Deref`]: crate::ops::Deref "ops::Deref"
//! [`mem::swap`]: crate::mem::swap //! [Target]: crate::ops::Deref::Target "ops::Deref::Target"
//! [`mem::forget`]: crate::mem::forget //! [`DerefMut`]: crate::ops::DerefMut "ops::DerefMut"
//! [`Box<T>`]: ../../std/boxed/struct.Box.html //! [`mem::swap`]: crate::mem::swap "mem::swap"
//! [`Vec<T>`]: ../../std/vec/struct.Vec.html //! [`mem::forget`]: crate::mem::forget "mem::forget"
//! [`Vec::set_len`]: ../../std/vec/struct.Vec.html#method.set_len //! [Vec]: ../../std/vec/struct.Vec.html "Vec"
//! [`Box`]: ../../std/boxed/struct.Box.html //! [`Vec::set_len`]: ../../std/vec/struct.Vec.html#method.set_len "Vec::set_len"
//! [Vec::pop]: ../../std/vec/struct.Vec.html#method.pop //! [Box]: ../../std/boxed/struct.Box.html "Box"
//! [Vec::push]: ../../std/vec/struct.Vec.html#method.push //! [Vec::pop]: ../../std/vec/struct.Vec.html#method.pop "Vec::pop"
//! [`Rc`]: ../../std/rc/struct.Rc.html //! [Vec::push]: ../../std/vec/struct.Vec.html#method.push "Vec::push"
//! [`RefCell<T>`]: crate::cell::RefCell //! [Rc]: ../../std/rc/struct.Rc.html "rc::Rc"
//! [`drop`]: Drop::drop //! [RefCell]: crate::cell::RefCell "cell::RefCell"
//! [`VecDeque<T>`]: ../../std/collections/struct.VecDeque.html //! [`drop`]: Drop::drop "Drop::drop"
//! [`Some(v)`]: Some //! [VecDeque]: ../../std/collections/struct.VecDeque.html "collections::VecDeque"
//! [`ptr::write`]: crate::ptr::write //! [`ptr::write`]: crate::ptr::write "ptr::write"
//! [`Future`]: crate::future::Future //! [`Future`]: crate::future::Future "future::Future"
//! [drop-impl]: #drop-implementation //! [drop-impl]: #drop-implementation
//! [drop-guarantee]: #drop-guarantee //! [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")] #![stable(feature = "pin", since = "1.33.0")]

View file

@ -66,7 +66,6 @@
#![feature(unwrap_infallible)] #![feature(unwrap_infallible)]
#![feature(option_result_unwrap_unchecked)] #![feature(option_result_unwrap_unchecked)]
#![feature(result_into_ok_or_err)] #![feature(result_into_ok_or_err)]
#![feature(peekable_peek_mut)]
#![feature(ptr_metadata)] #![feature(ptr_metadata)]
#![feature(once_cell)] #![feature(once_cell)]
#![feature(unsized_tuple_coercion)] #![feature(unsized_tuple_coercion)]

View file

@ -294,17 +294,14 @@ warning: unclosed HTML tag `h1`
warning: 2 warnings emitted warning: 2 warnings emitted
``` ```
## non_autolinks ## bare_urls
This lint is **nightly-only** and **warns by default**. It detects links which This lint is **warn-by-default**. It detects URLs which are not links.
could use the "automatic" link syntax. For example: For example:
```rust ```rust
/// http://example.org /// http://example.org
/// [http://example.com](http://example.com)
/// [http://example.net] /// [http://example.net]
///
/// [http://example.com]: http://example.com
pub fn foo() {} pub fn foo() {}
``` ```
@ -312,22 +309,18 @@ Which will give:
```text ```text
warning: this URL is not a hyperlink warning: this URL is not a hyperlink
--> foo.rs:1:5 --> links.rs:1:5
| |
1 | /// http://example.org 1 | /// http://example.org
| ^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://example.org>` | ^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://example.org>`
| |
= note: `#[warn(rustdoc::non_autolinks)]` on by default = note: `#[warn(rustdoc::bare_urls)]` 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>`
warning: this URL is not a hyperlink warning: this URL is not a hyperlink
--> foo.rs:3:6 --> links.rs:3:6
| |
3 | /// [http://example.net] 3 | /// [http://example.net]
| ^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://example.net>` | ^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://example.net>`
warning: 2 warnings emitted
``` ```

View file

@ -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 /// Processes program tokens, classifying strings of text by highlighting
/// category (`Class`). /// category (`Class`).
struct Classifier<'a> { struct Classifier<'a> {
@ -144,6 +154,8 @@ struct Classifier<'a> {
in_macro: bool, in_macro: bool,
in_macro_nonterminal: bool, in_macro_nonterminal: bool,
edition: Edition, edition: Edition,
byte_pos: u32,
src: &'a str,
} }
impl<'a> Classifier<'a> { impl<'a> Classifier<'a> {
@ -155,6 +167,68 @@ impl<'a> Classifier<'a> {
in_macro: false, in_macro: false,
in_macro_nonterminal: false, in_macro_nonterminal: false,
edition, 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. /// token is used.
fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'a>)) { fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'a>)) {
with_default_session_globals(|| { 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.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 { TokenKind::And => match lookahead {
Some(TokenKind::And) => { Some(TokenKind::And) => {
let _and = self.tokens.next(); self.next();
sink(Highlight::Token { text: "&&", class: Some(Class::Op) }); sink(Highlight::Token { text: "&&", class: Some(Class::Op) });
return; return;
} }
Some(TokenKind::Eq) => { Some(TokenKind::Eq) => {
let _eq = self.tokens.next(); self.next();
sink(Highlight::Token { text: "&=", class: Some(Class::Op) }); sink(Highlight::Token { text: "&=", class: Some(Class::Op) });
return; return;
} }
@ -260,7 +351,7 @@ impl<'a> Classifier<'a> {
match lookahead { match lookahead {
// Case 1: #![inner_attribute] // Case 1: #![inner_attribute]
Some(TokenKind::Bang) => { Some(TokenKind::Bang) => {
let _not = self.tokens.next().unwrap(); self.next();
if let Some(TokenKind::OpenBracket) = self.peek() { if let Some(TokenKind::OpenBracket) = self.peek() {
self.in_attribute = true; self.in_attribute = true;
sink(Highlight::EnterSpan { class: Class::Attribute }); sink(Highlight::EnterSpan { class: Class::Attribute });
@ -304,20 +395,18 @@ impl<'a> Classifier<'a> {
sink(Highlight::Token { text, class: None }); sink(Highlight::Token { text, class: None });
return; return;
} }
TokenKind::Ident => match text { TokenKind::Ident => match get_real_ident_class(text, self.edition) {
"ref" | "mut" => Class::RefKeyWord, Class::Ident => match text {
"self" | "Self" => Class::Self_,
"false" | "true" => Class::Bool,
"Option" | "Result" => Class::PreludeTy, "Option" | "Result" => Class::PreludeTy,
"Some" | "None" | "Ok" | "Err" => Class::PreludeVal, "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 => { _ if self.in_macro_nonterminal => {
self.in_macro_nonterminal = false; self.in_macro_nonterminal = false;
Class::MacroNonTerminal Class::MacroNonTerminal
} }
_ => Class::Ident, _ => Class::Ident,
}, },
c => c,
},
TokenKind::RawIdent => Class::Ident, TokenKind::RawIdent => Class::Ident,
TokenKind::Lifetime { .. } => Class::Lifetime, TokenKind::Lifetime { .. } => Class::Lifetime,
}; };

View file

@ -10,6 +10,8 @@
</style> </style>
<pre><code><span class="attribute">#![<span class="ident">crate_type</span> <span class="op">=</span> <span class="string">&quot;lib&quot;</span>]</span> <pre><code><span class="attribute">#![<span class="ident">crate_type</span> <span class="op">=</span> <span class="string">&quot;lib&quot;</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">&quot;linux&quot;</span>)]</span> <span class="attribute">#[<span class="ident">cfg</span>(<span class="ident">target_os</span> <span class="op">=</span> <span class="string">&quot;linux&quot;</span>)]</span>
<span class="kw">fn</span> <span class="ident">main</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">&amp;&amp;</span> <span class="bool-val">false</span> <span class="op">|</span><span class="op">|</span> <span class="bool-val">true</span>; <span class="kw">let</span> <span class="ident">foo</span> <span class="op">=</span> <span class="bool-val">true</span> <span class="op">&amp;&amp;</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="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">&amp;</span><span class="kw-2">mut</span> <span class="ident">bar</span>); <span class="macro">mac!</span>(<span class="ident">foo</span>, <span class="kw-2">&amp;</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">&lt;</span> <span class="ident">N</span> <span class="op">&amp;&amp;</span> <span class="ident">index</span> <span class="op">&lt;</span><span class="op">=</span> <span class="self">self</span>.<span class="ident">length</span>); <span class="macro">assert!</span>(<span class="self">self</span>.<span class="ident">length</span> <span class="op">&lt;</span> <span class="ident">N</span> <span class="op">&amp;&amp;</span> <span class="ident">index</span> <span class="op">&lt;</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">&quot;gateau&quot;</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">&amp;</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">&gt;</span> {}
}
} }
<span class="macro">macro_rules!</span> <span class="ident">bar</span> { <span class="macro">macro_rules!</span> <span class="ident">bar</span> {

View file

@ -1,5 +1,7 @@
#![crate_type = "lib"] #![crate_type = "lib"]
use std::path::{Path, PathBuf};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn main() { fn main() {
let foo = true && false || true; let foo = true && false || true;
@ -9,6 +11,14 @@ fn main() {
let _ = *foo; let _ = *foo;
mac!(foo, &mut bar); mac!(foo, &mut bar);
assert!(self.length < N && index <= self.length); 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 { macro_rules! bar {

View file

@ -148,14 +148,13 @@ declare_rustdoc_lint! {
} }
declare_rustdoc_lint! { declare_rustdoc_lint! {
/// The `non_autolinks` lint detects when a URL could be written using /// The `bare_urls` lint detects when a URL is not a hyperlink.
/// only angle brackets. This is a `rustdoc` only lint, see the /// This is a `rustdoc` only lint, see the documentation in the [rustdoc book].
/// documentation in the [rustdoc book].
/// ///
/// [rustdoc book]: ../../../rustdoc/lints.html#non_autolinks /// [rustdoc book]: ../../../rustdoc/lints.html#bare_urls
NON_AUTOLINKS, BARE_URLS,
Warn, 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(|| { 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, PRIVATE_DOC_TESTS,
INVALID_CODEBLOCK_ATTRIBUTES, INVALID_CODEBLOCK_ATTRIBUTES,
INVALID_HTML_TAGS, INVALID_HTML_TAGS,
NON_AUTOLINKS, BARE_URLS,
MISSING_CRATE_LEVEL_DOCS, MISSING_CRATE_LEVEL_DOCS,
] ]
}); });
@ -185,4 +184,6 @@ crate fn register_lints(_sess: &Session, lint_store: &mut LintStore) {
} }
lint_store lint_store
.register_renamed("intra_doc_link_resolution_failure", "rustdoc::broken_intra_doc_links"); .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");
} }

View file

@ -4,37 +4,42 @@ use crate::core::DocContext;
use crate::fold::DocFolder; use crate::fold::DocFolder;
use crate::html::markdown::opts; use crate::html::markdown::opts;
use core::ops::Range; use core::ops::Range;
use pulldown_cmark::{Event, LinkType, Parser, Tag}; use pulldown_cmark::{Event, Parser, Tag};
use regex::Regex; use regex::Regex;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use std::lazy::SyncLazy;
use std::mem;
crate const CHECK_NON_AUTOLINKS: Pass = Pass { crate const CHECK_BARE_URLS: Pass = Pass {
name: "check-non-autolinks", name: "check-bare-urls",
run: check_non_autolinks, run: check_bare_urls,
description: "detects URLs that could be linkified", 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"https?://", // url scheme
r"([-a-zA-Z0-9@:%._\+~#=]{2,256}\.)+", // one or more subdomains r"([-a-zA-Z0-9@:%._\+~#=]{2,256}\.)+", // one or more subdomains
r"[a-zA-Z]{2,63}", // root domain r"[a-zA-Z]{2,63}", // root domain
r"\b([-a-zA-Z0-9@:%_\+.~#?&/=]*)" // optional query or url fragments 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>, cx: &'a mut DocContext<'tcx>,
regex: Regex,
} }
impl<'a, 'tcx> NonAutolinksLinter<'a, 'tcx> { impl<'a, 'tcx> BareUrlsLinter<'a, 'tcx> {
fn find_raw_urls( fn find_raw_urls(
&self, &self,
text: &str, text: &str,
range: Range<usize>, range: Range<usize>,
f: &impl Fn(&DocContext<'_>, &str, &str, 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 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 = match_.as_str();
let url_range = match_.range(); let url_range = match_.range();
f( f(
@ -47,18 +52,11 @@ impl<'a, 'tcx> NonAutolinksLinter<'a, 'tcx> {
} }
} }
crate fn check_non_autolinks(krate: Crate, cx: &mut DocContext<'_>) -> Crate { crate fn check_bare_urls(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
if !cx.tcx.sess.is_nightly_build() { BareUrlsLinter { cx }.fold_crate(krate)
krate
} else {
let mut coll =
NonAutolinksLinter { cx, regex: Regex::new(URL_REGEX).expect("failed to build regex") };
coll.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> { fn fold_item(&mut self, item: Item) -> Option<Item> {
let hir_id = match DocContext::as_local_hir_id(self.cx.tcx, item.def_id) { let hir_id = match DocContext::as_local_hir_id(self.cx.tcx, item.def_id) {
Some(hir_id) => hir_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) let sp = super::source_span_for_markdown_range(cx.tcx, &dox, &range, &item.attrs)
.or_else(|| span_of_attrs(&item.attrs)) .or_else(|| span_of_attrs(&item.attrs))
.unwrap_or(item.span.inner()); .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) lint.build(msg)
.span_suggestion( .span_suggestion(
sp, sp,
@ -89,37 +87,16 @@ impl<'a, 'tcx> DocFolder for NonAutolinksLinter<'a, 'tcx> {
while let Some((event, range)) = p.next() { while let Some((event, range)) = p.next() {
match event { 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::Text(s) => self.find_raw_urls(&s, range, &report_diag),
Event::Start(Tag::CodeBlock(_)) => { // We don't want to check the text inside code blocks or links.
// We don't want to check the text inside the code blocks. Event::Start(tag @ (Tag::CodeBlock(_) | Tag::Link(..))) => {
while let Some((event, _)) = p.next() { while let Some((event, _)) = p.next() {
match event { match event {
Event::End(Tag::CodeBlock(_)) => break, Event::End(end)
if mem::discriminant(&end) == mem::discriminant(&tag) =>
{
break;
}
_ => {} _ => {}
} }
} }

View file

@ -12,8 +12,8 @@ use crate::core::DocContext;
mod stripper; mod stripper;
crate use stripper::*; crate use stripper::*;
mod non_autolinks; mod bare_urls;
crate use self::non_autolinks::CHECK_NON_AUTOLINKS; crate use self::bare_urls::CHECK_BARE_URLS;
mod strip_hidden; mod strip_hidden;
crate use self::strip_hidden::STRIP_HIDDEN; crate use self::strip_hidden::STRIP_HIDDEN;
@ -90,7 +90,7 @@ crate const PASSES: &[Pass] = &[
COLLECT_TRAIT_IMPLS, COLLECT_TRAIT_IMPLS,
CALCULATE_DOC_COVERAGE, CALCULATE_DOC_COVERAGE,
CHECK_INVALID_HTML_TAGS, CHECK_INVALID_HTML_TAGS,
CHECK_NON_AUTOLINKS, CHECK_BARE_URLS,
]; ];
/// The list of passes run by default. /// 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_CODE_BLOCK_SYNTAX),
ConditionalPass::always(CHECK_INVALID_HTML_TAGS), ConditionalPass::always(CHECK_INVALID_HTML_TAGS),
ConditionalPass::always(PROPAGATE_DOC_CFG), 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. /// The list of default passes run when `--doc-coverage` is passed to rustdoc.

View file

@ -4,3 +4,8 @@
// stable channel. // stable channel.
//! [x] //! [x]
//~^ ERROR unresolved link //~^ ERROR unresolved link
#![deny(rustdoc::non_autolinks)]
//~^ WARNING renamed to `rustdoc::bare_urls`
//! http://example.com
//~^ ERROR not a hyperlink

View file

@ -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` error: unresolved link to `x`
--> $DIR/renamed-lint-still-applies.rs:5:6 --> $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)]` = note: `#[deny(rustdoc::broken_intra_doc_links)]` implied by `#[deny(broken_intra_doc_links)]`
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` = 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

View file

@ -8,8 +8,12 @@
//~^ ERROR unknown lint: `rustdoc::x` //~^ ERROR unknown lint: `rustdoc::x`
#![deny(intra_doc_link_resolution_failure)] #![deny(intra_doc_link_resolution_failure)]
//~^ ERROR renamed to `rustdoc::broken_intra_doc_links` //~^ ERROR renamed to `rustdoc::broken_intra_doc_links`
#![deny(non_autolinks)] #![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 // FIXME: the old names for rustdoc lints should warn by default once `rustdoc::` makes it to the
// stable channel. // stable channel.

View file

@ -28,19 +28,31 @@ note: the lint level is defined here
LL | #![deny(renamed_and_removed_lints)] 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 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)] LL | #![deny(rustdoc)]
| ^^^^^^^ | ^^^^^^^
error: unknown lint: `rustdoc::intra_doc_link_resolution_failure` 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)] LL | #![deny(rustdoc::intra_doc_link_resolution_failure)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: Compilation failed, aborting rustdoc error: Compilation failed, aborting rustdoc
error: aborting due to 6 previous errors error: aborting due to 8 previous errors

View file

@ -1,14 +1,4 @@
#![deny(rustdoc::non_autolinks)] #![deny(rustdoc::bare_urls)]
/// [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() {}
/// https://somewhere.com /// https://somewhere.com
//~^ ERROR this URL is not a hyperlink //~^ ERROR this URL is not a hyperlink
@ -54,12 +44,14 @@ pub fn c() {}
/// ///
/// ``` /// ```
/// This link should not be linted: http://example.com /// 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) /// [should_not.lint](should_not.lint)
pub fn everything_is_fine_here() {} pub fn everything_is_fine_here() {}
#[allow(rustdoc::non_autolinks)] #[allow(rustdoc::bare_urls)]
pub mod foo { pub mod foo {
/// https://somewhere.com/a?hello=12&bye=11#xyz /// https://somewhere.com/a?hello=12&bye=11#xyz
pub fn bar() {} pub fn bar() {}

View file

@ -1,122 +1,110 @@
error: unneeded long form for URL error: this URL is not a hyperlink
--> $DIR/url-improvements.rs:3:5 --> $DIR/url-improvements.rs:3:5
| |
LL | /// [http://aa.com](http://aa.com) LL | /// https://somewhere.com
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://aa.com>` | ^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com>`
| |
note: the lint level is defined here note: the lint level is defined here
--> $DIR/url-improvements.rs:1:9 --> $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 --> $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 LL | /// https://somewhere.com/a
| ^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com/a>` | ^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com/a>`
error: this URL is not a hyperlink error: this URL is not a hyperlink
--> $DIR/url-improvements.rs:17:5 --> $DIR/url-improvements.rs:7:5
| |
LL | /// https://www.somewhere.com LL | /// https://www.somewhere.com
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://www.somewhere.com>` | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://www.somewhere.com>`
error: this URL is not a hyperlink 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 LL | /// https://www.somewhere.com/a
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://www.somewhere.com/a>` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://www.somewhere.com/a>`
error: this URL is not a hyperlink error: this URL is not a hyperlink
--> $DIR/url-improvements.rs:21:5 --> $DIR/url-improvements.rs:11:5
| |
LL | /// https://subdomain.example.com LL | /// https://subdomain.example.com
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://subdomain.example.com>` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://subdomain.example.com>`
error: this URL is not a hyperlink error: this URL is not a hyperlink
--> $DIR/url-improvements.rs:23:5 --> $DIR/url-improvements.rs:13:5
| |
LL | /// https://somewhere.com? LL | /// https://somewhere.com?
| ^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com?>` | ^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com?>`
error: this URL is not a hyperlink error: this URL is not a hyperlink
--> $DIR/url-improvements.rs:25:5 --> $DIR/url-improvements.rs:15:5
| |
LL | /// https://somewhere.com/a? LL | /// https://somewhere.com/a?
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com/a?>` | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com/a?>`
error: this URL is not a hyperlink 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 LL | /// https://somewhere.com?hello=12
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com?hello=12>` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com?hello=12>`
error: this URL is not a hyperlink 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 LL | /// https://somewhere.com/a?hello=12
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<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 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 LL | /// https://example.com?hello=12#xyz
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<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 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 LL | /// https://example.com/a?hello=12#xyz
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<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 error: this URL is not a hyperlink
--> $DIR/url-improvements.rs:35:5 --> $DIR/url-improvements.rs:25:5
| |
LL | /// https://example.com#xyz LL | /// https://example.com#xyz
| ^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://example.com#xyz>` | ^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://example.com#xyz>`
error: this URL is not a hyperlink 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 LL | /// https://example.com/a#xyz
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://example.com/a#xyz>` | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://example.com/a#xyz>`
error: this URL is not a hyperlink 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 LL | /// https://somewhere.com?hello=12&bye=11
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<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 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 LL | /// https://somewhere.com/a?hello=12&bye=11
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<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 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 LL | /// https://somewhere.com?hello=12&bye=11#xyz
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<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 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 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>` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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

View file

@ -6,11 +6,146 @@ document.
## Unreleased / In Rust Nightly ## 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 ## 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) [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 ## 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) [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 [configuration file]: ./rust-clippy#configuration
[pull3665]: https://github.com/rust-lang/rust-clippy/pull/3665 [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 [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 --> <!-- lint disable no-unused-definitions -->
<!-- begin autogenerated links to lint list --> <!-- 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 [`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 [`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 [`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 [`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 [`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 [`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_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_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_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_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_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 [`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 [`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 [`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_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 [`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 [`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 [`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 [`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_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_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_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_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 [`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn

View file

@ -5,7 +5,7 @@
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. 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). 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. You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.

View file

@ -68,7 +68,7 @@ pub fn run(check: bool, verbose: bool) {
continue; continue;
} }
success &= rustfmt(context, &path)?; success &= rustfmt(context, path)?;
} }
Ok(success) Ok(success)

View file

@ -101,7 +101,7 @@ impl Lint {
#[must_use] #[must_use]
pub fn gen_lint_group_list<'a>(lints: impl Iterator<Item = &'a Lint>) -> Vec<String> { pub fn gen_lint_group_list<'a>(lints: impl Iterator<Item = &'a Lint>) -> Vec<String> {
lints lints
.map(|l| format!(" LintId::of(&{}::{}),", l.module, l.name.to_uppercase())) .map(|l| format!(" LintId::of({}::{}),", l.module, l.name.to_uppercase()))
.sorted() .sorted()
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }
@ -154,17 +154,17 @@ pub fn gen_register_lint_list<'a>(
let header = " store.register_lints(&[".to_string(); let header = " store.register_lints(&[".to_string();
let footer = " ]);".to_string(); let footer = " ]);".to_string();
let internal_lints = internal_lints 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| { .map(|l| {
format!( format!(
" #[cfg(feature = \"internal-lints\")]\n &{}::{},", " #[cfg(feature = \"internal-lints\")]\n {}::{},",
l.module, l.module,
l.name.to_uppercase() l.name.to_uppercase()
) )
}); });
let other_lints = usable_lints let other_lints = usable_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!(" &{}::{},", l.module, l.name.to_uppercase())) .map(|l| format!(" {}::{},", l.module, l.name.to_uppercase()))
.sorted(); .sorted();
let mut lint_list = vec![header]; let mut lint_list = vec![header];
lint_list.extend(internal_lints); lint_list.extend(internal_lints);
@ -550,9 +550,9 @@ fn test_gen_lint_group_list() {
Lint::new("internal", "internal_style", "abc", None, "module_name"), Lint::new("internal", "internal_style", "abc", None, "module_name"),
]; ];
let expected = vec![ let expected = vec![
" LintId::of(&module_name::ABC),".to_string(), " LintId::of(module_name::ABC),".to_string(),
" LintId::of(&module_name::INTERNAL),".to_string(), " LintId::of(module_name::INTERNAL),".to_string(),
" LintId::of(&module_name::SHOULD_ASSERT_EQ),".to_string(), " LintId::of(module_name::SHOULD_ASSERT_EQ),".to_string(),
]; ];
assert_eq!(expected, gen_lint_group_list(lints.iter())); assert_eq!(expected, gen_lint_group_list(lints.iter()));
} }

View 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 })
}

View file

@ -71,7 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
return; return;
} }
if_chain! { 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 let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), lit);
if is_true; if is_true;
then { then {
@ -82,7 +82,7 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
if assert_span.from_expansion() { if assert_span.from_expansion() {
return; 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 { match assert_match {
// matched assert but not message // matched assert but not message
AssertKind::WithoutMessage(false) => lint_false_without_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. /// 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> { fn match_assert_with_message<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<AssertKind> {
if_chain! { if_chain! {
if let ExprKind::If(ref cond, ref then, _) = expr.kind; if let ExprKind::If(cond, then, _) = expr.kind;
if let ExprKind::Unary(UnOp::Not, ref expr) = cond.kind; if let ExprKind::Unary(UnOp::Not, expr) = cond.kind;
// bind the first argument of the `assert!` macro // bind the first argument of the `assert!` macro
if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), expr); if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), expr);
// block // block
if let ExprKind::Block(ref block, _) = then.kind; if let ExprKind::Block(block, _) = then.kind;
if block.stmts.is_empty(); if block.stmts.is_empty();
if let Some(block_expr) = &block.expr; if let Some(block_expr) = &block.expr;
// inner block is optional. unwrap it if it exists, or use the expression as is otherwise. // 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 { 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, _ => &block.expr,
}; };
// function call // function call

View file

@ -85,7 +85,7 @@ fn match_ordering_def_path(cx: &LateContext<'_>, did: DefId, orderings: &[&str])
fn check_atomic_load_store(cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_atomic_load_store(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { 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(); let method = method_path.ident.name.as_str();
if type_is_atomic(cx, &args[0]); if type_is_atomic(cx, &args[0]);
if method == "load" || method == "store"; 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<'_>) { fn check_memory_fence(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { 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 ExprKind::Path(ref func_qpath) = func.kind;
if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id(); if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
if ["fence", "compiler_fence"] 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<'_>) { fn check_atomic_compare_exchange(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { 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(); let method = method_path.ident.name.as_str();
if type_is_atomic(cx, &args[0]); if type_is_atomic(cx, &args[0]);
if method == "compare_exchange" || method == "compare_exchange_weak" || method == "fetch_update"; if method == "compare_exchange" || method == "compare_exchange_weak" || method == "fetch_update";

View file

@ -250,12 +250,8 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) { fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) {
if let Some(items) = &attr.meta_item_list() { if let Some(items) = &attr.meta_item_list() {
if let Some(ident) = attr.ident() { if let Some(ident) = attr.ident() {
let ident = &*ident.as_str(); if is_lint_level(ident.name) {
match ident { check_clippy_lint_names(cx, ident.name, items);
"allow" | "warn" | "deny" | "forbid" => {
check_clippy_lint_names(cx, ident, items);
},
_ => {},
} }
if items.is_empty() || !attr.has_name(sym::deprecated) { if items.is_empty() || !attr.has_name(sym::deprecated) {
return; return;
@ -288,9 +284,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
return; return;
} }
if let Some(lint_list) = &attr.meta_item_list() { if let Some(lint_list) = &attr.meta_item_list() {
if let Some(ident) = attr.ident() { if attr.ident().map_or(false, |ident| is_lint_level(ident.name)) {
match &*ident.as_str() {
"allow" | "warn" | "deny" | "forbid" => {
// permit `unused_imports`, `deprecated`, `unreachable_pub`, // permit `unused_imports`, `deprecated`, `unreachable_pub`,
// `clippy::wildcard_imports`, and `clippy::enum_glob_use` for `use` items // `clippy::wildcard_imports`, and `clippy::enum_glob_use` for `use` items
// and `unused_imports` for `extern crate` items with `macro_use` // 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::deprecated)
|| is_word(lint, sym!(unreachable_pub)) || is_word(lint, sym!(unreachable_pub))
|| is_word(lint, sym!(unused)) || is_word(lint, sym!(unused))
|| extract_clippy_lint(lint) || extract_clippy_lint(lint).map_or(false, |s| s == "wildcard_imports")
.map_or(false, |s| s == "wildcard_imports")
|| extract_clippy_lint(lint).map_or(false, |s| s == "enum_glob_use") || extract_clippy_lint(lint).map_or(false, |s| s == "enum_glob_use")
{ {
return; 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 meta_item.path.segments.len() > 1;
if let tool_name = meta_item.path.segments[0].ident; if let tool_name = meta_item.path.segments[0].ident;
if tool_name.name == sym::clippy; if tool_name.name == sym::clippy;
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
then { then {
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
return Some(lint_name.as_str()); return Some(lint_name.as_str());
} }
} }
None 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 { for lint in items {
if let Some(lint_name) = extract_clippy_lint(lint) { 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( span_lint_and_help(
cx, cx,
BLANKET_CLIPPY_RESTRICTION_LINTS, BLANKET_CLIPPY_RESTRICTION_LINTS,
@ -602,7 +592,7 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
if let NestedMetaItem::MetaItem(meta) = item { if let NestedMetaItem::MetaItem(meta) = item {
match &meta.kind { match &meta.kind {
MetaItemKind::List(list) => { MetaItemKind::List(list) => {
mismatched.extend(find_mismatched_target_os(&list)); mismatched.extend(find_mismatched_target_os(list));
}, },
MetaItemKind::Word => { MetaItemKind::Word => {
if_chain! { if_chain! {
@ -629,7 +619,7 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
then { then {
let mess = "operating system used in target family position"; 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 // Avoid showing the unix suggestion multiple times in case
// we have more than one mismatch for unix-like systems // we have more than one mismatch for unix-like systems
let mut unix_suggested = false; 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)
}

View file

@ -101,7 +101,7 @@ impl LateLintPass<'_> for AwaitHolding {
let typeck_results = cx.tcx.typeck_body(body_id); let typeck_results = cx.tcx.typeck_body(body_id);
check_interior_types( check_interior_types(
cx, cx,
&typeck_results.generator_interior_types.as_ref().skip_binder(), typeck_results.generator_interior_types.as_ref().skip_binder(),
body.value.span, body.value.span,
); );
} }

View file

@ -38,19 +38,17 @@ declare_lint_pass!(ByteCount => [NAIVE_BYTECOUNT]);
impl<'tcx> LateLintPass<'tcx> for ByteCount { impl<'tcx> LateLintPass<'tcx> for ByteCount {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { 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.ident.name == sym!(count);
if count_args.len() == 1; 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.ident.name == sym!(filter);
if filter_args.len() == 2; if filter_args.len() == 2;
if let ExprKind::Closure(_, _, body_id, _, _) = filter_args[1].kind; if let ExprKind::Closure(_, _, body_id, _, _) = filter_args[1].kind;
then {
let body = cx.tcx.hir().body(body_id); let body = cx.tcx.hir().body(body_id);
if_chain! {
if body.params.len() == 1; if body.params.len() == 1;
if let Some(argname) = get_pat_name(&body.params[0].pat); 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 ExprKind::Binary(ref op, l, r) = body.value.kind;
if op.node == BinOpKind::Eq; if op.node == BinOpKind::Eq;
if match_type(cx, if match_type(cx,
cx.typeck_results().expr_ty(&filter_args[0]).peel_refs(), 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() { if ty::Uint(UintTy::U8) != *cx.typeck_results().expr_ty(needle).peel_refs().kind() {
return; return;
} }
let haystack = if let ExprKind::MethodCall(ref path, _, ref args, _) = let haystack = if let ExprKind::MethodCall(path, _, args, _) =
filter_args[0].kind { filter_args[0].kind {
let p = path.ident.name; let p = path.ident.name;
if (p == sym::iter || p == sym!(iter_mut)) && args.len() == 1 { 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 { 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> { fn get_path_name(expr: &Expr<'_>) -> Option<Symbol> {
match expr.kind { 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) get_path_name(e)
}, },
ExprKind::Block(ref b, _) => { ExprKind::Block(b, _) => {
if b.stmts.is_empty() { if b.stmts.is_empty() {
b.expr.as_ref().and_then(|p| get_path_name(p)) b.expr.as_ref().and_then(|p| get_path_name(p))
} else { } else {

View file

@ -20,11 +20,10 @@ declare_clippy_lint! {
/// ///
/// **Example:** /// **Example:**
/// ```toml /// ```toml
/// # This `Cargo.toml` is missing an authors field: /// # This `Cargo.toml` is missing a description field:
/// [package] /// [package]
/// name = "clippy" /// name = "clippy"
/// version = "0.0.212" /// version = "0.0.212"
/// description = "A bunch of helpful lints to avoid common pitfalls in Rust"
/// repository = "https://github.com/rust-lang/rust-clippy" /// repository = "https://github.com/rust-lang/rust-clippy"
/// readme = "README.md" /// readme = "README.md"
/// license = "MIT OR Apache-2.0" /// license = "MIT OR Apache-2.0"
@ -32,14 +31,13 @@ declare_clippy_lint! {
/// categories = ["development-tools", "development-tools::cargo-plugins"] /// categories = ["development-tools", "development-tools::cargo-plugins"]
/// ``` /// ```
/// ///
/// Should include an authors field like: /// Should include a description field like:
/// ///
/// ```toml /// ```toml
/// # This `Cargo.toml` includes all common metadata /// # This `Cargo.toml` includes all common metadata
/// [package] /// [package]
/// name = "clippy" /// name = "clippy"
/// version = "0.0.212" /// version = "0.0.212"
/// authors = ["Someone <someone@rust-lang.org>"]
/// description = "A bunch of helpful lints to avoid common pitfalls in Rust" /// description = "A bunch of helpful lints to avoid common pitfalls in Rust"
/// repository = "https://github.com/rust-lang/rust-clippy" /// repository = "https://github.com/rust-lang/rust-clippy"
/// readme = "README.md" /// readme = "README.md"
@ -97,10 +95,6 @@ impl LateLintPass<'_> for CargoCommonMetadata {
// only run the lint if publish is `None` (`publish = true` or skipped entirely) // only run the lint if publish is `None` (`publish = true` or skipped entirely)
// or if the vector isn't empty (`publish = ["something"]`) // 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 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) { if is_empty_str(&package.description) {
missing_warning(cx, &package, "package.description"); missing_warning(cx, &package, "package.description");
} }

View file

@ -10,7 +10,7 @@ use rustc_target::abi::LayoutOf;
use super::CAST_PTR_ALIGNMENT; use super::CAST_PTR_ALIGNMENT;
pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { 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) { if is_hir_ty_cfg_dependant(cx, cast_to) {
return; return;
} }

View file

@ -30,7 +30,7 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
} }
// Don't lint for positive constants. // 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_chain! {
if let Some((Constant::Int(n), _)) = const_val; if let Some((Constant::Int(n), _)) = const_val;
if let ty::Int(ity) = *cast_from.kind(); 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. // 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 mut method_name = path.ident.name.as_str();
let allowed_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"]; let allowed_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"];
if_chain! { if_chain! {
if method_name == "unwrap"; if method_name == "unwrap";
if let Some(arglist) = method_chain_args(cast_op, &["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 { then {
method_name = inner_path.ident.name.as_str(); method_name = inner_path.ident.name.as_str();
} }

View file

@ -372,7 +372,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
return; 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) { if is_hir_ty_cfg_dependant(cx, cast_to) {
return; return;
} }

View file

@ -64,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for CheckedConversions {
let result = if_chain! { let result = if_chain! {
if !in_external_macro(cx.sess(), item.span); 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 { then {
match op.node { match op.node {
@ -200,7 +200,7 @@ impl ConversionType {
/// Check for `expr <= (to_type::MAX as from_type)` /// Check for `expr <= (to_type::MAX as from_type)`
fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> { fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
if_chain! { 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((candidate, check)) = normalize_le_ge(op, left, right);
if let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX"); 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 // 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)) normalize_le_ge(op, right, left).and_then(|(l, r)| check_function(l, r))
} else { } else {
None None
@ -260,7 +260,7 @@ fn get_types_from_cast<'a>(
// or `to_type::MAX as from_type` // or `to_type::MAX as from_type`
let call_from_cast: Option<(&Expr<'_>, &str)> = if_chain! { let call_from_cast: Option<(&Expr<'_>, &str)> = if_chain! {
// to_type::max_value(), from_type // 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 TyKind::Path(ref from_type_path) = &from_type.kind;
if let Some(from_sym) = int_ty_to_sym(from_type_path); 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(|| { let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| {
if_chain! { if_chain! {
// `from_type::from, to_type::max_value()` // `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()` // `to_type::max_value()`
if args.len() == 1; if args.len() == 1;
if let limit = &args[0]; if let limit = &args[0];
@ -317,13 +317,12 @@ fn get_types_from_cast<'a>(
/// Gets the type which implements the called function /// Gets the type which implements the called function
fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function: &str) -> Option<&'a str> { fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function: &str) -> Option<&'a str> {
if_chain! { 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 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; if let [int] = &*tp.segments;
let name = &int.ident.name.as_str();
then { then {
let name = &int.ident.name.as_str();
candidates.iter().find(|c| name == *c).cloned() candidates.iter().find(|c| name == *c).cloned()
} else { } else {
None 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 /// Gets the type as a string, if it is a supported integer
fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> { fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> {
if_chain! { if_chain! {
if let QPath::Resolved(_, ref path) = *path; if let QPath::Resolved(_, path) = *path;
if let [ty] = &*path.segments; if let [ty] = &*path.segments;
let name = &ty.ident.name.as_str();
then { then {
let name = &ty.ident.name.as_str();
INTS.iter().find(|c| name == *c).cloned() INTS.iter().find(|c| name == *c).cloned()
} else { } else {
None None

View file

@ -152,7 +152,7 @@ impl<'tcx> Visitor<'tcx> for CcHelper {
ExprKind::If(_, _, _) => { ExprKind::If(_, _, _) => {
self.cc += 1; self.cc += 1;
}, },
ExprKind::Match(_, ref arms, _) => { ExprKind::Match(_, arms, _) => {
if arms.len() > 1 { if arms.len() > 1 {
self.cc += 1; self.cc += 1;
} }

View file

@ -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>) { fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext<'tcx>) {
if_chain! {
let expr = strip_singleton_blocks(arm.body); let expr = strip_singleton_blocks(arm.body);
if_chain! {
if let ExprKind::Match(expr_in, arms_inner, _) = expr.kind; if let ExprKind::Match(expr_in, arms_inner, _) = expr.kind;
// the outer arm pattern and the inner match // the outer arm pattern and the inner match
if expr_in.span.ctxt() == arm.pat.span.ctxt(); if expr_in.span.ctxt() == arm.pat.span.ctxt();

View file

@ -71,10 +71,8 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain {
} }
for cond in conds.windows(2) { for cond in conds.windows(2) {
if let ( if let (&ExprKind::Binary(ref kind1, lhs1, rhs1), &ExprKind::Binary(ref kind2, lhs2, rhs2)) =
&ExprKind::Binary(ref kind1, ref lhs1, ref rhs1), (&cond[0].kind, &cond[1].kind)
&ExprKind::Binary(ref kind2, ref lhs2, ref rhs2),
) = (&cond[0].kind, &cond[1].kind)
{ {
if !kind_is_cmp(kind1.node) || !kind_is_cmp(kind2.node) { if !kind_is_cmp(kind1.node) || !kind_is_cmp(kind2.node) {
return; return;

View file

@ -1,9 +1,19 @@
use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
use clippy_utils::{eq_expr_value, in_macro, search_same, SpanlessEq, SpanlessHash}; use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt};
use clippy_utils::{get_parent_expr, if_sequence}; use clippy_utils::{
use rustc_hir::{Block, Expr, ExprKind}; 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_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map;
use rustc_session::{declare_lint_pass, declare_tool_lint}; 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! { declare_clippy_lint! {
/// **What it does:** Checks for consecutive `if`s with the same condition. /// **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" "`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 { impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !expr.span.from_expansion() { if !expr.span.from_expansion() {
if let ExprKind::If(_, _, _) = expr.kind {
// skip ifs directly in else, it will be checked in the parent if // skip ifs directly in else, it will be checked in the parent if
if let Some(&Expr { if let Some(&Expr {
kind: ExprKind::If(_, _, Some(ref else_expr)), kind: ExprKind::If(_, _, Some(else_expr)),
.. ..
}) = get_parent_expr(cx, expr) }) = get_parent_expr(cx, expr)
{ {
@ -121,27 +170,398 @@ impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
} }
let (conds, blocks) = if_sequence(expr); let (conds, blocks) = if_sequence(expr);
lint_same_then_else(cx, &blocks); // Conditions
lint_same_cond(cx, &conds); lint_same_cond(cx, &conds);
lint_same_fns_in_if_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`. /// Implementation of `BRANCHES_SHARING_CODE` and `IF_SAME_THEN_ELSE` if the blocks are equal.
fn lint_same_then_else(cx: &LateContext<'_>, blocks: &[&Block<'_>]) { fn lint_same_then_else<'tcx>(
let eq: &dyn Fn(&&Block<'_>, &&Block<'_>) -> bool = cx: &LateContext<'tcx>,
&|&lhs, &rhs| -> bool { SpanlessEq::new(cx).eq_block(lhs, rhs) }; 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( span_lint_and_note(
cx, cx,
IF_SAME_THEN_ELSE, IF_SAME_THEN_ELSE,
j.span, win[0].span,
"this `if` has identical blocks", "this `if` has identical blocks",
Some(i.span), Some(win[1].span),
"same as this", "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
}

View file

@ -33,7 +33,7 @@ declare_lint_pass!(CreateDir => [CREATE_DIR]);
impl LateLintPass<'_> for CreateDir { impl LateLintPass<'_> for CreateDir {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { 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 ExprKind::Path(ref path) = func.kind;
if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id(); 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); if match_def_path(cx, def_id, &paths::STD_FS_CREATE_DIR);

View file

@ -77,16 +77,16 @@ impl LateLintPass<'_> for Default {
if_chain! { if_chain! {
// Avoid cases already linted by `field_reassign_with_default` // Avoid cases already linted by `field_reassign_with_default`
if !self.reassigned_linted.contains(&expr.span); 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 !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
if let ExprKind::Path(ref qpath) = path.kind; if let ExprKind::Path(ref qpath) = path.kind;
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id(); 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); 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. // Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
if let QPath::Resolved(None, _path) = qpath; if let QPath::Resolved(None, _path) = qpath;
then {
let expr_ty = cx.typeck_results().expr_ty(expr); 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 // TODO: Work out a way to put "whatever the imported way of referencing
// this type in this file" rather than a fully-qualified type. // this type in this file" rather than a fully-qualified type.
let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did)); let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did));
@ -102,7 +102,6 @@ impl LateLintPass<'_> for Default {
} }
} }
} }
}
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) { 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! { let binding_type = if_chain! {
if let ty::Adt(adt_def, substs) = binding_type.kind(); if let ty::Adt(adt_def, substs) = binding_type.kind();
if !substs.is_empty(); if !substs.is_empty();
then {
let adt_def_ty_name = cx.tcx.item_name(adt_def.did); let adt_def_ty_name = cx.tcx.item_name(adt_def.did);
let generic_args = substs.iter().collect::<Vec<_>>(); let generic_args = substs.iter().collect::<Vec<_>>();
let tys_str = generic_args let tys_str = generic_args
@ -209,7 +209,6 @@ impl LateLintPass<'_> for Default {
.map(ToString::to_string) .map(ToString::to_string)
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "); .join(", ");
then {
format!("{}::<{}>", adt_def_ty_name, &tys_str) format!("{}::<{}>", adt_def_ty_name, &tys_str)
} else { } else {
binding_type.to_string() 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. /// 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 { fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool {
if_chain! { 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 ExprKind::Path(qpath) = &fn_expr.kind;
if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id); if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id);
then { 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>)> { fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> {
if_chain! { if_chain! {
// only take assignments // only take assignments
if let StmtKind::Semi(ref later_expr) = this.kind; if let StmtKind::Semi(later_expr) = this.kind;
if let ExprKind::Assign(ref assign_lhs, ref assign_rhs, _) = later_expr.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 // only take assignments to fields where the left-hand side field is a field of
// the same binding as the previous statement // 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 ExprKind::Path(QPath::Resolved(_, path)) = binding.kind;
if let Some(second_binding_name) = path.segments.last(); if let Some(second_binding_name) = path.segments.last();
if second_binding_name.ident.name == binding_name; if second_binding_name.ident.name == binding_name;

View file

@ -131,8 +131,8 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
}, },
ExprKind::Struct(_, fields, base) => { ExprKind::Struct(_, fields, base) => {
if_chain! {
let ty = self.cx.typeck_results().expr_ty(expr); let ty = self.cx.typeck_results().expr_ty(expr);
if_chain! {
if let Some(adt_def) = ty.ty_adt_def(); if let Some(adt_def) = ty.ty_adt_def();
if adt_def.is_struct(); if adt_def.is_struct();
if let Some(variant) = adt_def.variants.iter().next(); if let Some(variant) = adt_def.variants.iter().next();

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_then}; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_then};
use clippy_utils::paths; 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 clippy_utils::{get_trait_def_id, is_allowed, is_automatically_derived, match_def_path};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
@ -12,7 +12,7 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use rustc_middle::ty::{self, Ty}; use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint}; 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! { declare_clippy_lint! {
/// **What it does:** Checks for deriving `Hash` but implementing `PartialEq` /// **What it does:** Checks for deriving `Hash` but implementing `PartialEq`
@ -199,7 +199,7 @@ fn check_hash_peq<'tcx>(
then { then {
// Look for the PartialEq implementations for `ty` // Look for the PartialEq implementations for `ty`
cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| { 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 { if peq_is_automatically_derived == hash_is_automatically_derived {
return; return;
@ -253,7 +253,7 @@ fn check_ord_partial_ord<'tcx>(
then { then {
// Look for the PartialOrd implementations for `ty` // Look for the PartialOrd implementations for `ty`
cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| { 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 { if partial_ord_is_automatically_derived == ord_is_automatically_derived {
return; return;
@ -293,38 +293,44 @@ fn check_ord_partial_ord<'tcx>(
/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint. /// 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>) { fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
if cx let clone_id = match cx.tcx.lang_items().clone_trait() {
.tcx Some(id) if trait_ref.trait_def_id() == Some(id) => id,
.lang_items() _ => return,
.clone_trait() };
.map_or(false, |id| Some(id) == trait_ref.trait_def_id()) 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 !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(&copy_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; return;
} }
} else {
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() {
return; return;
} }
} }
for subst in substs { // Derive constrains all generic types to requiring Clone. Check if any type is not constrained for
if let ty::subst::GenericArgKind::Type(subst) = subst.unpack() { // this impl.
if let ty::Param(_) = subst.kind() { if ty_subs.types().any(|ty| !implements_trait(cx, ty, clone_id, &[])) {
return; return;
} }
}
}
}
},
_ => (),
}
span_lint_and_note( span_lint_and_note(
cx, cx,
@ -334,7 +340,6 @@ fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &T
Some(item.span), Some(item.span),
"consider deriving `Clone` or removing `Copy`", "consider deriving `Clone` or removing `Copy`",
); );
}
} }
/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint. /// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint.

View file

@ -11,7 +11,7 @@ use rustc_errors::emitter::EmitterWriter;
use rustc_errors::Handler; use rustc_errors::Handler;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; 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_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
@ -710,16 +710,22 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
// check for `begin_panic` // check for `begin_panic`
if_chain! { if_chain! {
if let ExprKind::Call(ref func_expr, _) = expr.kind; if let ExprKind::Call(func_expr, _) = expr.kind;
if let ExprKind::Path(QPath::Resolved(_, ref path)) = func_expr.kind; if let ExprKind::Path(QPath::Resolved(_, path)) = func_expr.kind;
if let Some(path_def_id) = path.res.opt_def_id(); if let Some(path_def_id) = path.res.opt_def_id();
if match_panic_def_id(self.cx, path_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(expr.span, "unreachable").is_none();
if !is_expn_of_debug_assertions(expr.span);
then { then {
self.panic_span = Some(expr.span); 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` // check for `unwrap`
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) { if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs(); 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); 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> { fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) 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())
}

View file

@ -47,7 +47,7 @@ impl<'tcx> DoubleComparisons {
}, },
_ => return, _ => 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; return;
} }
macro_rules! lint_double_comparison { macro_rules! lint_double_comparison {
@ -88,7 +88,7 @@ impl<'tcx> DoubleComparisons {
impl<'tcx> LateLintPass<'tcx> for DoubleComparisons { impl<'tcx> LateLintPass<'tcx> for DoubleComparisons {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { 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); Self::check_binop(cx, kind.node, lhs, rhs, expr.span);
} }
} }

View file

@ -50,7 +50,7 @@ impl EarlyLintPass for DoubleParens {
match expr.kind { match expr.kind {
ExprKind::Paren(ref in_paren) => match in_paren.kind { ExprKind::Paren(ref in_paren) => match in_paren.kind {
ExprKind::Paren(_) | ExprKind::Tup(_) => { 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 { if params.len() == 1 {
let param = &params[0]; let param = &params[0];
if let ExprKind::Paren(_) = param.kind { 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 { if params.len() == 2 {
let param = &params[1]; let param = &params[1];
if let ExprKind::Paren(_) = param.kind { if let ExprKind::Paren(_) = param.kind {
span_lint(cx, DOUBLE_PARENS, param.span, &msg); span_lint(cx, DOUBLE_PARENS, param.span, msg);
} }
} }
}, },

View file

@ -113,7 +113,7 @@ declare_lint_pass!(DropForgetRef => [DROP_REF, FORGET_REF, DROP_COPY, FORGET_COP
impl<'tcx> LateLintPass<'tcx> for DropForgetRef { impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { 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 let ExprKind::Path(ref qpath) = path.kind;
if args.len() == 1; if args.len() == 1;
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id(); if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();

View file

@ -43,8 +43,8 @@ declare_lint_pass!(DurationSubsec => [DURATION_SUBSEC]);
impl<'tcx> LateLintPass<'tcx> for DurationSubsec { impl<'tcx> LateLintPass<'tcx> for DurationSubsec {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Div, .. }, ref left, ref right) = expr.kind; if let ExprKind::Binary(Spanned { node: BinOpKind::Div, .. }, left, right) = expr.kind;
if let ExprKind::MethodCall(ref method_path, _ , ref args, _) = left.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 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); if let Some((Constant::Int(divisor), _)) = constant(cx, cx.typeck_results(), right);
then { then {

View file

@ -57,14 +57,14 @@ declare_lint_pass!(HashMapPass => [MAP_ENTRY]);
impl<'tcx> LateLintPass<'tcx> for HashMapPass { impl<'tcx> LateLintPass<'tcx> for HashMapPass {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { 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::If(check, then_block, ref else_block) = expr.kind {
if let ExprKind::Unary(UnOp::Not, ref check) = check.kind { if let ExprKind::Unary(UnOp::Not, check) = check.kind {
if let Some((ty, map, key)) = check_cond(cx, check) { if let Some((ty, map, key)) = check_cond(cx, check) {
// in case of `if !m.contains_key(&k) { m.insert(k, v); }` // in case of `if !m.contains_key(&k) { m.insert(k, v); }`
// we can give a better error message // we can give a better error message
let sole_expr = { let sole_expr = {
else_block.is_none() 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 (then_block.expr.is_some() as usize) + then_block.stmts.len() == 1
} else { } else {
true true
@ -81,9 +81,9 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
sole_expr, 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) { if let Some((ty, map, key)) = check_cond(cx, check) {
let mut visitor = InsertVisitor { let mut visitor = InsertVisitor {
cx, 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>)> { fn check_cond<'a>(cx: &LateContext<'_>, check: &'a Expr<'a>) -> Option<(&'static str, &'a Expr<'a>, &'a Expr<'a>)> {
if_chain! { 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 params.len() >= 2;
if path.ident.name == sym!(contains_key); 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 { then {
let map = &params[0]; let map = &params[0];
let obj_ty = cx.typeck_results().expr_ty(map).peel_refs(); 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<'_>) { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if_chain! { 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 params.len() == 3;
if path.ident.name == sym!(insert); if path.ident.name == sym!(insert);
if get_item_name(self.cx, self.map) == get_item_name(self.cx, &params[0]); if get_item_name(self.cx, self.map) == get_item_name(self.cx, &params[0]);

View file

@ -65,12 +65,12 @@ const ASSERT_MACRO_NAMES: [&str; 4] = ["assert_eq", "assert_ne", "debug_assert_e
impl<'tcx> LateLintPass<'tcx> for EqOp { impl<'tcx> LateLintPass<'tcx> for EqOp {
#[allow(clippy::similar_names, clippy::too_many_lines)] #[allow(clippy::similar_names, clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { 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 stmt in block.stmts {
for amn in &ASSERT_MACRO_NAMES { for amn in &ASSERT_MACRO_NAMES {
if_chain! { if_chain! {
if is_expn_of(stmt.span, amn).is_some(); 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 let Some(macro_args) = higher::extract_assert_macro_args(matchexpr);
if macro_args.len() == 2; if macro_args.len() == 2;
let (lhs, rhs) = (macro_args[0], macro_args[1]); 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() { if e.span.from_expansion() {
return; return;
} }
let macro_with_not_op = |expr_kind: &ExprKind<'_>| { 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) in_macro(expr.span)
} else { } else {
false false
@ -135,7 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
// do not suggest to dereference literals // do not suggest to dereference literals
(&ExprKind::Lit(..), _) | (_, &ExprKind::Lit(..)) => {}, (&ExprKind::Lit(..), _) | (_, &ExprKind::Lit(..)) => {},
// &foo == &bar // &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 lty = cx.typeck_results().expr_ty(l);
let rty = cx.typeck_results().expr_ty(r); let rty = cx.typeck_results().expr_ty(r);
let lcpy = is_copy(cx, lty); let lcpy = is_copy(cx, lty);
@ -198,7 +198,7 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
} }
}, },
// &foo == bar // &foo == bar
(&ExprKind::AddrOf(BorrowKind::Ref, _, ref l), _) => { (&ExprKind::AddrOf(BorrowKind::Ref, _, l), _) => {
let lty = cx.typeck_results().expr_ty(l); let lty = cx.typeck_results().expr_ty(l);
let lcpy = is_copy(cx, lty); let lcpy = is_copy(cx, lty);
if (requires_ref || lcpy) if (requires_ref || lcpy)
@ -222,7 +222,7 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
} }
}, },
// foo == &bar // foo == &bar
(_, &ExprKind::AddrOf(BorrowKind::Ref, _, ref r)) => { (_, &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => {
let rty = cx.typeck_results().expr_ty(r); let rty = cx.typeck_results().expr_ty(r);
let rcpy = is_copy(cx, rty); let rcpy = is_copy(cx, rty);
if (requires_ref || rcpy) if (requires_ref || rcpy)

View file

@ -34,7 +34,7 @@ impl<'tcx> LateLintPass<'tcx> for ErasingOp {
if e.span.from_expansion() { if e.span.from_expansion() {
return; 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 { match cmp.node {
BinOpKind::Mul | BinOpKind::BitAnd => { BinOpKind::Mul | BinOpKind::BitAnd => {
check(cx, left, e.span); check(cx, left, e.span);

View file

@ -87,7 +87,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
} }
fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) { 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 body = cx.tcx.hir().body(eid);
let ex = &body.value; let ex = &body.value;
@ -109,7 +109,7 @@ fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) {
} }
if_chain!( 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; if let ExprKind::Path(_) = caller.kind;
@ -142,7 +142,7 @@ fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) {
); );
if_chain!( 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; // 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(); 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); 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 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, &[]) && implements_trait(cx, actual_type_of_self, trait_id, &[])
{ {
return Some(cx.tcx.def_path_str(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(|_| { cx.tcx.impl_of_method(method_def_id).and_then(|_| {
//a type may implicitly implement other type's methods (e.g. Deref) //a type may implicitly implement other type's methods (e.g. Deref)
if match_types(expected_type_of_self, &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)); return Some(get_type_name(cx, actual_type_of_self));
} }
None 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 { fn match_borrow_depth(lhs: Ty<'_>, rhs: Ty<'_>) -> bool {
match (&lhs.kind(), &rhs.kind()) { 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(_, _, _))), (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 { fn get_type_name(cx: &LateContext<'_>, ty: Ty<'_>) -> String {
match ty.kind() { match ty.kind() {
ty::Adt(t, _) => cx.tcx.def_path_str(t.did), 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(), _ => ty.to_string(),
} }
} }
@ -230,7 +230,7 @@ fn compare_inputs(
for (closure_input, function_arg) in closure_inputs.zip(call_args) { for (closure_input, function_arg) in closure_inputs.zip(call_args) {
if let PatKind::Binding(_, _, ident, _) = closure_input.pat.kind { if let PatKind::Binding(_, _, ident, _) = closure_input.pat.kind {
// XXXManishearth Should I be checking the binding mode here? // 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 p.segments.len() != 1 {
// If it's a proper path, it can't be a local variable // If it's a proper path, it can't be a local variable
return false; return false;

View file

@ -71,7 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for EvalOrderDependence {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// Find a write to a local variable. // Find a write to a local variable.
match expr.kind { 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) { if let Some(var) = path_to_local(lhs) {
let mut visitor = ReadVisitor { let mut visitor = ReadVisitor {
cx, cx,
@ -87,12 +87,12 @@ impl<'tcx> LateLintPass<'tcx> for EvalOrderDependence {
} }
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
match stmt.kind { match stmt.kind {
StmtKind::Local(ref local) => { StmtKind::Local(local) => {
if let Local { init: Some(ref e), .. } = **local { if let Local { init: Some(e), .. } = local {
DivergenceVisitor { cx }.visit_expr(e); 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(..) => {}, StmtKind::Item(..) => {},
} }
} }
@ -106,7 +106,7 @@ impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> {
fn maybe_walk_expr(&mut self, e: &'tcx Expr<'_>) { fn maybe_walk_expr(&mut self, e: &'tcx Expr<'_>) {
match e.kind { match e.kind {
ExprKind::Closure(..) => {}, ExprKind::Closure(..) => {},
ExprKind::Match(ref e, arms, _) => { ExprKind::Match(e, arms, _) => {
self.visit_expr(e); self.visit_expr(e);
for arm in arms { for arm in arms {
if let Some(Guard::If(if_expr)) = arm.guard { 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<'_>) { fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
match e.kind { match e.kind {
ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => self.report_diverging_sub_expr(e), 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); let typ = self.cx.typeck_results().expr_ty(func);
match typ.kind() { match typ.kind() {
ty::FnDef(..) | ty::FnPtr(_) => { 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 { fn check_stmt<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, stmt: &'tcx Stmt<'_>) -> StopEarly {
match stmt.kind { 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 // If the declaration is of a local variable, check its initializer
// expression if it has one. Otherwise, keep going. // expression if it has one. Otherwise, keep going.
StmtKind::Local(ref local) => local StmtKind::Local(local) => local
.init .init
.as_ref() .as_ref()
.map_or(StopEarly::KeepGoing, |expr| check_expr(vis, expr)), .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 = ...`. /// Returns `true` if `expr` is the LHS of an assignment, like `expr = ...`.
fn is_in_assignment_position(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { fn is_in_assignment_position(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let Some(parent) = get_parent_expr(cx, expr) { 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; return lhs.hir_id == expr.hir_id;
} }
} }

View file

@ -1,9 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_help; 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_ast::ast::{AssocItemKind, Extern, FnKind, FnSig, ImplKind, Item, ItemKind, TraitKind, Ty, TyKind};
use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span; use rustc_span::{sym, Span};
use std::convert::TryInto; use std::convert::TryInto;
@ -138,7 +138,7 @@ impl EarlyLintPass for ExcessiveBools {
} }
match &item.kind { match &item.kind {
ItemKind::Struct(variant_data, _) => { 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; return;
} }

View file

@ -28,21 +28,19 @@ declare_lint_pass!(Exit => [EXIT]);
impl<'tcx> LateLintPass<'tcx> for Exit { impl<'tcx> LateLintPass<'tcx> for Exit {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if_chain! { 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 ExprKind::Path(ref path) = path_expr.kind;
if let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id(); 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); if match_def_path(cx, def_id, &paths::EXIT);
then {
let parent = cx.tcx.hir().get_parent_item(e.hir_id); 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 // If the next item up is a function we check if it is an entry point
// and only then emit a linter warning // and only then emit a linter warning
let def_id = cx.tcx.hir().local_def_id(parent); 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`"); span_lint(cx, EXIT, e.span, "usage of `process::exit`");
} }
} }
} }
}
}
} }

View file

@ -34,11 +34,11 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
// match call to unwrap // 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; if unwrap_fun.ident.name == sym::unwrap;
// match call to write_fmt // match call to write_fmt
if !unwrap_args.is_empty(); 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; unwrap_args[0].kind;
if write_fun.ident.name == sym!(write_fmt); if write_fun.ident.name == sym!(write_fmt);
// match calls to std::io::stdout() / std::io::stderr () // match calls to std::io::stdout() / std::io::stderr ()
@ -135,10 +135,10 @@ fn write_output_string(write_args: &[Expr<'_>]) -> Option<String> {
if_chain! { if_chain! {
// Obtain the string that should be printed // Obtain the string that should be printed
if write_args.len() > 1; 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 !output_args.is_empty();
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref output_string_expr) = output_args[0].kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, output_string_expr) = output_args[0].kind;
if let ExprKind::Array(ref string_exprs) = output_string_expr.kind; if let ExprKind::Array(string_exprs) = output_string_expr.kind;
// we only want to provide an automatic suggestion for simple (non-format) strings // we only want to provide an automatic suggestion for simple (non-format) strings
if string_exprs.len() == 1; if string_exprs.len() == 1;
if let ExprKind::Lit(ref lit) = string_exprs[0].kind; if let ExprKind::Lit(ref lit) = string_exprs[0].kind;

View file

@ -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<'_>) { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
// check for `begin_panic` // check for `begin_panic`
if_chain! { if_chain! {
if let ExprKind::Call(ref func_expr, _) = expr.kind; if let ExprKind::Call(func_expr, _) = expr.kind;
if let ExprKind::Path(QPath::Resolved(_, ref path)) = func_expr.kind; if let ExprKind::Path(QPath::Resolved(_, path)) = func_expr.kind;
if let Some(path_def_id) = path.res.opt_def_id(); if let Some(path_def_id) = path.res.opt_def_id();
if match_panic_def_id(self.lcx, path_def_id); if match_panic_def_id(self.lcx, path_def_id);
if is_expn_of(expr.span, "unreachable").is_none(); if is_expn_of(expr.span, "unreachable").is_none();

View file

@ -48,7 +48,7 @@ impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs {
let rhs; let rhs;
// check if expr is a binary expression with a lt or gt operator // 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 { match op.node {
BinOpKind::Lt => { BinOpKind::Lt => {
lhs = left; lhs = left;
@ -88,8 +88,8 @@ impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs {
if let ty::Float(_) = t_val_r.kind(); if let ty::Float(_) = t_val_r.kind();
then { then {
let sug_l = sugg::Sugg::hir(cx, &val_l, ".."); let sug_l = sugg::Sugg::hir(cx, val_l, "..");
let sug_r = sugg::Sugg::hir(cx, &val_r, ".."); let sug_r = sugg::Sugg::hir(cx, val_r, "..");
// format the suggestion // format the suggestion
let suggestion = format!("{}.abs()", sugg::make_assoc(AssocOp::Subtract, &sug_l, &sug_r).maybe_par()); let suggestion = format!("{}.abs()", sugg::make_assoc(AssocOp::Subtract, &sug_l, &sug_r).maybe_par());
// spans the lint // spans the lint

View file

@ -61,8 +61,8 @@ declare_lint_pass!(FloatLiteral => [EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL]);
impl<'tcx> LateLintPass<'tcx> for FloatLiteral { impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if_chain! {
let ty = cx.typeck_results().expr_ty(expr); let ty = cx.typeck_results().expr_ty(expr);
if_chain! {
if let ty::Float(fty) = *ty.kind(); if let ty::Float(fty) = *ty.kind();
if let hir::ExprKind::Lit(ref lit) = expr.kind; if let hir::ExprKind::Lit(ref lit) = expr.kind;
if let LitKind::Float(sym, lit_float_ty) = lit.node; if let LitKind::Float(sym, lit_float_ty) = lit.node;

View file

@ -131,7 +131,7 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su
let mut suggestion = Sugg::hir(cx, expr, ".."); let mut suggestion = Sugg::hir(cx, expr, "..");
if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind { if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind {
expr = &inner_expr; expr = inner_expr;
} }
if_chain! { if_chain! {
@ -313,8 +313,8 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
Spanned { Spanned {
node: BinOpKind::Add, .. node: BinOpKind::Add, ..
}, },
ref lhs, lhs,
ref rhs, rhs,
) = parent.kind ) = parent.kind
{ {
let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs }; 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({}, {})", "{}.mul_add({}, {})",
Sugg::hir(cx, &args[0], ".."), Sugg::hir(cx, &args[0], ".."),
Sugg::hir(cx, &args[0], ".."), Sugg::hir(cx, &args[0], ".."),
Sugg::hir(cx, &other_addend, ".."), Sugg::hir(cx, other_addend, ".."),
), ),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
@ -356,18 +356,18 @@ fn detect_hypot(cx: &LateContext<'_>, args: &[Expr<'_>]) -> Option<String> {
Spanned { Spanned {
node: BinOpKind::Add, .. node: BinOpKind::Add, ..
}, },
ref add_lhs, add_lhs,
ref add_rhs, add_rhs,
) = args[0].kind ) = args[0].kind
{ {
// check if expression of the form x * x + y * y // check if expression of the form x * x + y * y
if_chain! { 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, .. }, lmul_lhs, 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, .. }, rmul_lhs, rmul_rhs) = add_rhs.kind;
if eq_expr_value(cx, lmul_lhs, lmul_rhs); if eq_expr_value(cx, lmul_lhs, lmul_rhs);
if eq_expr_value(cx, rmul_lhs, rmul_rhs); if eq_expr_value(cx, rmul_lhs, rmul_rhs);
then { 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( if let ExprKind::MethodCall(
PathSegment { ident: lmethod_name, .. }, PathSegment { ident: lmethod_name, .. },
ref _lspan, ref _lspan,
ref largs, largs,
_ _
) = add_lhs.kind; ) = add_lhs.kind;
if let ExprKind::MethodCall( if let ExprKind::MethodCall(
PathSegment { ident: rmethod_name, .. }, PathSegment { ident: rmethod_name, .. },
ref _rspan, ref _rspan,
ref rargs, rargs,
_ _
) = add_rhs.kind; ) = add_rhs.kind;
if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi"; 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 // and suggest usage of `x.exp_m1() - (y - 1)` instead
fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { 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 cx.typeck_results().expr_ty(lhs).is_floating_point();
if let Some((value, _)) = constant(cx, cx.typeck_results(), rhs); if let Some((value, _)) = constant(cx, cx.typeck_results(), rhs);
if F32(1.0) == value || F64(1.0) == value; 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 cx.typeck_results().expr_ty(&method_args[0]).is_floating_point();
if path.ident.name.as_str() == "exp"; if path.ident.name.as_str() == "exp";
then { 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>)> { fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
if_chain! { 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(lhs).is_floating_point();
if cx.typeck_results().expr_ty(rhs).is_floating_point(); if cx.typeck_results().expr_ty(rhs).is_floating_point();
then { 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 { fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
if_chain! { 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_a, .. }, _, 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_b, .. }, _, args_b, _) = expr_b.kind;
then { then {
return method_name_a.as_str() == method_name_b.as_str() && return method_name_a.as_str() == method_name_b.as_str() &&
args_a.len() == args_b.len() && args_a.len() == args_b.len() &&
@ -630,8 +630,8 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
rhs, rhs,
) = &expr.kind; ) = &expr.kind;
if are_same_base_logs(cx, lhs, rhs); if are_same_base_logs(cx, lhs, rhs);
if let ExprKind::MethodCall(_, _, ref largs, _) = lhs.kind; if let ExprKind::MethodCall(_, _, largs, _) = lhs.kind;
if let ExprKind::MethodCall(_, _, ref rargs, _) = rhs.kind; if let ExprKind::MethodCall(_, _, rargs, _) = rhs.kind;
then { then {
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
@ -675,7 +675,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
expr.span, expr.span,
"conversion to degrees can be done more accurately", "conversion to degrees can be done more accurately",
"consider using", "consider using",
format!("{}.to_degrees()", Sugg::hir(cx, &mul_lhs, "..")), format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..")),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
} else if } else if
@ -688,7 +688,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
expr.span, expr.span,
"conversion to radians can be done more accurately", "conversion to radians can be done more accurately",
"consider using", "consider using",
format!("{}.to_radians()", Sugg::hir(cx, &mul_lhs, "..")), format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..")),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
} }
@ -698,7 +698,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic { impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { 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]); let recv_ty = cx.typeck_results().expr_ty(&args[0]);
if recv_ty.is_floating_point() { if recv_ty.is_floating_point() {

View file

@ -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> { fn on_argumentv1_new<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) -> Option<String> {
if_chain! { if_chain! {
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref format_args) = expr.kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, format_args) = expr.kind;
if let ExprKind::Array(ref elems) = arms[0].body.kind; if let ExprKind::Array(elems) = arms[0].body.kind;
if elems.len() == 1; if elems.len() == 1;
if let Some(args) = match_function_call(cx, &elems[0], &paths::FMT_ARGUMENTV1_NEW); if let Some(args) = match_function_call(cx, &elems[0], &paths::FMT_ARGUMENTV1_NEW);
// matches `core::fmt::Display::fmt` // 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 let Some(did) = cx.qpath_res(qpath, args[1].hir_id).opt_def_id();
if match_def_path(cx, did, &paths::DISPLAY_FMT_METHOD); if match_def_path(cx, did, &paths::DISPLAY_FMT_METHOD);
// check `(arg0,)` in match block // 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; if pats.len() == 1;
then { 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) { if *ty.kind() != rustc_middle::ty::Str && !is_type_diagnostic_item(cx, ty, sym::string_type) {
return None; return None;
} }
@ -101,7 +101,7 @@ fn on_argumentv1_new<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &
} }
} else { } else {
let snip = snippet(cx, format_args.span, "<arg>"); 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) { if path.ident.name == sym!(to_string) {
return Some(format!("{}", snip)); 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 let Some(args) = match_function_call(cx, expr, &paths::FMT_ARGUMENTS_NEW_V1);
if args.len() == 2; if args.len() == 2;
// Argument 1 in `new_v1()` // Argument 1 in `new_v1()`
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arr) = args[0].kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, arr) = args[0].kind;
if let ExprKind::Array(ref pieces) = arr.kind; if let ExprKind::Array(pieces) = arr.kind;
if pieces.len() == 1; if pieces.len() == 1;
if let ExprKind::Lit(ref lit) = pieces[0].kind; if let ExprKind::Lit(ref lit) = pieces[0].kind;
if let LitKind::Str(ref s, _) = lit.node; if let LitKind::Str(ref s, _) = lit.node;
// Argument 2 in `new_v1()` // Argument 2 in `new_v1()`
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg1) = args[1].kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, arg1) = args[1].kind;
if let ExprKind::Match(ref matchee, ref arms, MatchSource::Normal) = arg1.kind; if let ExprKind::Match(matchee, arms, MatchSource::Normal) = arg1.kind;
if arms.len() == 1; if arms.len() == 1;
if let ExprKind::Tup(ref tup) = matchee.kind; if let ExprKind::Tup(tup) = matchee.kind;
then { then {
// `format!("foo")` expansion contains `match () { () => [], }` // `format!("foo")` expansion contains `match () { () => [], }`
if tup.is_empty() { 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 args.len() == 3;
if check_unformatted(&args[2]); if check_unformatted(&args[2]);
// Argument 1 in `new_v1_formatted()` // Argument 1 in `new_v1_formatted()`
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arr) = args[0].kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, arr) = args[0].kind;
if let ExprKind::Array(ref pieces) = arr.kind; if let ExprKind::Array(pieces) = arr.kind;
if pieces.len() == 1; if pieces.len() == 1;
if let ExprKind::Lit(ref lit) = pieces[0].kind; if let ExprKind::Lit(ref lit) = pieces[0].kind;
if let LitKind::Str(..) = lit.node; if let LitKind::Str(..) = lit.node;
// Argument 2 in `new_v1_formatted()` // Argument 2 in `new_v1_formatted()`
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg1) = args[1].kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, arg1) = args[1].kind;
if let ExprKind::Match(ref matchee, ref arms, MatchSource::Normal) = arg1.kind; if let ExprKind::Match(matchee, arms, MatchSource::Normal) = arg1.kind;
if arms.len() == 1; if arms.len() == 1;
if let ExprKind::Tup(ref tup) = matchee.kind; if let ExprKind::Tup(tup) = matchee.kind;
then { then {
return on_argumentv1_new(cx, &tup[0], arms); 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 { fn check_unformatted(expr: &Expr<'_>) -> bool {
if_chain! { if_chain! {
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) = expr.kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
if let ExprKind::Array(ref exprs) = expr.kind; if let ExprKind::Array(exprs) = expr.kind;
if exprs.len() == 1; if exprs.len() == 1;
// struct `core::fmt::rt::v1::Argument` // 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); if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format);
// struct `core::fmt::rt::v1::FormatSpec` // 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 Some(precision_field) = fields.iter().find(|f| f.ident.name == sym::precision);
if let ExprKind::Path(ref precision_path) = precision_field.expr.kind; if let ExprKind::Path(ref precision_path) = precision_field.expr.kind;
if last_path_segment(precision_path).ident.name == sym::Implied; if last_path_segment(precision_path).ident.name == sym::Implied;

View file

@ -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_snippet) = snippet_opt(cx, else_span);
if let Some(else_pos) = else_snippet.find("else"); if let Some(else_pos) = else_snippet.find("else");
if else_snippet[else_pos..].contains('\n'); if else_snippet[else_pos..].contains('\n');
let else_desc = if is_if(else_) { "if" } else { "{..}" };
then { then {
let else_desc = if is_if(else_) { "if" } else { "{..}" };
span_lint_and_note( span_lint_and_note(
cx, cx,
SUSPICIOUS_ELSE_FORMATTING, SUSPICIOUS_ELSE_FORMATTING,

View file

@ -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, &param.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
}

View 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);
}
}

View 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
}

View file

@ -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`",
);
}
}
}
}

View file

@ -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",
);
}
}
}

View file

@ -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
),
);
}
}

View file

@ -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
),
)
}
}

View file

@ -51,7 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for GetLastWithLen {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
// Is a method call // 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" // Method name is "get"
if path.ident.name == sym!(get); if path.ident.name == sym!(get);

View file

@ -35,7 +35,7 @@ impl<'tcx> LateLintPass<'tcx> for IdentityOp {
if e.span.from_expansion() { if e.span.from_expansion() {
return; 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) { if is_allowed(cx, cmp, left, right) {
return; return;
} }

View file

@ -55,8 +55,8 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex {
cx, cx,
}; };
if let ExprKind::Match( if let ExprKind::Match(
ref op, op,
ref arms, arms,
MatchSource::IfLetDesugar { MatchSource::IfLetDesugar {
contains_else_clause: true, contains_else_clause: true,
}, },
@ -64,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex {
{ {
op_visit.visit_expr(op); op_visit.visit_expr(op);
if op_visit.mutex_lock_called { if op_visit.mutex_lock_called {
for arm in *arms { for arm in arms {
arm_visit.visit_arm(arm); arm_visit.visit_arm(arm);
} }
@ -94,14 +94,11 @@ impl<'tcx> Visitor<'tcx> for OppVisitor<'_, 'tcx> {
type Map = Map<'tcx>; type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if_chain! { if let Some(mutex) = is_mutex_lock_call(self.cx, expr) {
if let Some(mutex) = is_mutex_lock_call(self.cx, expr);
then {
self.found_mutex = Some(mutex); self.found_mutex = Some(mutex);
self.mutex_lock_called = true; self.mutex_lock_called = true;
return; return;
} }
}
visit::walk_expr(self, expr); visit::walk_expr(self, expr);
} }
@ -121,14 +118,11 @@ impl<'tcx> Visitor<'tcx> for ArmVisitor<'_, 'tcx> {
type Map = Map<'tcx>; type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if_chain! { if let Some(mutex) = is_mutex_lock_call(self.cx, expr) {
if let Some(mutex) = is_mutex_lock_call(self.cx, expr);
then {
self.found_mutex = Some(mutex); self.found_mutex = Some(mutex);
self.mutex_lock_called = true; self.mutex_lock_called = true;
return; return;
} }
}
visit::walk_expr(self, expr); visit::walk_expr(self, expr);
} }

View file

@ -44,9 +44,9 @@ declare_lint_pass!(OkIfLet => [IF_LET_SOME_RESULT]);
impl<'tcx> LateLintPass<'tcx> for OkIfLet { impl<'tcx> LateLintPass<'tcx> for OkIfLet {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { //begin checking variables 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::Match(op, 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 ExprKind::MethodCall(_, ok_span, 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 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 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 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"; if rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(x, false)) == "Some";

View file

@ -72,15 +72,15 @@ impl LateLintPass<'_> for IfThenSomeElseNone {
} }
if_chain! { if_chain! {
if let ExprKind::If(ref cond, ref then, Some(ref els)) = expr.kind; if let ExprKind::If(cond, then, Some(els)) = expr.kind;
if let ExprKind::Block(ref then_block, _) = then.kind; if let ExprKind::Block(then_block, _) = then.kind;
if let Some(ref then_expr) = then_block.expr; if let Some(then_expr) = then_block.expr;
if let ExprKind::Call(ref then_call, [then_arg]) = then_expr.kind; if let ExprKind::Call(then_call, [then_arg]) = then_expr.kind;
if let ExprKind::Path(ref then_call_qpath) = then_call.kind; if let ExprKind::Path(ref then_call_qpath) = then_call.kind;
if match_qpath(then_call_qpath, &clippy_utils::paths::OPTION_SOME); 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 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 let ExprKind::Path(ref els_call_qpath) = els_expr.kind;
if match_qpath(els_call_qpath, &clippy_utils::paths::OPTION_NONE); if match_qpath(els_call_qpath, &clippy_utils::paths::OPTION_NONE);
then { then {

View 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())
}
}

View file

@ -100,10 +100,10 @@ fn expr_match(cx: &LateContext<'_>, expr: &Expr<'_>) {
if check_all_arms { if check_all_arms {
for arm in arms { for arm in arms {
expr_match(cx, &arm.body); expr_match(cx, arm.body);
} }
} else { } 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 // skip if it already has a return statement

View file

@ -46,21 +46,21 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
if let ExprKind::If(cond, then, None) = &expr.kind; if let ExprKind::If(cond, then, None) = &expr.kind;
// Check if the conditional expression is a binary operation // 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 < // Ensure that the binary operator is >, != and <
if BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node; 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 // 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(); if block.stmts.len() == 1 && block.expr.is_none();
// Check if assign operation is done // 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); if let Some(target) = subtracts_one(cx, e);
// Extracting out the variable name // 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 { then {
// Handle symmetric conditions in the if statement // 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); 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 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"])) { if INT_TYPES.iter().any(|int_type| match_qpath(cond_num_path, &[int_type, "min_value"])) {
print_lint_and_sugg(cx, &var_name, expr); 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>> { fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &Expr<'a>) -> Option<&'a Expr<'a>> {
match expr.kind { match expr.kind {
ExprKind::AssignOp(ref op1, ref target, ref value) => { ExprKind::AssignOp(ref op1, target, value) => {
if_chain! { if_chain! {
if BinOpKind::Sub == op1.node; if BinOpKind::Sub == op1.node;
// Check if literal being subtracted is one // 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_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 BinOpKind::Sub == op1.node;
if SpanlessEq::new(cx).eq_expr(left1, target); if SpanlessEq::new(cx).eq_expr(left1, target);

View file

@ -88,7 +88,7 @@ declare_lint_pass!(IndexingSlicing => [INDEXING_SLICING, OUT_OF_BOUNDS_INDEXING]
impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { 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(); let ty = cx.typeck_results().expr_ty(array).peel_refs();
if let Some(range) = higher::range(index) { if let Some(range) = higher::range(index) {
// Ranged indexes, i.e., &x[n..m], &x[n..], &x[..n] and &x[..] // 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