Auto merge of #84008 - Dylan-DPC:rollup-invxvg8, r=Dylan-DPC
Rollup of 6 pull requests Successful merges: - #80733 (Improve links in inline code in `core::pin`.) - #81764 (Stabilize `rustdoc::bare_urls` lint) - #81938 (Stabilize `peekable_peek_mut`) - #83980 (Fix outdated crate names in compiler docs) - #83992 (Merge idents when generating source content) - #84001 (Update Clippy) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
2e495d2e84
391 changed files with 10850 additions and 6311 deletions
|
@ -1,3 +1,3 @@
|
||||||
//! Definitions shared by macros / syntax extensions and e.g. librustc_middle.
|
//! Definitions shared by macros / syntax extensions and e.g. `rustc_middle`.
|
||||||
|
|
||||||
pub mod allocator;
|
pub mod allocator;
|
||||||
|
|
|
@ -59,7 +59,7 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
|
||||||
|
|
||||||
/// Requirements for a `StableHashingContext` to be used in this crate.
|
/// 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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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].
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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>>);
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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].
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
</style>
|
</style>
|
||||||
<pre><code><span class="attribute">#![<span class="ident">crate_type</span> <span class="op">=</span> <span class="string">"lib"</span>]</span>
|
<pre><code><span class="attribute">#![<span class="ident">crate_type</span> <span class="op">=</span> <span class="string">"lib"</span>]</span>
|
||||||
|
|
||||||
|
<span class="kw">use</span> <span class="ident">std::path</span>::{<span class="ident">Path</span>, <span class="ident">PathBuf</span>};
|
||||||
|
|
||||||
<span class="attribute">#[<span class="ident">cfg</span>(<span class="ident">target_os</span> <span class="op">=</span> <span class="string">"linux"</span>)]</span>
|
<span class="attribute">#[<span class="ident">cfg</span>(<span class="ident">target_os</span> <span class="op">=</span> <span class="string">"linux"</span>)]</span>
|
||||||
<span class="kw">fn</span> <span class="ident">main</span>() {
|
<span class="kw">fn</span> <span class="ident">main</span>() {
|
||||||
<span class="kw">let</span> <span class="ident">foo</span> <span class="op">=</span> <span class="bool-val">true</span> <span class="op">&&</span> <span class="bool-val">false</span> <span class="op">|</span><span class="op">|</span> <span class="bool-val">true</span>;
|
<span class="kw">let</span> <span class="ident">foo</span> <span class="op">=</span> <span class="bool-val">true</span> <span class="op">&&</span> <span class="bool-val">false</span> <span class="op">|</span><span class="op">|</span> <span class="bool-val">true</span>;
|
||||||
|
@ -19,6 +21,14 @@
|
||||||
<span class="kw">let</span> <span class="kw">_</span> <span class="op">=</span> <span class="kw-2">*</span><span class="ident">foo</span>;
|
<span class="kw">let</span> <span class="kw">_</span> <span class="op">=</span> <span class="kw-2">*</span><span class="ident">foo</span>;
|
||||||
<span class="macro">mac!</span>(<span class="ident">foo</span>, <span class="kw-2">&</span><span class="kw-2">mut</span> <span class="ident">bar</span>);
|
<span class="macro">mac!</span>(<span class="ident">foo</span>, <span class="kw-2">&</span><span class="kw-2">mut</span> <span class="ident">bar</span>);
|
||||||
<span class="macro">assert!</span>(<span class="self">self</span>.<span class="ident">length</span> <span class="op"><</span> <span class="ident">N</span> <span class="op">&&</span> <span class="ident">index</span> <span class="op"><</span><span class="op">=</span> <span class="self">self</span>.<span class="ident">length</span>);
|
<span class="macro">assert!</span>(<span class="self">self</span>.<span class="ident">length</span> <span class="op"><</span> <span class="ident">N</span> <span class="op">&&</span> <span class="ident">index</span> <span class="op"><</span><span class="op">=</span> <span class="self">self</span>.<span class="ident">length</span>);
|
||||||
|
<span class="ident">::std::env::var</span>(<span class="string">"gateau"</span>).<span class="ident">is_ok</span>();
|
||||||
|
<span class="attribute">#[<span class="ident">rustfmt::skip</span>]</span>
|
||||||
|
<span class="kw">let</span> <span class="ident">s</span>:<span class="ident">std</span><span class="ident">::path::PathBuf</span> <span class="op">=</span> <span class="ident">std::path::PathBuf::new</span>();
|
||||||
|
<span class="kw">let</span> <span class="kw-2">mut</span> <span class="ident">s</span> <span class="op">=</span> <span class="ident">String::new</span>();
|
||||||
|
|
||||||
|
<span class="kw">match</span> <span class="kw-2">&</span><span class="ident">s</span> {
|
||||||
|
<span class="kw-2">ref</span> <span class="kw-2">mut</span> <span class="ident">x</span> <span class="op">=</span><span class="op">></span> {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<span class="macro">macro_rules!</span> <span class="ident">bar</span> {
|
<span class="macro">macro_rules!</span> <span class="ident">bar</span> {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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() {}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()));
|
||||||
}
|
}
|
||||||
|
|
173
src/tools/clippy/clippy_lints/src/absurd_extreme_comparisons.rs
Normal file
173
src/tools/clippy/clippy_lints/src/absurd_extreme_comparisons.rs
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||||
|
use rustc_lint::{LateContext, LateLintPass};
|
||||||
|
use rustc_middle::ty;
|
||||||
|
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||||
|
|
||||||
|
use crate::consts::{constant, Constant};
|
||||||
|
|
||||||
|
use clippy_utils::comparisons::{normalize_comparison, Rel};
|
||||||
|
use clippy_utils::diagnostics::span_lint_and_help;
|
||||||
|
use clippy_utils::source::snippet;
|
||||||
|
use clippy_utils::ty::is_isize_or_usize;
|
||||||
|
use clippy_utils::{clip, int_bits, unsext};
|
||||||
|
|
||||||
|
declare_clippy_lint! {
|
||||||
|
/// **What it does:** Checks for comparisons where one side of the relation is
|
||||||
|
/// either the minimum or maximum value for its type and warns if it involves a
|
||||||
|
/// case that is always true or always false. Only integer and boolean types are
|
||||||
|
/// checked.
|
||||||
|
///
|
||||||
|
/// **Why is this bad?** An expression like `min <= x` may misleadingly imply
|
||||||
|
/// that it is possible for `x` to be less than the minimum. Expressions like
|
||||||
|
/// `max < x` are probably mistakes.
|
||||||
|
///
|
||||||
|
/// **Known problems:** For `usize` the size of the current compile target will
|
||||||
|
/// be assumed (e.g., 64 bits on 64 bit systems). This means code that uses such
|
||||||
|
/// a comparison to detect target pointer width will trigger this lint. One can
|
||||||
|
/// use `mem::sizeof` and compare its value or conditional compilation
|
||||||
|
/// attributes
|
||||||
|
/// like `#[cfg(target_pointer_width = "64")] ..` instead.
|
||||||
|
///
|
||||||
|
/// **Example:**
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// let vec: Vec<isize> = Vec::new();
|
||||||
|
/// if vec.len() <= 0 {}
|
||||||
|
/// if 100 > i32::MAX {}
|
||||||
|
/// ```
|
||||||
|
pub ABSURD_EXTREME_COMPARISONS,
|
||||||
|
correctness,
|
||||||
|
"a comparison with a maximum or minimum value that is always true or false"
|
||||||
|
}
|
||||||
|
|
||||||
|
declare_lint_pass!(AbsurdExtremeComparisons => [ABSURD_EXTREME_COMPARISONS]);
|
||||||
|
|
||||||
|
impl<'tcx> LateLintPass<'tcx> for AbsurdExtremeComparisons {
|
||||||
|
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||||
|
if let ExprKind::Binary(ref cmp, lhs, rhs) = expr.kind {
|
||||||
|
if let Some((culprit, result)) = detect_absurd_comparison(cx, cmp.node, lhs, rhs) {
|
||||||
|
if !expr.span.from_expansion() {
|
||||||
|
let msg = "this comparison involving the minimum or maximum element for this \
|
||||||
|
type contains a case that is always true or always false";
|
||||||
|
|
||||||
|
let conclusion = match result {
|
||||||
|
AbsurdComparisonResult::AlwaysFalse => "this comparison is always false".to_owned(),
|
||||||
|
AbsurdComparisonResult::AlwaysTrue => "this comparison is always true".to_owned(),
|
||||||
|
AbsurdComparisonResult::InequalityImpossible => format!(
|
||||||
|
"the case where the two sides are not equal never occurs, consider using `{} == {}` \
|
||||||
|
instead",
|
||||||
|
snippet(cx, lhs.span, "lhs"),
|
||||||
|
snippet(cx, rhs.span, "rhs")
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let help = format!(
|
||||||
|
"because `{}` is the {} value for this type, {}",
|
||||||
|
snippet(cx, culprit.expr.span, "x"),
|
||||||
|
match culprit.which {
|
||||||
|
ExtremeType::Minimum => "minimum",
|
||||||
|
ExtremeType::Maximum => "maximum",
|
||||||
|
},
|
||||||
|
conclusion
|
||||||
|
);
|
||||||
|
|
||||||
|
span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ExtremeType {
|
||||||
|
Minimum,
|
||||||
|
Maximum,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExtremeExpr<'a> {
|
||||||
|
which: ExtremeType,
|
||||||
|
expr: &'a Expr<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AbsurdComparisonResult {
|
||||||
|
AlwaysFalse,
|
||||||
|
AlwaysTrue,
|
||||||
|
InequalityImpossible,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_cast_between_fixed_and_target<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
|
||||||
|
if let ExprKind::Cast(cast_exp, _) = expr.kind {
|
||||||
|
let precast_ty = cx.typeck_results().expr_ty(cast_exp);
|
||||||
|
let cast_ty = cx.typeck_results().expr_ty(expr);
|
||||||
|
|
||||||
|
return is_isize_or_usize(precast_ty) != is_isize_or_usize(cast_ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_absurd_comparison<'tcx>(
|
||||||
|
cx: &LateContext<'tcx>,
|
||||||
|
op: BinOpKind,
|
||||||
|
lhs: &'tcx Expr<'_>,
|
||||||
|
rhs: &'tcx Expr<'_>,
|
||||||
|
) -> Option<(ExtremeExpr<'tcx>, AbsurdComparisonResult)> {
|
||||||
|
use AbsurdComparisonResult::{AlwaysFalse, AlwaysTrue, InequalityImpossible};
|
||||||
|
use ExtremeType::{Maximum, Minimum};
|
||||||
|
// absurd comparison only makes sense on primitive types
|
||||||
|
// primitive types don't implement comparison operators with each other
|
||||||
|
if cx.typeck_results().expr_ty(lhs) != cx.typeck_results().expr_ty(rhs) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// comparisons between fix sized types and target sized types are considered unanalyzable
|
||||||
|
if is_cast_between_fixed_and_target(cx, lhs) || is_cast_between_fixed_and_target(cx, rhs) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (rel, normalized_lhs, normalized_rhs) = normalize_comparison(op, lhs, rhs)?;
|
||||||
|
|
||||||
|
let lx = detect_extreme_expr(cx, normalized_lhs);
|
||||||
|
let rx = detect_extreme_expr(cx, normalized_rhs);
|
||||||
|
|
||||||
|
Some(match rel {
|
||||||
|
Rel::Lt => {
|
||||||
|
match (lx, rx) {
|
||||||
|
(Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, AlwaysFalse), // max < x
|
||||||
|
(_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, AlwaysFalse), // x < min
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Rel::Le => {
|
||||||
|
match (lx, rx) {
|
||||||
|
(Some(l @ ExtremeExpr { which: Minimum, .. }), _) => (l, AlwaysTrue), // min <= x
|
||||||
|
(Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, InequalityImpossible), // max <= x
|
||||||
|
(_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, InequalityImpossible), // x <= min
|
||||||
|
(_, Some(r @ ExtremeExpr { which: Maximum, .. })) => (r, AlwaysTrue), // x <= max
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Rel::Ne | Rel::Eq => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_extreme_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<ExtremeExpr<'tcx>> {
|
||||||
|
let ty = cx.typeck_results().expr_ty(expr);
|
||||||
|
|
||||||
|
let cv = constant(cx, cx.typeck_results(), expr)?.0;
|
||||||
|
|
||||||
|
let which = match (ty.kind(), cv) {
|
||||||
|
(&ty::Bool, Constant::Bool(false)) | (&ty::Uint(_), Constant::Int(0)) => ExtremeType::Minimum,
|
||||||
|
(&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MIN >> (128 - int_bits(cx.tcx, ity)), ity) => {
|
||||||
|
ExtremeType::Minimum
|
||||||
|
},
|
||||||
|
|
||||||
|
(&ty::Bool, Constant::Bool(true)) => ExtremeType::Maximum,
|
||||||
|
(&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MAX >> (128 - int_bits(cx.tcx, ity)), ity) => {
|
||||||
|
ExtremeType::Maximum
|
||||||
|
},
|
||||||
|
(&ty::Uint(uty), Constant::Int(i)) if clip(cx.tcx, u128::MAX, uty) == i => ExtremeType::Maximum,
|
||||||
|
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
Some(ExtremeExpr { which, expr })
|
||||||
|
}
|
|
@ -71,7 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
|
||||||
return;
|
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
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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(©_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.
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = ¶ms[0];
|
let param = ¶ms[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 = ¶ms[1];
|
let param = ¶ms[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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 = ¶ms[0];
|
let map = ¶ms[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, ¶ms[0]);
|
if get_item_name(self.cx, self.map) == get_item_name(self.cx, ¶ms[0]);
|
||||||
|
|
|
@ -65,12 +65,12 @@ const ASSERT_MACRO_NAMES: [&str; 4] = ["assert_eq", "assert_ne", "debug_assert_e
|
||||||
impl<'tcx> LateLintPass<'tcx> for EqOp {
|
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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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`");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -1,738 +0,0 @@
|
||||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};
|
|
||||||
use clippy_utils::source::{snippet, snippet_opt};
|
|
||||||
use clippy_utils::ty::{is_must_use_ty, is_type_diagnostic_item, type_is_unsafe_function};
|
|
||||||
use clippy_utils::{
|
|
||||||
attr_by_name, attrs::is_proc_macro, is_trait_impl_item, iter_input_pats, match_def_path, must_use_attr,
|
|
||||||
path_to_local, return_ty, trait_ref_of_method,
|
|
||||||
};
|
|
||||||
use if_chain::if_chain;
|
|
||||||
use rustc_ast::ast::Attribute;
|
|
||||||
use rustc_data_structures::fx::FxHashSet;
|
|
||||||
use rustc_errors::Applicability;
|
|
||||||
use rustc_hir as hir;
|
|
||||||
use rustc_hir::intravisit;
|
|
||||||
use rustc_hir::{def::Res, def_id::DefId, QPath};
|
|
||||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
|
||||||
use rustc_middle::hir::map::Map;
|
|
||||||
use rustc_middle::lint::in_external_macro;
|
|
||||||
use rustc_middle::ty::{self, Ty};
|
|
||||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
|
||||||
use rustc_span::source_map::Span;
|
|
||||||
use rustc_span::sym;
|
|
||||||
use rustc_target::spec::abi::Abi;
|
|
||||||
use rustc_typeck::hir_ty_to_ty;
|
|
||||||
|
|
||||||
declare_clippy_lint! {
|
|
||||||
/// **What it does:** Checks for functions with too many parameters.
|
|
||||||
///
|
|
||||||
/// **Why is this bad?** Functions with lots of parameters are considered bad
|
|
||||||
/// style and reduce readability (“what does the 5th parameter mean?”). Consider
|
|
||||||
/// grouping some parameters into a new type.
|
|
||||||
///
|
|
||||||
/// **Known problems:** None.
|
|
||||||
///
|
|
||||||
/// **Example:**
|
|
||||||
/// ```rust
|
|
||||||
/// # struct Color;
|
|
||||||
/// fn foo(x: u32, y: u32, name: &str, c: Color, w: f32, h: f32, a: f32, b: f32) {
|
|
||||||
/// // ..
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub TOO_MANY_ARGUMENTS,
|
|
||||||
complexity,
|
|
||||||
"functions with too many arguments"
|
|
||||||
}
|
|
||||||
|
|
||||||
declare_clippy_lint! {
|
|
||||||
/// **What it does:** Checks for functions with a large amount of lines.
|
|
||||||
///
|
|
||||||
/// **Why is this bad?** Functions with a lot of lines are harder to understand
|
|
||||||
/// due to having to look at a larger amount of code to understand what the
|
|
||||||
/// function is doing. Consider splitting the body of the function into
|
|
||||||
/// multiple functions.
|
|
||||||
///
|
|
||||||
/// **Known problems:** None.
|
|
||||||
///
|
|
||||||
/// **Example:**
|
|
||||||
/// ```rust
|
|
||||||
/// fn im_too_long() {
|
|
||||||
/// println!("");
|
|
||||||
/// // ... 100 more LoC
|
|
||||||
/// println!("");
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub TOO_MANY_LINES,
|
|
||||||
pedantic,
|
|
||||||
"functions with too many lines"
|
|
||||||
}
|
|
||||||
|
|
||||||
declare_clippy_lint! {
|
|
||||||
/// **What it does:** Checks for public functions that dereference raw pointer
|
|
||||||
/// arguments but are not marked unsafe.
|
|
||||||
///
|
|
||||||
/// **Why is this bad?** The function should probably be marked `unsafe`, since
|
|
||||||
/// for an arbitrary raw pointer, there is no way of telling for sure if it is
|
|
||||||
/// valid.
|
|
||||||
///
|
|
||||||
/// **Known problems:**
|
|
||||||
///
|
|
||||||
/// * It does not check functions recursively so if the pointer is passed to a
|
|
||||||
/// private non-`unsafe` function which does the dereferencing, the lint won't
|
|
||||||
/// trigger.
|
|
||||||
/// * It only checks for arguments whose type are raw pointers, not raw pointers
|
|
||||||
/// got from an argument in some other way (`fn foo(bar: &[*const u8])` or
|
|
||||||
/// `some_argument.get_raw_ptr()`).
|
|
||||||
///
|
|
||||||
/// **Example:**
|
|
||||||
/// ```rust,ignore
|
|
||||||
/// // Bad
|
|
||||||
/// pub fn foo(x: *const u8) {
|
|
||||||
/// println!("{}", unsafe { *x });
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// // Good
|
|
||||||
/// pub unsafe fn foo(x: *const u8) {
|
|
||||||
/// println!("{}", unsafe { *x });
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub NOT_UNSAFE_PTR_ARG_DEREF,
|
|
||||||
correctness,
|
|
||||||
"public functions dereferencing raw pointer arguments but not marked `unsafe`"
|
|
||||||
}
|
|
||||||
|
|
||||||
declare_clippy_lint! {
|
|
||||||
/// **What it does:** Checks for a [`#[must_use]`] attribute on
|
|
||||||
/// unit-returning functions and methods.
|
|
||||||
///
|
|
||||||
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
|
|
||||||
///
|
|
||||||
/// **Why is this bad?** Unit values are useless. The attribute is likely
|
|
||||||
/// a remnant of a refactoring that removed the return type.
|
|
||||||
///
|
|
||||||
/// **Known problems:** None.
|
|
||||||
///
|
|
||||||
/// **Examples:**
|
|
||||||
/// ```rust
|
|
||||||
/// #[must_use]
|
|
||||||
/// fn useless() { }
|
|
||||||
/// ```
|
|
||||||
pub MUST_USE_UNIT,
|
|
||||||
style,
|
|
||||||
"`#[must_use]` attribute on a unit-returning function / method"
|
|
||||||
}
|
|
||||||
|
|
||||||
declare_clippy_lint! {
|
|
||||||
/// **What it does:** Checks for a [`#[must_use]`] attribute without
|
|
||||||
/// further information on functions and methods that return a type already
|
|
||||||
/// marked as `#[must_use]`.
|
|
||||||
///
|
|
||||||
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
|
|
||||||
///
|
|
||||||
/// **Why is this bad?** The attribute isn't needed. Not using the result
|
|
||||||
/// will already be reported. Alternatively, one can add some text to the
|
|
||||||
/// attribute to improve the lint message.
|
|
||||||
///
|
|
||||||
/// **Known problems:** None.
|
|
||||||
///
|
|
||||||
/// **Examples:**
|
|
||||||
/// ```rust
|
|
||||||
/// #[must_use]
|
|
||||||
/// fn double_must_use() -> Result<(), ()> {
|
|
||||||
/// unimplemented!();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub DOUBLE_MUST_USE,
|
|
||||||
style,
|
|
||||||
"`#[must_use]` attribute on a `#[must_use]`-returning function / method"
|
|
||||||
}
|
|
||||||
|
|
||||||
declare_clippy_lint! {
|
|
||||||
/// **What it does:** Checks for public functions that have no
|
|
||||||
/// [`#[must_use]`] attribute, but return something not already marked
|
|
||||||
/// must-use, have no mutable arg and mutate no statics.
|
|
||||||
///
|
|
||||||
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
|
|
||||||
///
|
|
||||||
/// **Why is this bad?** Not bad at all, this lint just shows places where
|
|
||||||
/// you could add the attribute.
|
|
||||||
///
|
|
||||||
/// **Known problems:** The lint only checks the arguments for mutable
|
|
||||||
/// types without looking if they are actually changed. On the other hand,
|
|
||||||
/// it also ignores a broad range of potentially interesting side effects,
|
|
||||||
/// because we cannot decide whether the programmer intends the function to
|
|
||||||
/// be called for the side effect or the result. Expect many false
|
|
||||||
/// positives. At least we don't lint if the result type is unit or already
|
|
||||||
/// `#[must_use]`.
|
|
||||||
///
|
|
||||||
/// **Examples:**
|
|
||||||
/// ```rust
|
|
||||||
/// // this could be annotated with `#[must_use]`.
|
|
||||||
/// fn id<T>(t: T) -> T { t }
|
|
||||||
/// ```
|
|
||||||
pub MUST_USE_CANDIDATE,
|
|
||||||
pedantic,
|
|
||||||
"function or method that could take a `#[must_use]` attribute"
|
|
||||||
}
|
|
||||||
|
|
||||||
declare_clippy_lint! {
|
|
||||||
/// **What it does:** Checks for public functions that return a `Result`
|
|
||||||
/// with an `Err` type of `()`. It suggests using a custom type that
|
|
||||||
/// implements [`std::error::Error`].
|
|
||||||
///
|
|
||||||
/// **Why is this bad?** Unit does not implement `Error` and carries no
|
|
||||||
/// further information about what went wrong.
|
|
||||||
///
|
|
||||||
/// **Known problems:** Of course, this lint assumes that `Result` is used
|
|
||||||
/// for a fallible operation (which is after all the intended use). However
|
|
||||||
/// code may opt to (mis)use it as a basic two-variant-enum. In that case,
|
|
||||||
/// the suggestion is misguided, and the code should use a custom enum
|
|
||||||
/// instead.
|
|
||||||
///
|
|
||||||
/// **Examples:**
|
|
||||||
/// ```rust
|
|
||||||
/// pub fn read_u8() -> Result<u8, ()> { Err(()) }
|
|
||||||
/// ```
|
|
||||||
/// should become
|
|
||||||
/// ```rust,should_panic
|
|
||||||
/// use std::fmt;
|
|
||||||
///
|
|
||||||
/// #[derive(Debug)]
|
|
||||||
/// pub struct EndOfStream;
|
|
||||||
///
|
|
||||||
/// impl fmt::Display for EndOfStream {
|
|
||||||
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
/// write!(f, "End of Stream")
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// impl std::error::Error for EndOfStream { }
|
|
||||||
///
|
|
||||||
/// pub fn read_u8() -> Result<u8, EndOfStream> { Err(EndOfStream) }
|
|
||||||
///# fn main() {
|
|
||||||
///# read_u8().unwrap();
|
|
||||||
///# }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Note that there are crates that simplify creating the error type, e.g.
|
|
||||||
/// [`thiserror`](https://docs.rs/thiserror).
|
|
||||||
pub RESULT_UNIT_ERR,
|
|
||||||
style,
|
|
||||||
"public function returning `Result` with an `Err` type of `()`"
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct Functions {
|
|
||||||
threshold: u64,
|
|
||||||
max_lines: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Functions {
|
|
||||||
pub fn new(threshold: u64, max_lines: u64) -> Self {
|
|
||||||
Self { threshold, max_lines }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_lint_pass!(Functions => [
|
|
||||||
TOO_MANY_ARGUMENTS,
|
|
||||||
TOO_MANY_LINES,
|
|
||||||
NOT_UNSAFE_PTR_ARG_DEREF,
|
|
||||||
MUST_USE_UNIT,
|
|
||||||
DOUBLE_MUST_USE,
|
|
||||||
MUST_USE_CANDIDATE,
|
|
||||||
RESULT_UNIT_ERR,
|
|
||||||
]);
|
|
||||||
|
|
||||||
impl<'tcx> LateLintPass<'tcx> for Functions {
|
|
||||||
fn check_fn(
|
|
||||||
&mut self,
|
|
||||||
cx: &LateContext<'tcx>,
|
|
||||||
kind: intravisit::FnKind<'tcx>,
|
|
||||||
decl: &'tcx hir::FnDecl<'_>,
|
|
||||||
body: &'tcx hir::Body<'_>,
|
|
||||||
span: Span,
|
|
||||||
hir_id: hir::HirId,
|
|
||||||
) {
|
|
||||||
let unsafety = match kind {
|
|
||||||
intravisit::FnKind::ItemFn(_, _, hir::FnHeader { unsafety, .. }, _) => unsafety,
|
|
||||||
intravisit::FnKind::Method(_, sig, _) => sig.header.unsafety,
|
|
||||||
intravisit::FnKind::Closure => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
// don't warn for implementations, it's not their fault
|
|
||||||
if !is_trait_impl_item(cx, hir_id) {
|
|
||||||
// don't lint extern functions decls, it's not their fault either
|
|
||||||
match kind {
|
|
||||||
intravisit::FnKind::Method(
|
|
||||||
_,
|
|
||||||
&hir::FnSig {
|
|
||||||
header: hir::FnHeader { abi: Abi::Rust, .. },
|
|
||||||
..
|
|
||||||
},
|
|
||||||
_,
|
|
||||||
)
|
|
||||||
| intravisit::FnKind::ItemFn(_, _, hir::FnHeader { abi: Abi::Rust, .. }, _) => {
|
|
||||||
self.check_arg_number(cx, decl, span.with_hi(decl.output.span().hi()))
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::check_raw_ptr(cx, unsafety, decl, body, hir_id);
|
|
||||||
self.check_line_number(cx, span, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
|
|
||||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
|
||||||
let attr = must_use_attr(attrs);
|
|
||||||
if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind {
|
|
||||||
let is_public = cx.access_levels.is_exported(item.hir_id());
|
|
||||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
|
||||||
if is_public {
|
|
||||||
check_result_unit_err(cx, &sig.decl, item.span, fn_header_span);
|
|
||||||
}
|
|
||||||
if let Some(attr) = attr {
|
|
||||||
check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if is_public && !is_proc_macro(cx.sess(), attrs) && attr_by_name(attrs, "no_mangle").is_none() {
|
|
||||||
check_must_use_candidate(
|
|
||||||
cx,
|
|
||||||
&sig.decl,
|
|
||||||
cx.tcx.hir().body(*body_id),
|
|
||||||
item.span,
|
|
||||||
item.hir_id(),
|
|
||||||
item.span.with_hi(sig.decl.output.span().hi()),
|
|
||||||
"this function could have a `#[must_use]` attribute",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
|
|
||||||
if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind {
|
|
||||||
let is_public = cx.access_levels.is_exported(item.hir_id());
|
|
||||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
|
||||||
if is_public && trait_ref_of_method(cx, item.hir_id()).is_none() {
|
|
||||||
check_result_unit_err(cx, &sig.decl, item.span, fn_header_span);
|
|
||||||
}
|
|
||||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
|
||||||
let attr = must_use_attr(attrs);
|
|
||||||
if let Some(attr) = attr {
|
|
||||||
check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr);
|
|
||||||
} else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.hir_id()).is_none()
|
|
||||||
{
|
|
||||||
check_must_use_candidate(
|
|
||||||
cx,
|
|
||||||
&sig.decl,
|
|
||||||
cx.tcx.hir().body(*body_id),
|
|
||||||
item.span,
|
|
||||||
item.hir_id(),
|
|
||||||
item.span.with_hi(sig.decl.output.span().hi()),
|
|
||||||
"this method could have a `#[must_use]` attribute",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
|
|
||||||
if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind {
|
|
||||||
// don't lint extern functions decls, it's not their fault
|
|
||||||
if sig.header.abi == Abi::Rust {
|
|
||||||
self.check_arg_number(cx, &sig.decl, item.span.with_hi(sig.decl.output.span().hi()));
|
|
||||||
}
|
|
||||||
let is_public = cx.access_levels.is_exported(item.hir_id());
|
|
||||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
|
||||||
if is_public {
|
|
||||||
check_result_unit_err(cx, &sig.decl, item.span, fn_header_span);
|
|
||||||
}
|
|
||||||
|
|
||||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
|
||||||
let attr = must_use_attr(attrs);
|
|
||||||
if let Some(attr) = attr {
|
|
||||||
check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr);
|
|
||||||
}
|
|
||||||
if let hir::TraitFn::Provided(eid) = *eid {
|
|
||||||
let body = cx.tcx.hir().body(eid);
|
|
||||||
Self::check_raw_ptr(cx, sig.header.unsafety, &sig.decl, body, item.hir_id());
|
|
||||||
|
|
||||||
if attr.is_none() && is_public && !is_proc_macro(cx.sess(), attrs) {
|
|
||||||
check_must_use_candidate(
|
|
||||||
cx,
|
|
||||||
&sig.decl,
|
|
||||||
body,
|
|
||||||
item.span,
|
|
||||||
item.hir_id(),
|
|
||||||
item.span.with_hi(sig.decl.output.span().hi()),
|
|
||||||
"this method could have a `#[must_use]` attribute",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'tcx> Functions {
|
|
||||||
fn check_arg_number(self, cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, fn_span: Span) {
|
|
||||||
let args = decl.inputs.len() as u64;
|
|
||||||
if args > self.threshold {
|
|
||||||
span_lint(
|
|
||||||
cx,
|
|
||||||
TOO_MANY_ARGUMENTS,
|
|
||||||
fn_span,
|
|
||||||
&format!("this function has too many arguments ({}/{})", args, self.threshold),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_line_number(self, cx: &LateContext<'_>, span: Span, body: &'tcx hir::Body<'_>) {
|
|
||||||
if in_external_macro(cx.sess(), span) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let code_snippet = snippet(cx, body.value.span, "..");
|
|
||||||
let mut line_count: u64 = 0;
|
|
||||||
let mut in_comment = false;
|
|
||||||
let mut code_in_line;
|
|
||||||
|
|
||||||
// Skip the surrounding function decl.
|
|
||||||
let start_brace_idx = code_snippet.find('{').map_or(0, |i| i + 1);
|
|
||||||
let end_brace_idx = code_snippet.rfind('}').unwrap_or_else(|| code_snippet.len());
|
|
||||||
let function_lines = code_snippet[start_brace_idx..end_brace_idx].lines();
|
|
||||||
|
|
||||||
for mut line in function_lines {
|
|
||||||
code_in_line = false;
|
|
||||||
loop {
|
|
||||||
line = line.trim_start();
|
|
||||||
if line.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if in_comment {
|
|
||||||
if let Some(i) = line.find("*/") {
|
|
||||||
line = &line[i + 2..];
|
|
||||||
in_comment = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let multi_idx = line.find("/*").unwrap_or_else(|| line.len());
|
|
||||||
let single_idx = line.find("//").unwrap_or_else(|| line.len());
|
|
||||||
code_in_line |= multi_idx > 0 && single_idx > 0;
|
|
||||||
// Implies multi_idx is below line.len()
|
|
||||||
if multi_idx < single_idx {
|
|
||||||
line = &line[multi_idx + 2..];
|
|
||||||
in_comment = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if code_in_line {
|
|
||||||
line_count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if line_count > self.max_lines {
|
|
||||||
span_lint(
|
|
||||||
cx,
|
|
||||||
TOO_MANY_LINES,
|
|
||||||
span,
|
|
||||||
&format!("this function has too many lines ({}/{})", line_count, self.max_lines),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_raw_ptr(
|
|
||||||
cx: &LateContext<'tcx>,
|
|
||||||
unsafety: hir::Unsafety,
|
|
||||||
decl: &'tcx hir::FnDecl<'_>,
|
|
||||||
body: &'tcx hir::Body<'_>,
|
|
||||||
hir_id: hir::HirId,
|
|
||||||
) {
|
|
||||||
let expr = &body.value;
|
|
||||||
if unsafety == hir::Unsafety::Normal && cx.access_levels.is_exported(hir_id) {
|
|
||||||
let raw_ptrs = iter_input_pats(decl, body)
|
|
||||||
.zip(decl.inputs.iter())
|
|
||||||
.filter_map(|(arg, ty)| raw_ptr_arg(arg, ty))
|
|
||||||
.collect::<FxHashSet<_>>();
|
|
||||||
|
|
||||||
if !raw_ptrs.is_empty() {
|
|
||||||
let typeck_results = cx.tcx.typeck_body(body.id());
|
|
||||||
let mut v = DerefVisitor {
|
|
||||||
cx,
|
|
||||||
ptrs: raw_ptrs,
|
|
||||||
typeck_results,
|
|
||||||
};
|
|
||||||
|
|
||||||
intravisit::walk_expr(&mut v, expr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_result_unit_err(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, item_span: Span, fn_header_span: Span) {
|
|
||||||
if_chain! {
|
|
||||||
if !in_external_macro(cx.sess(), item_span);
|
|
||||||
if let hir::FnRetTy::Return(ref ty) = decl.output;
|
|
||||||
let ty = hir_ty_to_ty(cx.tcx, ty);
|
|
||||||
if is_type_diagnostic_item(cx, ty, sym::result_type);
|
|
||||||
if let ty::Adt(_, substs) = ty.kind();
|
|
||||||
let err_ty = substs.type_at(1);
|
|
||||||
if err_ty.is_unit();
|
|
||||||
then {
|
|
||||||
span_lint_and_help(
|
|
||||||
cx,
|
|
||||||
RESULT_UNIT_ERR,
|
|
||||||
fn_header_span,
|
|
||||||
"this returns a `Result<_, ()>",
|
|
||||||
None,
|
|
||||||
"use a custom Error type instead",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_needless_must_use(
|
|
||||||
cx: &LateContext<'_>,
|
|
||||||
decl: &hir::FnDecl<'_>,
|
|
||||||
item_id: hir::HirId,
|
|
||||||
item_span: Span,
|
|
||||||
fn_header_span: Span,
|
|
||||||
attr: &Attribute,
|
|
||||||
) {
|
|
||||||
if in_external_macro(cx.sess(), item_span) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if returns_unit(decl) {
|
|
||||||
span_lint_and_then(
|
|
||||||
cx,
|
|
||||||
MUST_USE_UNIT,
|
|
||||||
fn_header_span,
|
|
||||||
"this unit-returning function has a `#[must_use]` attribute",
|
|
||||||
|diag| {
|
|
||||||
diag.span_suggestion(
|
|
||||||
attr.span,
|
|
||||||
"remove the attribute",
|
|
||||||
"".into(),
|
|
||||||
Applicability::MachineApplicable,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else if !attr.value_str().is_some() && is_must_use_ty(cx, return_ty(cx, item_id)) {
|
|
||||||
span_lint_and_help(
|
|
||||||
cx,
|
|
||||||
DOUBLE_MUST_USE,
|
|
||||||
fn_header_span,
|
|
||||||
"this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`",
|
|
||||||
None,
|
|
||||||
"either add some descriptive text or remove the attribute",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_must_use_candidate<'tcx>(
|
|
||||||
cx: &LateContext<'tcx>,
|
|
||||||
decl: &'tcx hir::FnDecl<'_>,
|
|
||||||
body: &'tcx hir::Body<'_>,
|
|
||||||
item_span: Span,
|
|
||||||
item_id: hir::HirId,
|
|
||||||
fn_span: Span,
|
|
||||||
msg: &str,
|
|
||||||
) {
|
|
||||||
if has_mutable_arg(cx, body)
|
|
||||||
|| mutates_static(cx, body)
|
|
||||||
|| in_external_macro(cx.sess(), item_span)
|
|
||||||
|| returns_unit(decl)
|
|
||||||
|| !cx.access_levels.is_exported(item_id)
|
|
||||||
|| is_must_use_ty(cx, return_ty(cx, item_id))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |diag| {
|
|
||||||
if let Some(snippet) = snippet_opt(cx, fn_span) {
|
|
||||||
diag.span_suggestion(
|
|
||||||
fn_span,
|
|
||||||
"add the attribute",
|
|
||||||
format!("#[must_use] {}", snippet),
|
|
||||||
Applicability::MachineApplicable,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn returns_unit(decl: &hir::FnDecl<'_>) -> bool {
|
|
||||||
match decl.output {
|
|
||||||
hir::FnRetTy::DefaultReturn(_) => true,
|
|
||||||
hir::FnRetTy::Return(ref ty) => match ty.kind {
|
|
||||||
hir::TyKind::Tup(ref tys) => tys.is_empty(),
|
|
||||||
hir::TyKind::Never => true,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_mutable_arg(cx: &LateContext<'_>, body: &hir::Body<'_>) -> bool {
|
|
||||||
let mut tys = FxHashSet::default();
|
|
||||||
body.params.iter().any(|param| is_mutable_pat(cx, ¶m.pat, &mut tys))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_mutable_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, tys: &mut FxHashSet<DefId>) -> bool {
|
|
||||||
if let hir::PatKind::Wild = pat.kind {
|
|
||||||
return false; // ignore `_` patterns
|
|
||||||
}
|
|
||||||
if cx.tcx.has_typeck_results(pat.hir_id.owner.to_def_id()) {
|
|
||||||
is_mutable_ty(cx, &cx.tcx.typeck(pat.hir_id.owner).pat_ty(pat), pat.span, tys)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]];
|
|
||||||
|
|
||||||
fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &mut FxHashSet<DefId>) -> bool {
|
|
||||||
match *ty.kind() {
|
|
||||||
// primitive types are never mutable
|
|
||||||
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false,
|
|
||||||
ty::Adt(ref adt, ref substs) => {
|
|
||||||
tys.insert(adt.did) && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
|
|
||||||
|| KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path))
|
|
||||||
&& substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys))
|
|
||||||
},
|
|
||||||
ty::Tuple(ref substs) => substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)),
|
|
||||||
ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys),
|
|
||||||
ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => {
|
|
||||||
mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys)
|
|
||||||
},
|
|
||||||
// calling something constitutes a side effect, so return true on all callables
|
|
||||||
// also never calls need not be used, so return true for them, too
|
|
||||||
_ => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn raw_ptr_arg(arg: &hir::Param<'_>, ty: &hir::Ty<'_>) -> Option<hir::HirId> {
|
|
||||||
if let (&hir::PatKind::Binding(_, id, _, _), &hir::TyKind::Ptr(_)) = (&arg.pat.kind, &ty.kind) {
|
|
||||||
Some(id)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DerefVisitor<'a, 'tcx> {
|
|
||||||
cx: &'a LateContext<'tcx>,
|
|
||||||
ptrs: FxHashSet<hir::HirId>,
|
|
||||||
typeck_results: &'a ty::TypeckResults<'tcx>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'tcx> intravisit::Visitor<'tcx> for DerefVisitor<'a, 'tcx> {
|
|
||||||
type Map = Map<'tcx>;
|
|
||||||
|
|
||||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
|
|
||||||
match expr.kind {
|
|
||||||
hir::ExprKind::Call(ref f, args) => {
|
|
||||||
let ty = self.typeck_results.expr_ty(f);
|
|
||||||
|
|
||||||
if type_is_unsafe_function(self.cx, ty) {
|
|
||||||
for arg in args {
|
|
||||||
self.check_arg(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hir::ExprKind::MethodCall(_, _, args, _) => {
|
|
||||||
let def_id = self.typeck_results.type_dependent_def_id(expr.hir_id).unwrap();
|
|
||||||
let base_type = self.cx.tcx.type_of(def_id);
|
|
||||||
|
|
||||||
if type_is_unsafe_function(self.cx, base_type) {
|
|
||||||
for arg in args {
|
|
||||||
self.check_arg(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hir::ExprKind::Unary(hir::UnOp::Deref, ref ptr) => self.check_arg(ptr),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
intravisit::walk_expr(self, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
|
|
||||||
intravisit::NestedVisitorMap::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'tcx> DerefVisitor<'a, 'tcx> {
|
|
||||||
fn check_arg(&self, ptr: &hir::Expr<'_>) {
|
|
||||||
if let Some(id) = path_to_local(ptr) {
|
|
||||||
if self.ptrs.contains(&id) {
|
|
||||||
span_lint(
|
|
||||||
self.cx,
|
|
||||||
NOT_UNSAFE_PTR_ARG_DEREF,
|
|
||||||
ptr.span,
|
|
||||||
"this public function dereferences a raw pointer but is not marked `unsafe`",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct StaticMutVisitor<'a, 'tcx> {
|
|
||||||
cx: &'a LateContext<'tcx>,
|
|
||||||
mutates_static: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> {
|
|
||||||
type Map = Map<'tcx>;
|
|
||||||
|
|
||||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
|
|
||||||
use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall};
|
|
||||||
|
|
||||||
if self.mutates_static {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
match expr.kind {
|
|
||||||
Call(_, args) | MethodCall(_, _, args, _) => {
|
|
||||||
let mut tys = FxHashSet::default();
|
|
||||||
for arg in args {
|
|
||||||
if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
|
|
||||||
&& is_mutable_ty(
|
|
||||||
self.cx,
|
|
||||||
self.cx.tcx.typeck(arg.hir_id.owner).expr_ty(arg),
|
|
||||||
arg.span,
|
|
||||||
&mut tys,
|
|
||||||
)
|
|
||||||
&& is_mutated_static(arg)
|
|
||||||
{
|
|
||||||
self.mutates_static = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tys.clear();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Assign(ref target, ..) | AssignOp(_, ref target, _) | AddrOf(_, hir::Mutability::Mut, ref target) => {
|
|
||||||
self.mutates_static |= is_mutated_static(target)
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
|
|
||||||
intravisit::NestedVisitorMap::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_mutated_static(e: &hir::Expr<'_>) -> bool {
|
|
||||||
use hir::ExprKind::{Field, Index, Path};
|
|
||||||
|
|
||||||
match e.kind {
|
|
||||||
Path(QPath::Resolved(_, path)) => !matches!(path.res, Res::Local(_)),
|
|
||||||
Path(_) => true,
|
|
||||||
Field(ref inner, _) | Index(ref inner, _) => is_mutated_static(inner),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool {
|
|
||||||
let mut v = StaticMutVisitor {
|
|
||||||
cx,
|
|
||||||
mutates_static: false,
|
|
||||||
};
|
|
||||||
intravisit::walk_expr(&mut v, &body.value);
|
|
||||||
v.mutates_static
|
|
||||||
}
|
|
267
src/tools/clippy/clippy_lints/src/functions/mod.rs
Normal file
267
src/tools/clippy/clippy_lints/src/functions/mod.rs
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
mod must_use;
|
||||||
|
mod not_unsafe_ptr_arg_deref;
|
||||||
|
mod result_unit_err;
|
||||||
|
mod too_many_arguments;
|
||||||
|
mod too_many_lines;
|
||||||
|
|
||||||
|
use rustc_hir as hir;
|
||||||
|
use rustc_hir::intravisit;
|
||||||
|
use rustc_lint::{LateContext, LateLintPass};
|
||||||
|
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||||
|
use rustc_span::Span;
|
||||||
|
|
||||||
|
declare_clippy_lint! {
|
||||||
|
/// **What it does:** Checks for functions with too many parameters.
|
||||||
|
///
|
||||||
|
/// **Why is this bad?** Functions with lots of parameters are considered bad
|
||||||
|
/// style and reduce readability (“what does the 5th parameter mean?”). Consider
|
||||||
|
/// grouping some parameters into a new type.
|
||||||
|
///
|
||||||
|
/// **Known problems:** None.
|
||||||
|
///
|
||||||
|
/// **Example:**
|
||||||
|
/// ```rust
|
||||||
|
/// # struct Color;
|
||||||
|
/// fn foo(x: u32, y: u32, name: &str, c: Color, w: f32, h: f32, a: f32, b: f32) {
|
||||||
|
/// // ..
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub TOO_MANY_ARGUMENTS,
|
||||||
|
complexity,
|
||||||
|
"functions with too many arguments"
|
||||||
|
}
|
||||||
|
|
||||||
|
declare_clippy_lint! {
|
||||||
|
/// **What it does:** Checks for functions with a large amount of lines.
|
||||||
|
///
|
||||||
|
/// **Why is this bad?** Functions with a lot of lines are harder to understand
|
||||||
|
/// due to having to look at a larger amount of code to understand what the
|
||||||
|
/// function is doing. Consider splitting the body of the function into
|
||||||
|
/// multiple functions.
|
||||||
|
///
|
||||||
|
/// **Known problems:** None.
|
||||||
|
///
|
||||||
|
/// **Example:**
|
||||||
|
/// ```rust
|
||||||
|
/// fn im_too_long() {
|
||||||
|
/// println!("");
|
||||||
|
/// // ... 100 more LoC
|
||||||
|
/// println!("");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub TOO_MANY_LINES,
|
||||||
|
pedantic,
|
||||||
|
"functions with too many lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
declare_clippy_lint! {
|
||||||
|
/// **What it does:** Checks for public functions that dereference raw pointer
|
||||||
|
/// arguments but are not marked `unsafe`.
|
||||||
|
///
|
||||||
|
/// **Why is this bad?** The function should probably be marked `unsafe`, since
|
||||||
|
/// for an arbitrary raw pointer, there is no way of telling for sure if it is
|
||||||
|
/// valid.
|
||||||
|
///
|
||||||
|
/// **Known problems:**
|
||||||
|
///
|
||||||
|
/// * It does not check functions recursively so if the pointer is passed to a
|
||||||
|
/// private non-`unsafe` function which does the dereferencing, the lint won't
|
||||||
|
/// trigger.
|
||||||
|
/// * It only checks for arguments whose type are raw pointers, not raw pointers
|
||||||
|
/// got from an argument in some other way (`fn foo(bar: &[*const u8])` or
|
||||||
|
/// `some_argument.get_raw_ptr()`).
|
||||||
|
///
|
||||||
|
/// **Example:**
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// // Bad
|
||||||
|
/// pub fn foo(x: *const u8) {
|
||||||
|
/// println!("{}", unsafe { *x });
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Good
|
||||||
|
/// pub unsafe fn foo(x: *const u8) {
|
||||||
|
/// println!("{}", unsafe { *x });
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub NOT_UNSAFE_PTR_ARG_DEREF,
|
||||||
|
correctness,
|
||||||
|
"public functions dereferencing raw pointer arguments but not marked `unsafe`"
|
||||||
|
}
|
||||||
|
|
||||||
|
declare_clippy_lint! {
|
||||||
|
/// **What it does:** Checks for a [`#[must_use]`] attribute on
|
||||||
|
/// unit-returning functions and methods.
|
||||||
|
///
|
||||||
|
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
|
||||||
|
///
|
||||||
|
/// **Why is this bad?** Unit values are useless. The attribute is likely
|
||||||
|
/// a remnant of a refactoring that removed the return type.
|
||||||
|
///
|
||||||
|
/// **Known problems:** None.
|
||||||
|
///
|
||||||
|
/// **Examples:**
|
||||||
|
/// ```rust
|
||||||
|
/// #[must_use]
|
||||||
|
/// fn useless() { }
|
||||||
|
/// ```
|
||||||
|
pub MUST_USE_UNIT,
|
||||||
|
style,
|
||||||
|
"`#[must_use]` attribute on a unit-returning function / method"
|
||||||
|
}
|
||||||
|
|
||||||
|
declare_clippy_lint! {
|
||||||
|
/// **What it does:** Checks for a [`#[must_use]`] attribute without
|
||||||
|
/// further information on functions and methods that return a type already
|
||||||
|
/// marked as `#[must_use]`.
|
||||||
|
///
|
||||||
|
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
|
||||||
|
///
|
||||||
|
/// **Why is this bad?** The attribute isn't needed. Not using the result
|
||||||
|
/// will already be reported. Alternatively, one can add some text to the
|
||||||
|
/// attribute to improve the lint message.
|
||||||
|
///
|
||||||
|
/// **Known problems:** None.
|
||||||
|
///
|
||||||
|
/// **Examples:**
|
||||||
|
/// ```rust
|
||||||
|
/// #[must_use]
|
||||||
|
/// fn double_must_use() -> Result<(), ()> {
|
||||||
|
/// unimplemented!();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub DOUBLE_MUST_USE,
|
||||||
|
style,
|
||||||
|
"`#[must_use]` attribute on a `#[must_use]`-returning function / method"
|
||||||
|
}
|
||||||
|
|
||||||
|
declare_clippy_lint! {
|
||||||
|
/// **What it does:** Checks for public functions that have no
|
||||||
|
/// [`#[must_use]`] attribute, but return something not already marked
|
||||||
|
/// must-use, have no mutable arg and mutate no statics.
|
||||||
|
///
|
||||||
|
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
|
||||||
|
///
|
||||||
|
/// **Why is this bad?** Not bad at all, this lint just shows places where
|
||||||
|
/// you could add the attribute.
|
||||||
|
///
|
||||||
|
/// **Known problems:** The lint only checks the arguments for mutable
|
||||||
|
/// types without looking if they are actually changed. On the other hand,
|
||||||
|
/// it also ignores a broad range of potentially interesting side effects,
|
||||||
|
/// because we cannot decide whether the programmer intends the function to
|
||||||
|
/// be called for the side effect or the result. Expect many false
|
||||||
|
/// positives. At least we don't lint if the result type is unit or already
|
||||||
|
/// `#[must_use]`.
|
||||||
|
///
|
||||||
|
/// **Examples:**
|
||||||
|
/// ```rust
|
||||||
|
/// // this could be annotated with `#[must_use]`.
|
||||||
|
/// fn id<T>(t: T) -> T { t }
|
||||||
|
/// ```
|
||||||
|
pub MUST_USE_CANDIDATE,
|
||||||
|
pedantic,
|
||||||
|
"function or method that could take a `#[must_use]` attribute"
|
||||||
|
}
|
||||||
|
|
||||||
|
declare_clippy_lint! {
|
||||||
|
/// **What it does:** Checks for public functions that return a `Result`
|
||||||
|
/// with an `Err` type of `()`. It suggests using a custom type that
|
||||||
|
/// implements `std::error::Error`.
|
||||||
|
///
|
||||||
|
/// **Why is this bad?** Unit does not implement `Error` and carries no
|
||||||
|
/// further information about what went wrong.
|
||||||
|
///
|
||||||
|
/// **Known problems:** Of course, this lint assumes that `Result` is used
|
||||||
|
/// for a fallible operation (which is after all the intended use). However
|
||||||
|
/// code may opt to (mis)use it as a basic two-variant-enum. In that case,
|
||||||
|
/// the suggestion is misguided, and the code should use a custom enum
|
||||||
|
/// instead.
|
||||||
|
///
|
||||||
|
/// **Examples:**
|
||||||
|
/// ```rust
|
||||||
|
/// pub fn read_u8() -> Result<u8, ()> { Err(()) }
|
||||||
|
/// ```
|
||||||
|
/// should become
|
||||||
|
/// ```rust,should_panic
|
||||||
|
/// use std::fmt;
|
||||||
|
///
|
||||||
|
/// #[derive(Debug)]
|
||||||
|
/// pub struct EndOfStream;
|
||||||
|
///
|
||||||
|
/// impl fmt::Display for EndOfStream {
|
||||||
|
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
/// write!(f, "End of Stream")
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl std::error::Error for EndOfStream { }
|
||||||
|
///
|
||||||
|
/// pub fn read_u8() -> Result<u8, EndOfStream> { Err(EndOfStream) }
|
||||||
|
///# fn main() {
|
||||||
|
///# read_u8().unwrap();
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Note that there are crates that simplify creating the error type, e.g.
|
||||||
|
/// [`thiserror`](https://docs.rs/thiserror).
|
||||||
|
pub RESULT_UNIT_ERR,
|
||||||
|
style,
|
||||||
|
"public function returning `Result` with an `Err` type of `()`"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct Functions {
|
||||||
|
too_many_arguments_threshold: u64,
|
||||||
|
too_many_lines_threshold: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Functions {
|
||||||
|
pub fn new(too_many_arguments_threshold: u64, too_many_lines_threshold: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
too_many_arguments_threshold,
|
||||||
|
too_many_lines_threshold,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_lint_pass!(Functions => [
|
||||||
|
TOO_MANY_ARGUMENTS,
|
||||||
|
TOO_MANY_LINES,
|
||||||
|
NOT_UNSAFE_PTR_ARG_DEREF,
|
||||||
|
MUST_USE_UNIT,
|
||||||
|
DOUBLE_MUST_USE,
|
||||||
|
MUST_USE_CANDIDATE,
|
||||||
|
RESULT_UNIT_ERR,
|
||||||
|
]);
|
||||||
|
|
||||||
|
impl<'tcx> LateLintPass<'tcx> for Functions {
|
||||||
|
fn check_fn(
|
||||||
|
&mut self,
|
||||||
|
cx: &LateContext<'tcx>,
|
||||||
|
kind: intravisit::FnKind<'tcx>,
|
||||||
|
decl: &'tcx hir::FnDecl<'_>,
|
||||||
|
body: &'tcx hir::Body<'_>,
|
||||||
|
span: Span,
|
||||||
|
hir_id: hir::HirId,
|
||||||
|
) {
|
||||||
|
too_many_arguments::check_fn(cx, kind, decl, span, hir_id, self.too_many_arguments_threshold);
|
||||||
|
too_many_lines::check_fn(cx, span, body, self.too_many_lines_threshold);
|
||||||
|
not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, hir_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
|
||||||
|
must_use::check_item(cx, item);
|
||||||
|
result_unit_err::check_item(cx, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
|
||||||
|
must_use::check_impl_item(cx, item);
|
||||||
|
result_unit_err::check_impl_item(cx, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
|
||||||
|
too_many_arguments::check_trait_item(cx, item, self.too_many_arguments_threshold);
|
||||||
|
not_unsafe_ptr_arg_deref::check_trait_item(cx, item);
|
||||||
|
must_use::check_trait_item(cx, item);
|
||||||
|
result_unit_err::check_trait_item(cx, item);
|
||||||
|
}
|
||||||
|
}
|
272
src/tools/clippy/clippy_lints/src/functions/must_use.rs
Normal file
272
src/tools/clippy/clippy_lints/src/functions/must_use.rs
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
use rustc_ast::ast::Attribute;
|
||||||
|
use rustc_errors::Applicability;
|
||||||
|
use rustc_hir::def_id::DefIdSet;
|
||||||
|
use rustc_hir::{self as hir, def::Res, intravisit, QPath};
|
||||||
|
use rustc_lint::{LateContext, LintContext};
|
||||||
|
use rustc_middle::{
|
||||||
|
hir::map::Map,
|
||||||
|
lint::in_external_macro,
|
||||||
|
ty::{self, Ty},
|
||||||
|
};
|
||||||
|
use rustc_span::{sym, Span};
|
||||||
|
|
||||||
|
use clippy_utils::attrs::is_proc_macro;
|
||||||
|
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
|
||||||
|
use clippy_utils::source::snippet_opt;
|
||||||
|
use clippy_utils::ty::is_must_use_ty;
|
||||||
|
use clippy_utils::{match_def_path, must_use_attr, return_ty, trait_ref_of_method};
|
||||||
|
|
||||||
|
use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT};
|
||||||
|
|
||||||
|
pub(super) fn check_item(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
|
||||||
|
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||||
|
let attr = must_use_attr(attrs);
|
||||||
|
if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind {
|
||||||
|
let is_public = cx.access_levels.is_exported(item.hir_id());
|
||||||
|
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||||
|
if let Some(attr) = attr {
|
||||||
|
check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
|
||||||
|
return;
|
||||||
|
} else if is_public && !is_proc_macro(cx.sess(), attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) {
|
||||||
|
check_must_use_candidate(
|
||||||
|
cx,
|
||||||
|
sig.decl,
|
||||||
|
cx.tcx.hir().body(*body_id),
|
||||||
|
item.span,
|
||||||
|
item.hir_id(),
|
||||||
|
item.span.with_hi(sig.decl.output.span().hi()),
|
||||||
|
"this function could have a `#[must_use]` attribute",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn check_impl_item(cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
|
||||||
|
if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind {
|
||||||
|
let is_public = cx.access_levels.is_exported(item.hir_id());
|
||||||
|
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||||
|
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||||
|
let attr = must_use_attr(attrs);
|
||||||
|
if let Some(attr) = attr {
|
||||||
|
check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
|
||||||
|
} else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.hir_id()).is_none() {
|
||||||
|
check_must_use_candidate(
|
||||||
|
cx,
|
||||||
|
sig.decl,
|
||||||
|
cx.tcx.hir().body(*body_id),
|
||||||
|
item.span,
|
||||||
|
item.hir_id(),
|
||||||
|
item.span.with_hi(sig.decl.output.span().hi()),
|
||||||
|
"this method could have a `#[must_use]` attribute",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn check_trait_item(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
|
||||||
|
if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind {
|
||||||
|
let is_public = cx.access_levels.is_exported(item.hir_id());
|
||||||
|
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||||
|
|
||||||
|
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||||
|
let attr = must_use_attr(attrs);
|
||||||
|
if let Some(attr) = attr {
|
||||||
|
check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
|
||||||
|
} else if let hir::TraitFn::Provided(eid) = *eid {
|
||||||
|
let body = cx.tcx.hir().body(eid);
|
||||||
|
if attr.is_none() && is_public && !is_proc_macro(cx.sess(), attrs) {
|
||||||
|
check_must_use_candidate(
|
||||||
|
cx,
|
||||||
|
sig.decl,
|
||||||
|
body,
|
||||||
|
item.span,
|
||||||
|
item.hir_id(),
|
||||||
|
item.span.with_hi(sig.decl.output.span().hi()),
|
||||||
|
"this method could have a `#[must_use]` attribute",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_needless_must_use(
|
||||||
|
cx: &LateContext<'_>,
|
||||||
|
decl: &hir::FnDecl<'_>,
|
||||||
|
item_id: hir::HirId,
|
||||||
|
item_span: Span,
|
||||||
|
fn_header_span: Span,
|
||||||
|
attr: &Attribute,
|
||||||
|
) {
|
||||||
|
if in_external_macro(cx.sess(), item_span) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if returns_unit(decl) {
|
||||||
|
span_lint_and_then(
|
||||||
|
cx,
|
||||||
|
MUST_USE_UNIT,
|
||||||
|
fn_header_span,
|
||||||
|
"this unit-returning function has a `#[must_use]` attribute",
|
||||||
|
|diag| {
|
||||||
|
diag.span_suggestion(
|
||||||
|
attr.span,
|
||||||
|
"remove the attribute",
|
||||||
|
"".into(),
|
||||||
|
Applicability::MachineApplicable,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if attr.value_str().is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) {
|
||||||
|
span_lint_and_help(
|
||||||
|
cx,
|
||||||
|
DOUBLE_MUST_USE,
|
||||||
|
fn_header_span,
|
||||||
|
"this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`",
|
||||||
|
None,
|
||||||
|
"either add some descriptive text or remove the attribute",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_must_use_candidate<'tcx>(
|
||||||
|
cx: &LateContext<'tcx>,
|
||||||
|
decl: &'tcx hir::FnDecl<'_>,
|
||||||
|
body: &'tcx hir::Body<'_>,
|
||||||
|
item_span: Span,
|
||||||
|
item_id: hir::HirId,
|
||||||
|
fn_span: Span,
|
||||||
|
msg: &str,
|
||||||
|
) {
|
||||||
|
if has_mutable_arg(cx, body)
|
||||||
|
|| mutates_static(cx, body)
|
||||||
|
|| in_external_macro(cx.sess(), item_span)
|
||||||
|
|| returns_unit(decl)
|
||||||
|
|| !cx.access_levels.is_exported(item_id)
|
||||||
|
|| is_must_use_ty(cx, return_ty(cx, item_id))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |diag| {
|
||||||
|
if let Some(snippet) = snippet_opt(cx, fn_span) {
|
||||||
|
diag.span_suggestion(
|
||||||
|
fn_span,
|
||||||
|
"add the attribute",
|
||||||
|
format!("#[must_use] {}", snippet),
|
||||||
|
Applicability::MachineApplicable,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn returns_unit(decl: &hir::FnDecl<'_>) -> bool {
|
||||||
|
match decl.output {
|
||||||
|
hir::FnRetTy::DefaultReturn(_) => true,
|
||||||
|
hir::FnRetTy::Return(ty) => match ty.kind {
|
||||||
|
hir::TyKind::Tup(tys) => tys.is_empty(),
|
||||||
|
hir::TyKind::Never => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_mutable_arg(cx: &LateContext<'_>, body: &hir::Body<'_>) -> bool {
|
||||||
|
let mut tys = DefIdSet::default();
|
||||||
|
body.params.iter().any(|param| is_mutable_pat(cx, param.pat, &mut tys))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_mutable_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, tys: &mut DefIdSet) -> bool {
|
||||||
|
if let hir::PatKind::Wild = pat.kind {
|
||||||
|
return false; // ignore `_` patterns
|
||||||
|
}
|
||||||
|
if cx.tcx.has_typeck_results(pat.hir_id.owner.to_def_id()) {
|
||||||
|
is_mutable_ty(cx, cx.tcx.typeck(pat.hir_id.owner).pat_ty(pat), pat.span, tys)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]];
|
||||||
|
|
||||||
|
fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &mut DefIdSet) -> bool {
|
||||||
|
match *ty.kind() {
|
||||||
|
// primitive types are never mutable
|
||||||
|
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false,
|
||||||
|
ty::Adt(adt, substs) => {
|
||||||
|
tys.insert(adt.did) && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
|
||||||
|
|| KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path))
|
||||||
|
&& substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys))
|
||||||
|
},
|
||||||
|
ty::Tuple(substs) => substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)),
|
||||||
|
ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys),
|
||||||
|
ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => {
|
||||||
|
mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys)
|
||||||
|
},
|
||||||
|
// calling something constitutes a side effect, so return true on all callables
|
||||||
|
// also never calls need not be used, so return true for them, too
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StaticMutVisitor<'a, 'tcx> {
|
||||||
|
cx: &'a LateContext<'tcx>,
|
||||||
|
mutates_static: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> {
|
||||||
|
type Map = Map<'tcx>;
|
||||||
|
|
||||||
|
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
|
||||||
|
use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall};
|
||||||
|
|
||||||
|
if self.mutates_static {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match expr.kind {
|
||||||
|
Call(_, args) | MethodCall(_, _, args, _) => {
|
||||||
|
let mut tys = DefIdSet::default();
|
||||||
|
for arg in args {
|
||||||
|
if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
|
||||||
|
&& is_mutable_ty(
|
||||||
|
self.cx,
|
||||||
|
self.cx.tcx.typeck(arg.hir_id.owner).expr_ty(arg),
|
||||||
|
arg.span,
|
||||||
|
&mut tys,
|
||||||
|
)
|
||||||
|
&& is_mutated_static(arg)
|
||||||
|
{
|
||||||
|
self.mutates_static = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tys.clear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Assign(target, ..) | AssignOp(_, target, _) | AddrOf(_, hir::Mutability::Mut, target) => {
|
||||||
|
self.mutates_static |= is_mutated_static(target)
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
|
||||||
|
intravisit::NestedVisitorMap::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_mutated_static(e: &hir::Expr<'_>) -> bool {
|
||||||
|
use hir::ExprKind::{Field, Index, Path};
|
||||||
|
|
||||||
|
match e.kind {
|
||||||
|
Path(QPath::Resolved(_, path)) => !matches!(path.res, Res::Local(_)),
|
||||||
|
Path(_) => true,
|
||||||
|
Field(inner, _) | Index(inner, _) => is_mutated_static(inner),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool {
|
||||||
|
let mut v = StaticMutVisitor {
|
||||||
|
cx,
|
||||||
|
mutates_static: false,
|
||||||
|
};
|
||||||
|
intravisit::walk_expr(&mut v, &body.value);
|
||||||
|
v.mutates_static
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
use rustc_hir::{self as hir, intravisit, HirIdSet};
|
||||||
|
use rustc_lint::LateContext;
|
||||||
|
use rustc_middle::{hir::map::Map, ty};
|
||||||
|
|
||||||
|
use clippy_utils::diagnostics::span_lint;
|
||||||
|
use clippy_utils::ty::type_is_unsafe_function;
|
||||||
|
use clippy_utils::{iter_input_pats, path_to_local};
|
||||||
|
|
||||||
|
use super::NOT_UNSAFE_PTR_ARG_DEREF;
|
||||||
|
|
||||||
|
pub(super) fn check_fn(
|
||||||
|
cx: &LateContext<'tcx>,
|
||||||
|
kind: intravisit::FnKind<'tcx>,
|
||||||
|
decl: &'tcx hir::FnDecl<'tcx>,
|
||||||
|
body: &'tcx hir::Body<'tcx>,
|
||||||
|
hir_id: hir::HirId,
|
||||||
|
) {
|
||||||
|
let unsafety = match kind {
|
||||||
|
intravisit::FnKind::ItemFn(_, _, hir::FnHeader { unsafety, .. }, _) => unsafety,
|
||||||
|
intravisit::FnKind::Method(_, sig, _) => sig.header.unsafety,
|
||||||
|
intravisit::FnKind::Closure => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
check_raw_ptr(cx, unsafety, decl, body, hir_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn check_trait_item(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
|
||||||
|
if let hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(eid)) = item.kind {
|
||||||
|
let body = cx.tcx.hir().body(eid);
|
||||||
|
check_raw_ptr(cx, sig.header.unsafety, sig.decl, body, item.hir_id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_raw_ptr(
|
||||||
|
cx: &LateContext<'tcx>,
|
||||||
|
unsafety: hir::Unsafety,
|
||||||
|
decl: &'tcx hir::FnDecl<'tcx>,
|
||||||
|
body: &'tcx hir::Body<'tcx>,
|
||||||
|
hir_id: hir::HirId,
|
||||||
|
) {
|
||||||
|
let expr = &body.value;
|
||||||
|
if unsafety == hir::Unsafety::Normal && cx.access_levels.is_exported(hir_id) {
|
||||||
|
let raw_ptrs = iter_input_pats(decl, body)
|
||||||
|
.zip(decl.inputs.iter())
|
||||||
|
.filter_map(|(arg, ty)| raw_ptr_arg(arg, ty))
|
||||||
|
.collect::<HirIdSet>();
|
||||||
|
|
||||||
|
if !raw_ptrs.is_empty() {
|
||||||
|
let typeck_results = cx.tcx.typeck_body(body.id());
|
||||||
|
let mut v = DerefVisitor {
|
||||||
|
cx,
|
||||||
|
ptrs: raw_ptrs,
|
||||||
|
typeck_results,
|
||||||
|
};
|
||||||
|
|
||||||
|
intravisit::walk_expr(&mut v, expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn raw_ptr_arg(arg: &hir::Param<'_>, ty: &hir::Ty<'_>) -> Option<hir::HirId> {
|
||||||
|
if let (&hir::PatKind::Binding(_, id, _, _), &hir::TyKind::Ptr(_)) = (&arg.pat.kind, &ty.kind) {
|
||||||
|
Some(id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DerefVisitor<'a, 'tcx> {
|
||||||
|
cx: &'a LateContext<'tcx>,
|
||||||
|
ptrs: HirIdSet,
|
||||||
|
typeck_results: &'a ty::TypeckResults<'tcx>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'tcx> intravisit::Visitor<'tcx> for DerefVisitor<'a, 'tcx> {
|
||||||
|
type Map = Map<'tcx>;
|
||||||
|
|
||||||
|
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
|
||||||
|
match expr.kind {
|
||||||
|
hir::ExprKind::Call(f, args) => {
|
||||||
|
let ty = self.typeck_results.expr_ty(f);
|
||||||
|
|
||||||
|
if type_is_unsafe_function(self.cx, ty) {
|
||||||
|
for arg in args {
|
||||||
|
self.check_arg(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hir::ExprKind::MethodCall(_, _, args, _) => {
|
||||||
|
let def_id = self.typeck_results.type_dependent_def_id(expr.hir_id).unwrap();
|
||||||
|
let base_type = self.cx.tcx.type_of(def_id);
|
||||||
|
|
||||||
|
if type_is_unsafe_function(self.cx, base_type) {
|
||||||
|
for arg in args {
|
||||||
|
self.check_arg(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hir::ExprKind::Unary(hir::UnOp::Deref, ptr) => self.check_arg(ptr),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
intravisit::walk_expr(self, expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
|
||||||
|
intravisit::NestedVisitorMap::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'tcx> DerefVisitor<'a, 'tcx> {
|
||||||
|
fn check_arg(&self, ptr: &hir::Expr<'_>) {
|
||||||
|
if let Some(id) = path_to_local(ptr) {
|
||||||
|
if self.ptrs.contains(&id) {
|
||||||
|
span_lint(
|
||||||
|
self.cx,
|
||||||
|
NOT_UNSAFE_PTR_ARG_DEREF,
|
||||||
|
ptr.span,
|
||||||
|
"this public function dereferences a raw pointer but is not marked `unsafe`",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
use rustc_hir as hir;
|
||||||
|
use rustc_lint::{LateContext, LintContext};
|
||||||
|
use rustc_middle::lint::in_external_macro;
|
||||||
|
use rustc_middle::ty;
|
||||||
|
use rustc_span::{sym, Span};
|
||||||
|
use rustc_typeck::hir_ty_to_ty;
|
||||||
|
|
||||||
|
use if_chain::if_chain;
|
||||||
|
|
||||||
|
use clippy_utils::diagnostics::span_lint_and_help;
|
||||||
|
use clippy_utils::trait_ref_of_method;
|
||||||
|
use clippy_utils::ty::is_type_diagnostic_item;
|
||||||
|
|
||||||
|
use super::RESULT_UNIT_ERR;
|
||||||
|
|
||||||
|
pub(super) fn check_item(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
|
||||||
|
if let hir::ItemKind::Fn(ref sig, ref _generics, _) = item.kind {
|
||||||
|
let is_public = cx.access_levels.is_exported(item.hir_id());
|
||||||
|
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||||
|
if is_public {
|
||||||
|
check_result_unit_err(cx, sig.decl, item.span, fn_header_span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn check_impl_item(cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
|
||||||
|
if let hir::ImplItemKind::Fn(ref sig, _) = item.kind {
|
||||||
|
let is_public = cx.access_levels.is_exported(item.hir_id());
|
||||||
|
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||||
|
if is_public && trait_ref_of_method(cx, item.hir_id()).is_none() {
|
||||||
|
check_result_unit_err(cx, sig.decl, item.span, fn_header_span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn check_trait_item(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
|
||||||
|
if let hir::TraitItemKind::Fn(ref sig, _) = item.kind {
|
||||||
|
let is_public = cx.access_levels.is_exported(item.hir_id());
|
||||||
|
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||||
|
if is_public {
|
||||||
|
check_result_unit_err(cx, sig.decl, item.span, fn_header_span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_result_unit_err(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, item_span: Span, fn_header_span: Span) {
|
||||||
|
if_chain! {
|
||||||
|
if !in_external_macro(cx.sess(), item_span);
|
||||||
|
if let hir::FnRetTy::Return(ty) = decl.output;
|
||||||
|
let ty = hir_ty_to_ty(cx.tcx, ty);
|
||||||
|
if is_type_diagnostic_item(cx, ty, sym::result_type);
|
||||||
|
if let ty::Adt(_, substs) = ty.kind();
|
||||||
|
let err_ty = substs.type_at(1);
|
||||||
|
if err_ty.is_unit();
|
||||||
|
then {
|
||||||
|
span_lint_and_help(
|
||||||
|
cx,
|
||||||
|
RESULT_UNIT_ERR,
|
||||||
|
fn_header_span,
|
||||||
|
"this returns a `Result<_, ()>`",
|
||||||
|
None,
|
||||||
|
"use a custom `Error` type instead",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
use rustc_hir::{self as hir, intravisit};
|
||||||
|
use rustc_lint::LateContext;
|
||||||
|
use rustc_span::Span;
|
||||||
|
use rustc_target::spec::abi::Abi;
|
||||||
|
|
||||||
|
use clippy_utils::diagnostics::span_lint;
|
||||||
|
use clippy_utils::is_trait_impl_item;
|
||||||
|
|
||||||
|
use super::TOO_MANY_ARGUMENTS;
|
||||||
|
|
||||||
|
pub(super) fn check_fn(
|
||||||
|
cx: &LateContext<'tcx>,
|
||||||
|
kind: intravisit::FnKind<'tcx>,
|
||||||
|
decl: &'tcx hir::FnDecl<'_>,
|
||||||
|
span: Span,
|
||||||
|
hir_id: hir::HirId,
|
||||||
|
too_many_arguments_threshold: u64,
|
||||||
|
) {
|
||||||
|
// don't warn for implementations, it's not their fault
|
||||||
|
if !is_trait_impl_item(cx, hir_id) {
|
||||||
|
// don't lint extern functions decls, it's not their fault either
|
||||||
|
match kind {
|
||||||
|
intravisit::FnKind::Method(
|
||||||
|
_,
|
||||||
|
&hir::FnSig {
|
||||||
|
header: hir::FnHeader { abi: Abi::Rust, .. },
|
||||||
|
..
|
||||||
|
},
|
||||||
|
_,
|
||||||
|
)
|
||||||
|
| intravisit::FnKind::ItemFn(_, _, hir::FnHeader { abi: Abi::Rust, .. }, _) => check_arg_number(
|
||||||
|
cx,
|
||||||
|
decl,
|
||||||
|
span.with_hi(decl.output.span().hi()),
|
||||||
|
too_many_arguments_threshold,
|
||||||
|
),
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn check_trait_item(
|
||||||
|
cx: &LateContext<'tcx>,
|
||||||
|
item: &'tcx hir::TraitItem<'_>,
|
||||||
|
too_many_arguments_threshold: u64,
|
||||||
|
) {
|
||||||
|
if let hir::TraitItemKind::Fn(ref sig, _) = item.kind {
|
||||||
|
// don't lint extern functions decls, it's not their fault
|
||||||
|
if sig.header.abi == Abi::Rust {
|
||||||
|
check_arg_number(
|
||||||
|
cx,
|
||||||
|
sig.decl,
|
||||||
|
item.span.with_hi(sig.decl.output.span().hi()),
|
||||||
|
too_many_arguments_threshold,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_arg_number(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, fn_span: Span, too_many_arguments_threshold: u64) {
|
||||||
|
let args = decl.inputs.len() as u64;
|
||||||
|
if args > too_many_arguments_threshold {
|
||||||
|
span_lint(
|
||||||
|
cx,
|
||||||
|
TOO_MANY_ARGUMENTS,
|
||||||
|
fn_span,
|
||||||
|
&format!(
|
||||||
|
"this function has too many arguments ({}/{})",
|
||||||
|
args, too_many_arguments_threshold
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
use rustc_hir as hir;
|
||||||
|
use rustc_lint::{LateContext, LintContext};
|
||||||
|
use rustc_middle::lint::in_external_macro;
|
||||||
|
use rustc_span::Span;
|
||||||
|
|
||||||
|
use clippy_utils::diagnostics::span_lint;
|
||||||
|
use clippy_utils::source::snippet;
|
||||||
|
|
||||||
|
use super::TOO_MANY_LINES;
|
||||||
|
|
||||||
|
pub(super) fn check_fn(cx: &LateContext<'_>, span: Span, body: &'tcx hir::Body<'_>, too_many_lines_threshold: u64) {
|
||||||
|
if in_external_macro(cx.sess(), span) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let code_snippet = snippet(cx, body.value.span, "..");
|
||||||
|
let mut line_count: u64 = 0;
|
||||||
|
let mut in_comment = false;
|
||||||
|
let mut code_in_line;
|
||||||
|
|
||||||
|
// Skip the surrounding function decl.
|
||||||
|
let start_brace_idx = code_snippet.find('{').map_or(0, |i| i + 1);
|
||||||
|
let end_brace_idx = code_snippet.rfind('}').unwrap_or_else(|| code_snippet.len());
|
||||||
|
let function_lines = code_snippet[start_brace_idx..end_brace_idx].lines();
|
||||||
|
|
||||||
|
for mut line in function_lines {
|
||||||
|
code_in_line = false;
|
||||||
|
loop {
|
||||||
|
line = line.trim_start();
|
||||||
|
if line.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if in_comment {
|
||||||
|
if let Some(i) = line.find("*/") {
|
||||||
|
line = &line[i + 2..];
|
||||||
|
in_comment = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let multi_idx = line.find("/*").unwrap_or_else(|| line.len());
|
||||||
|
let single_idx = line.find("//").unwrap_or_else(|| line.len());
|
||||||
|
code_in_line |= multi_idx > 0 && single_idx > 0;
|
||||||
|
// Implies multi_idx is below line.len()
|
||||||
|
if multi_idx < single_idx {
|
||||||
|
line = &line[multi_idx + 2..];
|
||||||
|
in_comment = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if code_in_line {
|
||||||
|
line_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if line_count > too_many_lines_threshold {
|
||||||
|
span_lint(
|
||||||
|
cx,
|
||||||
|
TOO_MANY_LINES,
|
||||||
|
span,
|
||||||
|
&format!(
|
||||||
|
"this function has too many lines ({}/{})",
|
||||||
|
line_count, too_many_lines_threshold
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,7 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for GetLastWithLen {
|
||||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
377
src/tools/clippy/clippy_lints/src/implicit_hasher.rs
Normal file
377
src/tools/clippy/clippy_lints/src/implicit_hasher.rs
Normal file
|
@ -0,0 +1,377 @@
|
||||||
|
#![allow(rustc::default_hash_types)]
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use rustc_errors::DiagnosticBuilder;
|
||||||
|
use rustc_hir as hir;
|
||||||
|
use rustc_hir::intravisit::{walk_body, walk_expr, walk_ty, NestedVisitorMap, Visitor};
|
||||||
|
use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind};
|
||||||
|
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||||
|
use rustc_middle::hir::map::Map;
|
||||||
|
use rustc_middle::lint::in_external_macro;
|
||||||
|
use rustc_middle::ty::{Ty, TyS, TypeckResults};
|
||||||
|
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||||
|
use rustc_span::source_map::Span;
|
||||||
|
use rustc_span::symbol::sym;
|
||||||
|
use rustc_typeck::hir_ty_to_ty;
|
||||||
|
|
||||||
|
use if_chain::if_chain;
|
||||||
|
|
||||||
|
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
|
||||||
|
use clippy_utils::paths;
|
||||||
|
use clippy_utils::source::{snippet, snippet_opt};
|
||||||
|
use clippy_utils::ty::is_type_diagnostic_item;
|
||||||
|
use clippy_utils::{differing_macro_contexts, match_path};
|
||||||
|
|
||||||
|
declare_clippy_lint! {
|
||||||
|
/// **What it does:** Checks for public `impl` or `fn` missing generalization
|
||||||
|
/// over different hashers and implicitly defaulting to the default hashing
|
||||||
|
/// algorithm (`SipHash`).
|
||||||
|
///
|
||||||
|
/// **Why is this bad?** `HashMap` or `HashSet` with custom hashers cannot be
|
||||||
|
/// used with them.
|
||||||
|
///
|
||||||
|
/// **Known problems:** Suggestions for replacing constructors can contain
|
||||||
|
/// false-positives. Also applying suggestions can require modification of other
|
||||||
|
/// pieces of code, possibly including external crates.
|
||||||
|
///
|
||||||
|
/// **Example:**
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::collections::HashMap;
|
||||||
|
/// # use std::hash::{Hash, BuildHasher};
|
||||||
|
/// # trait Serialize {};
|
||||||
|
/// impl<K: Hash + Eq, V> Serialize for HashMap<K, V> { }
|
||||||
|
///
|
||||||
|
/// pub fn foo(map: &mut HashMap<i32, i32>) { }
|
||||||
|
/// ```
|
||||||
|
/// could be rewritten as
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::collections::HashMap;
|
||||||
|
/// # use std::hash::{Hash, BuildHasher};
|
||||||
|
/// # trait Serialize {};
|
||||||
|
/// impl<K: Hash + Eq, V, S: BuildHasher> Serialize for HashMap<K, V, S> { }
|
||||||
|
///
|
||||||
|
/// pub fn foo<S: BuildHasher>(map: &mut HashMap<i32, i32, S>) { }
|
||||||
|
/// ```
|
||||||
|
pub IMPLICIT_HASHER,
|
||||||
|
pedantic,
|
||||||
|
"missing generalization over different hashers"
|
||||||
|
}
|
||||||
|
|
||||||
|
declare_lint_pass!(ImplicitHasher => [IMPLICIT_HASHER]);
|
||||||
|
|
||||||
|
impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
|
||||||
|
#[allow(clippy::cast_possible_truncation, clippy::too_many_lines)]
|
||||||
|
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||||
|
use rustc_span::BytePos;
|
||||||
|
|
||||||
|
fn suggestion<'tcx>(
|
||||||
|
cx: &LateContext<'tcx>,
|
||||||
|
diag: &mut DiagnosticBuilder<'_>,
|
||||||
|
generics_span: Span,
|
||||||
|
generics_suggestion_span: Span,
|
||||||
|
target: &ImplicitHasherType<'_>,
|
||||||
|
vis: ImplicitHasherConstructorVisitor<'_, '_, '_>,
|
||||||
|
) {
|
||||||
|
let generics_snip = snippet(cx, generics_span, "");
|
||||||
|
// trim `<` `>`
|
||||||
|
let generics_snip = if generics_snip.is_empty() {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
&generics_snip[1..generics_snip.len() - 1]
|
||||||
|
};
|
||||||
|
|
||||||
|
multispan_sugg(
|
||||||
|
diag,
|
||||||
|
"consider adding a type parameter",
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
generics_suggestion_span,
|
||||||
|
format!(
|
||||||
|
"<{}{}S: ::std::hash::BuildHasher{}>",
|
||||||
|
generics_snip,
|
||||||
|
if generics_snip.is_empty() { "" } else { ", " },
|
||||||
|
if vis.suggestions.is_empty() {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
// request users to add `Default` bound so that generic constructors can be used
|
||||||
|
" + Default"
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
target.span(),
|
||||||
|
format!("{}<{}, S>", target.type_name(), target.type_arguments(),),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if !vis.suggestions.is_empty() {
|
||||||
|
multispan_sugg(diag, "...and use generic constructor", vis.suggestions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cx.access_levels.is_exported(item.hir_id()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match item.kind {
|
||||||
|
ItemKind::Impl(ref impl_) => {
|
||||||
|
let mut vis = ImplicitHasherTypeVisitor::new(cx);
|
||||||
|
vis.visit_ty(impl_.self_ty);
|
||||||
|
|
||||||
|
for target in &vis.found {
|
||||||
|
if differing_macro_contexts(item.span, target.span()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let generics_suggestion_span = impl_.generics.span.substitute_dummy({
|
||||||
|
let pos = snippet_opt(cx, item.span.until(target.span()))
|
||||||
|
.and_then(|snip| Some(item.span.lo() + BytePos(snip.find("impl")? as u32 + 4)));
|
||||||
|
if let Some(pos) = pos {
|
||||||
|
Span::new(pos, pos, item.span.data().ctxt)
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
|
||||||
|
for item in impl_.items.iter().map(|item| cx.tcx.hir().impl_item(item.id)) {
|
||||||
|
ctr_vis.visit_impl_item(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
span_lint_and_then(
|
||||||
|
cx,
|
||||||
|
IMPLICIT_HASHER,
|
||||||
|
target.span(),
|
||||||
|
&format!(
|
||||||
|
"impl for `{}` should be generalized over different hashers",
|
||||||
|
target.type_name()
|
||||||
|
),
|
||||||
|
move |diag| {
|
||||||
|
suggestion(cx, diag, impl_.generics.span, generics_suggestion_span, target, ctr_vis);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ItemKind::Fn(ref sig, ref generics, body_id) => {
|
||||||
|
let body = cx.tcx.hir().body(body_id);
|
||||||
|
|
||||||
|
for ty in sig.decl.inputs {
|
||||||
|
let mut vis = ImplicitHasherTypeVisitor::new(cx);
|
||||||
|
vis.visit_ty(ty);
|
||||||
|
|
||||||
|
for target in &vis.found {
|
||||||
|
if in_external_macro(cx.sess(), generics.span) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let generics_suggestion_span = generics.span.substitute_dummy({
|
||||||
|
let pos = snippet_opt(cx, item.span.until(body.params[0].pat.span))
|
||||||
|
.and_then(|snip| {
|
||||||
|
let i = snip.find("fn")?;
|
||||||
|
Some(item.span.lo() + BytePos((i + (&snip[i..]).find('(')?) as u32))
|
||||||
|
})
|
||||||
|
.expect("failed to create span for type parameters");
|
||||||
|
Span::new(pos, pos, item.span.data().ctxt)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
|
||||||
|
ctr_vis.visit_body(body);
|
||||||
|
|
||||||
|
span_lint_and_then(
|
||||||
|
cx,
|
||||||
|
IMPLICIT_HASHER,
|
||||||
|
target.span(),
|
||||||
|
&format!(
|
||||||
|
"parameter of type `{}` should be generalized over different hashers",
|
||||||
|
target.type_name()
|
||||||
|
),
|
||||||
|
move |diag| {
|
||||||
|
suggestion(cx, diag, generics.span, generics_suggestion_span, target, ctr_vis);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ImplicitHasherType<'tcx> {
|
||||||
|
HashMap(Span, Ty<'tcx>, Cow<'static, str>, Cow<'static, str>),
|
||||||
|
HashSet(Span, Ty<'tcx>, Cow<'static, str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tcx> ImplicitHasherType<'tcx> {
|
||||||
|
/// Checks that `ty` is a target type without a `BuildHasher`.
|
||||||
|
fn new(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> Option<Self> {
|
||||||
|
if let TyKind::Path(QPath::Resolved(None, path)) = hir_ty.kind {
|
||||||
|
let params: Vec<_> = path
|
||||||
|
.segments
|
||||||
|
.last()
|
||||||
|
.as_ref()?
|
||||||
|
.args
|
||||||
|
.as_ref()?
|
||||||
|
.args
|
||||||
|
.iter()
|
||||||
|
.filter_map(|arg| match arg {
|
||||||
|
GenericArg::Type(ty) => Some(ty),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let params_len = params.len();
|
||||||
|
|
||||||
|
let ty = hir_ty_to_ty(cx.tcx, hir_ty);
|
||||||
|
|
||||||
|
if is_type_diagnostic_item(cx, ty, sym::hashmap_type) && params_len == 2 {
|
||||||
|
Some(ImplicitHasherType::HashMap(
|
||||||
|
hir_ty.span,
|
||||||
|
ty,
|
||||||
|
snippet(cx, params[0].span, "K"),
|
||||||
|
snippet(cx, params[1].span, "V"),
|
||||||
|
))
|
||||||
|
} else if is_type_diagnostic_item(cx, ty, sym::hashset_type) && params_len == 1 {
|
||||||
|
Some(ImplicitHasherType::HashSet(
|
||||||
|
hir_ty.span,
|
||||||
|
ty,
|
||||||
|
snippet(cx, params[0].span, "T"),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_name(&self) -> &'static str {
|
||||||
|
match *self {
|
||||||
|
ImplicitHasherType::HashMap(..) => "HashMap",
|
||||||
|
ImplicitHasherType::HashSet(..) => "HashSet",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_arguments(&self) -> String {
|
||||||
|
match *self {
|
||||||
|
ImplicitHasherType::HashMap(.., ref k, ref v) => format!("{}, {}", k, v),
|
||||||
|
ImplicitHasherType::HashSet(.., ref t) => format!("{}", t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ty(&self) -> Ty<'tcx> {
|
||||||
|
match *self {
|
||||||
|
ImplicitHasherType::HashMap(_, ty, ..) | ImplicitHasherType::HashSet(_, ty, ..) => ty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
match *self {
|
||||||
|
ImplicitHasherType::HashMap(span, ..) | ImplicitHasherType::HashSet(span, ..) => span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ImplicitHasherTypeVisitor<'a, 'tcx> {
|
||||||
|
cx: &'a LateContext<'tcx>,
|
||||||
|
found: Vec<ImplicitHasherType<'tcx>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'tcx> ImplicitHasherTypeVisitor<'a, 'tcx> {
|
||||||
|
fn new(cx: &'a LateContext<'tcx>) -> Self {
|
||||||
|
Self { cx, found: vec![] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'tcx> Visitor<'tcx> for ImplicitHasherTypeVisitor<'a, 'tcx> {
|
||||||
|
type Map = Map<'tcx>;
|
||||||
|
|
||||||
|
fn visit_ty(&mut self, t: &'tcx hir::Ty<'_>) {
|
||||||
|
if let Some(target) = ImplicitHasherType::new(self.cx, t) {
|
||||||
|
self.found.push(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
walk_ty(self, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||||
|
NestedVisitorMap::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Looks for default-hasher-dependent constructors like `HashMap::new`.
|
||||||
|
struct ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
|
||||||
|
cx: &'a LateContext<'tcx>,
|
||||||
|
maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
|
||||||
|
target: &'b ImplicitHasherType<'tcx>,
|
||||||
|
suggestions: BTreeMap<Span, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b, 'tcx> ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
|
||||||
|
fn new(cx: &'a LateContext<'tcx>, target: &'b ImplicitHasherType<'tcx>) -> Self {
|
||||||
|
Self {
|
||||||
|
cx,
|
||||||
|
maybe_typeck_results: cx.maybe_typeck_results(),
|
||||||
|
target,
|
||||||
|
suggestions: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
|
||||||
|
type Map = Map<'tcx>;
|
||||||
|
|
||||||
|
fn visit_body(&mut self, body: &'tcx Body<'_>) {
|
||||||
|
let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body.id()));
|
||||||
|
walk_body(self, body);
|
||||||
|
self.maybe_typeck_results = old_maybe_typeck_results;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
|
||||||
|
if_chain! {
|
||||||
|
if let ExprKind::Call(fun, args) = e.kind;
|
||||||
|
if let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind;
|
||||||
|
if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind;
|
||||||
|
then {
|
||||||
|
if !TyS::same_type(self.target.ty(), self.maybe_typeck_results.unwrap().expr_ty(e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if match_path(ty_path, &paths::HASHMAP) {
|
||||||
|
if method.ident.name == sym::new {
|
||||||
|
self.suggestions
|
||||||
|
.insert(e.span, "HashMap::default()".to_string());
|
||||||
|
} else if method.ident.name == sym!(with_capacity) {
|
||||||
|
self.suggestions.insert(
|
||||||
|
e.span,
|
||||||
|
format!(
|
||||||
|
"HashMap::with_capacity_and_hasher({}, Default::default())",
|
||||||
|
snippet(self.cx, args[0].span, "capacity"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if match_path(ty_path, &paths::HASHSET) {
|
||||||
|
if method.ident.name == sym::new {
|
||||||
|
self.suggestions
|
||||||
|
.insert(e.span, "HashSet::default()".to_string());
|
||||||
|
} else if method.ident.name == sym!(with_capacity) {
|
||||||
|
self.suggestions.insert(
|
||||||
|
e.span,
|
||||||
|
format!(
|
||||||
|
"HashSet::with_capacity_and_hasher({}, Default::default())",
|
||||||
|
snippet(self.cx, args[0].span, "capacity"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
walk_expr(self, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||||
|
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
|
||||||
|
}
|
||||||
|
}
|
|
@ -100,10 +100,10 @@ fn expr_match(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||||
|
|
||||||
if check_all_arms {
|
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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue