Auto merge of #120375 - matthiaskrgr:rollup-ueakvms, r=matthiaskrgr
Rollup of 9 pull requests Successful merges: - #117420 (Make `#![allow_internal_unstable(..)]` work with `stmt_expr_attributes`) - #117678 (Stabilize `slice_group_by`) - #119917 (Remove special-case handling of `vec.split_off(0)`) - #120117 (Update `std::io::Error::downcast` return type) - #120329 (RFC 3349 precursors) - #120339 (privacy: Refactor top-level visiting in `NamePrivacyVisitor`) - #120345 (Clippy subtree update) - #120360 (Don't fire `OPAQUE_HIDDEN_INFERRED_BOUND` on sized return of AFIT) - #120372 (Fix outdated comment on Box) r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
e7bbe8ce93
190 changed files with 3195 additions and 1374 deletions
18
Cargo.lock
18
Cargo.lock
|
|
@ -596,11 +596,11 @@ dependencies = [
|
||||||
name = "clippy_dev"
|
name = "clippy_dev"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick 0.7.20",
|
"aho-corasick 1.0.2",
|
||||||
"clap",
|
"clap",
|
||||||
"indoc",
|
"indoc",
|
||||||
"itertools",
|
"itertools",
|
||||||
"opener 0.5.2",
|
"opener",
|
||||||
"shell-escape",
|
"shell-escape",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
@ -610,7 +610,7 @@ name = "clippy_lints"
|
||||||
version = "0.1.77"
|
version = "0.1.77"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"cargo_metadata 0.15.4",
|
"cargo_metadata 0.18.0",
|
||||||
"clippy_config",
|
"clippy_config",
|
||||||
"clippy_utils",
|
"clippy_utils",
|
||||||
"declare_clippy_lint",
|
"declare_clippy_lint",
|
||||||
|
|
@ -2351,7 +2351,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"memchr",
|
"memchr",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"opener 0.6.1",
|
"opener",
|
||||||
"pathdiff",
|
"pathdiff",
|
||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
"regex",
|
"regex",
|
||||||
|
|
@ -2626,16 +2626,6 @@ version = "1.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "opener"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "293c15678e37254c15bd2f092314abb4e51d7fdde05c2021279c12631b54f005"
|
|
||||||
dependencies = [
|
|
||||||
"bstr",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opener"
|
name = "opener"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@
|
||||||
use crate::ast::{self, LitKind, MetaItemLit, StrStyle};
|
use crate::ast::{self, LitKind, MetaItemLit, StrStyle};
|
||||||
use crate::token::{self, Token};
|
use crate::token::{self, Token};
|
||||||
use rustc_lexer::unescape::{
|
use rustc_lexer::unescape::{
|
||||||
byte_from_char, unescape_byte, unescape_c_string, unescape_char, unescape_literal, CStrUnit,
|
byte_from_char, unescape_byte, unescape_char, unescape_mixed, unescape_unicode, MixedUnit, Mode,
|
||||||
Mode,
|
|
||||||
};
|
};
|
||||||
use rustc_span::symbol::{kw, sym, Symbol};
|
use rustc_span::symbol::{kw, sym, Symbol};
|
||||||
use rustc_span::Span;
|
use rustc_span::Span;
|
||||||
|
|
@ -48,6 +47,9 @@ impl LitKind {
|
||||||
return Err(LitError::InvalidSuffix);
|
return Err(LitError::InvalidSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For byte/char/string literals, chars and escapes have already been
|
||||||
|
// checked in the lexer (in `cook_lexer_literal`). So we can assume all
|
||||||
|
// chars and escapes are valid here.
|
||||||
Ok(match kind {
|
Ok(match kind {
|
||||||
token::Bool => {
|
token::Bool => {
|
||||||
assert!(symbol.is_bool_lit());
|
assert!(symbol.is_bool_lit());
|
||||||
|
|
@ -56,12 +58,12 @@ impl LitKind {
|
||||||
token::Byte => {
|
token::Byte => {
|
||||||
return unescape_byte(symbol.as_str())
|
return unescape_byte(symbol.as_str())
|
||||||
.map(LitKind::Byte)
|
.map(LitKind::Byte)
|
||||||
.map_err(|_| LitError::LexerError);
|
.map_err(|_| panic!("failed to unescape byte literal"));
|
||||||
}
|
}
|
||||||
token::Char => {
|
token::Char => {
|
||||||
return unescape_char(symbol.as_str())
|
return unescape_char(symbol.as_str())
|
||||||
.map(LitKind::Char)
|
.map(LitKind::Char)
|
||||||
.map_err(|_| LitError::LexerError);
|
.map_err(|_| panic!("failed to unescape char literal"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// There are some valid suffixes for integer and float literals,
|
// There are some valid suffixes for integer and float literals,
|
||||||
|
|
@ -77,26 +79,22 @@ impl LitKind {
|
||||||
let s = symbol.as_str();
|
let s = symbol.as_str();
|
||||||
// Vanilla strings are so common we optimize for the common case where no chars
|
// Vanilla strings are so common we optimize for the common case where no chars
|
||||||
// requiring special behaviour are present.
|
// requiring special behaviour are present.
|
||||||
let symbol = if s.contains(['\\', '\r']) {
|
let symbol = if s.contains('\\') {
|
||||||
let mut buf = String::with_capacity(s.len());
|
let mut buf = String::with_capacity(s.len());
|
||||||
let mut error = Ok(());
|
|
||||||
// Force-inlining here is aggressive but the closure is
|
// Force-inlining here is aggressive but the closure is
|
||||||
// called on every char in the string, so it can be
|
// called on every char in the string, so it can be hot in
|
||||||
// hot in programs with many long strings.
|
// programs with many long strings containing escapes.
|
||||||
unescape_literal(
|
unescape_unicode(
|
||||||
s,
|
s,
|
||||||
Mode::Str,
|
Mode::Str,
|
||||||
&mut #[inline(always)]
|
&mut #[inline(always)]
|
||||||
|_, unescaped_char| match unescaped_char {
|
|_, c| match c {
|
||||||
Ok(c) => buf.push(c),
|
Ok(c) => buf.push(c),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if err.is_fatal() {
|
assert!(!err.is_fatal(), "failed to unescape string literal")
|
||||||
error = Err(LitError::LexerError);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
error?;
|
|
||||||
Symbol::intern(&buf)
|
Symbol::intern(&buf)
|
||||||
} else {
|
} else {
|
||||||
symbol
|
symbol
|
||||||
|
|
@ -104,86 +102,46 @@ impl LitKind {
|
||||||
LitKind::Str(symbol, ast::StrStyle::Cooked)
|
LitKind::Str(symbol, ast::StrStyle::Cooked)
|
||||||
}
|
}
|
||||||
token::StrRaw(n) => {
|
token::StrRaw(n) => {
|
||||||
// Raw strings have no escapes, so we only need to check for invalid chars, and we
|
// Raw strings have no escapes so no work is needed here.
|
||||||
// can reuse the symbol on success.
|
|
||||||
let mut error = Ok(());
|
|
||||||
unescape_literal(symbol.as_str(), Mode::RawStr, &mut |_, unescaped_char| {
|
|
||||||
match unescaped_char {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(err) => {
|
|
||||||
if err.is_fatal() {
|
|
||||||
error = Err(LitError::LexerError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
error?;
|
|
||||||
LitKind::Str(symbol, ast::StrStyle::Raw(n))
|
LitKind::Str(symbol, ast::StrStyle::Raw(n))
|
||||||
}
|
}
|
||||||
token::ByteStr => {
|
token::ByteStr => {
|
||||||
let s = symbol.as_str();
|
let s = symbol.as_str();
|
||||||
let mut buf = Vec::with_capacity(s.len());
|
let mut buf = Vec::with_capacity(s.len());
|
||||||
let mut error = Ok(());
|
unescape_unicode(s, Mode::ByteStr, &mut |_, c| match c {
|
||||||
unescape_literal(s, Mode::ByteStr, &mut |_, c| match c {
|
|
||||||
Ok(c) => buf.push(byte_from_char(c)),
|
Ok(c) => buf.push(byte_from_char(c)),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if err.is_fatal() {
|
assert!(!err.is_fatal(), "failed to unescape string literal")
|
||||||
error = Err(LitError::LexerError);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
error?;
|
|
||||||
LitKind::ByteStr(buf.into(), StrStyle::Cooked)
|
LitKind::ByteStr(buf.into(), StrStyle::Cooked)
|
||||||
}
|
}
|
||||||
token::ByteStrRaw(n) => {
|
token::ByteStrRaw(n) => {
|
||||||
// Raw strings have no escapes, so we only need to check for invalid chars, and we
|
// Raw strings have no escapes so we can convert the symbol
|
||||||
// can convert the symbol directly to a `Lrc<u8>` on success.
|
// directly to a `Lrc<u8>`.
|
||||||
let s = symbol.as_str();
|
let buf = symbol.as_str().to_owned().into_bytes();
|
||||||
let mut error = Ok(());
|
LitKind::ByteStr(buf.into(), StrStyle::Raw(n))
|
||||||
unescape_literal(s, Mode::RawByteStr, &mut |_, c| match c {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(err) => {
|
|
||||||
if err.is_fatal() {
|
|
||||||
error = Err(LitError::LexerError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
LitKind::ByteStr(s.to_owned().into_bytes().into(), StrStyle::Raw(n))
|
|
||||||
}
|
}
|
||||||
token::CStr => {
|
token::CStr => {
|
||||||
let s = symbol.as_str();
|
let s = symbol.as_str();
|
||||||
let mut buf = Vec::with_capacity(s.len());
|
let mut buf = Vec::with_capacity(s.len());
|
||||||
let mut error = Ok(());
|
unescape_mixed(s, Mode::CStr, &mut |_span, c| match c {
|
||||||
unescape_c_string(s, Mode::CStr, &mut |_span, c| match c {
|
Ok(MixedUnit::Char(c)) => {
|
||||||
Ok(CStrUnit::Byte(b)) => buf.push(b),
|
|
||||||
Ok(CStrUnit::Char(c)) => {
|
|
||||||
buf.extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes())
|
buf.extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes())
|
||||||
}
|
}
|
||||||
|
Ok(MixedUnit::HighByte(b)) => buf.push(b),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if err.is_fatal() {
|
assert!(!err.is_fatal(), "failed to unescape C string literal")
|
||||||
error = Err(LitError::LexerError);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
error?;
|
|
||||||
buf.push(0);
|
buf.push(0);
|
||||||
LitKind::CStr(buf.into(), StrStyle::Cooked)
|
LitKind::CStr(buf.into(), StrStyle::Cooked)
|
||||||
}
|
}
|
||||||
token::CStrRaw(n) => {
|
token::CStrRaw(n) => {
|
||||||
// Raw strings have no escapes, so we only need to check for invalid chars, and we
|
// Raw strings have no escapes so we can convert the symbol
|
||||||
// can convert the symbol directly to a `Lrc<u8>` on success.
|
// directly to a `Lrc<u8>` after appending the terminating NUL
|
||||||
let s = symbol.as_str();
|
// char.
|
||||||
let mut error = Ok(());
|
let mut buf = symbol.as_str().to_owned().into_bytes();
|
||||||
unescape_c_string(s, Mode::RawCStr, &mut |_, c| match c {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(err) => {
|
|
||||||
if err.is_fatal() {
|
|
||||||
error = Err(LitError::LexerError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
error?;
|
|
||||||
let mut buf = s.to_owned().into_bytes();
|
|
||||||
buf.push(0);
|
buf.push(0);
|
||||||
LitKind::CStr(buf.into(), StrStyle::Raw(n))
|
LitKind::CStr(buf.into(), StrStyle::Raw(n))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ index 897a5e9..331f66f 100644
|
||||||
-#![cfg_attr(target_has_atomic = "128", feature(integer_atomics))]
|
-#![cfg_attr(target_has_atomic = "128", feature(integer_atomics))]
|
||||||
#![cfg_attr(test, feature(cfg_match))]
|
#![cfg_attr(test, feature(cfg_match))]
|
||||||
#![feature(int_roundings)]
|
#![feature(int_roundings)]
|
||||||
#![feature(slice_group_by)]
|
#![feature(split_array)]
|
||||||
diff --git a/atomic.rs b/atomic.rs
|
diff --git a/atomic.rs b/atomic.rs
|
||||||
index b735957..ea728b6 100644
|
index b735957..ea728b6 100644
|
||||||
--- a/atomic.rs
|
--- a/atomic.rs
|
||||||
|
|
|
||||||
|
|
@ -373,7 +373,9 @@ impl<'a> StripUnconfigured<'a> {
|
||||||
/// If attributes are not allowed on expressions, emit an error for `attr`
|
/// If attributes are not allowed on expressions, emit an error for `attr`
|
||||||
#[instrument(level = "trace", skip(self))]
|
#[instrument(level = "trace", skip(self))]
|
||||||
pub(crate) fn maybe_emit_expr_attr_err(&self, attr: &Attribute) {
|
pub(crate) fn maybe_emit_expr_attr_err(&self, attr: &Attribute) {
|
||||||
if self.features.is_some_and(|features| !features.stmt_expr_attributes) {
|
if self.features.is_some_and(|features| !features.stmt_expr_attributes)
|
||||||
|
&& !attr.span.allows_unstable(sym::stmt_expr_attributes)
|
||||||
|
{
|
||||||
let mut err = feature_err(
|
let mut err = feature_err(
|
||||||
&self.sess,
|
&self.sess,
|
||||||
sym::stmt_expr_attributes,
|
sym::stmt_expr_attributes,
|
||||||
|
|
|
||||||
|
|
@ -80,12 +80,12 @@ impl EscapeError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes a contents of a literal (without quotes) and produces a sequence of
|
/// Takes the contents of a unicode-only (non-mixed-utf8) literal (without
|
||||||
/// escaped characters or errors.
|
/// quotes) and produces a sequence of escaped characters or errors.
|
||||||
///
|
///
|
||||||
/// Values are returned by invoking `callback`. For `Char` and `Byte` modes,
|
/// Values are returned by invoking `callback`. For `Char` and `Byte` modes,
|
||||||
/// the callback will be called exactly once.
|
/// the callback will be called exactly once.
|
||||||
pub fn unescape_literal<F>(src: &str, mode: Mode, callback: &mut F)
|
pub fn unescape_unicode<F>(src: &str, mode: Mode, callback: &mut F)
|
||||||
where
|
where
|
||||||
F: FnMut(Range<usize>, Result<char, EscapeError>),
|
F: FnMut(Range<usize>, Result<char, EscapeError>),
|
||||||
{
|
{
|
||||||
|
|
@ -97,50 +97,63 @@ where
|
||||||
}
|
}
|
||||||
Str | ByteStr => unescape_non_raw_common(src, mode, callback),
|
Str | ByteStr => unescape_non_raw_common(src, mode, callback),
|
||||||
RawStr | RawByteStr => check_raw_common(src, mode, callback),
|
RawStr | RawByteStr => check_raw_common(src, mode, callback),
|
||||||
CStr | RawCStr => unreachable!(),
|
RawCStr => check_raw_common(src, mode, &mut |r, mut result| {
|
||||||
|
if let Ok('\0') = result {
|
||||||
|
result = Err(EscapeError::NulInCStr);
|
||||||
|
}
|
||||||
|
callback(r, result)
|
||||||
|
}),
|
||||||
|
CStr => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A unit within CStr. Must not be a nul character.
|
/// Used for mixed utf8 string literals, i.e. those that allow both unicode
|
||||||
pub enum CStrUnit {
|
/// chars and high bytes.
|
||||||
Byte(u8),
|
pub enum MixedUnit {
|
||||||
|
/// Used for ASCII chars (written directly or via `\x00`..`\x7f` escapes)
|
||||||
|
/// and Unicode chars (written directly or via `\u` escapes).
|
||||||
|
///
|
||||||
|
/// For example, if '¥' appears in a string it is represented here as
|
||||||
|
/// `MixedUnit::Char('¥')`, and it will be appended to the relevant byte
|
||||||
|
/// string as the two-byte UTF-8 sequence `[0xc2, 0xa5]`
|
||||||
Char(char),
|
Char(char),
|
||||||
|
|
||||||
|
/// Used for high bytes (`\x80`..`\xff`).
|
||||||
|
///
|
||||||
|
/// For example, if `\xa5` appears in a string it is represented here as
|
||||||
|
/// `MixedUnit::HighByte(0xa5)`, and it will be appended to the relevant
|
||||||
|
/// byte string as the single byte `0xa5`.
|
||||||
|
HighByte(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u8> for CStrUnit {
|
impl From<char> for MixedUnit {
|
||||||
fn from(value: u8) -> Self {
|
fn from(c: char) -> Self {
|
||||||
CStrUnit::Byte(value)
|
MixedUnit::Char(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<char> for CStrUnit {
|
impl From<u8> for MixedUnit {
|
||||||
fn from(value: char) -> Self {
|
fn from(n: u8) -> Self {
|
||||||
CStrUnit::Char(value)
|
if n.is_ascii() { MixedUnit::Char(n as char) } else { MixedUnit::HighByte(n) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unescape_c_string<F>(src: &str, mode: Mode, callback: &mut F)
|
/// Takes the contents of a mixed-utf8 literal (without quotes) and produces
|
||||||
|
/// a sequence of escaped characters or errors.
|
||||||
|
///
|
||||||
|
/// Values are returned by invoking `callback`.
|
||||||
|
pub fn unescape_mixed<F>(src: &str, mode: Mode, callback: &mut F)
|
||||||
where
|
where
|
||||||
F: FnMut(Range<usize>, Result<CStrUnit, EscapeError>),
|
F: FnMut(Range<usize>, Result<MixedUnit, EscapeError>),
|
||||||
{
|
{
|
||||||
match mode {
|
match mode {
|
||||||
CStr => {
|
CStr => unescape_non_raw_common(src, mode, &mut |r, mut result| {
|
||||||
unescape_non_raw_common(src, mode, &mut |r, mut result| {
|
if let Ok(MixedUnit::Char('\0')) = result {
|
||||||
if let Ok(CStrUnit::Byte(0) | CStrUnit::Char('\0')) = result {
|
result = Err(EscapeError::NulInCStr);
|
||||||
result = Err(EscapeError::NulInCStr);
|
}
|
||||||
}
|
callback(r, result)
|
||||||
callback(r, result)
|
}),
|
||||||
});
|
Char | Byte | Str | RawStr | ByteStr | RawByteStr | RawCStr => unreachable!(),
|
||||||
}
|
|
||||||
RawCStr => {
|
|
||||||
check_raw_common(src, mode, &mut |r, mut result| {
|
|
||||||
if let Ok('\0') = result {
|
|
||||||
result = Err(EscapeError::NulInCStr);
|
|
||||||
}
|
|
||||||
callback(r, result.map(CStrUnit::Char))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Char | Byte | Str | RawStr | ByteStr | RawByteStr => unreachable!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,29 +194,29 @@ impl Mode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Non-byte literals should have `\xXX` escapes that are within the ASCII range.
|
/// Are `\x80`..`\xff` allowed?
|
||||||
fn ascii_escapes_should_be_ascii(self) -> bool {
|
fn allow_high_bytes(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Char | Str => true,
|
Char | Str => false,
|
||||||
Byte | ByteStr | CStr => false,
|
Byte | ByteStr | CStr => true,
|
||||||
RawStr | RawByteStr | RawCStr => unreachable!(),
|
RawStr | RawByteStr | RawCStr => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether characters within the literal must be within the ASCII range.
|
/// Are unicode (non-ASCII) chars allowed?
|
||||||
#[inline]
|
#[inline]
|
||||||
fn chars_should_be_ascii(self) -> bool {
|
fn allow_unicode_chars(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Byte | ByteStr | RawByteStr => true,
|
Byte | ByteStr | RawByteStr => false,
|
||||||
Char | Str | RawStr | CStr | RawCStr => false,
|
Char | Str | RawStr | CStr | RawCStr => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Byte literals do not allow unicode escape.
|
/// Are unicode escapes (`\u`) allowed?
|
||||||
fn is_unicode_escape_disallowed(self) -> bool {
|
fn allow_unicode_escapes(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Byte | ByteStr => true,
|
Byte | ByteStr => false,
|
||||||
Char | Str | CStr => false,
|
Char | Str | CStr => true,
|
||||||
RawByteStr | RawStr | RawCStr => unreachable!(),
|
RawByteStr | RawStr | RawCStr => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -217,20 +230,19 @@ impl Mode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scan_escape<T: From<u8> + From<char>>(
|
fn scan_escape<T: From<char> + From<u8>>(
|
||||||
chars: &mut Chars<'_>,
|
chars: &mut Chars<'_>,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
) -> Result<T, EscapeError> {
|
) -> Result<T, EscapeError> {
|
||||||
// Previous character was '\\', unescape what follows.
|
// Previous character was '\\', unescape what follows.
|
||||||
let res = match chars.next().ok_or(EscapeError::LoneSlash)? {
|
let res: char = match chars.next().ok_or(EscapeError::LoneSlash)? {
|
||||||
'"' => b'"',
|
'"' => '"',
|
||||||
'n' => b'\n',
|
'n' => '\n',
|
||||||
'r' => b'\r',
|
'r' => '\r',
|
||||||
't' => b'\t',
|
't' => '\t',
|
||||||
'\\' => b'\\',
|
'\\' => '\\',
|
||||||
'\'' => b'\'',
|
'\'' => '\'',
|
||||||
'0' => b'\0',
|
'0' => '\0',
|
||||||
|
|
||||||
'x' => {
|
'x' => {
|
||||||
// Parse hexadecimal character code.
|
// Parse hexadecimal character code.
|
||||||
|
|
||||||
|
|
@ -240,25 +252,23 @@ fn scan_escape<T: From<u8> + From<char>>(
|
||||||
let lo = chars.next().ok_or(EscapeError::TooShortHexEscape)?;
|
let lo = chars.next().ok_or(EscapeError::TooShortHexEscape)?;
|
||||||
let lo = lo.to_digit(16).ok_or(EscapeError::InvalidCharInHexEscape)?;
|
let lo = lo.to_digit(16).ok_or(EscapeError::InvalidCharInHexEscape)?;
|
||||||
|
|
||||||
let value = hi * 16 + lo;
|
let value = (hi * 16 + lo) as u8;
|
||||||
|
|
||||||
if mode.ascii_escapes_should_be_ascii() && !is_ascii(value) {
|
return if !mode.allow_high_bytes() && !value.is_ascii() {
|
||||||
return Err(EscapeError::OutOfRangeHexEscape);
|
Err(EscapeError::OutOfRangeHexEscape)
|
||||||
}
|
} else {
|
||||||
|
// This may be a high byte, but that will only happen if `T` is
|
||||||
value as u8
|
// `MixedUnit`, because of the `allow_high_bytes` check above.
|
||||||
|
Ok(T::from(value as u8))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
'u' => return scan_unicode(chars, mode.allow_unicode_escapes()).map(T::from),
|
||||||
'u' => return scan_unicode(chars, mode.is_unicode_escape_disallowed()).map(Into::into),
|
|
||||||
_ => return Err(EscapeError::InvalidEscape),
|
_ => return Err(EscapeError::InvalidEscape),
|
||||||
};
|
};
|
||||||
Ok(res.into())
|
Ok(T::from(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scan_unicode(
|
fn scan_unicode(chars: &mut Chars<'_>, allow_unicode_escapes: bool) -> Result<char, EscapeError> {
|
||||||
chars: &mut Chars<'_>,
|
|
||||||
is_unicode_escape_disallowed: bool,
|
|
||||||
) -> Result<char, EscapeError> {
|
|
||||||
// We've parsed '\u', now we have to parse '{..}'.
|
// We've parsed '\u', now we have to parse '{..}'.
|
||||||
|
|
||||||
if chars.next() != Some('{') {
|
if chars.next() != Some('{') {
|
||||||
|
|
@ -286,7 +296,7 @@ fn scan_unicode(
|
||||||
|
|
||||||
// Incorrect syntax has higher priority for error reporting
|
// Incorrect syntax has higher priority for error reporting
|
||||||
// than unallowed value for a literal.
|
// than unallowed value for a literal.
|
||||||
if is_unicode_escape_disallowed {
|
if !allow_unicode_escapes {
|
||||||
return Err(EscapeError::UnicodeEscapeInByte);
|
return Err(EscapeError::UnicodeEscapeInByte);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -312,12 +322,8 @@ fn scan_unicode(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn ascii_check(c: char, chars_should_be_ascii: bool) -> Result<char, EscapeError> {
|
fn ascii_check(c: char, allow_unicode_chars: bool) -> Result<char, EscapeError> {
|
||||||
if chars_should_be_ascii && !c.is_ascii() {
|
if allow_unicode_chars || c.is_ascii() { Ok(c) } else { Err(EscapeError::NonAsciiCharInByte) }
|
||||||
Err(EscapeError::NonAsciiCharInByte)
|
|
||||||
} else {
|
|
||||||
Ok(c)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unescape_char_or_byte(chars: &mut Chars<'_>, mode: Mode) -> Result<char, EscapeError> {
|
fn unescape_char_or_byte(chars: &mut Chars<'_>, mode: Mode) -> Result<char, EscapeError> {
|
||||||
|
|
@ -326,7 +332,7 @@ fn unescape_char_or_byte(chars: &mut Chars<'_>, mode: Mode) -> Result<char, Esca
|
||||||
'\\' => scan_escape(chars, mode),
|
'\\' => scan_escape(chars, mode),
|
||||||
'\n' | '\t' | '\'' => Err(EscapeError::EscapeOnlyChar),
|
'\n' | '\t' | '\'' => Err(EscapeError::EscapeOnlyChar),
|
||||||
'\r' => Err(EscapeError::BareCarriageReturn),
|
'\r' => Err(EscapeError::BareCarriageReturn),
|
||||||
_ => ascii_check(c, mode.chars_should_be_ascii()),
|
_ => ascii_check(c, mode.allow_unicode_chars()),
|
||||||
}?;
|
}?;
|
||||||
if chars.next().is_some() {
|
if chars.next().is_some() {
|
||||||
return Err(EscapeError::MoreThanOneChar);
|
return Err(EscapeError::MoreThanOneChar);
|
||||||
|
|
@ -336,12 +342,12 @@ fn unescape_char_or_byte(chars: &mut Chars<'_>, mode: Mode) -> Result<char, Esca
|
||||||
|
|
||||||
/// Takes a contents of a string literal (without quotes) and produces a
|
/// Takes a contents of a string literal (without quotes) and produces a
|
||||||
/// sequence of escaped characters or errors.
|
/// sequence of escaped characters or errors.
|
||||||
fn unescape_non_raw_common<F, T: From<u8> + From<char>>(src: &str, mode: Mode, callback: &mut F)
|
fn unescape_non_raw_common<F, T: From<char> + From<u8>>(src: &str, mode: Mode, callback: &mut F)
|
||||||
where
|
where
|
||||||
F: FnMut(Range<usize>, Result<T, EscapeError>),
|
F: FnMut(Range<usize>, Result<T, EscapeError>),
|
||||||
{
|
{
|
||||||
let mut chars = src.chars();
|
let mut chars = src.chars();
|
||||||
let chars_should_be_ascii = mode.chars_should_be_ascii(); // get this outside the loop
|
let allow_unicode_chars = mode.allow_unicode_chars(); // get this outside the loop
|
||||||
|
|
||||||
// The `start` and `end` computation here is complicated because
|
// The `start` and `end` computation here is complicated because
|
||||||
// `skip_ascii_whitespace` makes us to skip over chars without counting
|
// `skip_ascii_whitespace` makes us to skip over chars without counting
|
||||||
|
|
@ -366,7 +372,7 @@ where
|
||||||
}
|
}
|
||||||
'"' => Err(EscapeError::EscapeOnlyChar),
|
'"' => Err(EscapeError::EscapeOnlyChar),
|
||||||
'\r' => Err(EscapeError::BareCarriageReturn),
|
'\r' => Err(EscapeError::BareCarriageReturn),
|
||||||
_ => ascii_check(c, chars_should_be_ascii).map(Into::into),
|
_ => ascii_check(c, allow_unicode_chars).map(T::from),
|
||||||
};
|
};
|
||||||
let end = src.len() - chars.as_str().len();
|
let end = src.len() - chars.as_str().len();
|
||||||
callback(start..end, res);
|
callback(start..end, res);
|
||||||
|
|
@ -408,7 +414,7 @@ where
|
||||||
F: FnMut(Range<usize>, Result<char, EscapeError>),
|
F: FnMut(Range<usize>, Result<char, EscapeError>),
|
||||||
{
|
{
|
||||||
let mut chars = src.chars();
|
let mut chars = src.chars();
|
||||||
let chars_should_be_ascii = mode.chars_should_be_ascii(); // get this outside the loop
|
let allow_unicode_chars = mode.allow_unicode_chars(); // get this outside the loop
|
||||||
|
|
||||||
// The `start` and `end` computation here matches the one in
|
// The `start` and `end` computation here matches the one in
|
||||||
// `unescape_non_raw_common` for consistency, even though this function
|
// `unescape_non_raw_common` for consistency, even though this function
|
||||||
|
|
@ -417,7 +423,7 @@ where
|
||||||
let start = src.len() - chars.as_str().len() - c.len_utf8();
|
let start = src.len() - chars.as_str().len() - c.len_utf8();
|
||||||
let res = match c {
|
let res = match c {
|
||||||
'\r' => Err(EscapeError::BareCarriageReturnInRawString),
|
'\r' => Err(EscapeError::BareCarriageReturnInRawString),
|
||||||
_ => ascii_check(c, chars_should_be_ascii),
|
_ => ascii_check(c, allow_unicode_chars),
|
||||||
};
|
};
|
||||||
let end = src.len() - chars.as_str().len();
|
let end = src.len() - chars.as_str().len();
|
||||||
callback(start..end, res);
|
callback(start..end, res);
|
||||||
|
|
@ -430,7 +436,3 @@ pub fn byte_from_char(c: char) -> u8 {
|
||||||
debug_assert!(res <= u8::MAX as u32, "guaranteed because of ByteStr");
|
debug_assert!(res <= u8::MAX as u32, "guaranteed because of ByteStr");
|
||||||
res as u8
|
res as u8
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_ascii(x: u32) -> bool {
|
|
||||||
x <= 0x7F
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ fn test_unescape_char_good() {
|
||||||
fn test_unescape_str_warn() {
|
fn test_unescape_str_warn() {
|
||||||
fn check(literal: &str, expected: &[(Range<usize>, Result<char, EscapeError>)]) {
|
fn check(literal: &str, expected: &[(Range<usize>, Result<char, EscapeError>)]) {
|
||||||
let mut unescaped = Vec::with_capacity(literal.len());
|
let mut unescaped = Vec::with_capacity(literal.len());
|
||||||
unescape_literal(literal, Mode::Str, &mut |range, res| unescaped.push((range, res)));
|
unescape_unicode(literal, Mode::Str, &mut |range, res| unescaped.push((range, res)));
|
||||||
assert_eq!(unescaped, expected);
|
assert_eq!(unescaped, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,7 +124,7 @@ fn test_unescape_str_warn() {
|
||||||
fn test_unescape_str_good() {
|
fn test_unescape_str_good() {
|
||||||
fn check(literal_text: &str, expected: &str) {
|
fn check(literal_text: &str, expected: &str) {
|
||||||
let mut buf = Ok(String::with_capacity(literal_text.len()));
|
let mut buf = Ok(String::with_capacity(literal_text.len()));
|
||||||
unescape_literal(literal_text, Mode::Str, &mut |range, c| {
|
unescape_unicode(literal_text, Mode::Str, &mut |range, c| {
|
||||||
if let Ok(b) = &mut buf {
|
if let Ok(b) = &mut buf {
|
||||||
match c {
|
match c {
|
||||||
Ok(c) => b.push(c),
|
Ok(c) => b.push(c),
|
||||||
|
|
@ -241,7 +241,7 @@ fn test_unescape_byte_good() {
|
||||||
fn test_unescape_byte_str_good() {
|
fn test_unescape_byte_str_good() {
|
||||||
fn check(literal_text: &str, expected: &[u8]) {
|
fn check(literal_text: &str, expected: &[u8]) {
|
||||||
let mut buf = Ok(Vec::with_capacity(literal_text.len()));
|
let mut buf = Ok(Vec::with_capacity(literal_text.len()));
|
||||||
unescape_literal(literal_text, Mode::ByteStr, &mut |range, c| {
|
unescape_unicode(literal_text, Mode::ByteStr, &mut |range, c| {
|
||||||
if let Ok(b) = &mut buf {
|
if let Ok(b) = &mut buf {
|
||||||
match c {
|
match c {
|
||||||
Ok(c) => b.push(byte_from_char(c)),
|
Ok(c) => b.push(byte_from_char(c)),
|
||||||
|
|
@ -264,7 +264,7 @@ fn test_unescape_byte_str_good() {
|
||||||
fn test_unescape_raw_str() {
|
fn test_unescape_raw_str() {
|
||||||
fn check(literal: &str, expected: &[(Range<usize>, Result<char, EscapeError>)]) {
|
fn check(literal: &str, expected: &[(Range<usize>, Result<char, EscapeError>)]) {
|
||||||
let mut unescaped = Vec::with_capacity(literal.len());
|
let mut unescaped = Vec::with_capacity(literal.len());
|
||||||
unescape_literal(literal, Mode::RawStr, &mut |range, res| unescaped.push((range, res)));
|
unescape_unicode(literal, Mode::RawStr, &mut |range, res| unescaped.push((range, res)));
|
||||||
assert_eq!(unescaped, expected);
|
assert_eq!(unescaped, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -276,7 +276,7 @@ fn test_unescape_raw_str() {
|
||||||
fn test_unescape_raw_byte_str() {
|
fn test_unescape_raw_byte_str() {
|
||||||
fn check(literal: &str, expected: &[(Range<usize>, Result<char, EscapeError>)]) {
|
fn check(literal: &str, expected: &[(Range<usize>, Result<char, EscapeError>)]) {
|
||||||
let mut unescaped = Vec::with_capacity(literal.len());
|
let mut unescaped = Vec::with_capacity(literal.len());
|
||||||
unescape_literal(literal, Mode::RawByteStr, &mut |range, res| unescaped.push((range, res)));
|
unescape_unicode(literal, Mode::RawByteStr, &mut |range, res| unescaped.push((range, res)));
|
||||||
assert_eq!(unescaped, expected);
|
assert_eq!(unescaped, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use rustc_macros::{LintDiagnostic, Subdiagnostic};
|
||||||
use rustc_middle::ty::{
|
use rustc_middle::ty::{
|
||||||
self, fold::BottomUpFolder, print::TraitPredPrintModifiersAndPath, Ty, TypeFoldable,
|
self, fold::BottomUpFolder, print::TraitPredPrintModifiersAndPath, Ty, TypeFoldable,
|
||||||
};
|
};
|
||||||
use rustc_span::Span;
|
use rustc_span::{symbol::kw, Span};
|
||||||
use rustc_trait_selection::traits;
|
use rustc_trait_selection::traits;
|
||||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
|
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
|
||||||
|
|
||||||
|
|
@ -96,6 +96,17 @@ impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HACK: `async fn() -> Self` in traits is "ok"...
|
||||||
|
// This is not really that great, but it's similar to why the `-> Self`
|
||||||
|
// return type is well-formed in traits even when `Self` isn't sized.
|
||||||
|
if let ty::Param(param_ty) = *proj_term.kind()
|
||||||
|
&& param_ty.name == kw::SelfUpper
|
||||||
|
&& matches!(opaque.origin, hir::OpaqueTyOrigin::AsyncFn(_))
|
||||||
|
&& opaque.in_trait
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let proj_ty =
|
let proj_ty =
|
||||||
Ty::new_projection(cx.tcx, proj.projection_ty.def_id, proj.projection_ty.args);
|
Ty::new_projection(cx.tcx, proj.projection_ty.def_id, proj.projection_ty.args);
|
||||||
// For every instance of the projection type in the bounds,
|
// For every instance of the projection type in the bounds,
|
||||||
|
|
|
||||||
|
|
@ -400,7 +400,7 @@ impl<'sess, 'src> StringReader<'sess, 'src> {
|
||||||
.with_code(error_code!(E0762))
|
.with_code(error_code!(E0762))
|
||||||
.emit()
|
.emit()
|
||||||
}
|
}
|
||||||
self.cook_quoted(token::Char, Mode::Char, start, end, 1, 1) // ' '
|
self.cook_unicode(token::Char, Mode::Char, start, end, 1, 1) // ' '
|
||||||
}
|
}
|
||||||
rustc_lexer::LiteralKind::Byte { terminated } => {
|
rustc_lexer::LiteralKind::Byte { terminated } => {
|
||||||
if !terminated {
|
if !terminated {
|
||||||
|
|
@ -412,7 +412,7 @@ impl<'sess, 'src> StringReader<'sess, 'src> {
|
||||||
.with_code(error_code!(E0763))
|
.with_code(error_code!(E0763))
|
||||||
.emit()
|
.emit()
|
||||||
}
|
}
|
||||||
self.cook_quoted(token::Byte, Mode::Byte, start, end, 2, 1) // b' '
|
self.cook_unicode(token::Byte, Mode::Byte, start, end, 2, 1) // b' '
|
||||||
}
|
}
|
||||||
rustc_lexer::LiteralKind::Str { terminated } => {
|
rustc_lexer::LiteralKind::Str { terminated } => {
|
||||||
if !terminated {
|
if !terminated {
|
||||||
|
|
@ -424,7 +424,7 @@ impl<'sess, 'src> StringReader<'sess, 'src> {
|
||||||
.with_code(error_code!(E0765))
|
.with_code(error_code!(E0765))
|
||||||
.emit()
|
.emit()
|
||||||
}
|
}
|
||||||
self.cook_quoted(token::Str, Mode::Str, start, end, 1, 1) // " "
|
self.cook_unicode(token::Str, Mode::Str, start, end, 1, 1) // " "
|
||||||
}
|
}
|
||||||
rustc_lexer::LiteralKind::ByteStr { terminated } => {
|
rustc_lexer::LiteralKind::ByteStr { terminated } => {
|
||||||
if !terminated {
|
if !terminated {
|
||||||
|
|
@ -436,7 +436,7 @@ impl<'sess, 'src> StringReader<'sess, 'src> {
|
||||||
.with_code(error_code!(E0766))
|
.with_code(error_code!(E0766))
|
||||||
.emit()
|
.emit()
|
||||||
}
|
}
|
||||||
self.cook_quoted(token::ByteStr, Mode::ByteStr, start, end, 2, 1) // b" "
|
self.cook_unicode(token::ByteStr, Mode::ByteStr, start, end, 2, 1) // b" "
|
||||||
}
|
}
|
||||||
rustc_lexer::LiteralKind::CStr { terminated } => {
|
rustc_lexer::LiteralKind::CStr { terminated } => {
|
||||||
if !terminated {
|
if !terminated {
|
||||||
|
|
@ -448,13 +448,13 @@ impl<'sess, 'src> StringReader<'sess, 'src> {
|
||||||
.with_code(error_code!(E0767))
|
.with_code(error_code!(E0767))
|
||||||
.emit()
|
.emit()
|
||||||
}
|
}
|
||||||
self.cook_c_string(token::CStr, Mode::CStr, start, end, 2, 1) // c" "
|
self.cook_mixed(token::CStr, Mode::CStr, start, end, 2, 1) // c" "
|
||||||
}
|
}
|
||||||
rustc_lexer::LiteralKind::RawStr { n_hashes } => {
|
rustc_lexer::LiteralKind::RawStr { n_hashes } => {
|
||||||
if let Some(n_hashes) = n_hashes {
|
if let Some(n_hashes) = n_hashes {
|
||||||
let n = u32::from(n_hashes);
|
let n = u32::from(n_hashes);
|
||||||
let kind = token::StrRaw(n_hashes);
|
let kind = token::StrRaw(n_hashes);
|
||||||
self.cook_quoted(kind, Mode::RawStr, start, end, 2 + n, 1 + n) // r##" "##
|
self.cook_unicode(kind, Mode::RawStr, start, end, 2 + n, 1 + n) // r##" "##
|
||||||
} else {
|
} else {
|
||||||
self.report_raw_str_error(start, 1);
|
self.report_raw_str_error(start, 1);
|
||||||
}
|
}
|
||||||
|
|
@ -463,7 +463,7 @@ impl<'sess, 'src> StringReader<'sess, 'src> {
|
||||||
if let Some(n_hashes) = n_hashes {
|
if let Some(n_hashes) = n_hashes {
|
||||||
let n = u32::from(n_hashes);
|
let n = u32::from(n_hashes);
|
||||||
let kind = token::ByteStrRaw(n_hashes);
|
let kind = token::ByteStrRaw(n_hashes);
|
||||||
self.cook_quoted(kind, Mode::RawByteStr, start, end, 3 + n, 1 + n) // br##" "##
|
self.cook_unicode(kind, Mode::RawByteStr, start, end, 3 + n, 1 + n) // br##" "##
|
||||||
} else {
|
} else {
|
||||||
self.report_raw_str_error(start, 2);
|
self.report_raw_str_error(start, 2);
|
||||||
}
|
}
|
||||||
|
|
@ -472,7 +472,7 @@ impl<'sess, 'src> StringReader<'sess, 'src> {
|
||||||
if let Some(n_hashes) = n_hashes {
|
if let Some(n_hashes) = n_hashes {
|
||||||
let n = u32::from(n_hashes);
|
let n = u32::from(n_hashes);
|
||||||
let kind = token::CStrRaw(n_hashes);
|
let kind = token::CStrRaw(n_hashes);
|
||||||
self.cook_c_string(kind, Mode::RawCStr, start, end, 3 + n, 1 + n) // cr##" "##
|
self.cook_unicode(kind, Mode::RawCStr, start, end, 3 + n, 1 + n) // cr##" "##
|
||||||
} else {
|
} else {
|
||||||
self.report_raw_str_error(start, 2);
|
self.report_raw_str_error(start, 2);
|
||||||
}
|
}
|
||||||
|
|
@ -735,7 +735,7 @@ impl<'sess, 'src> StringReader<'sess, 'src> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cook_quoted(
|
fn cook_unicode(
|
||||||
&self,
|
&self,
|
||||||
kind: token::LitKind,
|
kind: token::LitKind,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
|
|
@ -745,13 +745,13 @@ impl<'sess, 'src> StringReader<'sess, 'src> {
|
||||||
postfix_len: u32,
|
postfix_len: u32,
|
||||||
) -> (token::LitKind, Symbol) {
|
) -> (token::LitKind, Symbol) {
|
||||||
self.cook_common(kind, mode, start, end, prefix_len, postfix_len, |src, mode, callback| {
|
self.cook_common(kind, mode, start, end, prefix_len, postfix_len, |src, mode, callback| {
|
||||||
unescape::unescape_literal(src, mode, &mut |span, result| {
|
unescape::unescape_unicode(src, mode, &mut |span, result| {
|
||||||
callback(span, result.map(drop))
|
callback(span, result.map(drop))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cook_c_string(
|
fn cook_mixed(
|
||||||
&self,
|
&self,
|
||||||
kind: token::LitKind,
|
kind: token::LitKind,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
|
|
@ -761,7 +761,7 @@ impl<'sess, 'src> StringReader<'sess, 'src> {
|
||||||
postfix_len: u32,
|
postfix_len: u32,
|
||||||
) -> (token::LitKind, Symbol) {
|
) -> (token::LitKind, Symbol) {
|
||||||
self.cook_common(kind, mode, start, end, prefix_len, postfix_len, |src, mode, callback| {
|
self.cook_common(kind, mode, start, end, prefix_len, postfix_len, |src, mode, callback| {
|
||||||
unescape::unescape_c_string(src, mode, &mut |span, result| {
|
unescape::unescape_mixed(src, mode, &mut |span, result| {
|
||||||
callback(span, result.map(drop))
|
callback(span, result.map(drop))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1056,7 +1056,7 @@ fn find_width_map_from_snippet(
|
||||||
fn unescape_string(string: &str) -> Option<string::String> {
|
fn unescape_string(string: &str) -> Option<string::String> {
|
||||||
let mut buf = string::String::new();
|
let mut buf = string::String::new();
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
unescape::unescape_literal(string, unescape::Mode::Str, &mut |_, unescaped_char| {
|
unescape::unescape_unicode(string, unescape::Mode::Str, &mut |_, unescaped_char| {
|
||||||
match unescaped_char {
|
match unescaped_char {
|
||||||
Ok(c) => buf.push(c),
|
Ok(c) => buf.push(c),
|
||||||
Err(_) => ok = false,
|
Err(_) => ok = false,
|
||||||
|
|
|
||||||
|
|
@ -917,7 +917,7 @@ impl<'tcx> DeadVisitor<'tcx> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dead_codes.sort_by_key(|v| v.level);
|
dead_codes.sort_by_key(|v| v.level);
|
||||||
for group in dead_codes[..].group_by(|a, b| a.level == b.level) {
|
for group in dead_codes[..].chunk_by(|a, b| a.level == b.level) {
|
||||||
self.lint_at_single_level(&group, participle, Some(def_id), report_on);
|
self.lint_at_single_level(&group, participle, Some(def_id), report_on);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
#![feature(let_chains)]
|
#![feature(let_chains)]
|
||||||
#![feature(map_try_insert)]
|
#![feature(map_try_insert)]
|
||||||
#![feature(min_specialization)]
|
#![feature(min_specialization)]
|
||||||
#![feature(slice_group_by)]
|
|
||||||
#![feature(try_blocks)]
|
#![feature(try_blocks)]
|
||||||
#![deny(rustc::untranslatable_diagnostic)]
|
#![deny(rustc::untranslatable_diagnostic)]
|
||||||
#![deny(rustc::diagnostic_outside_of_impl)]
|
#![deny(rustc::diagnostic_outside_of_impl)]
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ use rustc_hir::def::{DefKind, Res};
|
||||||
use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId, CRATE_DEF_ID};
|
use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId, CRATE_DEF_ID};
|
||||||
use rustc_hir::intravisit::{self, Visitor};
|
use rustc_hir::intravisit::{self, Visitor};
|
||||||
use rustc_hir::{AssocItemKind, ForeignItemKind, ItemId, PatKind};
|
use rustc_hir::{AssocItemKind, ForeignItemKind, ItemId, PatKind};
|
||||||
use rustc_middle::hir::nested_filter;
|
|
||||||
use rustc_middle::middle::privacy::{EffectiveVisibilities, EffectiveVisibility, Level};
|
use rustc_middle::middle::privacy::{EffectiveVisibilities, EffectiveVisibility, Level};
|
||||||
use rustc_middle::query::Providers;
|
use rustc_middle::query::Providers;
|
||||||
use rustc_middle::ty::GenericArgs;
|
use rustc_middle::ty::GenericArgs;
|
||||||
|
|
@ -34,9 +33,9 @@ use rustc_span::hygiene::Transparency;
|
||||||
use rustc_span::symbol::{kw, sym, Ident};
|
use rustc_span::symbol::{kw, sym, Ident};
|
||||||
use rustc_span::Span;
|
use rustc_span::Span;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
use std::{fmt, mem};
|
|
||||||
|
|
||||||
use errors::{
|
use errors::{
|
||||||
FieldIsPrivate, FieldIsPrivateLabel, FromPrivateDependencyInPublicInterface, InPublicInterface,
|
FieldIsPrivate, FieldIsPrivateLabel, FromPrivateDependencyInPublicInterface, InPublicInterface,
|
||||||
|
|
@ -933,7 +932,6 @@ impl<'tcx, 'a> Visitor<'tcx> for TestReachabilityVisitor<'tcx, 'a> {
|
||||||
struct NamePrivacyVisitor<'tcx> {
|
struct NamePrivacyVisitor<'tcx> {
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
maybe_typeck_results: Option<&'tcx ty::TypeckResults<'tcx>>,
|
maybe_typeck_results: Option<&'tcx ty::TypeckResults<'tcx>>,
|
||||||
current_item: LocalDefId,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> NamePrivacyVisitor<'tcx> {
|
impl<'tcx> NamePrivacyVisitor<'tcx> {
|
||||||
|
|
@ -949,6 +947,7 @@ impl<'tcx> NamePrivacyVisitor<'tcx> {
|
||||||
// Checks that a field in a struct constructor (expression or pattern) is accessible.
|
// Checks that a field in a struct constructor (expression or pattern) is accessible.
|
||||||
fn check_field(
|
fn check_field(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
hir_id: hir::HirId, // ID of the field use
|
||||||
use_ctxt: Span, // syntax context of the field name at the use site
|
use_ctxt: Span, // syntax context of the field name at the use site
|
||||||
span: Span, // span of the field pattern, e.g., `x: 0`
|
span: Span, // span of the field pattern, e.g., `x: 0`
|
||||||
def: ty::AdtDef<'tcx>, // definition of the struct or enum
|
def: ty::AdtDef<'tcx>, // definition of the struct or enum
|
||||||
|
|
@ -961,7 +960,6 @@ impl<'tcx> NamePrivacyVisitor<'tcx> {
|
||||||
|
|
||||||
// definition of the field
|
// definition of the field
|
||||||
let ident = Ident::new(kw::Empty, use_ctxt);
|
let ident = Ident::new(kw::Empty, use_ctxt);
|
||||||
let hir_id = self.tcx.local_def_id_to_hir_id(self.current_item);
|
|
||||||
let def_id = self.tcx.adjust_ident_and_get_scope(ident, def.did(), hir_id).1;
|
let def_id = self.tcx.adjust_ident_and_get_scope(ident, def.did(), hir_id).1;
|
||||||
if !field.vis.is_accessible_from(def_id, self.tcx) {
|
if !field.vis.is_accessible_from(def_id, self.tcx) {
|
||||||
self.tcx.dcx().emit_err(FieldIsPrivate {
|
self.tcx.dcx().emit_err(FieldIsPrivate {
|
||||||
|
|
@ -980,33 +978,13 @@ impl<'tcx> NamePrivacyVisitor<'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> Visitor<'tcx> for NamePrivacyVisitor<'tcx> {
|
impl<'tcx> Visitor<'tcx> for NamePrivacyVisitor<'tcx> {
|
||||||
type NestedFilter = nested_filter::All;
|
fn visit_nested_body(&mut self, body_id: hir::BodyId) {
|
||||||
|
|
||||||
/// We want to visit items in the context of their containing
|
|
||||||
/// module and so forth, so supply a crate for doing a deep walk.
|
|
||||||
fn nested_visit_map(&mut self) -> Self::Map {
|
|
||||||
self.tcx.hir()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_mod(&mut self, _m: &'tcx hir::Mod<'tcx>, _s: Span, _n: hir::HirId) {
|
|
||||||
// Don't visit nested modules, since we run a separate visitor walk
|
|
||||||
// for each module in `effective_visibilities`
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_nested_body(&mut self, body: hir::BodyId) {
|
|
||||||
let old_maybe_typeck_results =
|
let old_maybe_typeck_results =
|
||||||
self.maybe_typeck_results.replace(self.tcx.typeck_body(body));
|
self.maybe_typeck_results.replace(self.tcx.typeck_body(body_id));
|
||||||
let body = self.tcx.hir().body(body);
|
self.visit_body(self.tcx.hir().body(body_id));
|
||||||
self.visit_body(body);
|
|
||||||
self.maybe_typeck_results = old_maybe_typeck_results;
|
self.maybe_typeck_results = old_maybe_typeck_results;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
|
|
||||||
let orig_current_item = mem::replace(&mut self.current_item, item.owner_id.def_id);
|
|
||||||
intravisit::walk_item(self, item);
|
|
||||||
self.current_item = orig_current_item;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
|
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
|
||||||
if let hir::ExprKind::Struct(qpath, fields, ref base) = expr.kind {
|
if let hir::ExprKind::Struct(qpath, fields, ref base) = expr.kind {
|
||||||
let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
|
let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
|
||||||
|
|
@ -1020,17 +998,17 @@ impl<'tcx> Visitor<'tcx> for NamePrivacyVisitor<'tcx> {
|
||||||
let field = fields
|
let field = fields
|
||||||
.iter()
|
.iter()
|
||||||
.find(|f| self.typeck_results().field_index(f.hir_id) == vf_index);
|
.find(|f| self.typeck_results().field_index(f.hir_id) == vf_index);
|
||||||
let (use_ctxt, span) = match field {
|
let (hir_id, use_ctxt, span) = match field {
|
||||||
Some(field) => (field.ident.span, field.span),
|
Some(field) => (field.hir_id, field.ident.span, field.span),
|
||||||
None => (base.span, base.span),
|
None => (base.hir_id, base.span, base.span),
|
||||||
};
|
};
|
||||||
self.check_field(use_ctxt, span, adt, variant_field, true);
|
self.check_field(hir_id, use_ctxt, span, adt, variant_field, true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for field in fields {
|
for field in fields {
|
||||||
let use_ctxt = field.ident.span;
|
let (hir_id, use_ctxt, span) = (field.hir_id, field.ident.span, field.span);
|
||||||
let index = self.typeck_results().field_index(field.hir_id);
|
let index = self.typeck_results().field_index(field.hir_id);
|
||||||
self.check_field(use_ctxt, field.span, adt, &variant.fields[index], false);
|
self.check_field(hir_id, use_ctxt, span, adt, &variant.fields[index], false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1044,9 +1022,9 @@ impl<'tcx> Visitor<'tcx> for NamePrivacyVisitor<'tcx> {
|
||||||
let adt = self.typeck_results().pat_ty(pat).ty_adt_def().unwrap();
|
let adt = self.typeck_results().pat_ty(pat).ty_adt_def().unwrap();
|
||||||
let variant = adt.variant_of_res(res);
|
let variant = adt.variant_of_res(res);
|
||||||
for field in fields {
|
for field in fields {
|
||||||
let use_ctxt = field.ident.span;
|
let (hir_id, use_ctxt, span) = (field.hir_id, field.ident.span, field.span);
|
||||||
let index = self.typeck_results().field_index(field.hir_id);
|
let index = self.typeck_results().field_index(field.hir_id);
|
||||||
self.check_field(use_ctxt, field.span, adt, &variant.fields[index], false);
|
self.check_field(hir_id, use_ctxt, span, adt, &variant.fields[index], false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1741,17 +1719,12 @@ pub fn provide(providers: &mut Providers) {
|
||||||
|
|
||||||
fn check_mod_privacy(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) {
|
fn check_mod_privacy(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) {
|
||||||
// Check privacy of names not checked in previous compilation stages.
|
// Check privacy of names not checked in previous compilation stages.
|
||||||
let mut visitor = NamePrivacyVisitor {
|
let mut visitor = NamePrivacyVisitor { tcx, maybe_typeck_results: None };
|
||||||
tcx,
|
tcx.hir().visit_item_likes_in_module(module_def_id, &mut visitor);
|
||||||
maybe_typeck_results: None,
|
|
||||||
current_item: module_def_id.to_local_def_id(),
|
|
||||||
};
|
|
||||||
let (module, span, hir_id) = tcx.hir().get_module(module_def_id);
|
|
||||||
|
|
||||||
intravisit::walk_mod(&mut visitor, module, hir_id);
|
|
||||||
|
|
||||||
// Check privacy of explicitly written types and traits as well as
|
// Check privacy of explicitly written types and traits as well as
|
||||||
// inferred types of expressions and patterns.
|
// inferred types of expressions and patterns.
|
||||||
|
let span = tcx.def_span(module_def_id);
|
||||||
let mut visitor = TypePrivacyVisitor { tcx, module_def_id, maybe_typeck_results: None, span };
|
let mut visitor = TypePrivacyVisitor { tcx, module_def_id, maybe_typeck_results: None, span };
|
||||||
tcx.hir().visit_item_likes_in_module(module_def_id, &mut visitor);
|
tcx.hir().visit_item_likes_in_module(module_def_id, &mut visitor);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -191,8 +191,7 @@ mod thin;
|
||||||
#[fundamental]
|
#[fundamental]
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
// The declaration of the `Box` struct must be kept in sync with the
|
// The declaration of the `Box` struct must be kept in sync with the
|
||||||
// `alloc::alloc::box_free` function or ICEs will happen. See the comment
|
// compiler or ICEs will happen.
|
||||||
// on `box_free` for more details.
|
|
||||||
pub struct Box<
|
pub struct Box<
|
||||||
T: ?Sized,
|
T: ?Sized,
|
||||||
#[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
|
#[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,6 @@
|
||||||
#![feature(set_ptr_value)]
|
#![feature(set_ptr_value)]
|
||||||
#![feature(sized_type_properties)]
|
#![feature(sized_type_properties)]
|
||||||
#![feature(slice_from_ptr_range)]
|
#![feature(slice_from_ptr_range)]
|
||||||
#![feature(slice_group_by)]
|
|
||||||
#![feature(slice_ptr_get)]
|
#![feature(slice_ptr_get)]
|
||||||
#![feature(slice_ptr_len)]
|
#![feature(slice_ptr_len)]
|
||||||
#![feature(slice_range)]
|
#![feature(slice_range)]
|
||||||
|
|
|
||||||
|
|
@ -51,14 +51,14 @@ pub use core::slice::{from_mut, from_ref};
|
||||||
pub use core::slice::{from_mut_ptr_range, from_ptr_range};
|
pub use core::slice::{from_mut_ptr_range, from_ptr_range};
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
pub use core::slice::{from_raw_parts, from_raw_parts_mut};
|
pub use core::slice::{from_raw_parts, from_raw_parts_mut};
|
||||||
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
|
pub use core::slice::{ChunkBy, ChunkByMut};
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
pub use core::slice::{Chunks, Windows};
|
pub use core::slice::{Chunks, Windows};
|
||||||
#[stable(feature = "chunks_exact", since = "1.31.0")]
|
#[stable(feature = "chunks_exact", since = "1.31.0")]
|
||||||
pub use core::slice::{ChunksExact, ChunksExactMut};
|
pub use core::slice::{ChunksExact, ChunksExactMut};
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
pub use core::slice::{ChunksMut, Split, SplitMut};
|
pub use core::slice::{ChunksMut, Split, SplitMut};
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
|
||||||
pub use core::slice::{GroupBy, GroupByMut};
|
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
pub use core::slice::{Iter, IterMut};
|
pub use core::slice::{Iter, IterMut};
|
||||||
#[stable(feature = "rchunks", since = "1.31.0")]
|
#[stable(feature = "rchunks", since = "1.31.0")]
|
||||||
|
|
|
||||||
|
|
@ -2204,14 +2204,6 @@ impl<T, A: Allocator> Vec<T, A> {
|
||||||
assert_failed(at, self.len());
|
assert_failed(at, self.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
if at == 0 {
|
|
||||||
// the new vector can take over the original buffer and avoid the copy
|
|
||||||
return mem::replace(
|
|
||||||
self,
|
|
||||||
Vec::with_capacity_in(self.capacity(), self.allocator().clone()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let other_len = self.len - at;
|
let other_len = self.len - at;
|
||||||
let mut other = Vec::with_capacity_in(other_len, self.allocator().clone());
|
let mut other = Vec::with_capacity_in(other_len, self.allocator().clone());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@
|
||||||
#![feature(iter_advance_by)]
|
#![feature(iter_advance_by)]
|
||||||
#![feature(iter_next_chunk)]
|
#![feature(iter_next_chunk)]
|
||||||
#![feature(round_char_boundary)]
|
#![feature(round_char_boundary)]
|
||||||
#![feature(slice_group_by)]
|
|
||||||
#![feature(slice_partition_dedup)]
|
#![feature(slice_partition_dedup)]
|
||||||
#![feature(string_remove_matches)]
|
#![feature(string_remove_matches)]
|
||||||
#![feature(const_btree_len)]
|
#![feature(const_btree_len)]
|
||||||
|
|
|
||||||
|
|
@ -1614,10 +1614,10 @@ fn subslice_patterns() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_group_by() {
|
fn test_chunk_by() {
|
||||||
let slice = &[1, 1, 1, 3, 3, 2, 2, 2, 1, 0];
|
let slice = &[1, 1, 1, 3, 3, 2, 2, 2, 1, 0];
|
||||||
|
|
||||||
let mut iter = slice.group_by(|a, b| a == b);
|
let mut iter = slice.chunk_by(|a, b| a == b);
|
||||||
assert_eq!(iter.next(), Some(&[1, 1, 1][..]));
|
assert_eq!(iter.next(), Some(&[1, 1, 1][..]));
|
||||||
assert_eq!(iter.next(), Some(&[3, 3][..]));
|
assert_eq!(iter.next(), Some(&[3, 3][..]));
|
||||||
assert_eq!(iter.next(), Some(&[2, 2, 2][..]));
|
assert_eq!(iter.next(), Some(&[2, 2, 2][..]));
|
||||||
|
|
@ -1625,7 +1625,7 @@ fn test_group_by() {
|
||||||
assert_eq!(iter.next(), Some(&[0][..]));
|
assert_eq!(iter.next(), Some(&[0][..]));
|
||||||
assert_eq!(iter.next(), None);
|
assert_eq!(iter.next(), None);
|
||||||
|
|
||||||
let mut iter = slice.group_by(|a, b| a == b);
|
let mut iter = slice.chunk_by(|a, b| a == b);
|
||||||
assert_eq!(iter.next_back(), Some(&[0][..]));
|
assert_eq!(iter.next_back(), Some(&[0][..]));
|
||||||
assert_eq!(iter.next_back(), Some(&[1][..]));
|
assert_eq!(iter.next_back(), Some(&[1][..]));
|
||||||
assert_eq!(iter.next_back(), Some(&[2, 2, 2][..]));
|
assert_eq!(iter.next_back(), Some(&[2, 2, 2][..]));
|
||||||
|
|
@ -1633,7 +1633,7 @@ fn test_group_by() {
|
||||||
assert_eq!(iter.next_back(), Some(&[1, 1, 1][..]));
|
assert_eq!(iter.next_back(), Some(&[1, 1, 1][..]));
|
||||||
assert_eq!(iter.next_back(), None);
|
assert_eq!(iter.next_back(), None);
|
||||||
|
|
||||||
let mut iter = slice.group_by(|a, b| a == b);
|
let mut iter = slice.chunk_by(|a, b| a == b);
|
||||||
assert_eq!(iter.next(), Some(&[1, 1, 1][..]));
|
assert_eq!(iter.next(), Some(&[1, 1, 1][..]));
|
||||||
assert_eq!(iter.next_back(), Some(&[0][..]));
|
assert_eq!(iter.next_back(), Some(&[0][..]));
|
||||||
assert_eq!(iter.next(), Some(&[3, 3][..]));
|
assert_eq!(iter.next(), Some(&[3, 3][..]));
|
||||||
|
|
@ -1643,10 +1643,10 @@ fn test_group_by() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_group_by_mut() {
|
fn test_chunk_by_mut() {
|
||||||
let slice = &mut [1, 1, 1, 3, 3, 2, 2, 2, 1, 0];
|
let slice = &mut [1, 1, 1, 3, 3, 2, 2, 2, 1, 0];
|
||||||
|
|
||||||
let mut iter = slice.group_by_mut(|a, b| a == b);
|
let mut iter = slice.chunk_by_mut(|a, b| a == b);
|
||||||
assert_eq!(iter.next(), Some(&mut [1, 1, 1][..]));
|
assert_eq!(iter.next(), Some(&mut [1, 1, 1][..]));
|
||||||
assert_eq!(iter.next(), Some(&mut [3, 3][..]));
|
assert_eq!(iter.next(), Some(&mut [3, 3][..]));
|
||||||
assert_eq!(iter.next(), Some(&mut [2, 2, 2][..]));
|
assert_eq!(iter.next(), Some(&mut [2, 2, 2][..]));
|
||||||
|
|
@ -1654,7 +1654,7 @@ fn test_group_by_mut() {
|
||||||
assert_eq!(iter.next(), Some(&mut [0][..]));
|
assert_eq!(iter.next(), Some(&mut [0][..]));
|
||||||
assert_eq!(iter.next(), None);
|
assert_eq!(iter.next(), None);
|
||||||
|
|
||||||
let mut iter = slice.group_by_mut(|a, b| a == b);
|
let mut iter = slice.chunk_by_mut(|a, b| a == b);
|
||||||
assert_eq!(iter.next_back(), Some(&mut [0][..]));
|
assert_eq!(iter.next_back(), Some(&mut [0][..]));
|
||||||
assert_eq!(iter.next_back(), Some(&mut [1][..]));
|
assert_eq!(iter.next_back(), Some(&mut [1][..]));
|
||||||
assert_eq!(iter.next_back(), Some(&mut [2, 2, 2][..]));
|
assert_eq!(iter.next_back(), Some(&mut [2, 2, 2][..]));
|
||||||
|
|
@ -1662,7 +1662,7 @@ fn test_group_by_mut() {
|
||||||
assert_eq!(iter.next_back(), Some(&mut [1, 1, 1][..]));
|
assert_eq!(iter.next_back(), Some(&mut [1, 1, 1][..]));
|
||||||
assert_eq!(iter.next_back(), None);
|
assert_eq!(iter.next_back(), None);
|
||||||
|
|
||||||
let mut iter = slice.group_by_mut(|a, b| a == b);
|
let mut iter = slice.chunk_by_mut(|a, b| a == b);
|
||||||
assert_eq!(iter.next(), Some(&mut [1, 1, 1][..]));
|
assert_eq!(iter.next(), Some(&mut [1, 1, 1][..]));
|
||||||
assert_eq!(iter.next_back(), Some(&mut [0][..]));
|
assert_eq!(iter.next_back(), Some(&mut [0][..]));
|
||||||
assert_eq!(iter.next(), Some(&mut [3, 3][..]));
|
assert_eq!(iter.next(), Some(&mut [3, 3][..]));
|
||||||
|
|
|
||||||
|
|
@ -958,23 +958,35 @@ fn test_append() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_split_off() {
|
fn test_split_off() {
|
||||||
let mut vec = vec![1, 2, 3, 4, 5, 6];
|
let mut vec = vec![1, 2, 3, 4, 5, 6];
|
||||||
|
let orig_ptr = vec.as_ptr();
|
||||||
let orig_capacity = vec.capacity();
|
let orig_capacity = vec.capacity();
|
||||||
let vec2 = vec.split_off(4);
|
|
||||||
|
let split_off = vec.split_off(4);
|
||||||
assert_eq!(vec, [1, 2, 3, 4]);
|
assert_eq!(vec, [1, 2, 3, 4]);
|
||||||
assert_eq!(vec2, [5, 6]);
|
assert_eq!(split_off, [5, 6]);
|
||||||
assert_eq!(vec.capacity(), orig_capacity);
|
assert_eq!(vec.capacity(), orig_capacity);
|
||||||
|
assert_eq!(vec.as_ptr(), orig_ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_split_off_take_all() {
|
fn test_split_off_take_all() {
|
||||||
let mut vec = vec![1, 2, 3, 4, 5, 6];
|
// Allocate enough capacity that we can tell whether the split-off vector's
|
||||||
|
// capacity is based on its size, or (incorrectly) on the original capacity.
|
||||||
|
let mut vec = Vec::with_capacity(1000);
|
||||||
|
vec.extend([1, 2, 3, 4, 5, 6]);
|
||||||
let orig_ptr = vec.as_ptr();
|
let orig_ptr = vec.as_ptr();
|
||||||
let orig_capacity = vec.capacity();
|
let orig_capacity = vec.capacity();
|
||||||
let vec2 = vec.split_off(0);
|
|
||||||
|
let split_off = vec.split_off(0);
|
||||||
assert_eq!(vec, []);
|
assert_eq!(vec, []);
|
||||||
assert_eq!(vec2, [1, 2, 3, 4, 5, 6]);
|
assert_eq!(split_off, [1, 2, 3, 4, 5, 6]);
|
||||||
assert_eq!(vec.capacity(), orig_capacity);
|
assert_eq!(vec.capacity(), orig_capacity);
|
||||||
assert_eq!(vec2.as_ptr(), orig_ptr);
|
assert_eq!(vec.as_ptr(), orig_ptr);
|
||||||
|
|
||||||
|
// The split-off vector should be newly-allocated, and should not have
|
||||||
|
// stolen the original vector's allocation.
|
||||||
|
assert!(split_off.capacity() < orig_capacity);
|
||||||
|
assert_ne!(split_off.as_ptr(), orig_ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -3248,26 +3248,26 @@ unsafe impl<'a, T> TrustedRandomAccessNoCoerce for IterMut<'a, T> {
|
||||||
|
|
||||||
/// An iterator over slice in (non-overlapping) chunks separated by a predicate.
|
/// An iterator over slice in (non-overlapping) chunks separated by a predicate.
|
||||||
///
|
///
|
||||||
/// This struct is created by the [`group_by`] method on [slices].
|
/// This struct is created by the [`chunk_by`] method on [slices].
|
||||||
///
|
///
|
||||||
/// [`group_by`]: slice::group_by
|
/// [`chunk_by`]: slice::chunk_by
|
||||||
/// [slices]: slice
|
/// [slices]: slice
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
#[must_use = "iterators are lazy and do nothing unless consumed"]
|
#[must_use = "iterators are lazy and do nothing unless consumed"]
|
||||||
pub struct GroupBy<'a, T: 'a, P> {
|
pub struct ChunkBy<'a, T: 'a, P> {
|
||||||
slice: &'a [T],
|
slice: &'a [T],
|
||||||
predicate: P,
|
predicate: P,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
impl<'a, T: 'a, P> GroupBy<'a, T, P> {
|
impl<'a, T: 'a, P> ChunkBy<'a, T, P> {
|
||||||
pub(super) fn new(slice: &'a [T], predicate: P) -> Self {
|
pub(super) fn new(slice: &'a [T], predicate: P) -> Self {
|
||||||
GroupBy { slice, predicate }
|
ChunkBy { slice, predicate }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
impl<'a, T: 'a, P> Iterator for GroupBy<'a, T, P>
|
impl<'a, T: 'a, P> Iterator for ChunkBy<'a, T, P>
|
||||||
where
|
where
|
||||||
P: FnMut(&T, &T) -> bool,
|
P: FnMut(&T, &T) -> bool,
|
||||||
{
|
{
|
||||||
|
|
@ -3300,8 +3300,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
impl<'a, T: 'a, P> DoubleEndedIterator for GroupBy<'a, T, P>
|
impl<'a, T: 'a, P> DoubleEndedIterator for ChunkBy<'a, T, P>
|
||||||
where
|
where
|
||||||
P: FnMut(&T, &T) -> bool,
|
P: FnMut(&T, &T) -> bool,
|
||||||
{
|
{
|
||||||
|
|
@ -3322,39 +3322,39 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
impl<'a, T: 'a, P> FusedIterator for GroupBy<'a, T, P> where P: FnMut(&T, &T) -> bool {}
|
impl<'a, T: 'a, P> FusedIterator for ChunkBy<'a, T, P> where P: FnMut(&T, &T) -> bool {}
|
||||||
|
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
impl<'a, T: 'a + fmt::Debug, P> fmt::Debug for GroupBy<'a, T, P> {
|
impl<'a, T: 'a + fmt::Debug, P> fmt::Debug for ChunkBy<'a, T, P> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("GroupBy").field("slice", &self.slice).finish()
|
f.debug_struct("ChunkBy").field("slice", &self.slice).finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An iterator over slice in (non-overlapping) mutable chunks separated
|
/// An iterator over slice in (non-overlapping) mutable chunks separated
|
||||||
/// by a predicate.
|
/// by a predicate.
|
||||||
///
|
///
|
||||||
/// This struct is created by the [`group_by_mut`] method on [slices].
|
/// This struct is created by the [`chunk_by_mut`] method on [slices].
|
||||||
///
|
///
|
||||||
/// [`group_by_mut`]: slice::group_by_mut
|
/// [`chunk_by_mut`]: slice::chunk_by_mut
|
||||||
/// [slices]: slice
|
/// [slices]: slice
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
#[must_use = "iterators are lazy and do nothing unless consumed"]
|
#[must_use = "iterators are lazy and do nothing unless consumed"]
|
||||||
pub struct GroupByMut<'a, T: 'a, P> {
|
pub struct ChunkByMut<'a, T: 'a, P> {
|
||||||
slice: &'a mut [T],
|
slice: &'a mut [T],
|
||||||
predicate: P,
|
predicate: P,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
impl<'a, T: 'a, P> GroupByMut<'a, T, P> {
|
impl<'a, T: 'a, P> ChunkByMut<'a, T, P> {
|
||||||
pub(super) fn new(slice: &'a mut [T], predicate: P) -> Self {
|
pub(super) fn new(slice: &'a mut [T], predicate: P) -> Self {
|
||||||
GroupByMut { slice, predicate }
|
ChunkByMut { slice, predicate }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
impl<'a, T: 'a, P> Iterator for GroupByMut<'a, T, P>
|
impl<'a, T: 'a, P> Iterator for ChunkByMut<'a, T, P>
|
||||||
where
|
where
|
||||||
P: FnMut(&T, &T) -> bool,
|
P: FnMut(&T, &T) -> bool,
|
||||||
{
|
{
|
||||||
|
|
@ -3388,8 +3388,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
impl<'a, T: 'a, P> DoubleEndedIterator for GroupByMut<'a, T, P>
|
impl<'a, T: 'a, P> DoubleEndedIterator for ChunkByMut<'a, T, P>
|
||||||
where
|
where
|
||||||
P: FnMut(&T, &T) -> bool,
|
P: FnMut(&T, &T) -> bool,
|
||||||
{
|
{
|
||||||
|
|
@ -3411,12 +3411,12 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
impl<'a, T: 'a, P> FusedIterator for GroupByMut<'a, T, P> where P: FnMut(&T, &T) -> bool {}
|
impl<'a, T: 'a, P> FusedIterator for ChunkByMut<'a, T, P> where P: FnMut(&T, &T) -> bool {}
|
||||||
|
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
impl<'a, T: 'a + fmt::Debug, P> fmt::Debug for GroupByMut<'a, T, P> {
|
impl<'a, T: 'a + fmt::Debug, P> fmt::Debug for ChunkByMut<'a, T, P> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("GroupByMut").field("slice", &self.slice).finish()
|
f.debug_struct("ChunkByMut").field("slice", &self.slice).finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,8 +68,8 @@ pub use iter::{ArrayChunks, ArrayChunksMut};
|
||||||
#[unstable(feature = "array_windows", issue = "75027")]
|
#[unstable(feature = "array_windows", issue = "75027")]
|
||||||
pub use iter::ArrayWindows;
|
pub use iter::ArrayWindows;
|
||||||
|
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
pub use iter::{GroupBy, GroupByMut};
|
pub use iter::{ChunkBy, ChunkByMut};
|
||||||
|
|
||||||
#[stable(feature = "split_inclusive", since = "1.51.0")]
|
#[stable(feature = "split_inclusive", since = "1.51.0")]
|
||||||
pub use iter::{SplitInclusive, SplitInclusiveMut};
|
pub use iter::{SplitInclusive, SplitInclusiveMut};
|
||||||
|
|
@ -1748,18 +1748,16 @@ impl<T> [T] {
|
||||||
/// Returns an iterator over the slice producing non-overlapping runs
|
/// Returns an iterator over the slice producing non-overlapping runs
|
||||||
/// of elements using the predicate to separate them.
|
/// of elements using the predicate to separate them.
|
||||||
///
|
///
|
||||||
/// The predicate is called on two elements following themselves,
|
/// The predicate is called for every pair of consecutive elements,
|
||||||
/// it means the predicate is called on `slice[0]` and `slice[1]`
|
/// meaning that it is called on `slice[0]` and `slice[1]`,
|
||||||
/// then on `slice[1]` and `slice[2]` and so on.
|
/// followed by `slice[1]` and `slice[2]`, and so on.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// #![feature(slice_group_by)]
|
|
||||||
///
|
|
||||||
/// let slice = &[1, 1, 1, 3, 3, 2, 2, 2];
|
/// let slice = &[1, 1, 1, 3, 3, 2, 2, 2];
|
||||||
///
|
///
|
||||||
/// let mut iter = slice.group_by(|a, b| a == b);
|
/// let mut iter = slice.chunk_by(|a, b| a == b);
|
||||||
///
|
///
|
||||||
/// assert_eq!(iter.next(), Some(&[1, 1, 1][..]));
|
/// assert_eq!(iter.next(), Some(&[1, 1, 1][..]));
|
||||||
/// assert_eq!(iter.next(), Some(&[3, 3][..]));
|
/// assert_eq!(iter.next(), Some(&[3, 3][..]));
|
||||||
|
|
@ -1770,41 +1768,37 @@ impl<T> [T] {
|
||||||
/// This method can be used to extract the sorted subslices:
|
/// This method can be used to extract the sorted subslices:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// #![feature(slice_group_by)]
|
|
||||||
///
|
|
||||||
/// let slice = &[1, 1, 2, 3, 2, 3, 2, 3, 4];
|
/// let slice = &[1, 1, 2, 3, 2, 3, 2, 3, 4];
|
||||||
///
|
///
|
||||||
/// let mut iter = slice.group_by(|a, b| a <= b);
|
/// let mut iter = slice.chunk_by(|a, b| a <= b);
|
||||||
///
|
///
|
||||||
/// assert_eq!(iter.next(), Some(&[1, 1, 2, 3][..]));
|
/// assert_eq!(iter.next(), Some(&[1, 1, 2, 3][..]));
|
||||||
/// assert_eq!(iter.next(), Some(&[2, 3][..]));
|
/// assert_eq!(iter.next(), Some(&[2, 3][..]));
|
||||||
/// assert_eq!(iter.next(), Some(&[2, 3, 4][..]));
|
/// assert_eq!(iter.next(), Some(&[2, 3, 4][..]));
|
||||||
/// assert_eq!(iter.next(), None);
|
/// assert_eq!(iter.next(), None);
|
||||||
/// ```
|
/// ```
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn group_by<F>(&self, pred: F) -> GroupBy<'_, T, F>
|
pub fn chunk_by<F>(&self, pred: F) -> ChunkBy<'_, T, F>
|
||||||
where
|
where
|
||||||
F: FnMut(&T, &T) -> bool,
|
F: FnMut(&T, &T) -> bool,
|
||||||
{
|
{
|
||||||
GroupBy::new(self, pred)
|
ChunkBy::new(self, pred)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over the slice producing non-overlapping mutable
|
/// Returns an iterator over the slice producing non-overlapping mutable
|
||||||
/// runs of elements using the predicate to separate them.
|
/// runs of elements using the predicate to separate them.
|
||||||
///
|
///
|
||||||
/// The predicate is called on two elements following themselves,
|
/// The predicate is called for every pair of consecutive elements,
|
||||||
/// it means the predicate is called on `slice[0]` and `slice[1]`
|
/// meaning that it is called on `slice[0]` and `slice[1]`,
|
||||||
/// then on `slice[1]` and `slice[2]` and so on.
|
/// followed by `slice[1]` and `slice[2]`, and so on.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// #![feature(slice_group_by)]
|
|
||||||
///
|
|
||||||
/// let slice = &mut [1, 1, 1, 3, 3, 2, 2, 2];
|
/// let slice = &mut [1, 1, 1, 3, 3, 2, 2, 2];
|
||||||
///
|
///
|
||||||
/// let mut iter = slice.group_by_mut(|a, b| a == b);
|
/// let mut iter = slice.chunk_by_mut(|a, b| a == b);
|
||||||
///
|
///
|
||||||
/// assert_eq!(iter.next(), Some(&mut [1, 1, 1][..]));
|
/// assert_eq!(iter.next(), Some(&mut [1, 1, 1][..]));
|
||||||
/// assert_eq!(iter.next(), Some(&mut [3, 3][..]));
|
/// assert_eq!(iter.next(), Some(&mut [3, 3][..]));
|
||||||
|
|
@ -1815,24 +1809,22 @@ impl<T> [T] {
|
||||||
/// This method can be used to extract the sorted subslices:
|
/// This method can be used to extract the sorted subslices:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// #![feature(slice_group_by)]
|
|
||||||
///
|
|
||||||
/// let slice = &mut [1, 1, 2, 3, 2, 3, 2, 3, 4];
|
/// let slice = &mut [1, 1, 2, 3, 2, 3, 2, 3, 4];
|
||||||
///
|
///
|
||||||
/// let mut iter = slice.group_by_mut(|a, b| a <= b);
|
/// let mut iter = slice.chunk_by_mut(|a, b| a <= b);
|
||||||
///
|
///
|
||||||
/// assert_eq!(iter.next(), Some(&mut [1, 1, 2, 3][..]));
|
/// assert_eq!(iter.next(), Some(&mut [1, 1, 2, 3][..]));
|
||||||
/// assert_eq!(iter.next(), Some(&mut [2, 3][..]));
|
/// assert_eq!(iter.next(), Some(&mut [2, 3][..]));
|
||||||
/// assert_eq!(iter.next(), Some(&mut [2, 3, 4][..]));
|
/// assert_eq!(iter.next(), Some(&mut [2, 3, 4][..]));
|
||||||
/// assert_eq!(iter.next(), None);
|
/// assert_eq!(iter.next(), None);
|
||||||
/// ```
|
/// ```
|
||||||
#[unstable(feature = "slice_group_by", issue = "80552")]
|
#[stable(feature = "slice_group_by", since = "CURRENT_RUSTC_VERSION")]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn group_by_mut<F>(&mut self, pred: F) -> GroupByMut<'_, T, F>
|
pub fn chunk_by_mut<F>(&mut self, pred: F) -> ChunkByMut<'_, T, F>
|
||||||
where
|
where
|
||||||
F: FnMut(&T, &T) -> bool,
|
F: FnMut(&T, &T) -> bool,
|
||||||
{
|
{
|
||||||
GroupByMut::new(self, pred)
|
ChunkByMut::new(self, pred)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Divides one slice into two at an index.
|
/// Divides one slice into two at an index.
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,6 @@
|
||||||
#![cfg_attr(target_has_atomic = "128", feature(integer_atomics))]
|
#![cfg_attr(target_has_atomic = "128", feature(integer_atomics))]
|
||||||
#![cfg_attr(test, feature(cfg_match))]
|
#![cfg_attr(test, feature(cfg_match))]
|
||||||
#![feature(int_roundings)]
|
#![feature(int_roundings)]
|
||||||
#![feature(slice_group_by)]
|
|
||||||
#![feature(split_array)]
|
#![feature(split_array)]
|
||||||
#![feature(strict_provenance)]
|
#![feature(strict_provenance)]
|
||||||
#![feature(strict_provenance_atomic_ptr)]
|
#![feature(strict_provenance_atomic_ptr)]
|
||||||
|
|
|
||||||
|
|
@ -816,12 +816,12 @@ impl Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to downgrade the inner error to `E` if any.
|
/// Attempt to downcast the inner error to `E` if any.
|
||||||
///
|
///
|
||||||
/// If this [`Error`] was constructed via [`new`] then this function will
|
/// If this [`Error`] was constructed via [`new`] then this function will
|
||||||
/// attempt to perform downgrade on it, otherwise it will return [`Err`].
|
/// attempt to perform downgrade on it, otherwise it will return [`Err`].
|
||||||
///
|
///
|
||||||
/// If downgrade succeeds, it will return [`Ok`], otherwise it will also
|
/// If the downcast succeeds, it will return [`Ok`], otherwise it will also
|
||||||
/// return [`Err`].
|
/// return [`Err`].
|
||||||
///
|
///
|
||||||
/// [`new`]: Error::new
|
/// [`new`]: Error::new
|
||||||
|
|
@ -852,13 +852,39 @@ impl Error {
|
||||||
/// impl From<io::Error> for E {
|
/// impl From<io::Error> for E {
|
||||||
/// fn from(err: io::Error) -> E {
|
/// fn from(err: io::Error) -> E {
|
||||||
/// err.downcast::<E>()
|
/// err.downcast::<E>()
|
||||||
/// .map(|b| *b)
|
|
||||||
/// .unwrap_or_else(E::Io)
|
/// .unwrap_or_else(E::Io)
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
///
|
||||||
|
/// impl From<E> for io::Error {
|
||||||
|
/// fn from(err: E) -> io::Error {
|
||||||
|
/// match err {
|
||||||
|
/// E::Io(io_error) => io_error,
|
||||||
|
/// e => io::Error::new(io::ErrorKind::Other, e),
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # fn main() {
|
||||||
|
/// let e = E::SomeOtherVariant;
|
||||||
|
/// // Convert it to an io::Error
|
||||||
|
/// let io_error = io::Error::from(e);
|
||||||
|
/// // Cast it back to the original variant
|
||||||
|
/// let e = E::from(io_error);
|
||||||
|
/// assert!(matches!(e, E::SomeOtherVariant));
|
||||||
|
///
|
||||||
|
/// let io_error = io::Error::from(io::ErrorKind::AlreadyExists);
|
||||||
|
/// // Convert it to E
|
||||||
|
/// let e = E::from(io_error);
|
||||||
|
/// // Cast it back to the original variant
|
||||||
|
/// let io_error = io::Error::from(e);
|
||||||
|
/// assert_eq!(io_error.kind(), io::ErrorKind::AlreadyExists);
|
||||||
|
/// assert!(io_error.get_ref().is_none());
|
||||||
|
/// assert!(io_error.raw_os_error().is_none());
|
||||||
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[unstable(feature = "io_error_downcast", issue = "99262")]
|
#[unstable(feature = "io_error_downcast", issue = "99262")]
|
||||||
pub fn downcast<E>(self) -> result::Result<Box<E>, Self>
|
pub fn downcast<E>(self) -> result::Result<E, Self>
|
||||||
where
|
where
|
||||||
E: error::Error + Send + Sync + 'static,
|
E: error::Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
|
|
@ -872,7 +898,7 @@ impl Error {
|
||||||
// And the compiler should be able to eliminate the branch
|
// And the compiler should be able to eliminate the branch
|
||||||
// that produces `Err` here since b.error.is::<E>()
|
// that produces `Err` here since b.error.is::<E>()
|
||||||
// returns true.
|
// returns true.
|
||||||
Ok(res.unwrap())
|
Ok(*res.unwrap())
|
||||||
}
|
}
|
||||||
repr_data => Err(Self { repr: Repr::new(repr_data) }),
|
repr_data => Err(Self { repr: Repr::new(repr_data) }),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ impl error::Error for E {}
|
||||||
fn test_std_io_error_downcast() {
|
fn test_std_io_error_downcast() {
|
||||||
// Case 1: custom error, downcast succeeds
|
// Case 1: custom error, downcast succeeds
|
||||||
let io_error = Error::new(ErrorKind::Other, Bojji(true));
|
let io_error = Error::new(ErrorKind::Other, Bojji(true));
|
||||||
let e: Box<Bojji> = io_error.downcast().unwrap();
|
let e: Bojji = io_error.downcast().unwrap();
|
||||||
assert!(e.0);
|
assert!(e.0);
|
||||||
|
|
||||||
// Case 2: custom error, downcast fails
|
// Case 2: custom error, downcast fails
|
||||||
|
|
@ -166,7 +166,7 @@ fn test_std_io_error_downcast() {
|
||||||
|
|
||||||
// ensures that the custom error is intact
|
// ensures that the custom error is intact
|
||||||
assert_eq!(ErrorKind::Other, io_error.kind());
|
assert_eq!(ErrorKind::Other, io_error.kind());
|
||||||
let e: Box<Bojji> = io_error.downcast().unwrap();
|
let e: Bojji = io_error.downcast().unwrap();
|
||||||
assert!(e.0);
|
assert!(e.0);
|
||||||
|
|
||||||
// Case 3: os error
|
// Case 3: os error
|
||||||
|
|
|
||||||
|
|
@ -5606,6 +5606,7 @@ Released 2018-09-13
|
||||||
[`suspicious_else_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_else_formatting
|
[`suspicious_else_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_else_formatting
|
||||||
[`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map
|
[`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map
|
||||||
[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl
|
[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl
|
||||||
|
[`suspicious_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_open_options
|
||||||
[`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings
|
[`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings
|
||||||
[`suspicious_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_splitn
|
[`suspicious_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_splitn
|
||||||
[`suspicious_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_to_owned
|
[`suspicious_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_to_owned
|
||||||
|
|
@ -5814,6 +5815,7 @@ Released 2018-09-13
|
||||||
[`absolute-paths-max-segments`]: https://doc.rust-lang.org/clippy/lint_configuration.html#absolute-paths-max-segments
|
[`absolute-paths-max-segments`]: https://doc.rust-lang.org/clippy/lint_configuration.html#absolute-paths-max-segments
|
||||||
[`absolute-paths-allowed-crates`]: https://doc.rust-lang.org/clippy/lint_configuration.html#absolute-paths-allowed-crates
|
[`absolute-paths-allowed-crates`]: https://doc.rust-lang.org/clippy/lint_configuration.html#absolute-paths-allowed-crates
|
||||||
[`allowed-dotfiles`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allowed-dotfiles
|
[`allowed-dotfiles`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allowed-dotfiles
|
||||||
|
[`allowed-duplicate-crates`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allowed-duplicate-crates
|
||||||
[`enforce-iter-loop-reborrow`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enforce-iter-loop-reborrow
|
[`enforce-iter-loop-reborrow`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enforce-iter-loop-reborrow
|
||||||
[`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items
|
[`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items
|
||||||
[`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior
|
[`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ default configuration of Clippy. By default, any configuration will replace the
|
||||||
* `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
|
* `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
|
||||||
* `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
|
* `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
|
||||||
|
|
||||||
**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", "WebGL", "WebGL2", "WebGPU", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`
|
**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "WebGL", "WebGL2", "WebGPU", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`
|
||||||
|
|
||||||
---
|
---
|
||||||
**Affected lints:**
|
**Affected lints:**
|
||||||
|
|
@ -768,7 +768,19 @@ Additional dotfiles (files or directories starting with a dot) to allow
|
||||||
* [`path_ends_with_ext`](https://rust-lang.github.io/rust-clippy/master/index.html#path_ends_with_ext)
|
* [`path_ends_with_ext`](https://rust-lang.github.io/rust-clippy/master/index.html#path_ends_with_ext)
|
||||||
|
|
||||||
|
|
||||||
|
## `allowed-duplicate-crates`
|
||||||
|
A list of crate names to allow duplicates of
|
||||||
|
|
||||||
|
**Default Value:** `[]`
|
||||||
|
|
||||||
|
---
|
||||||
|
**Affected lints:**
|
||||||
|
* [`multiple_crate_versions`](https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions)
|
||||||
|
|
||||||
|
|
||||||
## `enforce-iter-loop-reborrow`
|
## `enforce-iter-loop-reborrow`
|
||||||
|
Whether to recommend using implicit into iter for reborrowed values.
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
```no_run
|
```no_run
|
||||||
let mut vec = vec![1, 2, 3];
|
let mut vec = vec![1, 2, 3];
|
||||||
|
|
@ -793,7 +805,7 @@ for _ in &mut *rmvec {}
|
||||||
|
|
||||||
|
|
||||||
## `check-private-items`
|
## `check-private-items`
|
||||||
|
Whether to also run the listed lints on private items.
|
||||||
|
|
||||||
**Default Value:** `false`
|
**Default Value:** `false`
|
||||||
|
|
||||||
|
|
@ -806,9 +818,10 @@ for _ in &mut *rmvec {}
|
||||||
|
|
||||||
|
|
||||||
## `pub-underscore-fields-behavior`
|
## `pub-underscore-fields-behavior`
|
||||||
|
Lint "public" fields in a struct that are prefixed with an underscore based on their
|
||||||
|
exported visibility, or whether they are marked as "pub".
|
||||||
|
|
||||||
|
**Default Value:** `"PubliclyExported"`
|
||||||
**Default Value:** `"PublicallyExported"`
|
|
||||||
|
|
||||||
---
|
---
|
||||||
**Affected lints:**
|
**Affected lints:**
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ use crate::msrvs::Msrv;
|
||||||
use crate::types::{DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename};
|
use crate::types::{DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename};
|
||||||
use crate::ClippyConfiguration;
|
use crate::ClippyConfiguration;
|
||||||
use rustc_data_structures::fx::FxHashSet;
|
use rustc_data_structures::fx::FxHashSet;
|
||||||
|
use rustc_errors::Applicability;
|
||||||
use rustc_session::Session;
|
use rustc_session::Session;
|
||||||
|
use rustc_span::edit_distance::edit_distance;
|
||||||
use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
|
use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
|
||||||
use serde::de::{IgnoredAny, IntoDeserializer, MapAccess, Visitor};
|
use serde::de::{IgnoredAny, IntoDeserializer, MapAccess, Visitor};
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
|
@ -26,7 +28,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
|
||||||
"NaN", "NaNs",
|
"NaN", "NaNs",
|
||||||
"OAuth", "GraphQL",
|
"OAuth", "GraphQL",
|
||||||
"OCaml",
|
"OCaml",
|
||||||
"OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS",
|
"OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry",
|
||||||
"WebGL", "WebGL2", "WebGPU",
|
"WebGL", "WebGL2", "WebGPU",
|
||||||
"TensorFlow",
|
"TensorFlow",
|
||||||
"TrueType",
|
"TrueType",
|
||||||
|
|
@ -59,18 +61,25 @@ impl TryConf {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct ConfError {
|
struct ConfError {
|
||||||
message: String,
|
message: String,
|
||||||
|
suggestion: Option<Suggestion>,
|
||||||
span: Span,
|
span: Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfError {
|
impl ConfError {
|
||||||
fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self {
|
fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self {
|
||||||
let span = error.span().unwrap_or(0..file.source_len.0 as usize);
|
let span = error.span().unwrap_or(0..file.source_len.0 as usize);
|
||||||
Self::spanned(file, error.message(), span)
|
Self::spanned(file, error.message(), None, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spanned(file: &SourceFile, message: impl Into<String>, span: Range<usize>) -> Self {
|
fn spanned(
|
||||||
|
file: &SourceFile,
|
||||||
|
message: impl Into<String>,
|
||||||
|
suggestion: Option<Suggestion>,
|
||||||
|
span: Range<usize>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
message: message.into(),
|
message: message.into(),
|
||||||
|
suggestion,
|
||||||
span: Span::new(
|
span: Span::new(
|
||||||
file.start_pos + BytePos::from_usize(span.start),
|
file.start_pos + BytePos::from_usize(span.start),
|
||||||
file.start_pos + BytePos::from_usize(span.end),
|
file.start_pos + BytePos::from_usize(span.end),
|
||||||
|
|
@ -147,16 +156,18 @@ macro_rules! define_Conf {
|
||||||
match Field::deserialize(name.get_ref().as_str().into_deserializer()) {
|
match Field::deserialize(name.get_ref().as_str().into_deserializer()) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let e: FieldError = e;
|
let e: FieldError = e;
|
||||||
errors.push(ConfError::spanned(self.0, e.0, name.span()));
|
errors.push(ConfError::spanned(self.0, e.error, e.suggestion, name.span()));
|
||||||
}
|
}
|
||||||
$(Ok(Field::$name) => {
|
$(Ok(Field::$name) => {
|
||||||
$(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), name.span()));)?
|
$(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), None, name.span()));)?
|
||||||
let raw_value = map.next_value::<toml::Spanned<toml::Value>>()?;
|
let raw_value = map.next_value::<toml::Spanned<toml::Value>>()?;
|
||||||
let value_span = raw_value.span();
|
let value_span = raw_value.span();
|
||||||
match <$ty>::deserialize(raw_value.into_inner()) {
|
match <$ty>::deserialize(raw_value.into_inner()) {
|
||||||
Err(e) => errors.push(ConfError::spanned(self.0, e.to_string().replace('\n', " ").trim(), value_span)),
|
Err(e) => errors.push(ConfError::spanned(self.0, e.to_string().replace('\n', " ").trim(), None, value_span)),
|
||||||
Ok(value) => match $name {
|
Ok(value) => match $name {
|
||||||
Some(_) => errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), name.span())),
|
Some(_) => {
|
||||||
|
errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), None, name.span()));
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
$name = Some(value);
|
$name = Some(value);
|
||||||
// $new_conf is the same as one of the defined `$name`s, so
|
// $new_conf is the same as one of the defined `$name`s, so
|
||||||
|
|
@ -165,7 +176,7 @@ macro_rules! define_Conf {
|
||||||
Some(_) => errors.push(ConfError::spanned(self.0, concat!(
|
Some(_) => errors.push(ConfError::spanned(self.0, concat!(
|
||||||
"duplicate field `", stringify!($new_conf),
|
"duplicate field `", stringify!($new_conf),
|
||||||
"` (provided as `", stringify!($name), "`)"
|
"` (provided as `", stringify!($name), "`)"
|
||||||
), name.span())),
|
), None, name.span())),
|
||||||
None => $new_conf = $name.clone(),
|
None => $new_conf = $name.clone(),
|
||||||
})?
|
})?
|
||||||
},
|
},
|
||||||
|
|
@ -523,7 +534,11 @@ define_Conf! {
|
||||||
///
|
///
|
||||||
/// Additional dotfiles (files or directories starting with a dot) to allow
|
/// Additional dotfiles (files or directories starting with a dot) to allow
|
||||||
(allowed_dotfiles: FxHashSet<String> = FxHashSet::default()),
|
(allowed_dotfiles: FxHashSet<String> = FxHashSet::default()),
|
||||||
/// Lint: EXPLICIT_ITER_LOOP
|
/// Lint: MULTIPLE_CRATE_VERSIONS.
|
||||||
|
///
|
||||||
|
/// A list of crate names to allow duplicates of
|
||||||
|
(allowed_duplicate_crates: FxHashSet<String> = FxHashSet::default()),
|
||||||
|
/// Lint: EXPLICIT_ITER_LOOP.
|
||||||
///
|
///
|
||||||
/// Whether to recommend using implicit into iter for reborrowed values.
|
/// Whether to recommend using implicit into iter for reborrowed values.
|
||||||
///
|
///
|
||||||
|
|
@ -543,15 +558,15 @@ define_Conf! {
|
||||||
/// for _ in &mut *rmvec {}
|
/// for _ in &mut *rmvec {}
|
||||||
/// ```
|
/// ```
|
||||||
(enforce_iter_loop_reborrow: bool = false),
|
(enforce_iter_loop_reborrow: bool = false),
|
||||||
/// Lint: MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC, MISSING_PANICS_DOC, MISSING_ERRORS_DOC
|
/// Lint: MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC, MISSING_PANICS_DOC, MISSING_ERRORS_DOC.
|
||||||
///
|
///
|
||||||
/// Whether to also run the listed lints on private items.
|
/// Whether to also run the listed lints on private items.
|
||||||
(check_private_items: bool = false),
|
(check_private_items: bool = false),
|
||||||
/// Lint: PUB_UNDERSCORE_FIELDS
|
/// Lint: PUB_UNDERSCORE_FIELDS.
|
||||||
///
|
///
|
||||||
/// Lint "public" fields in a struct that are prefixed with an underscore based on their
|
/// Lint "public" fields in a struct that are prefixed with an underscore based on their
|
||||||
/// exported visibility, or whether they are marked as "pub".
|
/// exported visibility, or whether they are marked as "pub".
|
||||||
(pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PublicallyExported),
|
(pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Search for the configuration file.
|
/// Search for the configuration file.
|
||||||
|
|
@ -669,10 +684,16 @@ impl Conf {
|
||||||
|
|
||||||
// all conf errors are non-fatal, we just use the default conf in case of error
|
// all conf errors are non-fatal, we just use the default conf in case of error
|
||||||
for error in errors {
|
for error in errors {
|
||||||
sess.dcx().span_err(
|
let mut diag = sess.dcx().struct_span_err(
|
||||||
error.span,
|
error.span,
|
||||||
format!("error reading Clippy's configuration file: {}", error.message),
|
format!("error reading Clippy's configuration file: {}", error.message),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if let Some(sugg) = error.suggestion {
|
||||||
|
diag.span_suggestion(error.span, sugg.message, sugg.suggestion, Applicability::MaybeIncorrect);
|
||||||
|
}
|
||||||
|
|
||||||
|
diag.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
for warning in warnings {
|
for warning in warnings {
|
||||||
|
|
@ -689,19 +710,31 @@ impl Conf {
|
||||||
const SEPARATOR_WIDTH: usize = 4;
|
const SEPARATOR_WIDTH: usize = 4;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct FieldError(String);
|
struct FieldError {
|
||||||
|
error: String,
|
||||||
|
suggestion: Option<Suggestion>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Suggestion {
|
||||||
|
message: &'static str,
|
||||||
|
suggestion: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
impl std::error::Error for FieldError {}
|
impl std::error::Error for FieldError {}
|
||||||
|
|
||||||
impl Display for FieldError {
|
impl Display for FieldError {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
f.pad(&self.0)
|
f.pad(&self.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl serde::de::Error for FieldError {
|
impl serde::de::Error for FieldError {
|
||||||
fn custom<T: Display>(msg: T) -> Self {
|
fn custom<T: Display>(msg: T) -> Self {
|
||||||
Self(msg.to_string())
|
Self {
|
||||||
|
error: msg.to_string(),
|
||||||
|
suggestion: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self {
|
fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self {
|
||||||
|
|
@ -723,7 +756,20 @@ impl serde::de::Error for FieldError {
|
||||||
write!(msg, "{:SEPARATOR_WIDTH$}{field:column_width$}", " ").unwrap();
|
write!(msg, "{:SEPARATOR_WIDTH$}{field:column_width$}", " ").unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self(msg)
|
|
||||||
|
let suggestion = expected
|
||||||
|
.iter()
|
||||||
|
.filter_map(|expected| {
|
||||||
|
let dist = edit_distance(field, expected, 4)?;
|
||||||
|
Some((dist, expected))
|
||||||
|
})
|
||||||
|
.min_by_key(|&(dist, _)| dist)
|
||||||
|
.map(|(_, suggestion)| Suggestion {
|
||||||
|
message: "perhaps you meant",
|
||||||
|
suggestion,
|
||||||
|
});
|
||||||
|
|
||||||
|
Self { error: msg, suggestion }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ extern crate rustc_ast;
|
||||||
extern crate rustc_data_structures;
|
extern crate rustc_data_structures;
|
||||||
#[allow(unused_extern_crates)]
|
#[allow(unused_extern_crates)]
|
||||||
extern crate rustc_driver;
|
extern crate rustc_driver;
|
||||||
|
extern crate rustc_errors;
|
||||||
extern crate rustc_session;
|
extern crate rustc_session;
|
||||||
extern crate rustc_span;
|
extern crate rustc_span;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,6 @@ unimplemented_serialize! {
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub enum PubUnderscoreFieldsBehaviour {
|
pub enum PubUnderscoreFieldsBehaviour {
|
||||||
PublicallyExported,
|
PubliclyExported,
|
||||||
AllPubFields,
|
AllPubFields,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ version = "0.0.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aho-corasick = "0.7"
|
aho-corasick = "1.0"
|
||||||
clap = "4.1.4"
|
clap = "4.1.4"
|
||||||
indoc = "1.0"
|
indoc = "1.0"
|
||||||
itertools = "0.11"
|
itertools = "0.11"
|
||||||
opener = "0.5"
|
opener = "0.6"
|
||||||
shell-escape = "0.1"
|
shell-escape = "0.1"
|
||||||
walkdir = "2.3"
|
walkdir = "2.3"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -504,9 +504,8 @@ fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<S
|
||||||
}
|
}
|
||||||
|
|
||||||
let searcher = AhoCorasickBuilder::new()
|
let searcher = AhoCorasickBuilder::new()
|
||||||
.dfa(true)
|
|
||||||
.match_kind(aho_corasick::MatchKind::LeftmostLongest)
|
.match_kind(aho_corasick::MatchKind::LeftmostLongest)
|
||||||
.build_with_size::<u16, _, _>(replacements.iter().map(|&(x, _)| x.as_bytes()))
|
.build(replacements.iter().map(|&(x, _)| x.as_bytes()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut result = String::with_capacity(contents.len() + 1024);
|
let mut result = String::with_capacity(contents.len() + 1024);
|
||||||
|
|
@ -928,7 +927,7 @@ fn remove_line_splices(s: &str) -> String {
|
||||||
.and_then(|s| s.strip_suffix('"'))
|
.and_then(|s| s.strip_suffix('"'))
|
||||||
.unwrap_or_else(|| panic!("expected quoted string, found `{s}`"));
|
.unwrap_or_else(|| panic!("expected quoted string, found `{s}`"));
|
||||||
let mut res = String::with_capacity(s.len());
|
let mut res = String::with_capacity(s.len());
|
||||||
unescape::unescape_literal(s, unescape::Mode::Str, &mut |range, ch| {
|
unescape::unescape_unicode(s, unescape::Mode::Str, &mut |range, ch| {
|
||||||
if ch.is_ok() {
|
if ch.is_ok() {
|
||||||
res.push_str(&s[range]);
|
res.push_str(&s[range]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arrayvec = { version = "0.7", default-features = false }
|
arrayvec = { version = "0.7", default-features = false }
|
||||||
cargo_metadata = "0.15.3"
|
cargo_metadata = "0.18"
|
||||||
clippy_config = { path = "../clippy_config" }
|
clippy_config = { path = "../clippy_config" }
|
||||||
clippy_utils = { path = "../clippy_utils" }
|
clippy_utils = { path = "../clippy_utils" }
|
||||||
declare_clippy_lint = { path = "../declare_clippy_lint" }
|
declare_clippy_lint = { path = "../declare_clippy_lint" }
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ declare_clippy_lint! {
|
||||||
/// This lint warns when you use `Arc` with a type that does not implement `Send` or `Sync`.
|
/// This lint warns when you use `Arc` with a type that does not implement `Send` or `Sync`.
|
||||||
///
|
///
|
||||||
/// ### Why is this bad?
|
/// ### Why is this bad?
|
||||||
/// `Arc<T>` is an Atomic `RC<T>` and guarantees that updates to the reference counter are
|
/// `Arc<T>` is a thread-safe `Rc<T>` and guarantees that updates to the reference counter
|
||||||
/// Atomic. This is useful in multiprocessing scenarios. To send an `Arc<T>` across processes
|
/// use atomic operations. To send an `Arc<T>` across thread boundaries and
|
||||||
/// and make use of the atomic ref counter, `T` must be [both `Send` and `Sync`](https://doc.rust-lang.org/std/sync/struct.Arc.html#impl-Send-for-Arc%3CT%3E),
|
/// share ownership between multiple threads, `T` must be [both `Send` and `Sync`](https://doc.rust-lang.org/std/sync/struct.Arc.html#thread-safety),
|
||||||
/// either `T` should be made `Send + Sync` or an `Rc` should be used instead of an `Arc`
|
/// so either `T` should be made `Send + Sync` or an `Rc` should be used instead of an `Arc`
|
||||||
///
|
///
|
||||||
/// ### Example
|
/// ### Example
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,11 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInConditions {
|
||||||
);
|
);
|
||||||
|
|
||||||
if let ExprKind::Block(block, _) = &cond.kind {
|
if let ExprKind::Block(block, _) = &cond.kind {
|
||||||
|
if !block.span.eq_ctxt(expr.span) {
|
||||||
|
// If the block comes from a macro, or as an argument to a macro,
|
||||||
|
// do not lint.
|
||||||
|
return;
|
||||||
|
}
|
||||||
if block.rules == BlockCheckMode::DefaultBlock {
|
if block.rules == BlockCheckMode::DefaultBlock {
|
||||||
if block.stmts.is_empty() {
|
if block.stmts.is_empty() {
|
||||||
if let Some(ex) = &block.expr {
|
if let Some(ex) = &block.expr {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ mod wildcard_dependencies;
|
||||||
use cargo_metadata::MetadataCommand;
|
use cargo_metadata::MetadataCommand;
|
||||||
use clippy_utils::diagnostics::span_lint;
|
use clippy_utils::diagnostics::span_lint;
|
||||||
use clippy_utils::is_lint_allowed;
|
use clippy_utils::is_lint_allowed;
|
||||||
|
use rustc_data_structures::fx::FxHashSet;
|
||||||
use rustc_hir::hir_id::CRATE_HIR_ID;
|
use rustc_hir::hir_id::CRATE_HIR_ID;
|
||||||
use rustc_lint::{LateContext, LateLintPass, Lint};
|
use rustc_lint::{LateContext, LateLintPass, Lint};
|
||||||
use rustc_session::impl_lint_pass;
|
use rustc_session::impl_lint_pass;
|
||||||
|
|
@ -128,6 +129,8 @@ declare_clippy_lint! {
|
||||||
/// ### Known problems
|
/// ### Known problems
|
||||||
/// Because this can be caused purely by the dependencies
|
/// Because this can be caused purely by the dependencies
|
||||||
/// themselves, it's not always possible to fix this issue.
|
/// themselves, it's not always possible to fix this issue.
|
||||||
|
/// In those cases, you can allow that specific crate using
|
||||||
|
/// the `allowed_duplicate_crates` configuration option.
|
||||||
///
|
///
|
||||||
/// ### Example
|
/// ### Example
|
||||||
/// ```toml
|
/// ```toml
|
||||||
|
|
@ -163,6 +166,7 @@ declare_clippy_lint! {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Cargo {
|
pub struct Cargo {
|
||||||
|
pub allowed_duplicate_crates: FxHashSet<String>,
|
||||||
pub ignore_publish: bool,
|
pub ignore_publish: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,7 +212,7 @@ impl LateLintPass<'_> for Cargo {
|
||||||
{
|
{
|
||||||
match MetadataCommand::new().exec() {
|
match MetadataCommand::new().exec() {
|
||||||
Ok(metadata) => {
|
Ok(metadata) => {
|
||||||
multiple_crate_versions::check(cx, &metadata);
|
multiple_crate_versions::check(cx, &metadata, &self.allowed_duplicate_crates);
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
for lint in WITH_DEPS_LINTS {
|
for lint in WITH_DEPS_LINTS {
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,40 @@
|
||||||
use cargo_metadata::{DependencyKind, Metadata, Node, Package, PackageId};
|
use cargo_metadata::{DependencyKind, Metadata, Node, Package, PackageId};
|
||||||
use clippy_utils::diagnostics::span_lint;
|
use clippy_utils::diagnostics::span_lint;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use rustc_data_structures::fx::FxHashSet;
|
||||||
use rustc_hir::def_id::LOCAL_CRATE;
|
use rustc_hir::def_id::LOCAL_CRATE;
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
use rustc_span::DUMMY_SP;
|
use rustc_span::DUMMY_SP;
|
||||||
|
|
||||||
use super::MULTIPLE_CRATE_VERSIONS;
|
use super::MULTIPLE_CRATE_VERSIONS;
|
||||||
|
|
||||||
pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
|
pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata, allowed_duplicate_crates: &FxHashSet<String>) {
|
||||||
let local_name = cx.tcx.crate_name(LOCAL_CRATE);
|
let local_name = cx.tcx.crate_name(LOCAL_CRATE);
|
||||||
let mut packages = metadata.packages.clone();
|
let mut packages = metadata.packages.clone();
|
||||||
packages.sort_by(|a, b| a.name.cmp(&b.name));
|
packages.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
|
||||||
if let Some(resolve) = &metadata.resolve
|
if let Some(resolve) = &metadata.resolve
|
||||||
&& let Some(local_id) = packages.iter().find_map(|p| {
|
&& let Some(local_id) = packages.iter().find_map(|p| {
|
||||||
if p.name == local_name.as_str() {
|
// p.name contains the original crate names with dashes intact
|
||||||
|
// local_name contains the crate name as a namespace, with the dashes converted to underscores
|
||||||
|
// the code below temporarily rectifies this discrepancy
|
||||||
|
if p.name
|
||||||
|
.as_bytes()
|
||||||
|
.iter()
|
||||||
|
.map(|b| if b == &b'-' { &b'_' } else { b })
|
||||||
|
.eq(local_name.as_str().as_bytes())
|
||||||
|
{
|
||||||
Some(&p.id)
|
Some(&p.id)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
for (name, group) in &packages.iter().group_by(|p| p.name.clone()) {
|
for (name, group) in &packages
|
||||||
|
.iter()
|
||||||
|
.filter(|p| !allowed_duplicate_crates.contains(&p.name))
|
||||||
|
.group_by(|p| &p.name)
|
||||||
|
{
|
||||||
let group: Vec<&Package> = group.collect();
|
let group: Vec<&Package> = group.collect();
|
||||||
|
|
||||||
if group.len() <= 1 {
|
if group.len() <= 1 {
|
||||||
|
|
|
||||||
|
|
@ -439,6 +439,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
||||||
crate::methods::STR_SPLIT_AT_NEWLINE_INFO,
|
crate::methods::STR_SPLIT_AT_NEWLINE_INFO,
|
||||||
crate::methods::SUSPICIOUS_COMMAND_ARG_SPACE_INFO,
|
crate::methods::SUSPICIOUS_COMMAND_ARG_SPACE_INFO,
|
||||||
crate::methods::SUSPICIOUS_MAP_INFO,
|
crate::methods::SUSPICIOUS_MAP_INFO,
|
||||||
|
crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO,
|
||||||
crate::methods::SUSPICIOUS_SPLITN_INFO,
|
crate::methods::SUSPICIOUS_SPLITN_INFO,
|
||||||
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
|
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
|
||||||
crate::methods::TYPE_ID_ON_BOX_INFO,
|
crate::methods::TYPE_ID_ON_BOX_INFO,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||||
use clippy_utils::last_path_segment;
|
|
||||||
use clippy_utils::source::snippet_with_context;
|
use clippy_utils::source::snippet_with_context;
|
||||||
|
use clippy_utils::{last_path_segment, std_or_core};
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::{def, Expr, ExprKind, GenericArg, QPath, TyKind};
|
use rustc_hir::{def, Expr, ExprKind, GenericArg, QPath, TyKind};
|
||||||
use rustc_lint::{LateContext, LateLintPass};
|
use rustc_lint::{LateContext, LateLintPass};
|
||||||
|
|
@ -42,12 +42,14 @@ impl<'tcx> LateLintPass<'tcx> for DefaultIterEmpty {
|
||||||
&& ty.span.ctxt() == ctxt
|
&& ty.span.ctxt() == ctxt
|
||||||
{
|
{
|
||||||
let mut applicability = Applicability::MachineApplicable;
|
let mut applicability = Applicability::MachineApplicable;
|
||||||
let sugg = make_sugg(cx, ty_path, ctxt, &mut applicability);
|
let Some(path) = std_or_core(cx) else { return };
|
||||||
|
let path = format!("{path}::iter::empty");
|
||||||
|
let sugg = make_sugg(cx, ty_path, ctxt, &mut applicability, &path);
|
||||||
span_lint_and_sugg(
|
span_lint_and_sugg(
|
||||||
cx,
|
cx,
|
||||||
DEFAULT_INSTEAD_OF_ITER_EMPTY,
|
DEFAULT_INSTEAD_OF_ITER_EMPTY,
|
||||||
expr.span,
|
expr.span,
|
||||||
"`std::iter::empty()` is the more idiomatic way",
|
&format!("`{path}()` is the more idiomatic way"),
|
||||||
"try",
|
"try",
|
||||||
sugg,
|
sugg,
|
||||||
applicability,
|
applicability,
|
||||||
|
|
@ -61,6 +63,7 @@ fn make_sugg(
|
||||||
ty_path: &rustc_hir::QPath<'_>,
|
ty_path: &rustc_hir::QPath<'_>,
|
||||||
ctxt: SyntaxContext,
|
ctxt: SyntaxContext,
|
||||||
applicability: &mut Applicability,
|
applicability: &mut Applicability,
|
||||||
|
path: &str,
|
||||||
) -> String {
|
) -> String {
|
||||||
if let Some(last) = last_path_segment(ty_path).args
|
if let Some(last) = last_path_segment(ty_path).args
|
||||||
&& let Some(iter_ty) = last.args.iter().find_map(|arg| match arg {
|
&& let Some(iter_ty) = last.args.iter().find_map(|arg| match arg {
|
||||||
|
|
@ -69,10 +72,10 @@ fn make_sugg(
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
format!(
|
format!(
|
||||||
"std::iter::empty::<{}>()",
|
"{path}::<{}>()",
|
||||||
snippet_with_context(cx, iter_ty.span, ctxt, "..", applicability).0
|
snippet_with_context(cx, iter_ty.span, ctxt, "..", applicability).0
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
"std::iter::empty()".to_owned()
|
format!("{path}()")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||||
|
use clippy_utils::numeric_literal;
|
||||||
use clippy_utils::source::snippet_opt;
|
use clippy_utils::source::snippet_opt;
|
||||||
use clippy_utils::{get_parent_node, numeric_literal};
|
|
||||||
use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
|
use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::intravisit::{walk_expr, walk_stmt, Visitor};
|
use rustc_hir::intravisit::{walk_expr, walk_stmt, Visitor};
|
||||||
use rustc_hir::{Block, Body, Expr, ExprKind, FnRetTy, HirId, ItemKind, Lit, Node, Stmt, StmtKind};
|
use rustc_hir::{Block, Body, ConstContext, Expr, ExprKind, FnRetTy, HirId, Lit, Stmt, StmtKind};
|
||||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||||
use rustc_middle::lint::in_external_macro;
|
use rustc_middle::lint::in_external_macro;
|
||||||
use rustc_middle::ty::{self, FloatTy, IntTy, PolyFnSig, Ty};
|
use rustc_middle::ty::{self, FloatTy, IntTy, PolyFnSig, Ty};
|
||||||
|
|
@ -50,11 +50,11 @@ declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]);
|
||||||
|
|
||||||
impl<'tcx> LateLintPass<'tcx> for DefaultNumericFallback {
|
impl<'tcx> LateLintPass<'tcx> for DefaultNumericFallback {
|
||||||
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
|
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
|
||||||
let is_parent_const = if let Some(Node::Item(item)) = get_parent_node(cx.tcx, body.id().hir_id) {
|
let hir = cx.tcx.hir();
|
||||||
matches!(item.kind, ItemKind::Const(..))
|
let is_parent_const = matches!(
|
||||||
} else {
|
hir.body_const_context(hir.body_owner_def_id(body.id())),
|
||||||
false
|
Some(ConstContext::Const { inline: false } | ConstContext::Static(_))
|
||||||
};
|
);
|
||||||
let mut visitor = NumericFallbackVisitor::new(cx, is_parent_const);
|
let mut visitor = NumericFallbackVisitor::new(cx, is_parent_const);
|
||||||
visitor.visit_body(body);
|
visitor.visit_body(body);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
|
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
|
||||||
use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy};
|
use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy};
|
||||||
use clippy_utils::{is_lint_allowed, match_def_path, paths};
|
use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, match_def_path, paths};
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::def_id::DefId;
|
use rustc_hir::def_id::DefId;
|
||||||
use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor};
|
use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor};
|
||||||
|
|
@ -450,6 +450,7 @@ fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_r
|
||||||
&& let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq)
|
&& let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq)
|
||||||
&& let Some(def_id) = trait_ref.trait_def_id()
|
&& let Some(def_id) = trait_ref.trait_def_id()
|
||||||
&& cx.tcx.is_diagnostic_item(sym::PartialEq, def_id)
|
&& cx.tcx.is_diagnostic_item(sym::PartialEq, def_id)
|
||||||
|
&& !has_non_exhaustive_attr(cx.tcx, *adt)
|
||||||
&& let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id)
|
&& let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id)
|
||||||
&& !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, None, &[])
|
&& !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, None, &[])
|
||||||
// If all of our fields implement `Eq`, we can implement `Eq` too
|
// If all of our fields implement `Eq`, we can implement `Eq` too
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use clippy_utils::diagnostics::span_lint;
|
||||||
use rustc_ast::{CoroutineKind, Fn, FnRetTy, Item, ItemKind};
|
use rustc_ast::{CoroutineKind, Fn, FnRetTy, Item, ItemKind};
|
||||||
use rustc_data_structures::sync::Lrc;
|
use rustc_data_structures::sync::Lrc;
|
||||||
use rustc_errors::emitter::HumanEmitter;
|
use rustc_errors::emitter::HumanEmitter;
|
||||||
use rustc_errors::DiagCtxt;
|
use rustc_errors::{DiagCtxt, DiagnosticBuilder};
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
use rustc_parse::maybe_new_parser_from_source_str;
|
use rustc_parse::maybe_new_parser_from_source_str;
|
||||||
use rustc_parse::parser::ForceCollect;
|
use rustc_parse::parser::ForceCollect;
|
||||||
|
|
@ -53,7 +53,7 @@ pub fn check(
|
||||||
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code) {
|
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code) {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(errs) => {
|
Err(errs) => {
|
||||||
errs.into_iter().for_each(|err| err.cancel());
|
errs.into_iter().for_each(DiagnosticBuilder::cancel);
|
||||||
return (false, test_attr_spans);
|
return (false, test_attr_spans);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ use clippy_utils::source::snippet_opt;
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::intravisit::{walk_path, Visitor};
|
use rustc_hir::intravisit::{walk_path, Visitor};
|
||||||
use rustc_hir::{
|
use rustc_hir::{
|
||||||
GenericArg, GenericArgs, HirId, Impl, ImplItemKind, ImplItemRef, Item, ItemKind, PatKind, Path, PathSegment, Ty,
|
FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItemKind, ImplItemRef, Item, ItemKind, PatKind, Path,
|
||||||
TyKind,
|
PathSegment, Ty, TyKind,
|
||||||
};
|
};
|
||||||
use rustc_lint::{LateContext, LateLintPass};
|
use rustc_lint::{LateContext, LateLintPass};
|
||||||
use rustc_middle::hir::nested_filter::OnlyBodies;
|
use rustc_middle::hir::nested_filter::OnlyBodies;
|
||||||
|
|
@ -197,10 +197,13 @@ fn convert_to_from(
|
||||||
// fn into([mut] self) -> T -> fn into([mut] v: T) -> T
|
// fn into([mut] self) -> T -> fn into([mut] v: T) -> T
|
||||||
// ~~~~ ~~~~
|
// ~~~~ ~~~~
|
||||||
(self_ident.span, format!("val: {from}")),
|
(self_ident.span, format!("val: {from}")),
|
||||||
|
];
|
||||||
|
|
||||||
|
if let FnRetTy::Return(_) = sig.decl.output {
|
||||||
// fn into(self) -> T -> fn into(self) -> Self
|
// fn into(self) -> T -> fn into(self) -> Self
|
||||||
// ~ ~~~~
|
// ~ ~~~~
|
||||||
(sig.decl.output.span(), String::from("Self")),
|
suggestions.push((sig.decl.output.span(), String::from("Self")));
|
||||||
];
|
}
|
||||||
|
|
||||||
let mut finder = SelfFinder {
|
let mut finder = SelfFinder {
|
||||||
cx,
|
cx,
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,9 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl {
|
||||||
// List of spans to lint. (lint_span, first_span)
|
// List of spans to lint. (lint_span, first_span)
|
||||||
let mut lint_spans = Vec::new();
|
let mut lint_spans = Vec::new();
|
||||||
|
|
||||||
let Ok(impls) = cx.tcx.crate_inherent_impls(()) else { return };
|
let Ok(impls) = cx.tcx.crate_inherent_impls(()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let inherent_impls = cx
|
let inherent_impls = cx
|
||||||
.tcx
|
.tcx
|
||||||
.with_stable_hashing_context(|hcx| impls.inherent_impls.to_sorted(&hcx, true));
|
.with_stable_hashing_context(|hcx| impls.inherent_impls.to_sorted(&hcx, true));
|
||||||
|
|
|
||||||
|
|
@ -574,6 +574,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
||||||
warn_on_all_wildcard_imports,
|
warn_on_all_wildcard_imports,
|
||||||
check_private_items,
|
check_private_items,
|
||||||
pub_underscore_fields_behavior,
|
pub_underscore_fields_behavior,
|
||||||
|
ref allowed_duplicate_crates,
|
||||||
|
|
||||||
blacklisted_names: _,
|
blacklisted_names: _,
|
||||||
cyclomatic_complexity_threshold: _,
|
cyclomatic_complexity_threshold: _,
|
||||||
|
|
@ -719,7 +720,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
||||||
store.register_late_pass(|_| Box::new(needless_update::NeedlessUpdate));
|
store.register_late_pass(|_| Box::new(needless_update::NeedlessUpdate));
|
||||||
store.register_late_pass(|_| Box::new(needless_borrowed_ref::NeedlessBorrowedRef));
|
store.register_late_pass(|_| Box::new(needless_borrowed_ref::NeedlessBorrowedRef));
|
||||||
store.register_late_pass(|_| Box::new(borrow_deref_ref::BorrowDerefRef));
|
store.register_late_pass(|_| Box::new(borrow_deref_ref::BorrowDerefRef));
|
||||||
store.register_late_pass(|_| Box::new(no_effect::NoEffect));
|
store.register_late_pass(|_| Box::<no_effect::NoEffect>::default());
|
||||||
store.register_late_pass(|_| Box::new(temporary_assignment::TemporaryAssignment));
|
store.register_late_pass(|_| Box::new(temporary_assignment::TemporaryAssignment));
|
||||||
store.register_late_pass(move |_| Box::new(transmute::Transmute::new(msrv())));
|
store.register_late_pass(move |_| Box::new(transmute::Transmute::new(msrv())));
|
||||||
store.register_late_pass(move |_| {
|
store.register_late_pass(move |_| {
|
||||||
|
|
@ -947,6 +948,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
||||||
store.register_late_pass(move |_| {
|
store.register_late_pass(move |_| {
|
||||||
Box::new(cargo::Cargo {
|
Box::new(cargo::Cargo {
|
||||||
ignore_publish: cargo_ignore_publish,
|
ignore_publish: cargo_ignore_publish,
|
||||||
|
allowed_duplicate_crates: allowed_duplicate_crates.clone(),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef));
|
store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef));
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ pub(super) fn check<'tcx>(
|
||||||
vec.span,
|
vec.span,
|
||||||
"it looks like the same item is being pushed into this Vec",
|
"it looks like the same item is being pushed into this Vec",
|
||||||
None,
|
None,
|
||||||
&format!("try using vec![{item_str};SIZE] or {vec_str}.resize(NEW_SIZE, {item_str})"),
|
&format!("consider using vec![{item_str};SIZE] or {vec_str}.resize(NEW_SIZE, {item_str})"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lin
|
||||||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||||
use clippy_utils::sugg::Sugg;
|
use clippy_utils::sugg::Sugg;
|
||||||
use clippy_utils::ty::is_non_aggregate_primitive_type;
|
use clippy_utils::ty::is_non_aggregate_primitive_type;
|
||||||
use clippy_utils::{is_default_equivalent, is_res_lang_ctor, path_res, peel_ref_operators};
|
use clippy_utils::{is_default_equivalent, is_res_lang_ctor, path_res, peel_ref_operators, std_or_core};
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::LangItem::OptionNone;
|
use rustc_hir::LangItem::OptionNone;
|
||||||
use rustc_hir::{Expr, ExprKind};
|
use rustc_hir::{Expr, ExprKind};
|
||||||
|
|
@ -128,6 +128,7 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'
|
||||||
// check if replacement is mem::MaybeUninit::uninit().assume_init()
|
// check if replacement is mem::MaybeUninit::uninit().assume_init()
|
||||||
&& cx.tcx.is_diagnostic_item(sym::assume_init, method_def_id)
|
&& cx.tcx.is_diagnostic_item(sym::assume_init, method_def_id)
|
||||||
{
|
{
|
||||||
|
let Some(top_crate) = std_or_core(cx) else { return };
|
||||||
let mut applicability = Applicability::MachineApplicable;
|
let mut applicability = Applicability::MachineApplicable;
|
||||||
span_lint_and_sugg(
|
span_lint_and_sugg(
|
||||||
cx,
|
cx,
|
||||||
|
|
@ -136,7 +137,7 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'
|
||||||
"replacing with `mem::MaybeUninit::uninit().assume_init()`",
|
"replacing with `mem::MaybeUninit::uninit().assume_init()`",
|
||||||
"consider using",
|
"consider using",
|
||||||
format!(
|
format!(
|
||||||
"std::ptr::read({})",
|
"{top_crate}::ptr::read({})",
|
||||||
snippet_with_applicability(cx, dest.span, "", &mut applicability)
|
snippet_with_applicability(cx, dest.span, "", &mut applicability)
|
||||||
),
|
),
|
||||||
applicability,
|
applicability,
|
||||||
|
|
@ -149,6 +150,7 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'
|
||||||
&& let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id()
|
&& let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id()
|
||||||
{
|
{
|
||||||
if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, repl_def_id) {
|
if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, repl_def_id) {
|
||||||
|
let Some(top_crate) = std_or_core(cx) else { return };
|
||||||
let mut applicability = Applicability::MachineApplicable;
|
let mut applicability = Applicability::MachineApplicable;
|
||||||
span_lint_and_sugg(
|
span_lint_and_sugg(
|
||||||
cx,
|
cx,
|
||||||
|
|
@ -157,7 +159,7 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'
|
||||||
"replacing with `mem::uninitialized()`",
|
"replacing with `mem::uninitialized()`",
|
||||||
"consider using",
|
"consider using",
|
||||||
format!(
|
format!(
|
||||||
"std::ptr::read({})",
|
"{top_crate}::ptr::read({})",
|
||||||
snippet_with_applicability(cx, dest.span, "", &mut applicability)
|
snippet_with_applicability(cx, dest.span, "", &mut applicability)
|
||||||
),
|
),
|
||||||
applicability,
|
applicability,
|
||||||
|
|
@ -184,14 +186,17 @@ fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if is_default_equivalent(cx, src) && !in_external_macro(cx.tcx.sess, expr_span) {
|
if is_default_equivalent(cx, src) && !in_external_macro(cx.tcx.sess, expr_span) {
|
||||||
|
let Some(top_crate) = std_or_core(cx) else { return };
|
||||||
span_lint_and_then(
|
span_lint_and_then(
|
||||||
cx,
|
cx,
|
||||||
MEM_REPLACE_WITH_DEFAULT,
|
MEM_REPLACE_WITH_DEFAULT,
|
||||||
expr_span,
|
expr_span,
|
||||||
"replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take`",
|
&format!(
|
||||||
|
"replacing a value of type `T` with `T::default()` is better expressed using `{top_crate}::mem::take`"
|
||||||
|
),
|
||||||
|diag| {
|
|diag| {
|
||||||
if !expr_span.from_expansion() {
|
if !expr_span.from_expansion() {
|
||||||
let suggestion = format!("std::mem::take({})", snippet(cx, dest.span, ""));
|
let suggestion = format!("{top_crate}::mem::take({})", snippet(cx, dest.span, ""));
|
||||||
|
|
||||||
diag.span_suggestion(
|
diag.span_suggestion(
|
||||||
expr_span,
|
expr_span,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||||
use clippy_utils::source::snippet;
|
use clippy_utils::source::snippet;
|
||||||
use clippy_utils::{get_expr_use_or_unification_node, is_no_std_crate, is_res_lang_ctor, path_res};
|
use clippy_utils::{get_expr_use_or_unification_node, is_res_lang_ctor, path_res, std_or_core};
|
||||||
|
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::LangItem::{OptionNone, OptionSome};
|
use rustc_hir::LangItem::{OptionNone, OptionSome};
|
||||||
|
|
@ -58,10 +58,10 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, re
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Some(top_crate) = std_or_core(cx) else { return };
|
||||||
if let Some(i) = item {
|
if let Some(i) = item {
|
||||||
let sugg = format!(
|
let sugg = format!(
|
||||||
"{}::iter::once({}{})",
|
"{top_crate}::iter::once({}{})",
|
||||||
if is_no_std_crate(cx) { "core" } else { "std" },
|
|
||||||
iter_type.ref_prefix(),
|
iter_type.ref_prefix(),
|
||||||
snippet(cx, i.span, "...")
|
snippet(cx, i.span, "...")
|
||||||
);
|
);
|
||||||
|
|
@ -81,11 +81,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, re
|
||||||
expr.span,
|
expr.span,
|
||||||
&format!("`{method_name}` call on an empty collection"),
|
&format!("`{method_name}` call on an empty collection"),
|
||||||
"try",
|
"try",
|
||||||
if is_no_std_crate(cx) {
|
format!("{top_crate}::iter::empty()"),
|
||||||
"core::iter::empty()".to_string()
|
|
||||||
} else {
|
|
||||||
"std::iter::empty()".to_string()
|
|
||||||
},
|
|
||||||
Applicability::MaybeIncorrect,
|
Applicability::MaybeIncorrect,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, join_a
|
||||||
)
|
)
|
||||||
.span_suggestion(
|
.span_suggestion(
|
||||||
expr_span,
|
expr_span,
|
||||||
"if this is intentional, try using `Path::new` instead",
|
"if this is intentional, consider using `Path::new`",
|
||||||
format!("PathBuf::from({arg_str})"),
|
format!("PathBuf::from({arg_str})"),
|
||||||
Applicability::Unspecified,
|
Applicability::Unspecified,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ pub fn check(
|
||||||
super::MANUAL_SATURATING_ARITHMETIC,
|
super::MANUAL_SATURATING_ARITHMETIC,
|
||||||
expr.span,
|
expr.span,
|
||||||
"manual saturating arithmetic",
|
"manual saturating arithmetic",
|
||||||
&format!("try using `saturating_{arith}`"),
|
&format!("consider using `saturating_{arith}`"),
|
||||||
format!(
|
format!(
|
||||||
"{}.saturating_{arith}({})",
|
"{}.saturating_{arith}({})",
|
||||||
snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability),
|
snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability),
|
||||||
|
|
|
||||||
|
|
@ -113,9 +113,15 @@ fn handle_path(
|
||||||
if let Some(path_def_id) = cx.qpath_res(qpath, arg.hir_id).opt_def_id()
|
if let Some(path_def_id) = cx.qpath_res(qpath, arg.hir_id).opt_def_id()
|
||||||
&& match_def_path(cx, path_def_id, &paths::CLONE_TRAIT_METHOD)
|
&& match_def_path(cx, path_def_id, &paths::CLONE_TRAIT_METHOD)
|
||||||
{
|
{
|
||||||
// FIXME: It would be better to infer the type to check if it's copyable or not
|
// The `copied` and `cloned` methods are only available on `&T` and `&mut T` in `Option`
|
||||||
// to suggest to use `.copied()` instead of `.cloned()` where applicable.
|
// and `Result`.
|
||||||
lint_path(cx, e.span, recv.span);
|
if let ty::Adt(_, args) = cx.typeck_results().expr_ty(recv).kind()
|
||||||
|
&& let args = args.as_slice()
|
||||||
|
&& let Some(ty) = args.iter().find_map(|generic_arg| generic_arg.as_type())
|
||||||
|
&& ty.is_ref()
|
||||||
|
{
|
||||||
|
lint_path(cx, e.span, recv.span, is_copy(cx, ty.peel_refs()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,17 +145,19 @@ fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lint_path(cx: &LateContext<'_>, replace: Span, root: Span) {
|
fn lint_path(cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool) {
|
||||||
let mut applicability = Applicability::MachineApplicable;
|
let mut applicability = Applicability::MachineApplicable;
|
||||||
|
|
||||||
|
let replacement = if is_copy { "copied" } else { "cloned" };
|
||||||
|
|
||||||
span_lint_and_sugg(
|
span_lint_and_sugg(
|
||||||
cx,
|
cx,
|
||||||
MAP_CLONE,
|
MAP_CLONE,
|
||||||
replace,
|
replace,
|
||||||
"you are explicitly cloning with `.map()`",
|
"you are explicitly cloning with `.map()`",
|
||||||
"consider calling the dedicated `cloned` method",
|
&format!("consider calling the dedicated `{replacement}` method"),
|
||||||
format!(
|
format!(
|
||||||
"{}.cloned()",
|
"{}.{replacement}()",
|
||||||
snippet_with_applicability(cx, root, "..", &mut applicability),
|
snippet_with_applicability(cx, root, "..", &mut applicability),
|
||||||
),
|
),
|
||||||
applicability,
|
applicability,
|
||||||
|
|
|
||||||
|
|
@ -2827,6 +2827,44 @@ declare_clippy_lint! {
|
||||||
"nonsensical combination of options for opening a file"
|
"nonsensical combination of options for opening a file"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare_clippy_lint! {
|
||||||
|
/// ### What it does
|
||||||
|
/// Checks for the suspicious use of `OpenOptions::create()`
|
||||||
|
/// without an explicit `OpenOptions::truncate()`.
|
||||||
|
///
|
||||||
|
/// ### Why is this bad?
|
||||||
|
/// `create()` alone will either create a new file or open an
|
||||||
|
/// existing file. If the file already exists, it will be
|
||||||
|
/// overwritten when written to, but the file will not be
|
||||||
|
/// truncated by default.
|
||||||
|
/// If less data is written to the file
|
||||||
|
/// than it already contains, the remainder of the file will
|
||||||
|
/// remain unchanged, and the end of the file will contain old
|
||||||
|
/// data.
|
||||||
|
/// In most cases, one should either use `create_new` to ensure
|
||||||
|
/// the file is created from scratch, or ensure `truncate` is
|
||||||
|
/// called so that the truncation behaviour is explicit. `truncate(true)`
|
||||||
|
/// will ensure the file is entirely overwritten with new data, whereas
|
||||||
|
/// `truncate(false)` will explicitely keep the default behavior.
|
||||||
|
///
|
||||||
|
/// ### Example
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use std::fs::OpenOptions;
|
||||||
|
///
|
||||||
|
/// OpenOptions::new().create(true);
|
||||||
|
/// ```
|
||||||
|
/// Use instead:
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use std::fs::OpenOptions;
|
||||||
|
///
|
||||||
|
/// OpenOptions::new().create(true).truncate(true);
|
||||||
|
/// ```
|
||||||
|
#[clippy::version = "1.75.0"]
|
||||||
|
pub SUSPICIOUS_OPEN_OPTIONS,
|
||||||
|
suspicious,
|
||||||
|
"suspicious combination of options for opening a file"
|
||||||
|
}
|
||||||
|
|
||||||
declare_clippy_lint! {
|
declare_clippy_lint! {
|
||||||
/// ### What it does
|
/// ### What it does
|
||||||
///* Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push)
|
///* Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push)
|
||||||
|
|
@ -4033,6 +4071,7 @@ impl_lint_pass!(Methods => [
|
||||||
MAP_ERR_IGNORE,
|
MAP_ERR_IGNORE,
|
||||||
MUT_MUTEX_LOCK,
|
MUT_MUTEX_LOCK,
|
||||||
NONSENSICAL_OPEN_OPTIONS,
|
NONSENSICAL_OPEN_OPTIONS,
|
||||||
|
SUSPICIOUS_OPEN_OPTIONS,
|
||||||
PATH_BUF_PUSH_OVERWRITE,
|
PATH_BUF_PUSH_OVERWRITE,
|
||||||
RANGE_ZIP_WITH_LEN,
|
RANGE_ZIP_WITH_LEN,
|
||||||
REPEAT_ONCE,
|
REPEAT_ONCE,
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,74 @@
|
||||||
use clippy_utils::diagnostics::span_lint;
|
use rustc_data_structures::fx::FxHashMap;
|
||||||
use clippy_utils::ty::is_type_diagnostic_item;
|
|
||||||
|
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
|
||||||
|
use clippy_utils::ty::{is_type_diagnostic_item, match_type};
|
||||||
|
use clippy_utils::{match_any_def_paths, paths};
|
||||||
use rustc_ast::ast::LitKind;
|
use rustc_ast::ast::LitKind;
|
||||||
use rustc_hir::{Expr, ExprKind};
|
use rustc_hir::{Expr, ExprKind};
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
|
use rustc_middle::ty::Ty;
|
||||||
use rustc_span::source_map::Spanned;
|
use rustc_span::source_map::Spanned;
|
||||||
use rustc_span::{sym, Span};
|
use rustc_span::{sym, Span};
|
||||||
|
|
||||||
use super::NONSENSICAL_OPEN_OPTIONS;
|
use super::{NONSENSICAL_OPEN_OPTIONS, SUSPICIOUS_OPEN_OPTIONS};
|
||||||
|
|
||||||
|
fn is_open_options(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
|
||||||
|
is_type_diagnostic_item(cx, ty, sym::FsOpenOptions) || match_type(cx, ty, &paths::TOKIO_IO_OPEN_OPTIONS)
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) {
|
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) {
|
||||||
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
|
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
|
||||||
&& let Some(impl_id) = cx.tcx.impl_of_method(method_id)
|
&& let Some(impl_id) = cx.tcx.impl_of_method(method_id)
|
||||||
&& is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::FsOpenOptions)
|
&& is_open_options(cx, cx.tcx.type_of(impl_id).instantiate_identity())
|
||||||
{
|
{
|
||||||
let mut options = Vec::new();
|
let mut options = Vec::new();
|
||||||
get_open_options(cx, recv, &mut options);
|
if get_open_options(cx, recv, &mut options) {
|
||||||
check_open_options(cx, &options, e.span);
|
check_open_options(cx, &options, e.span);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
#[derive(Eq, PartialEq, Clone, Debug)]
|
||||||
enum Argument {
|
enum Argument {
|
||||||
True,
|
Set(bool),
|
||||||
False,
|
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||||
enum OpenOption {
|
enum OpenOption {
|
||||||
Write,
|
Append,
|
||||||
|
Create,
|
||||||
|
CreateNew,
|
||||||
Read,
|
Read,
|
||||||
Truncate,
|
Truncate,
|
||||||
Create,
|
Write,
|
||||||
Append,
|
}
|
||||||
|
impl std::fmt::Display for OpenOption {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
OpenOption::Append => write!(f, "append"),
|
||||||
|
OpenOption::Create => write!(f, "create"),
|
||||||
|
OpenOption::CreateNew => write!(f, "create_new"),
|
||||||
|
OpenOption::Read => write!(f, "read"),
|
||||||
|
OpenOption::Truncate => write!(f, "truncate"),
|
||||||
|
OpenOption::Write => write!(f, "write"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_open_options(cx: &LateContext<'_>, argument: &Expr<'_>, options: &mut Vec<(OpenOption, Argument)>) {
|
/// Collects information about a method call chain on `OpenOptions`.
|
||||||
if let ExprKind::MethodCall(path, receiver, arguments, _) = argument.kind {
|
/// Returns false if an unexpected expression kind was found "on the way",
|
||||||
|
/// and linting should then be avoided.
|
||||||
|
fn get_open_options(
|
||||||
|
cx: &LateContext<'_>,
|
||||||
|
argument: &Expr<'_>,
|
||||||
|
options: &mut Vec<(OpenOption, Argument, Span)>,
|
||||||
|
) -> bool {
|
||||||
|
if let ExprKind::MethodCall(path, receiver, arguments, span) = argument.kind {
|
||||||
let obj_ty = cx.typeck_results().expr_ty(receiver).peel_refs();
|
let obj_ty = cx.typeck_results().expr_ty(receiver).peel_refs();
|
||||||
|
|
||||||
// Only proceed if this is a call on some object of type std::fs::OpenOptions
|
// Only proceed if this is a call on some object of type std::fs::OpenOptions
|
||||||
if is_type_diagnostic_item(cx, obj_ty, sym::FsOpenOptions) && !arguments.is_empty() {
|
if !arguments.is_empty() && is_open_options(cx, obj_ty) {
|
||||||
let argument_option = match arguments[0].kind {
|
let argument_option = match arguments[0].kind {
|
||||||
ExprKind::Lit(span) => {
|
ExprKind::Lit(span) => {
|
||||||
if let Spanned {
|
if let Spanned {
|
||||||
|
|
@ -48,11 +76,12 @@ fn get_open_options(cx: &LateContext<'_>, argument: &Expr<'_>, options: &mut Vec
|
||||||
..
|
..
|
||||||
} = span
|
} = span
|
||||||
{
|
{
|
||||||
if *lit { Argument::True } else { Argument::False }
|
Argument::Set(*lit)
|
||||||
} else {
|
} else {
|
||||||
// The function is called with a literal which is not a boolean literal.
|
// The function is called with a literal which is not a boolean literal.
|
||||||
// This is theoretically possible, but not very likely.
|
// This is theoretically possible, but not very likely.
|
||||||
return;
|
// We'll ignore it for now
|
||||||
|
return get_open_options(cx, receiver, options);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => Argument::Unknown,
|
_ => Argument::Unknown,
|
||||||
|
|
@ -60,106 +89,77 @@ fn get_open_options(cx: &LateContext<'_>, argument: &Expr<'_>, options: &mut Vec
|
||||||
|
|
||||||
match path.ident.as_str() {
|
match path.ident.as_str() {
|
||||||
"create" => {
|
"create" => {
|
||||||
options.push((OpenOption::Create, argument_option));
|
options.push((OpenOption::Create, argument_option, span));
|
||||||
|
},
|
||||||
|
"create_new" => {
|
||||||
|
options.push((OpenOption::CreateNew, argument_option, span));
|
||||||
},
|
},
|
||||||
"append" => {
|
"append" => {
|
||||||
options.push((OpenOption::Append, argument_option));
|
options.push((OpenOption::Append, argument_option, span));
|
||||||
},
|
},
|
||||||
"truncate" => {
|
"truncate" => {
|
||||||
options.push((OpenOption::Truncate, argument_option));
|
options.push((OpenOption::Truncate, argument_option, span));
|
||||||
},
|
},
|
||||||
"read" => {
|
"read" => {
|
||||||
options.push((OpenOption::Read, argument_option));
|
options.push((OpenOption::Read, argument_option, span));
|
||||||
},
|
},
|
||||||
"write" => {
|
"write" => {
|
||||||
options.push((OpenOption::Write, argument_option));
|
options.push((OpenOption::Write, argument_option, span));
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
// Avoid linting altogether if this method is from a trait.
|
||||||
|
// This might be a user defined extension trait with a method like `truncate_write`
|
||||||
|
// which would be a false positive
|
||||||
|
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(argument.hir_id)
|
||||||
|
&& cx.tcx.trait_of_item(method_def_id).is_some()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get_open_options(cx, receiver, options);
|
get_open_options(cx, receiver, options)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
} else if let ExprKind::Call(callee, _) = argument.kind
|
||||||
|
&& let ExprKind::Path(path) = callee.kind
|
||||||
|
&& let Some(did) = cx.qpath_res(&path, callee.hir_id).opt_def_id()
|
||||||
|
{
|
||||||
|
match_any_def_paths(
|
||||||
|
cx,
|
||||||
|
did,
|
||||||
|
&[
|
||||||
|
&paths::TOKIO_IO_OPEN_OPTIONS_NEW,
|
||||||
|
&paths::OPEN_OPTIONS_NEW,
|
||||||
|
&paths::FILE_OPTIONS,
|
||||||
|
&paths::TOKIO_FILE_OPTIONS,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.is_some()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_open_options(cx: &LateContext<'_>, options: &[(OpenOption, Argument)], span: Span) {
|
fn check_open_options(cx: &LateContext<'_>, settings: &[(OpenOption, Argument, Span)], span: Span) {
|
||||||
let (mut create, mut append, mut truncate, mut read, mut write) = (false, false, false, false, false);
|
// The args passed to these methods, if they have been called
|
||||||
let (mut create_arg, mut append_arg, mut truncate_arg, mut read_arg, mut write_arg) =
|
let mut options = FxHashMap::default();
|
||||||
(false, false, false, false, false);
|
for (option, arg, sp) in settings {
|
||||||
// This code is almost duplicated (oh, the irony), but I haven't found a way to
|
if let Some((_, prev_span)) = options.insert(option.clone(), (arg.clone(), *sp)) {
|
||||||
// unify it.
|
span_lint(
|
||||||
|
cx,
|
||||||
for option in options {
|
NONSENSICAL_OPEN_OPTIONS,
|
||||||
match *option {
|
prev_span,
|
||||||
(OpenOption::Create, arg) => {
|
&format!("the method `{}` is called more than once", &option),
|
||||||
if create {
|
);
|
||||||
span_lint(
|
|
||||||
cx,
|
|
||||||
NONSENSICAL_OPEN_OPTIONS,
|
|
||||||
span,
|
|
||||||
"the method `create` is called more than once",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
create = true;
|
|
||||||
}
|
|
||||||
create_arg = create_arg || (arg == Argument::True);
|
|
||||||
},
|
|
||||||
(OpenOption::Append, arg) => {
|
|
||||||
if append {
|
|
||||||
span_lint(
|
|
||||||
cx,
|
|
||||||
NONSENSICAL_OPEN_OPTIONS,
|
|
||||||
span,
|
|
||||||
"the method `append` is called more than once",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
append = true;
|
|
||||||
}
|
|
||||||
append_arg = append_arg || (arg == Argument::True);
|
|
||||||
},
|
|
||||||
(OpenOption::Truncate, arg) => {
|
|
||||||
if truncate {
|
|
||||||
span_lint(
|
|
||||||
cx,
|
|
||||||
NONSENSICAL_OPEN_OPTIONS,
|
|
||||||
span,
|
|
||||||
"the method `truncate` is called more than once",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
truncate = true;
|
|
||||||
}
|
|
||||||
truncate_arg = truncate_arg || (arg == Argument::True);
|
|
||||||
},
|
|
||||||
(OpenOption::Read, arg) => {
|
|
||||||
if read {
|
|
||||||
span_lint(
|
|
||||||
cx,
|
|
||||||
NONSENSICAL_OPEN_OPTIONS,
|
|
||||||
span,
|
|
||||||
"the method `read` is called more than once",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
read = true;
|
|
||||||
}
|
|
||||||
read_arg = read_arg || (arg == Argument::True);
|
|
||||||
},
|
|
||||||
(OpenOption::Write, arg) => {
|
|
||||||
if write {
|
|
||||||
span_lint(
|
|
||||||
cx,
|
|
||||||
NONSENSICAL_OPEN_OPTIONS,
|
|
||||||
span,
|
|
||||||
"the method `write` is called more than once",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
write = true;
|
|
||||||
}
|
|
||||||
write_arg = write_arg || (arg == Argument::True);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if read && truncate && read_arg && truncate_arg && !(write && write_arg) {
|
if let Some((Argument::Set(true), _)) = options.get(&OpenOption::Read)
|
||||||
|
&& let Some((Argument::Set(true), _)) = options.get(&OpenOption::Truncate)
|
||||||
|
&& let None | Some((Argument::Set(false), _)) = options.get(&OpenOption::Write)
|
||||||
|
{
|
||||||
span_lint(
|
span_lint(
|
||||||
cx,
|
cx,
|
||||||
NONSENSICAL_OPEN_OPTIONS,
|
NONSENSICAL_OPEN_OPTIONS,
|
||||||
|
|
@ -167,7 +167,10 @@ fn check_open_options(cx: &LateContext<'_>, options: &[(OpenOption, Argument)],
|
||||||
"file opened with `truncate` and `read`",
|
"file opened with `truncate` and `read`",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if append && truncate && append_arg && truncate_arg {
|
|
||||||
|
if let Some((Argument::Set(true), _)) = options.get(&OpenOption::Append)
|
||||||
|
&& let Some((Argument::Set(true), _)) = options.get(&OpenOption::Truncate)
|
||||||
|
{
|
||||||
span_lint(
|
span_lint(
|
||||||
cx,
|
cx,
|
||||||
NONSENSICAL_OPEN_OPTIONS,
|
NONSENSICAL_OPEN_OPTIONS,
|
||||||
|
|
@ -175,4 +178,29 @@ fn check_open_options(cx: &LateContext<'_>, options: &[(OpenOption, Argument)],
|
||||||
"file opened with `append` and `truncate`",
|
"file opened with `append` and `truncate`",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some((Argument::Set(true), create_span)) = options.get(&OpenOption::Create)
|
||||||
|
&& let None = options.get(&OpenOption::Truncate)
|
||||||
|
&& let None | Some((Argument::Set(false), _)) = options.get(&OpenOption::Append)
|
||||||
|
{
|
||||||
|
span_lint_and_then(
|
||||||
|
cx,
|
||||||
|
SUSPICIOUS_OPEN_OPTIONS,
|
||||||
|
*create_span,
|
||||||
|
"file opened with `create`, but `truncate` behavior not defined",
|
||||||
|
|diag| {
|
||||||
|
diag.span_suggestion(
|
||||||
|
create_span.shrink_to_hi(),
|
||||||
|
"add",
|
||||||
|
".truncate(true)".to_string(),
|
||||||
|
rustc_errors::Applicability::MaybeIncorrect,
|
||||||
|
)
|
||||||
|
.help("if you intend to overwrite an existing file entirely, call `.truncate(true)`")
|
||||||
|
.help(
|
||||||
|
"if you instead know that you may want to keep some parts of the old file, call `.truncate(false)`",
|
||||||
|
)
|
||||||
|
.help("alternatively, use `.append(true)` to append to the file instead of overwriting it");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ pub(super) fn check(
|
||||||
};
|
};
|
||||||
let method_hint = if is_mut { "as_deref_mut" } else { "as_deref" };
|
let method_hint = if is_mut { "as_deref_mut" } else { "as_deref" };
|
||||||
let hint = format!("{}.{method_hint}()", snippet(cx, as_ref_recv.span, ".."));
|
let hint = format!("{}.{method_hint}()", snippet(cx, as_ref_recv.span, ".."));
|
||||||
let suggestion = format!("try using {method_hint} instead");
|
let suggestion = format!("consider using {method_hint}");
|
||||||
|
|
||||||
let msg = format!("called `{current_method}` on an `Option` value");
|
let msg = format!("called `{current_method}` on an `Option` value");
|
||||||
span_lint_and_sugg(
|
span_lint_and_sugg(
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ pub(super) fn check<'tcx>(
|
||||||
OPTION_MAP_OR_ERR_OK,
|
OPTION_MAP_OR_ERR_OK,
|
||||||
expr.span,
|
expr.span,
|
||||||
msg,
|
msg,
|
||||||
"try using `ok_or` instead",
|
"consider using `ok_or`",
|
||||||
format!("{self_snippet}.ok_or({err_snippet})"),
|
format!("{self_snippet}.ok_or({err_snippet})"),
|
||||||
Applicability::MachineApplicable,
|
Applicability::MachineApplicable,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ pub(super) fn check<'tcx>(
|
||||||
OPTION_MAP_OR_NONE,
|
OPTION_MAP_OR_NONE,
|
||||||
expr.span,
|
expr.span,
|
||||||
msg,
|
msg,
|
||||||
"try using `map` instead",
|
"consider using `map`",
|
||||||
format!("{self_snippet}.map({arg_snippet} {func_snippet})"),
|
format!("{self_snippet}.map({arg_snippet} {func_snippet})"),
|
||||||
Applicability::MachineApplicable,
|
Applicability::MachineApplicable,
|
||||||
);
|
);
|
||||||
|
|
@ -85,7 +85,7 @@ pub(super) fn check<'tcx>(
|
||||||
OPTION_MAP_OR_NONE,
|
OPTION_MAP_OR_NONE,
|
||||||
expr.span,
|
expr.span,
|
||||||
msg,
|
msg,
|
||||||
"try using `and_then` instead",
|
"consider using `and_then`",
|
||||||
format!("{self_snippet}.and_then({func_snippet})"),
|
format!("{self_snippet}.and_then({func_snippet})"),
|
||||||
Applicability::MachineApplicable,
|
Applicability::MachineApplicable,
|
||||||
);
|
);
|
||||||
|
|
@ -97,7 +97,7 @@ pub(super) fn check<'tcx>(
|
||||||
RESULT_MAP_OR_INTO_OPTION,
|
RESULT_MAP_OR_INTO_OPTION,
|
||||||
expr.span,
|
expr.span,
|
||||||
msg,
|
msg,
|
||||||
"try using `ok` instead",
|
"consider using `ok`",
|
||||||
format!("{self_snippet}.ok()"),
|
format!("{self_snippet}.ok()"),
|
||||||
Applicability::MachineApplicable,
|
Applicability::MachineApplicable,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ pub(super) fn check<'tcx>(
|
||||||
RESULT_MAP_OR_INTO_OPTION,
|
RESULT_MAP_OR_INTO_OPTION,
|
||||||
expr.span,
|
expr.span,
|
||||||
msg,
|
msg,
|
||||||
"try using `ok` instead",
|
"consider using `ok`",
|
||||||
format!("{self_snippet}.ok()"),
|
format!("{self_snippet}.ok()"),
|
||||||
Applicability::MachineApplicable,
|
Applicability::MachineApplicable,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ pub(super) fn check<'tcx>(
|
||||||
SEARCH_IS_SOME,
|
SEARCH_IS_SOME,
|
||||||
method_span.with_hi(expr.span.hi()),
|
method_span.with_hi(expr.span.hi()),
|
||||||
&msg,
|
&msg,
|
||||||
"use `any()` instead",
|
"consider using",
|
||||||
format!(
|
format!(
|
||||||
"any({})",
|
"any({})",
|
||||||
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
|
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
|
||||||
|
|
@ -77,7 +77,7 @@ pub(super) fn check<'tcx>(
|
||||||
SEARCH_IS_SOME,
|
SEARCH_IS_SOME,
|
||||||
expr.span,
|
expr.span,
|
||||||
&msg,
|
&msg,
|
||||||
"use `!_.any()` instead",
|
"consider using",
|
||||||
format!(
|
format!(
|
||||||
"!{iter}.any({})",
|
"!{iter}.any({})",
|
||||||
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
|
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
|
||||||
|
|
@ -118,7 +118,7 @@ pub(super) fn check<'tcx>(
|
||||||
SEARCH_IS_SOME,
|
SEARCH_IS_SOME,
|
||||||
method_span.with_hi(expr.span.hi()),
|
method_span.with_hi(expr.span.hi()),
|
||||||
&msg,
|
&msg,
|
||||||
"use `contains()` instead",
|
"consider using",
|
||||||
format!("contains({find_arg})"),
|
format!("contains({find_arg})"),
|
||||||
applicability,
|
applicability,
|
||||||
);
|
);
|
||||||
|
|
@ -132,7 +132,7 @@ pub(super) fn check<'tcx>(
|
||||||
SEARCH_IS_SOME,
|
SEARCH_IS_SOME,
|
||||||
expr.span,
|
expr.span,
|
||||||
&msg,
|
&msg,
|
||||||
"use `!_.contains()` instead",
|
"consider using",
|
||||||
format!("!{string}.contains({find_arg})"),
|
format!("!{string}.contains({find_arg})"),
|
||||||
applicability,
|
applicability,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ pub(super) fn check(
|
||||||
SINGLE_CHAR_PATTERN,
|
SINGLE_CHAR_PATTERN,
|
||||||
arg.span,
|
arg.span,
|
||||||
"single-character string constant used as pattern",
|
"single-character string constant used as pattern",
|
||||||
"try using a `char` instead",
|
"consider using a `char`",
|
||||||
hint,
|
hint,
|
||||||
applicability,
|
applicability,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ pub(super) fn check<'tcx>(
|
||||||
UNNECESSARY_JOIN,
|
UNNECESSARY_JOIN,
|
||||||
span.with_hi(expr.span.hi()),
|
span.with_hi(expr.span.hi()),
|
||||||
r#"called `.collect::<Vec<String>>().join("")` on an iterator"#,
|
r#"called `.collect::<Vec<String>>().join("")` on an iterator"#,
|
||||||
"try using",
|
"consider using",
|
||||||
"collect::<String>()".to_owned(),
|
"collect::<String>()".to_owned(),
|
||||||
applicability,
|
applicability,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ pub(super) fn check<'tcx>(
|
||||||
cx,
|
cx,
|
||||||
UNNECESSARY_SORT_BY,
|
UNNECESSARY_SORT_BY,
|
||||||
expr.span,
|
expr.span,
|
||||||
"use Vec::sort_by_key here instead",
|
"consider using `sort_by_key`",
|
||||||
"try",
|
"try",
|
||||||
format!(
|
format!(
|
||||||
"{}.sort{}_by_key(|{}| {})",
|
"{}.sort{}_by_key(|{}| {})",
|
||||||
|
|
@ -226,7 +226,7 @@ pub(super) fn check<'tcx>(
|
||||||
cx,
|
cx,
|
||||||
UNNECESSARY_SORT_BY,
|
UNNECESSARY_SORT_BY,
|
||||||
expr.span,
|
expr.span,
|
||||||
"use Vec::sort here instead",
|
"consider using `sort`",
|
||||||
"try",
|
"try",
|
||||||
format!(
|
format!(
|
||||||
"{}.sort{}()",
|
"{}.sort{}()",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||||
use clippy_utils::source::snippet_with_applicability;
|
use clippy_utils::source::snippet_with_applicability;
|
||||||
use clippy_utils::ty::walk_ptrs_ty_depth;
|
use clippy_utils::ty::walk_ptrs_ty_depth;
|
||||||
use clippy_utils::{get_parent_expr, is_diag_trait_item, match_def_path, paths, peel_blocks};
|
use clippy_utils::{
|
||||||
|
get_parent_expr, is_diag_trait_item, match_def_path, path_to_local_id, paths, peel_blocks, strip_pat_refs,
|
||||||
|
};
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
|
|
@ -108,9 +110,12 @@ fn check_qpath(cx: &LateContext<'_>, qpath: hir::QPath<'_>, hir_id: hir::HirId)
|
||||||
|
|
||||||
fn is_calling_clone(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
|
fn is_calling_clone(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
|
||||||
match arg.kind {
|
match arg.kind {
|
||||||
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
|
hir::ExprKind::Closure(&hir::Closure { body, .. })
|
||||||
// If it's a closure, we need to check what is called.
|
// If it's a closure, we need to check what is called.
|
||||||
let closure_body = cx.tcx.hir().body(body);
|
if let closure_body = cx.tcx.hir().body(body)
|
||||||
|
&& let [param] = closure_body.params
|
||||||
|
&& let hir::PatKind::Binding(_, local_id, ..) = strip_pat_refs(param.pat).kind =>
|
||||||
|
{
|
||||||
let closure_expr = peel_blocks(closure_body.value);
|
let closure_expr = peel_blocks(closure_body.value);
|
||||||
match closure_expr.kind {
|
match closure_expr.kind {
|
||||||
hir::ExprKind::MethodCall(method, obj, [], _) => {
|
hir::ExprKind::MethodCall(method, obj, [], _) => {
|
||||||
|
|
@ -122,14 +127,17 @@ fn is_calling_clone(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
|
||||||
// no autoderefs
|
// no autoderefs
|
||||||
&& !cx.typeck_results().expr_adjustments(obj).iter()
|
&& !cx.typeck_results().expr_adjustments(obj).iter()
|
||||||
.any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
|
.any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
|
||||||
|
&& path_to_local_id(obj, local_id)
|
||||||
{
|
{
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
hir::ExprKind::Call(call, [_]) => {
|
hir::ExprKind::Call(call, [recv]) => {
|
||||||
if let hir::ExprKind::Path(qpath) = call.kind {
|
if let hir::ExprKind::Path(qpath) = call.kind
|
||||||
|
&& path_to_local_id(recv, local_id)
|
||||||
|
{
|
||||||
check_qpath(cx, qpath, call.hir_id)
|
check_qpath(cx, qpath, call.hir_id)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,15 @@ use clippy_utils::visitors::for_each_expr_with_closures;
|
||||||
use clippy_utils::{get_parent_node, inherits_cfg, is_from_proc_macro, is_self};
|
use clippy_utils::{get_parent_node, inherits_cfg, is_from_proc_macro, is_self};
|
||||||
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
|
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::intravisit::{walk_qpath, FnKind, Visitor};
|
use rustc_hir::intravisit::FnKind;
|
||||||
use rustc_hir::{
|
use rustc_hir::{
|
||||||
BlockCheckMode, Body, Closure, Expr, ExprKind, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node,
|
BlockCheckMode, Body, Closure, Expr, ExprKind, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node,
|
||||||
PatKind, QPath,
|
PatKind,
|
||||||
};
|
};
|
||||||
use rustc_hir_typeck::expr_use_visitor as euv;
|
use rustc_hir_typeck::expr_use_visitor as euv;
|
||||||
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
|
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
|
||||||
use rustc_lint::{LateContext, LateLintPass};
|
use rustc_lint::{LateContext, LateLintPass};
|
||||||
use rustc_middle::hir::map::associated_body;
|
use rustc_middle::hir::map::associated_body;
|
||||||
use rustc_middle::hir::nested_filter::OnlyBodies;
|
|
||||||
use rustc_middle::mir::FakeReadCause;
|
use rustc_middle::mir::FakeReadCause;
|
||||||
use rustc_middle::ty::{self, Ty, TyCtxt, UpvarId, UpvarPath};
|
use rustc_middle::ty::{self, Ty, TyCtxt, UpvarId, UpvarPath};
|
||||||
use rustc_session::impl_lint_pass;
|
use rustc_session::impl_lint_pass;
|
||||||
|
|
@ -234,12 +233,29 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
|
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||||
cx.tcx.hir().visit_all_item_likes_in_crate(&mut FnNeedsMutVisitor {
|
// #11182; do not lint if mutability is required elsewhere
|
||||||
cx,
|
if let ExprKind::Path(..) = expr.kind
|
||||||
used_fn_def_ids: &mut self.used_fn_def_ids,
|
&& let Some(parent) = get_parent_node(cx.tcx, expr.hir_id)
|
||||||
});
|
&& let ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(expr).kind()
|
||||||
|
&& let Some(def_id) = def_id.as_local()
|
||||||
|
{
|
||||||
|
if let Node::Expr(e) = parent
|
||||||
|
&& let ExprKind::Call(call, _) = e.kind
|
||||||
|
&& call.hir_id == expr.hir_id
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't need to check each argument individually as you cannot coerce a function
|
||||||
|
// taking `&mut` -> `&`, for some reason, so if we've gotten this far we know it's
|
||||||
|
// passed as a `fn`-like argument (or is unified) and should ignore every "unused"
|
||||||
|
// argument entirely
|
||||||
|
self.used_fn_def_ids.insert(def_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
|
||||||
for (fn_def_id, unused) in self
|
for (fn_def_id, unused) in self
|
||||||
.fn_def_ids_to_maybe_unused_mut
|
.fn_def_ids_to_maybe_unused_mut
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -501,48 +517,3 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A final pass to check for paths referencing this function that require the argument to be
|
|
||||||
/// `&mut`, basically if the function is ever used as a `fn`-like argument.
|
|
||||||
struct FnNeedsMutVisitor<'a, 'tcx> {
|
|
||||||
cx: &'a LateContext<'tcx>,
|
|
||||||
used_fn_def_ids: &'a mut FxHashSet<LocalDefId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'tcx> Visitor<'tcx> for FnNeedsMutVisitor<'_, 'tcx> {
|
|
||||||
type NestedFilter = OnlyBodies;
|
|
||||||
|
|
||||||
fn nested_visit_map(&mut self) -> Self::Map {
|
|
||||||
self.cx.tcx.hir()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_qpath(&mut self, qpath: &'tcx QPath<'tcx>, hir_id: HirId, _: Span) {
|
|
||||||
walk_qpath(self, qpath, hir_id);
|
|
||||||
|
|
||||||
let Self { cx, used_fn_def_ids } = self;
|
|
||||||
|
|
||||||
// #11182; do not lint if mutability is required elsewhere
|
|
||||||
if let Node::Expr(expr) = cx.tcx.hir_node(hir_id)
|
|
||||||
&& let Some(parent) = get_parent_node(cx.tcx, expr.hir_id)
|
|
||||||
&& let ty::FnDef(def_id, _) = cx
|
|
||||||
.tcx
|
|
||||||
.typeck(cx.tcx.hir().enclosing_body_owner(hir_id))
|
|
||||||
.expr_ty(expr)
|
|
||||||
.kind()
|
|
||||||
&& let Some(def_id) = def_id.as_local()
|
|
||||||
{
|
|
||||||
if let Node::Expr(e) = parent
|
|
||||||
&& let ExprKind::Call(call, _) = e.kind
|
|
||||||
&& call.hir_id == expr.hir_id
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't need to check each argument individually as you cannot coerce a function
|
|
||||||
// taking `&mut` -> `&`, for some reason, so if we've gotten this far we know it's
|
|
||||||
// passed as a `fn`-like argument (or is unified) and should ignore every "unused"
|
|
||||||
// argument entirely
|
|
||||||
used_fn_def_ids.insert(def_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
|
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
|
||||||
use clippy_utils::source::snippet_opt;
|
use clippy_utils::source::snippet_opt;
|
||||||
use clippy_utils::ty::has_drop;
|
use clippy_utils::ty::has_drop;
|
||||||
use clippy_utils::{get_parent_node, is_lint_allowed, peel_blocks};
|
use clippy_utils::{any_parent_is_automatically_derived, get_parent_node, is_lint_allowed, path_to_local, peel_blocks};
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::def::{DefKind, Res};
|
use rustc_hir::def::{DefKind, Res};
|
||||||
use rustc_hir::{
|
use rustc_hir::{
|
||||||
is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, ItemKind, Node, PatKind, Stmt, StmtKind, UnsafeSource,
|
is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, HirId, HirIdMap, ItemKind, Node, PatKind, Stmt,
|
||||||
|
StmtKind, UnsafeSource,
|
||||||
};
|
};
|
||||||
use rustc_infer::infer::TyCtxtInferExt as _;
|
use rustc_infer::infer::TyCtxtInferExt as _;
|
||||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||||
use rustc_middle::lint::in_external_macro;
|
use rustc_middle::lint::in_external_macro;
|
||||||
use rustc_session::declare_lint_pass;
|
use rustc_session::impl_lint_pass;
|
||||||
|
use rustc_span::Span;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
declare_clippy_lint! {
|
declare_clippy_lint! {
|
||||||
|
|
@ -74,94 +76,125 @@ declare_clippy_lint! {
|
||||||
"outer expressions with no effect"
|
"outer expressions with no effect"
|
||||||
}
|
}
|
||||||
|
|
||||||
declare_lint_pass!(NoEffect => [NO_EFFECT, UNNECESSARY_OPERATION, NO_EFFECT_UNDERSCORE_BINDING]);
|
#[derive(Default)]
|
||||||
|
pub struct NoEffect {
|
||||||
|
underscore_bindings: HirIdMap<Span>,
|
||||||
|
local_bindings: Vec<Vec<HirId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_lint_pass!(NoEffect => [NO_EFFECT, UNNECESSARY_OPERATION, NO_EFFECT_UNDERSCORE_BINDING]);
|
||||||
|
|
||||||
impl<'tcx> LateLintPass<'tcx> for NoEffect {
|
impl<'tcx> LateLintPass<'tcx> for NoEffect {
|
||||||
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
|
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
|
||||||
if check_no_effect(cx, stmt) {
|
if self.check_no_effect(cx, stmt) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
check_unnecessary_operation(cx, stmt);
|
check_unnecessary_operation(cx, stmt);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn check_no_effect(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
|
fn check_block(&mut self, _: &LateContext<'tcx>, _: &'tcx rustc_hir::Block<'tcx>) {
|
||||||
if let StmtKind::Semi(expr) = stmt.kind {
|
self.local_bindings.push(Vec::default());
|
||||||
// move `expr.span.from_expansion()` ahead
|
}
|
||||||
if expr.span.from_expansion() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let expr = peel_blocks(expr);
|
|
||||||
|
|
||||||
if is_operator_overridden(cx, expr) {
|
fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx rustc_hir::Block<'tcx>) {
|
||||||
// Return `true`, to prevent `check_unnecessary_operation` from
|
for hir_id in self.local_bindings.pop().unwrap() {
|
||||||
// linting on this statement as well.
|
if let Some(span) = self.underscore_bindings.remove(&hir_id) {
|
||||||
return true;
|
span_lint_hir(
|
||||||
}
|
cx,
|
||||||
if has_no_effect(cx, expr) {
|
NO_EFFECT_UNDERSCORE_BINDING,
|
||||||
span_lint_hir_and_then(
|
hir_id,
|
||||||
cx,
|
span,
|
||||||
NO_EFFECT,
|
"binding to `_` prefixed variable with no side-effect",
|
||||||
expr.hir_id,
|
);
|
||||||
stmt.span,
|
}
|
||||||
"statement with no effect",
|
|
||||||
|diag| {
|
|
||||||
for parent in cx.tcx.hir().parent_iter(stmt.hir_id) {
|
|
||||||
if let Node::Item(item) = parent.1
|
|
||||||
&& let ItemKind::Fn(..) = item.kind
|
|
||||||
&& let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id)
|
|
||||||
&& let [.., final_stmt] = block.stmts
|
|
||||||
&& final_stmt.hir_id == stmt.hir_id
|
|
||||||
{
|
|
||||||
let expr_ty = cx.typeck_results().expr_ty(expr);
|
|
||||||
let mut ret_ty = cx
|
|
||||||
.tcx
|
|
||||||
.fn_sig(item.owner_id)
|
|
||||||
.instantiate_identity()
|
|
||||||
.output()
|
|
||||||
.skip_binder();
|
|
||||||
|
|
||||||
// Remove `impl Future<Output = T>` to get `T`
|
|
||||||
if cx.tcx.ty_is_opaque_future(ret_ty)
|
|
||||||
&& let Some(true_ret_ty) = cx.tcx.infer_ctxt().build().get_impl_future_output_ty(ret_ty)
|
|
||||||
{
|
|
||||||
ret_ty = true_ret_ty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ret_ty.is_unit() && ret_ty == expr_ty {
|
|
||||||
diag.span_suggestion(
|
|
||||||
stmt.span.shrink_to_lo(),
|
|
||||||
"did you mean to return it?",
|
|
||||||
"return ",
|
|
||||||
Applicability::MaybeIncorrect,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if let StmtKind::Local(local) = stmt.kind {
|
|
||||||
if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id)
|
|
||||||
&& let Some(init) = local.init
|
|
||||||
&& local.els.is_none()
|
|
||||||
&& !local.pat.span.from_expansion()
|
|
||||||
&& has_no_effect(cx, init)
|
|
||||||
&& let PatKind::Binding(_, _, ident, _) = local.pat.kind
|
|
||||||
&& ident.name.to_ident_string().starts_with('_')
|
|
||||||
{
|
|
||||||
span_lint_hir(
|
|
||||||
cx,
|
|
||||||
NO_EFFECT_UNDERSCORE_BINDING,
|
|
||||||
init.hir_id,
|
|
||||||
stmt.span,
|
|
||||||
"binding to `_` prefixed variable with no side-effect",
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
|
||||||
|
fn check_expr(&mut self, _: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
|
||||||
|
if let Some(def_id) = path_to_local(expr) {
|
||||||
|
self.underscore_bindings.remove(&def_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoEffect {
|
||||||
|
fn check_no_effect(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
|
||||||
|
if let StmtKind::Semi(expr) = stmt.kind {
|
||||||
|
// move `expr.span.from_expansion()` ahead
|
||||||
|
if expr.span.from_expansion() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let expr = peel_blocks(expr);
|
||||||
|
|
||||||
|
if is_operator_overridden(cx, expr) {
|
||||||
|
// Return `true`, to prevent `check_unnecessary_operation` from
|
||||||
|
// linting on this statement as well.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if has_no_effect(cx, expr) {
|
||||||
|
span_lint_hir_and_then(
|
||||||
|
cx,
|
||||||
|
NO_EFFECT,
|
||||||
|
expr.hir_id,
|
||||||
|
stmt.span,
|
||||||
|
"statement with no effect",
|
||||||
|
|diag| {
|
||||||
|
for parent in cx.tcx.hir().parent_iter(stmt.hir_id) {
|
||||||
|
if let Node::Item(item) = parent.1
|
||||||
|
&& let ItemKind::Fn(..) = item.kind
|
||||||
|
&& let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id)
|
||||||
|
&& let [.., final_stmt] = block.stmts
|
||||||
|
&& final_stmt.hir_id == stmt.hir_id
|
||||||
|
{
|
||||||
|
let expr_ty = cx.typeck_results().expr_ty(expr);
|
||||||
|
let mut ret_ty = cx
|
||||||
|
.tcx
|
||||||
|
.fn_sig(item.owner_id)
|
||||||
|
.instantiate_identity()
|
||||||
|
.output()
|
||||||
|
.skip_binder();
|
||||||
|
|
||||||
|
// Remove `impl Future<Output = T>` to get `T`
|
||||||
|
if cx.tcx.ty_is_opaque_future(ret_ty)
|
||||||
|
&& let Some(true_ret_ty) =
|
||||||
|
cx.tcx.infer_ctxt().build().get_impl_future_output_ty(ret_ty)
|
||||||
|
{
|
||||||
|
ret_ty = true_ret_ty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ret_ty.is_unit() && ret_ty == expr_ty {
|
||||||
|
diag.span_suggestion(
|
||||||
|
stmt.span.shrink_to_lo(),
|
||||||
|
"did you mean to return it?",
|
||||||
|
"return ",
|
||||||
|
Applicability::MaybeIncorrect,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if let StmtKind::Local(local) = stmt.kind {
|
||||||
|
if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id)
|
||||||
|
&& let Some(init) = local.init
|
||||||
|
&& local.els.is_none()
|
||||||
|
&& !local.pat.span.from_expansion()
|
||||||
|
&& has_no_effect(cx, init)
|
||||||
|
&& let PatKind::Binding(_, hir_id, ident, _) = local.pat.kind
|
||||||
|
&& ident.name.to_ident_string().starts_with('_')
|
||||||
|
&& !any_parent_is_automatically_derived(cx.tcx, local.hir_id)
|
||||||
|
{
|
||||||
|
if let Some(l) = self.local_bindings.last_mut() {
|
||||||
|
l.push(hir_id);
|
||||||
|
self.underscore_bindings.insert(hir_id, ident.span);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_operator_overridden(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
fn is_operator_overridden(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||||
use clippy_utils::source::snippet_opt;
|
use clippy_utils::source::snippet_opt;
|
||||||
|
use clippy_utils::std_or_core;
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
|
|
||||||
use super::PTR_EQ;
|
use super::PTR_EQ;
|
||||||
|
|
||||||
static LINT_MSG: &str = "use `std::ptr::eq` when comparing raw pointers";
|
|
||||||
|
|
||||||
pub(super) fn check<'tcx>(
|
pub(super) fn check<'tcx>(
|
||||||
cx: &LateContext<'tcx>,
|
cx: &LateContext<'tcx>,
|
||||||
expr: &'tcx Expr<'_>,
|
expr: &'tcx Expr<'_>,
|
||||||
|
|
@ -26,13 +25,14 @@ pub(super) fn check<'tcx>(
|
||||||
&& let Some(left_snip) = snippet_opt(cx, left_var.span)
|
&& let Some(left_snip) = snippet_opt(cx, left_var.span)
|
||||||
&& let Some(right_snip) = snippet_opt(cx, right_var.span)
|
&& let Some(right_snip) = snippet_opt(cx, right_var.span)
|
||||||
{
|
{
|
||||||
|
let Some(top_crate) = std_or_core(cx) else { return };
|
||||||
span_lint_and_sugg(
|
span_lint_and_sugg(
|
||||||
cx,
|
cx,
|
||||||
PTR_EQ,
|
PTR_EQ,
|
||||||
expr.span,
|
expr.span,
|
||||||
LINT_MSG,
|
&format!("use `{top_crate}::ptr::eq` when comparing raw pointers"),
|
||||||
"try",
|
"try",
|
||||||
format!("std::ptr::eq({left_snip}, {right_snip})"),
|
format!("{top_crate}::ptr::eq({left_snip}, {right_snip})"),
|
||||||
Applicability::MachineApplicable,
|
Applicability::MachineApplicable,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ impl<'tcx> LateLintPass<'tcx> for PubUnderscoreFields {
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_visible = |field: &FieldDef<'_>| match self.behavior {
|
let is_visible = |field: &FieldDef<'_>| match self.behavior {
|
||||||
PubUnderscoreFieldsBehaviour::PublicallyExported => cx.effective_visibilities.is_reachable(field.def_id),
|
PubUnderscoreFieldsBehaviour::PubliclyExported => cx.effective_visibilities.is_reachable(field.def_id),
|
||||||
PubUnderscoreFieldsBehaviour::AllPubFields => {
|
PubUnderscoreFieldsBehaviour::AllPubFields => {
|
||||||
// If there is a visibility span then the field is marked pub in some way.
|
// If there is a visibility span then the field is marked pub in some way.
|
||||||
!field.vis_span.is_empty()
|
!field.vis_span.is_empty()
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
||||||
|
use clippy_utils::get_enclosing_block;
|
||||||
use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
|
use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
|
||||||
use clippy_utils::source::snippet;
|
use clippy_utils::source::snippet;
|
||||||
use clippy_utils::visitors::for_each_expr;
|
|
||||||
use core::ops::ControlFlow;
|
use hir::{Expr, ExprKind, HirId, Local, PatKind, PathSegment, QPath, StmtKind};
|
||||||
use hir::{Expr, ExprKind, Local, PatKind, PathSegment, QPath, StmtKind};
|
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
|
use rustc_hir::def::Res;
|
||||||
|
use rustc_hir::intravisit::{walk_expr, Visitor};
|
||||||
use rustc_lint::{LateContext, LateLintPass};
|
use rustc_lint::{LateContext, LateLintPass};
|
||||||
use rustc_session::declare_lint_pass;
|
use rustc_session::declare_lint_pass;
|
||||||
|
|
||||||
|
|
@ -49,57 +51,40 @@ declare_lint_pass!(ReadZeroByteVec => [READ_ZERO_BYTE_VEC]);
|
||||||
|
|
||||||
impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
|
impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
|
||||||
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &hir::Block<'tcx>) {
|
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &hir::Block<'tcx>) {
|
||||||
for (idx, stmt) in block.stmts.iter().enumerate() {
|
for stmt in block.stmts {
|
||||||
if !stmt.span.from_expansion()
|
if stmt.span.from_expansion() {
|
||||||
// matches `let v = Vec::new();`
|
return;
|
||||||
&& let StmtKind::Local(local) = stmt.kind
|
}
|
||||||
&& let Local { pat, init: Some(init), .. } = local
|
|
||||||
&& let PatKind::Binding(_, _, ident, _) = pat.kind
|
if let StmtKind::Local(local) = stmt.kind
|
||||||
|
&& let Local {
|
||||||
|
pat, init: Some(init), ..
|
||||||
|
} = local
|
||||||
|
&& let PatKind::Binding(_, id, ident, _) = pat.kind
|
||||||
&& let Some(vec_init_kind) = get_vec_init_kind(cx, init)
|
&& let Some(vec_init_kind) = get_vec_init_kind(cx, init)
|
||||||
{
|
{
|
||||||
let visitor = |expr: &Expr<'_>| {
|
let mut visitor = ReadVecVisitor {
|
||||||
if let ExprKind::MethodCall(path, _, [arg], _) = expr.kind
|
local_id: id,
|
||||||
&& let PathSegment {
|
read_zero_expr: None,
|
||||||
ident: read_or_read_exact,
|
has_resize: false,
|
||||||
..
|
|
||||||
} = *path
|
|
||||||
&& matches!(read_or_read_exact.as_str(), "read" | "read_exact")
|
|
||||||
&& let ExprKind::AddrOf(_, hir::Mutability::Mut, inner) = arg.kind
|
|
||||||
&& let ExprKind::Path(QPath::Resolved(None, inner_path)) = inner.kind
|
|
||||||
&& let [inner_seg] = inner_path.segments
|
|
||||||
&& ident.name == inner_seg.ident.name
|
|
||||||
{
|
|
||||||
ControlFlow::Break(())
|
|
||||||
} else {
|
|
||||||
ControlFlow::Continue(())
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let (read_found, next_stmt_span) = if let Some(next_stmt) = block.stmts.get(idx + 1) {
|
let Some(enclosing_block) = get_enclosing_block(cx, id) else {
|
||||||
// case { .. stmt; stmt; .. }
|
|
||||||
(for_each_expr(next_stmt, visitor).is_some(), next_stmt.span)
|
|
||||||
} else if let Some(e) = block.expr {
|
|
||||||
// case { .. stmt; expr }
|
|
||||||
(for_each_expr(e, visitor).is_some(), e.span)
|
|
||||||
} else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
visitor.visit_block(enclosing_block);
|
||||||
|
|
||||||
if read_found && !next_stmt_span.from_expansion() {
|
if let Some(expr) = visitor.read_zero_expr {
|
||||||
let applicability = Applicability::MaybeIncorrect;
|
let applicability = Applicability::MaybeIncorrect;
|
||||||
match vec_init_kind {
|
match vec_init_kind {
|
||||||
VecInitKind::WithConstCapacity(len) => {
|
VecInitKind::WithConstCapacity(len) => {
|
||||||
span_lint_and_sugg(
|
span_lint_and_sugg(
|
||||||
cx,
|
cx,
|
||||||
READ_ZERO_BYTE_VEC,
|
READ_ZERO_BYTE_VEC,
|
||||||
next_stmt_span,
|
expr.span,
|
||||||
"reading zero byte data to `Vec`",
|
"reading zero byte data to `Vec`",
|
||||||
"try",
|
"try",
|
||||||
format!(
|
format!("{}.resize({len}, 0); {}", ident.as_str(), snippet(cx, expr.span, "..")),
|
||||||
"{}.resize({len}, 0); {}",
|
|
||||||
ident.as_str(),
|
|
||||||
snippet(cx, next_stmt_span, "..")
|
|
||||||
),
|
|
||||||
applicability,
|
applicability,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -108,25 +93,20 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
|
||||||
span_lint_and_sugg(
|
span_lint_and_sugg(
|
||||||
cx,
|
cx,
|
||||||
READ_ZERO_BYTE_VEC,
|
READ_ZERO_BYTE_VEC,
|
||||||
next_stmt_span,
|
expr.span,
|
||||||
"reading zero byte data to `Vec`",
|
"reading zero byte data to `Vec`",
|
||||||
"try",
|
"try",
|
||||||
format!(
|
format!(
|
||||||
"{}.resize({}, 0); {}",
|
"{}.resize({}, 0); {}",
|
||||||
ident.as_str(),
|
ident.as_str(),
|
||||||
snippet(cx, e.span, ".."),
|
snippet(cx, e.span, ".."),
|
||||||
snippet(cx, next_stmt_span, "..")
|
snippet(cx, expr.span, "..")
|
||||||
),
|
),
|
||||||
applicability,
|
applicability,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
span_lint(
|
span_lint(cx, READ_ZERO_BYTE_VEC, expr.span, "reading zero byte data to `Vec`");
|
||||||
cx,
|
|
||||||
READ_ZERO_BYTE_VEC,
|
|
||||||
next_stmt_span,
|
|
||||||
"reading zero byte data to `Vec`",
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,3 +114,47 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ReadVecVisitor<'tcx> {
|
||||||
|
local_id: HirId,
|
||||||
|
read_zero_expr: Option<&'tcx Expr<'tcx>>,
|
||||||
|
has_resize: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tcx> Visitor<'tcx> for ReadVecVisitor<'tcx> {
|
||||||
|
fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
|
||||||
|
if let ExprKind::MethodCall(path, receiver, args, _) = e.kind {
|
||||||
|
let PathSegment { ident, .. } = *path;
|
||||||
|
|
||||||
|
match ident.as_str() {
|
||||||
|
"read" | "read_exact" => {
|
||||||
|
let [arg] = args else { return };
|
||||||
|
if let ExprKind::AddrOf(_, hir::Mutability::Mut, inner) = arg.kind
|
||||||
|
&& let ExprKind::Path(QPath::Resolved(None, inner_path)) = inner.kind
|
||||||
|
&& let [inner_seg] = inner_path.segments
|
||||||
|
&& let Res::Local(res_id) = inner_seg.res
|
||||||
|
&& self.local_id == res_id
|
||||||
|
{
|
||||||
|
self.read_zero_expr = Some(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resize" => {
|
||||||
|
// If the Vec is resized, then it's a valid read
|
||||||
|
if let ExprKind::Path(QPath::Resolved(_, inner_path)) = receiver.kind
|
||||||
|
&& let Res::Local(res_id) = inner_path.res
|
||||||
|
&& self.local_id == res_id
|
||||||
|
{
|
||||||
|
self.has_resize = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.has_resize && self.read_zero_expr.is_none() {
|
||||||
|
walk_expr(self, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use rustc_errors::Applicability;
|
||||||
use rustc_hir::{Block, ExprKind};
|
use rustc_hir::{Block, ExprKind};
|
||||||
use rustc_lint::{LateContext, LateLintPass};
|
use rustc_lint::{LateContext, LateLintPass};
|
||||||
use rustc_session::declare_lint_pass;
|
use rustc_session::declare_lint_pass;
|
||||||
|
use rustc_span::{ExpnKind, MacroKind, Span};
|
||||||
|
|
||||||
declare_clippy_lint! {
|
declare_clippy_lint! {
|
||||||
/// ### What it does
|
/// ### What it does
|
||||||
|
|
@ -39,6 +40,7 @@ impl<'tcx> LateLintPass<'tcx> for SemicolonIfNothingReturned {
|
||||||
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
|
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
|
||||||
if !block.span.from_expansion()
|
if !block.span.from_expansion()
|
||||||
&& let Some(expr) = block.expr
|
&& let Some(expr) = block.expr
|
||||||
|
&& !from_attr_macro(expr.span)
|
||||||
&& let t_expr = cx.typeck_results().expr_ty(expr)
|
&& let t_expr = cx.typeck_results().expr_ty(expr)
|
||||||
&& t_expr.is_unit()
|
&& t_expr.is_unit()
|
||||||
&& let mut app = Applicability::MachineApplicable
|
&& let mut app = Applicability::MachineApplicable
|
||||||
|
|
@ -63,3 +65,7 @@ impl<'tcx> LateLintPass<'tcx> for SemicolonIfNothingReturned {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_attr_macro(span: Span) -> bool {
|
||||||
|
matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Macro(MacroKind::Attr, _))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_help;
|
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||||
use clippy_utils::{is_from_proc_macro, is_in_test_function};
|
use clippy_utils::{is_from_proc_macro, is_in_test_function};
|
||||||
use rustc_data_structures::fx::FxHashMap;
|
use rustc_data_structures::fx::FxHashMap;
|
||||||
use rustc_hir::def_id::LocalDefId;
|
use rustc_hir::def_id::LocalDefId;
|
||||||
|
|
@ -88,16 +88,18 @@ impl<'tcx> LateLintPass<'tcx> for SingleCallFn {
|
||||||
};
|
};
|
||||||
cx.tcx.hir().visit_all_item_likes_in_crate(&mut v);
|
cx.tcx.hir().visit_all_item_likes_in_crate(&mut v);
|
||||||
|
|
||||||
for usage in self.def_id_to_usage.values() {
|
for (&def_id, usage) in &self.def_id_to_usage {
|
||||||
let single_call_fn_span = usage.0;
|
let single_call_fn_span = usage.0;
|
||||||
if let [caller_span] = *usage.1 {
|
if let [caller_span] = *usage.1 {
|
||||||
span_lint_and_help(
|
span_lint_hir_and_then(
|
||||||
cx,
|
cx,
|
||||||
SINGLE_CALL_FN,
|
SINGLE_CALL_FN,
|
||||||
|
cx.tcx.local_def_id_to_hir_id(def_id),
|
||||||
single_call_fn_span,
|
single_call_fn_span,
|
||||||
"this function is only used once",
|
"this function is only used once",
|
||||||
Some(caller_span),
|
|diag| {
|
||||||
"used here",
|
diag.span_help(caller_span, "used here");
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -390,6 +390,14 @@ fn get_trait_info_from_bound<'a>(bound: &'a GenericBound<'_>) -> Option<(Res, &'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_ty_res(ty: Ty<'_>) -> Option<Res> {
|
||||||
|
match ty.kind {
|
||||||
|
TyKind::Path(QPath::Resolved(_, path)) => Some(path.res),
|
||||||
|
TyKind::Path(QPath::TypeRelative(ty, _)) => get_ty_res(*ty),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: ComparableTraitRef does not support nested bounds needed for associated_type_bounds
|
// FIXME: ComparableTraitRef does not support nested bounds needed for associated_type_bounds
|
||||||
fn into_comparable_trait_ref(trait_ref: &TraitRef<'_>) -> ComparableTraitRef {
|
fn into_comparable_trait_ref(trait_ref: &TraitRef<'_>) -> ComparableTraitRef {
|
||||||
ComparableTraitRef(
|
ComparableTraitRef(
|
||||||
|
|
@ -401,10 +409,8 @@ fn into_comparable_trait_ref(trait_ref: &TraitRef<'_>) -> ComparableTraitRef {
|
||||||
.filter_map(|segment| {
|
.filter_map(|segment| {
|
||||||
// get trait bound type arguments
|
// get trait bound type arguments
|
||||||
Some(segment.args?.args.iter().filter_map(|arg| {
|
Some(segment.args?.args.iter().filter_map(|arg| {
|
||||||
if let GenericArg::Type(ty) = arg
|
if let GenericArg::Type(ty) = arg {
|
||||||
&& let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
|
return get_ty_res(**ty);
|
||||||
{
|
|
||||||
return Some(path.res);
|
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use super::TRANSMUTE_INT_TO_CHAR;
|
use super::TRANSMUTE_INT_TO_CHAR;
|
||||||
use clippy_utils::diagnostics::span_lint_and_then;
|
use clippy_utils::diagnostics::span_lint_and_then;
|
||||||
use clippy_utils::sugg;
|
use clippy_utils::{std_or_core, sugg};
|
||||||
use rustc_ast as ast;
|
use rustc_ast as ast;
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::Expr;
|
use rustc_hir::Expr;
|
||||||
|
|
@ -25,6 +25,7 @@ pub(super) fn check<'tcx>(
|
||||||
e.span,
|
e.span,
|
||||||
&format!("transmute from a `{from_ty}` to a `char`"),
|
&format!("transmute from a `{from_ty}` to a `char`"),
|
||||||
|diag| {
|
|diag| {
|
||||||
|
let Some(top_crate) = std_or_core(cx) else { return };
|
||||||
let arg = sugg::Sugg::hir(cx, arg, "..");
|
let arg = sugg::Sugg::hir(cx, arg, "..");
|
||||||
let arg = if let ty::Int(_) = from_ty.kind() {
|
let arg = if let ty::Int(_) = from_ty.kind() {
|
||||||
arg.as_ty(ast::UintTy::U32.name_str())
|
arg.as_ty(ast::UintTy::U32.name_str())
|
||||||
|
|
@ -34,7 +35,7 @@ pub(super) fn check<'tcx>(
|
||||||
diag.span_suggestion(
|
diag.span_suggestion(
|
||||||
e.span,
|
e.span,
|
||||||
"consider using",
|
"consider using",
|
||||||
format!("std::char::from_u32({arg}).unwrap()"),
|
format!("{top_crate}::char::from_u32({arg}).unwrap()"),
|
||||||
Applicability::Unspecified,
|
Applicability::Unspecified,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use super::{TRANSMUTE_BYTES_TO_STR, TRANSMUTE_PTR_TO_PTR};
|
use super::{TRANSMUTE_BYTES_TO_STR, TRANSMUTE_PTR_TO_PTR};
|
||||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||||
use clippy_utils::source::snippet;
|
use clippy_utils::source::snippet;
|
||||||
use clippy_utils::sugg;
|
use clippy_utils::{std_or_core, sugg};
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::{Expr, Mutability};
|
use rustc_hir::{Expr, Mutability};
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
|
|
@ -25,6 +25,8 @@ pub(super) fn check<'tcx>(
|
||||||
&& let ty::Uint(ty::UintTy::U8) = slice_ty.kind()
|
&& let ty::Uint(ty::UintTy::U8) = slice_ty.kind()
|
||||||
&& from_mutbl == to_mutbl
|
&& from_mutbl == to_mutbl
|
||||||
{
|
{
|
||||||
|
let Some(top_crate) = std_or_core(cx) else { return true };
|
||||||
|
|
||||||
let postfix = if *from_mutbl == Mutability::Mut { "_mut" } else { "" };
|
let postfix = if *from_mutbl == Mutability::Mut { "_mut" } else { "" };
|
||||||
|
|
||||||
let snippet = snippet(cx, arg.span, "..");
|
let snippet = snippet(cx, arg.span, "..");
|
||||||
|
|
@ -36,9 +38,9 @@ pub(super) fn check<'tcx>(
|
||||||
&format!("transmute from a `{from_ty}` to a `{to_ty}`"),
|
&format!("transmute from a `{from_ty}` to a `{to_ty}`"),
|
||||||
"consider using",
|
"consider using",
|
||||||
if const_context {
|
if const_context {
|
||||||
format!("std::str::from_utf8_unchecked{postfix}({snippet})")
|
format!("{top_crate}::str::from_utf8_unchecked{postfix}({snippet})")
|
||||||
} else {
|
} else {
|
||||||
format!("std::str::from_utf8{postfix}({snippet}).unwrap()")
|
format!("{top_crate}::str::from_utf8{postfix}({snippet}).unwrap()")
|
||||||
},
|
},
|
||||||
Applicability::MaybeIncorrect,
|
Applicability::MaybeIncorrect,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,15 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ExprKind::MethodCall(segment, _receiver, &[_arg], _) if segment.ident.name == name.name => {
|
ExprKind::MethodCall(segment, receiver, &[_arg], _) if segment.ident.name == name.name => {
|
||||||
|
if let Some(ty) = cx.typeck_results().expr_ty_opt(receiver)
|
||||||
|
&& let Some(ty_id) = get_ty_def_id(ty)
|
||||||
|
&& self_arg != ty_id
|
||||||
|
{
|
||||||
|
// Since this called on a different type, the lint should not be
|
||||||
|
// triggered here.
|
||||||
|
return;
|
||||||
|
}
|
||||||
if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
||||||
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
|
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
|
||||||
&& trait_id == trait_def_id
|
&& trait_id == trait_def_id
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
|
use clippy_utils::diagnostics::span_lint_and_then;
|
||||||
use clippy_utils::{is_trait_method, is_try, match_trait_method, paths};
|
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths};
|
||||||
|
use hir::{ExprKind, PatKind};
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_lint::{LateContext, LateLintPass};
|
use rustc_lint::{LateContext, LateLintPass};
|
||||||
use rustc_session::declare_lint_pass;
|
use rustc_session::declare_lint_pass;
|
||||||
use rustc_span::sym;
|
use rustc_span::{sym, Span};
|
||||||
|
|
||||||
declare_clippy_lint! {
|
declare_clippy_lint! {
|
||||||
/// ### What it does
|
/// ### What it does
|
||||||
|
|
@ -45,126 +46,219 @@ declare_clippy_lint! {
|
||||||
|
|
||||||
declare_lint_pass!(UnusedIoAmount => [UNUSED_IO_AMOUNT]);
|
declare_lint_pass!(UnusedIoAmount => [UNUSED_IO_AMOUNT]);
|
||||||
|
|
||||||
impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
|
#[derive(Copy, Clone)]
|
||||||
fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
|
enum IoOp {
|
||||||
let (hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr)) = s.kind else {
|
AsyncWrite(bool),
|
||||||
return;
|
AsyncRead(bool),
|
||||||
};
|
SyncRead(bool),
|
||||||
|
SyncWrite(bool),
|
||||||
|
}
|
||||||
|
|
||||||
match expr.kind {
|
impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
|
||||||
hir::ExprKind::Match(res, _, _) if is_try(cx, expr).is_some() => {
|
/// We perform the check on the block level.
|
||||||
if let hir::ExprKind::Call(func, [ref arg_0, ..]) = res.kind {
|
/// If we want to catch match and if expressions that act as returns of the block
|
||||||
if matches!(
|
/// we need to check them at `check_expr` or `check_block` as they are not stmts
|
||||||
func.kind,
|
/// but we can't check them at `check_expr` because we need the broader context
|
||||||
hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, ..))
|
/// because we should do this only for the final expression of the block, and not for
|
||||||
) {
|
/// `StmtKind::Local` which binds values => the io amount is used.
|
||||||
check_map_error(cx, arg_0, expr);
|
///
|
||||||
}
|
/// To check for unused io amount in stmts, we only consider `StmtKind::Semi`.
|
||||||
} else {
|
/// `StmtKind::Local` is not considered because it binds values => the io amount is used.
|
||||||
check_map_error(cx, res, expr);
|
/// `StmtKind::Expr` is not considered because requires unit type => the io amount is used.
|
||||||
}
|
/// `StmtKind::Item` is not considered because it's not an expression.
|
||||||
},
|
///
|
||||||
hir::ExprKind::MethodCall(path, arg_0, ..) => match path.ident.as_str() {
|
/// We then check the individual expressions via `check_expr`. We use the same logic for
|
||||||
"expect" | "unwrap" | "unwrap_or" | "unwrap_or_else" | "is_ok" | "is_err" => {
|
/// semi expressions and the final expression as we need to check match and if expressions
|
||||||
check_map_error(cx, arg_0, expr);
|
/// for binding of the io amount to `Ok(_)`.
|
||||||
},
|
///
|
||||||
_ => (),
|
/// We explicitly check for the match source to be Normal as it needs special logic
|
||||||
},
|
/// to consider the arms, and we want to avoid breaking the logic for situations where things
|
||||||
_ => (),
|
/// get desugared to match.
|
||||||
|
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'tcx>) {
|
||||||
|
for stmt in block.stmts {
|
||||||
|
if let hir::StmtKind::Semi(exp) = stmt.kind {
|
||||||
|
check_expr(cx, exp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(exp) = block.expr
|
||||||
|
&& matches!(exp.kind, hir::ExprKind::If(_, _, _) | hir::ExprKind::Match(_, _, _))
|
||||||
|
{
|
||||||
|
check_expr(cx, exp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_expr<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) {
|
||||||
|
match expr.kind {
|
||||||
|
hir::ExprKind::If(cond, _, _)
|
||||||
|
if let ExprKind::Let(hir::Let { pat, init, .. }) = cond.kind
|
||||||
|
&& pattern_is_ignored_ok(cx, pat)
|
||||||
|
&& let Some(op) = should_lint(cx, init) =>
|
||||||
|
{
|
||||||
|
emit_lint(cx, cond.span, op, &[pat.span]);
|
||||||
|
},
|
||||||
|
hir::ExprKind::Match(expr, arms, hir::MatchSource::Normal) if let Some(op) = should_lint(cx, expr) => {
|
||||||
|
let found_arms: Vec<_> = arms
|
||||||
|
.iter()
|
||||||
|
.filter_map(|arm| {
|
||||||
|
if pattern_is_ignored_ok(cx, arm.pat) {
|
||||||
|
Some(arm.span)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if !found_arms.is_empty() {
|
||||||
|
emit_lint(cx, expr.span, op, found_arms.as_slice());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ if let Some(op) = should_lint(cx, expr) => {
|
||||||
|
emit_lint(cx, expr.span, op, &[]);
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_lint<'a>(cx: &LateContext<'a>, mut inner: &'a hir::Expr<'a>) -> Option<IoOp> {
|
||||||
|
inner = unpack_match(inner);
|
||||||
|
inner = unpack_try(inner);
|
||||||
|
inner = unpack_call_chain(inner);
|
||||||
|
inner = unpack_await(inner);
|
||||||
|
// we type-check it to get whether it's a read/write or their vectorized forms
|
||||||
|
// and keep only the ones that are produce io amount
|
||||||
|
check_io_mode(cx, inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pattern_is_ignored_ok(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> bool {
|
||||||
|
// the if checks whether we are in a result Ok( ) pattern
|
||||||
|
// and the return checks whether it is unhandled
|
||||||
|
|
||||||
|
if let PatKind::TupleStruct(ref path, inner_pat, ddp) = pat.kind
|
||||||
|
// we check against Result::Ok to avoid linting on Err(_) or something else.
|
||||||
|
&& is_res_lang_ctor(cx, cx.qpath_res(path, pat.hir_id), hir::LangItem::ResultOk)
|
||||||
|
{
|
||||||
|
return match (inner_pat, ddp.as_opt_usize()) {
|
||||||
|
// Ok(_) pattern
|
||||||
|
([inner_pat], None) if matches!(inner_pat.kind, PatKind::Wild) => true,
|
||||||
|
// Ok(..) pattern
|
||||||
|
([], Some(0)) => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
|
||||||
|
while let hir::ExprKind::MethodCall(path, receiver, ..) = expr.kind {
|
||||||
|
if matches!(
|
||||||
|
path.ident.as_str(),
|
||||||
|
"unwrap" | "expect" | "unwrap_or" | "unwrap_or_else" | "ok" | "is_ok" | "is_err" | "or_else" | "or"
|
||||||
|
) {
|
||||||
|
expr = receiver;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpack_try<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
|
||||||
|
while let hir::ExprKind::Call(func, [ref arg_0, ..]) = expr.kind
|
||||||
|
&& matches!(
|
||||||
|
func.kind,
|
||||||
|
hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, ..))
|
||||||
|
)
|
||||||
|
{
|
||||||
|
expr = arg_0;
|
||||||
|
}
|
||||||
|
expr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpack_match<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
|
||||||
|
while let hir::ExprKind::Match(res, _, _) = expr.kind {
|
||||||
|
expr = res;
|
||||||
|
}
|
||||||
|
expr
|
||||||
|
}
|
||||||
|
|
||||||
/// If `expr` is an (e).await, return the inner expression "e" that's being
|
/// If `expr` is an (e).await, return the inner expression "e" that's being
|
||||||
/// waited on. Otherwise return None.
|
/// waited on. Otherwise return None.
|
||||||
fn try_remove_await<'a>(expr: &'a hir::Expr<'a>) -> Option<&hir::Expr<'a>> {
|
fn unpack_await<'a>(expr: &'a hir::Expr<'a>) -> &hir::Expr<'a> {
|
||||||
if let hir::ExprKind::Match(expr, _, hir::MatchSource::AwaitDesugar) = expr.kind {
|
if let hir::ExprKind::Match(expr, _, hir::MatchSource::AwaitDesugar) = expr.kind {
|
||||||
if let hir::ExprKind::Call(func, [ref arg_0, ..]) = expr.kind {
|
if let hir::ExprKind::Call(func, [ref arg_0, ..]) = expr.kind {
|
||||||
if matches!(
|
if matches!(
|
||||||
func.kind,
|
func.kind,
|
||||||
hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..))
|
hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..))
|
||||||
) {
|
) {
|
||||||
return Some(arg_0);
|
return arg_0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
expr
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_map_error(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>) {
|
/// Check whether the current expr is a function call for an IO operation
|
||||||
let mut call = call;
|
fn check_io_mode(cx: &LateContext<'_>, call: &hir::Expr<'_>) -> Option<IoOp> {
|
||||||
while let hir::ExprKind::MethodCall(path, receiver, ..) = call.kind {
|
let hir::ExprKind::MethodCall(path, ..) = call.kind else {
|
||||||
if matches!(path.ident.as_str(), "or" | "or_else" | "ok") {
|
return None;
|
||||||
call = receiver;
|
};
|
||||||
} else {
|
|
||||||
break;
|
let vectorized = match path.ident.as_str() {
|
||||||
|
"write_vectored" | "read_vectored" => true,
|
||||||
|
"write" | "read" => false,
|
||||||
|
_ => {
|
||||||
|
return None;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
match (
|
||||||
|
is_trait_method(cx, call, sym::IoRead),
|
||||||
|
is_trait_method(cx, call, sym::IoWrite),
|
||||||
|
match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCREADEXT)
|
||||||
|
|| match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCREADEXT),
|
||||||
|
match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCWRITEEXT)
|
||||||
|
|| match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCWRITEEXT),
|
||||||
|
) {
|
||||||
|
(true, _, _, _) => Some(IoOp::SyncRead(vectorized)),
|
||||||
|
(_, true, _, _) => Some(IoOp::SyncWrite(vectorized)),
|
||||||
|
(_, _, true, _) => Some(IoOp::AsyncRead(vectorized)),
|
||||||
|
(_, _, _, true) => Some(IoOp::AsyncWrite(vectorized)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_lint(cx: &LateContext<'_>, span: Span, op: IoOp, wild_cards: &[Span]) {
|
||||||
|
let (msg, help) = match op {
|
||||||
|
IoOp::AsyncRead(false) => (
|
||||||
|
"read amount is not handled",
|
||||||
|
Some("use `AsyncReadExt::read_exact` instead, or handle partial reads"),
|
||||||
|
),
|
||||||
|
IoOp::SyncRead(false) => (
|
||||||
|
"read amount is not handled",
|
||||||
|
Some("use `Read::read_exact` instead, or handle partial reads"),
|
||||||
|
),
|
||||||
|
IoOp::SyncWrite(false) => (
|
||||||
|
"written amount is not handled",
|
||||||
|
Some("use `Write::write_all` instead, or handle partial writes"),
|
||||||
|
),
|
||||||
|
IoOp::AsyncWrite(false) => (
|
||||||
|
"written amount is not handled",
|
||||||
|
Some("use `AsyncWriteExt::write_all` instead, or handle partial writes"),
|
||||||
|
),
|
||||||
|
IoOp::SyncRead(true) | IoOp::AsyncRead(true) => ("read amount is not handled", None),
|
||||||
|
IoOp::SyncWrite(true) | IoOp::AsyncWrite(true) => ("written amount is not handled", None),
|
||||||
|
};
|
||||||
|
|
||||||
|
span_lint_and_then(cx, UNUSED_IO_AMOUNT, span, msg, |diag| {
|
||||||
|
if let Some(help_str) = help {
|
||||||
|
diag.help(help_str);
|
||||||
}
|
}
|
||||||
}
|
for span in wild_cards {
|
||||||
|
diag.span_note(
|
||||||
if let Some(call) = try_remove_await(call) {
|
*span,
|
||||||
check_method_call(cx, call, expr, true);
|
"the result is consumed here, but the amount of I/O bytes remains unhandled",
|
||||||
} else {
|
);
|
||||||
check_method_call(cx, call, expr, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_method_call(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>, is_await: bool) {
|
|
||||||
if let hir::ExprKind::MethodCall(path, ..) = call.kind {
|
|
||||||
let symbol = path.ident.as_str();
|
|
||||||
let read_trait = if is_await {
|
|
||||||
match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCREADEXT)
|
|
||||||
|| match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCREADEXT)
|
|
||||||
} else {
|
|
||||||
is_trait_method(cx, call, sym::IoRead)
|
|
||||||
};
|
|
||||||
let write_trait = if is_await {
|
|
||||||
match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCWRITEEXT)
|
|
||||||
|| match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCWRITEEXT)
|
|
||||||
} else {
|
|
||||||
is_trait_method(cx, call, sym::IoWrite)
|
|
||||||
};
|
|
||||||
|
|
||||||
match (read_trait, write_trait, symbol, is_await) {
|
|
||||||
(true, _, "read", false) => span_lint_and_help(
|
|
||||||
cx,
|
|
||||||
UNUSED_IO_AMOUNT,
|
|
||||||
expr.span,
|
|
||||||
"read amount is not handled",
|
|
||||||
None,
|
|
||||||
"use `Read::read_exact` instead, or handle partial reads",
|
|
||||||
),
|
|
||||||
(true, _, "read", true) => span_lint_and_help(
|
|
||||||
cx,
|
|
||||||
UNUSED_IO_AMOUNT,
|
|
||||||
expr.span,
|
|
||||||
"read amount is not handled",
|
|
||||||
None,
|
|
||||||
"use `AsyncReadExt::read_exact` instead, or handle partial reads",
|
|
||||||
),
|
|
||||||
(true, _, "read_vectored", _) => {
|
|
||||||
span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "read amount is not handled");
|
|
||||||
},
|
|
||||||
(_, true, "write", false) => span_lint_and_help(
|
|
||||||
cx,
|
|
||||||
UNUSED_IO_AMOUNT,
|
|
||||||
expr.span,
|
|
||||||
"written amount is not handled",
|
|
||||||
None,
|
|
||||||
"use `Write::write_all` instead, or handle partial writes",
|
|
||||||
),
|
|
||||||
(_, true, "write", true) => span_lint_and_help(
|
|
||||||
cx,
|
|
||||||
UNUSED_IO_AMOUNT,
|
|
||||||
expr.span,
|
|
||||||
"written amount is not handled",
|
|
||||||
None,
|
|
||||||
"use `AsyncWriteExt::write_all` instead, or handle partial writes",
|
|
||||||
),
|
|
||||||
(_, true, "write_vectored", _) => {
|
|
||||||
span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "written amount is not handled");
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ impl<'tcx> LateLintPass<'tcx> for AlmostStandardFormulation {
|
||||||
ident.span,
|
ident.span,
|
||||||
"non-standard lint formulation",
|
"non-standard lint formulation",
|
||||||
None,
|
None,
|
||||||
&format!("try using `{}` instead", formulation.correction),
|
&format!("consider using `{}`", formulation.correction),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,9 @@ pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
|
||||||
SimplifiedType::Str,
|
SimplifiedType::Str,
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|&ty| cx.tcx.incoherent_impls(ty).iter().copied());
|
.flat_map(|&ty| cx.tcx.incoherent_impls(ty).into_iter())
|
||||||
|
.flatten()
|
||||||
|
.copied();
|
||||||
for item_def_id in lang_items.iter().map(|(_, def_id)| def_id).chain(incoherent_impls) {
|
for item_def_id in lang_items.iter().map(|(_, def_id)| def_id).chain(incoherent_impls) {
|
||||||
let lang_item_path = cx.get_def_path(item_def_id);
|
let lang_item_path = cx.get_def_path(item_def_id);
|
||||||
if path_syms.starts_with(&lang_item_path) {
|
if path_syms.starts_with(&lang_item_path) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use rustc_ast::{ast, attr};
|
use rustc_ast::{ast, attr};
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
|
use rustc_middle::ty::{AdtDef, TyCtxt};
|
||||||
use rustc_session::Session;
|
use rustc_session::Session;
|
||||||
use rustc_span::sym;
|
use rustc_span::sym;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
@ -159,3 +160,14 @@ pub fn is_doc_hidden(attrs: &[ast::Attribute]) -> bool {
|
||||||
.filter_map(ast::Attribute::meta_item_list)
|
.filter_map(ast::Attribute::meta_item_list)
|
||||||
.any(|l| attr::list_contains_name(&l, sym::hidden))
|
.any(|l| attr::list_contains_name(&l, sym::hidden))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool {
|
||||||
|
adt.is_variant_list_non_exhaustive()
|
||||||
|
|| tcx.has_attr(adt.did(), sym::non_exhaustive)
|
||||||
|
|| adt.variants().iter().any(|variant_def| {
|
||||||
|
variant_def.is_field_list_non_exhaustive() || tcx.has_attr(variant_def.def_id, sym::non_exhaustive)
|
||||||
|
})
|
||||||
|
|| adt
|
||||||
|
.all_fields()
|
||||||
|
.any(|field_def| tcx.has_attr(field_def.did, sym::non_exhaustive))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ impl HirEqInterExpr<'_, '_, '_> {
|
||||||
/// Checks whether two blocks are the same.
|
/// Checks whether two blocks are the same.
|
||||||
#[expect(clippy::similar_names)]
|
#[expect(clippy::similar_names)]
|
||||||
fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
|
fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
|
||||||
use TokenKind::{BlockComment, LineComment, Semi, Whitespace};
|
use TokenKind::{Semi, Whitespace};
|
||||||
if left.stmts.len() != right.stmts.len() {
|
if left.stmts.len() != right.stmts.len() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +177,7 @@ impl HirEqInterExpr<'_, '_, '_> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if !eq_span_tokens(self.inner.cx, lstart..lstmt_span.lo, rstart..rstmt_span.lo, |t| {
|
if !eq_span_tokens(self.inner.cx, lstart..lstmt_span.lo, rstart..rstmt_span.lo, |t| {
|
||||||
!matches!(t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi)
|
!matches!(t, Whitespace | Semi)
|
||||||
}) {
|
}) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -212,7 +212,7 @@ impl HirEqInterExpr<'_, '_, '_> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
eq_span_tokens(self.inner.cx, lstart..lend, rstart..rend, |t| {
|
eq_span_tokens(self.inner.cx, lstart..lend, rstart..rend, |t| {
|
||||||
!matches!(t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi)
|
!matches!(t, Whitespace | Semi)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -536,7 +536,12 @@ fn find_primitive_impls<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator<It
|
||||||
"f32" => SimplifiedType::Float(FloatTy::F32),
|
"f32" => SimplifiedType::Float(FloatTy::F32),
|
||||||
"f64" => SimplifiedType::Float(FloatTy::F64),
|
"f64" => SimplifiedType::Float(FloatTy::F64),
|
||||||
#[allow(trivial_casts)]
|
#[allow(trivial_casts)]
|
||||||
_ => return Result::<_, rustc_errors::ErrorGuaranteed>::Ok(&[] as &[_]).into_iter().flatten().copied(),
|
_ => {
|
||||||
|
return Result::<_, rustc_errors::ErrorGuaranteed>::Ok(&[] as &[_])
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.copied();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
tcx.incoherent_impls(ty).into_iter().flatten().copied()
|
tcx.incoherent_impls(ty).into_iter().flatten().copied()
|
||||||
|
|
@ -1712,7 +1717,6 @@ pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
|
||||||
PatKind::Wild | PatKind::Never => false, // If `!` typechecked then the type is empty, so not refutable.
|
PatKind::Wild | PatKind::Never => false, // If `!` typechecked then the type is empty, so not refutable.
|
||||||
PatKind::Binding(_, _, _, pat) => pat.map_or(false, |pat| is_refutable(cx, pat)),
|
PatKind::Binding(_, _, _, pat) => pat.map_or(false, |pat| is_refutable(cx, pat)),
|
||||||
PatKind::Box(pat) | PatKind::Ref(pat, _) => is_refutable(cx, pat),
|
PatKind::Box(pat) | PatKind::Ref(pat, _) => is_refutable(cx, pat),
|
||||||
PatKind::Lit(..) | PatKind::Range(..) => true,
|
|
||||||
PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id),
|
PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id),
|
||||||
PatKind::Or(pats) => {
|
PatKind::Or(pats) => {
|
||||||
// TODO: should be the honest check, that pats is exhaustive set
|
// TODO: should be the honest check, that pats is exhaustive set
|
||||||
|
|
@ -1736,7 +1740,7 @@ pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PatKind::Err(_) => true,
|
PatKind::Lit(..) | PatKind::Range(..) | PatKind::Err(_) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
|
||||||
pub const EARLY_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "EarlyLintPass"];
|
pub const EARLY_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "EarlyLintPass"];
|
||||||
pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
|
pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
|
||||||
pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
|
pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
|
||||||
|
pub const FILE_OPTIONS: [&str; 4] = ["std", "fs", "File", "options"];
|
||||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||||
pub const FUTURES_IO_ASYNCREADEXT: [&str; 3] = ["futures_util", "io", "AsyncReadExt"];
|
pub const FUTURES_IO_ASYNCREADEXT: [&str; 3] = ["futures_util", "io", "AsyncReadExt"];
|
||||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||||
|
|
@ -50,6 +51,7 @@ pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
|
||||||
pub const LATE_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "LateLintPass"];
|
pub const LATE_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "LateLintPass"];
|
||||||
pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
|
pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
|
||||||
pub const MSRV: [&str; 3] = ["clippy_config", "msrvs", "Msrv"];
|
pub const MSRV: [&str; 3] = ["clippy_config", "msrvs", "Msrv"];
|
||||||
|
pub const OPEN_OPTIONS_NEW: [&str; 4] = ["std", "fs", "OpenOptions", "new"];
|
||||||
pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"];
|
pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"];
|
||||||
pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
|
pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
|
||||||
pub const PARKING_LOT_MUTEX_GUARD: [&str; 3] = ["lock_api", "mutex", "MutexGuard"];
|
pub const PARKING_LOT_MUTEX_GUARD: [&str; 3] = ["lock_api", "mutex", "MutexGuard"];
|
||||||
|
|
@ -89,9 +91,15 @@ pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol",
|
||||||
pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
|
pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
|
||||||
pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
|
pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
|
||||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||||
|
pub const TOKIO_FILE_OPTIONS: [&str; 5] = ["tokio", "fs", "file", "File", "options"];
|
||||||
|
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||||
pub const TOKIO_IO_ASYNCREADEXT: [&str; 5] = ["tokio", "io", "util", "async_read_ext", "AsyncReadExt"];
|
pub const TOKIO_IO_ASYNCREADEXT: [&str; 5] = ["tokio", "io", "util", "async_read_ext", "AsyncReadExt"];
|
||||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||||
pub const TOKIO_IO_ASYNCWRITEEXT: [&str; 5] = ["tokio", "io", "util", "async_write_ext", "AsyncWriteExt"];
|
pub const TOKIO_IO_ASYNCWRITEEXT: [&str; 5] = ["tokio", "io", "util", "async_write_ext", "AsyncWriteExt"];
|
||||||
|
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||||
|
pub const TOKIO_IO_OPEN_OPTIONS: [&str; 4] = ["tokio", "fs", "open_options", "OpenOptions"];
|
||||||
|
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||||
|
pub const TOKIO_IO_OPEN_OPTIONS_NEW: [&str; 5] = ["tokio", "fs", "open_options", "OpenOptions", "new"];
|
||||||
pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"];
|
pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"];
|
||||||
pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
|
pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
|
||||||
pub const VEC_DEQUE_ITER: [&str; 5] = ["alloc", "collections", "vec_deque", "VecDeque", "iter"];
|
pub const VEC_DEQUE_ITER: [&str; 5] = ["alloc", "collections", "vec_deque", "VecDeque", "iter"];
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,8 @@ pub fn implements_trait<'tcx>(
|
||||||
|
|
||||||
/// Same as `implements_trait` but allows using a `ParamEnv` different from the lint context.
|
/// Same as `implements_trait` but allows using a `ParamEnv` different from the lint context.
|
||||||
///
|
///
|
||||||
/// The `callee_id` argument is used to determine whether this is a function call in a `const fn` environment, used for checking const traits.
|
/// The `callee_id` argument is used to determine whether this is a function call in a `const fn`
|
||||||
|
/// environment, used for checking const traits.
|
||||||
pub fn implements_trait_with_env<'tcx>(
|
pub fn implements_trait_with_env<'tcx>(
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
param_env: ParamEnv<'tcx>,
|
param_env: ParamEnv<'tcx>,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "nightly-2024-01-11"
|
channel = "nightly-2024-01-25"
|
||||||
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
|
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
error: multiple versions for dependency `winapi`: 0.2.8, 0.3.9
|
||||||
|
|
|
||||||
|
= note: `-D clippy::multiple-crate-versions` implied by `-D warnings`
|
||||||
|
= help: to override `-D warnings` add `#[allow(clippy::multiple_crate_versions)]`
|
||||||
|
|
||||||
|
error: could not compile `multiple-crate-versions` (bin "multiple-crate-versions") due to 1 previous error
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Should not lint for dev or build dependencies. See issue 5041.
|
||||||
|
|
||||||
|
[package]
|
||||||
|
# purposefully separated by - instead of _
|
||||||
|
name = "multiple-crate-versions"
|
||||||
|
version = "0.1.0"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
# One of the versions of winapi is only a dev dependency: allowed
|
||||||
|
[dependencies]
|
||||||
|
winapi = "0.2"
|
||||||
|
ansi_term = "=0.11.0"
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
#![warn(clippy::multiple_crate_versions)]
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "multiple_crate_versions"
|
||||||
|
version = "0.1.0"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
winapi = "0.2"
|
||||||
|
ansi_term = "=0.11.0"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
allowed-duplicate-crates = ["winapi"]
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
#![warn(clippy::multiple_crate_versions)]
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -4,7 +4,7 @@ error: non-standard lint formulation
|
||||||
LL | /// Check for lint formulations that are correct
|
LL | /// Check for lint formulations that are correct
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= help: try using `Checks for` instead
|
= help: consider using `Checks for`
|
||||||
= note: `-D clippy::almost-standard-lint-formulation` implied by `-D warnings`
|
= note: `-D clippy::almost-standard-lint-formulation` implied by `-D warnings`
|
||||||
= help: to override `-D warnings` add `#[allow(clippy::almost_standard_lint_formulation)]`
|
= help: to override `-D warnings` add `#[allow(clippy::almost_standard_lint_formulation)]`
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ error: non-standard lint formulation
|
||||||
LL | /// Detects uses of incorrect formulations
|
LL | /// Detects uses of incorrect formulations
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= help: try using `Checks for` instead
|
= help: consider using `Checks for`
|
||||||
|
|
||||||
error: aborting due to 2 previous errors
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
error: use of a disallowed method `rustc_lint::context::LintContext::span_lint`
|
error: use of a disallowed method `rustc_lint::context::LintContext::span_lint`
|
||||||
--> $DIR/disallow_struct_span_lint.rs:14:5
|
--> $DIR/disallow_span_lint.rs:14:5
|
||||||
|
|
|
|
||||||
LL | cx.span_lint(lint, span, msg, |_| {});
|
LL | cx.span_lint(lint, span, msg, |_| {});
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
@ -8,10 +8,10 @@ LL | cx.span_lint(lint, span, msg, |_| {});
|
||||||
= help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]`
|
= help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]`
|
||||||
|
|
||||||
error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_lint`
|
error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_lint`
|
||||||
--> $DIR/disallow_struct_span_lint.rs:24:5
|
--> $DIR/disallow_span_lint.rs:24:5
|
||||||
|
|
|
|
||||||
LL | tcx.node_span_lint(lint, hir_id, span, msg, |_| {});
|
LL | tcx.node_span_lint(lint, hir_id, span, msg, |_| {});
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
error: aborting due to 2 previous errors
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
pub-underscore-fields-behavior = "PublicallyExported"
|
pub-underscore-fields-behavior = "PubliclyExported"
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ foobar = 42
|
||||||
# so is this one
|
# so is this one
|
||||||
barfoo = 53
|
barfoo = 53
|
||||||
|
|
||||||
|
# when using underscores instead of dashes, suggest the correct one
|
||||||
|
allow_mixed_uninlined_format_args = true
|
||||||
|
|
||||||
# that one is ignored
|
# that one is ignored
|
||||||
[third-party]
|
[third-party]
|
||||||
clippy-feature = "nightly"
|
clippy-feature = "nightly"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
//@no-rustfix
|
||||||
//@error-in-other-file: unknown field `foobar`, expected one of
|
//@error-in-other-file: unknown field `foobar`, expected one of
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
|
||||||
allow-private-module-inception
|
allow-private-module-inception
|
||||||
allow-unwrap-in-tests
|
allow-unwrap-in-tests
|
||||||
allowed-dotfiles
|
allowed-dotfiles
|
||||||
|
allowed-duplicate-crates
|
||||||
allowed-idents-below-min-chars
|
allowed-idents-below-min-chars
|
||||||
allowed-scripts
|
allowed-scripts
|
||||||
arithmetic-side-effects-allowed
|
arithmetic-side-effects-allowed
|
||||||
|
|
@ -87,6 +88,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
|
||||||
allow-private-module-inception
|
allow-private-module-inception
|
||||||
allow-unwrap-in-tests
|
allow-unwrap-in-tests
|
||||||
allowed-dotfiles
|
allowed-dotfiles
|
||||||
|
allowed-duplicate-crates
|
||||||
allowed-idents-below-min-chars
|
allowed-idents-below-min-chars
|
||||||
allowed-scripts
|
allowed-scripts
|
||||||
arithmetic-side-effects-allowed
|
arithmetic-side-effects-allowed
|
||||||
|
|
@ -150,5 +152,82 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
|
||||||
LL | barfoo = 53
|
LL | barfoo = 53
|
||||||
| ^^^^^^
|
| ^^^^^^
|
||||||
|
|
||||||
error: aborting due to 2 previous errors
|
error: error reading Clippy's configuration file: unknown field `allow_mixed_uninlined_format_args`, expected one of
|
||||||
|
absolute-paths-allowed-crates
|
||||||
|
absolute-paths-max-segments
|
||||||
|
accept-comment-above-attributes
|
||||||
|
accept-comment-above-statement
|
||||||
|
allow-dbg-in-tests
|
||||||
|
allow-expect-in-tests
|
||||||
|
allow-mixed-uninlined-format-args
|
||||||
|
allow-one-hash-in-raw-strings
|
||||||
|
allow-print-in-tests
|
||||||
|
allow-private-module-inception
|
||||||
|
allow-unwrap-in-tests
|
||||||
|
allowed-dotfiles
|
||||||
|
allowed-duplicate-crates
|
||||||
|
allowed-idents-below-min-chars
|
||||||
|
allowed-scripts
|
||||||
|
arithmetic-side-effects-allowed
|
||||||
|
arithmetic-side-effects-allowed-binary
|
||||||
|
arithmetic-side-effects-allowed-unary
|
||||||
|
array-size-threshold
|
||||||
|
avoid-breaking-exported-api
|
||||||
|
await-holding-invalid-types
|
||||||
|
blacklisted-names
|
||||||
|
cargo-ignore-publish
|
||||||
|
check-private-items
|
||||||
|
cognitive-complexity-threshold
|
||||||
|
cyclomatic-complexity-threshold
|
||||||
|
disallowed-macros
|
||||||
|
disallowed-methods
|
||||||
|
disallowed-names
|
||||||
|
disallowed-types
|
||||||
|
doc-valid-idents
|
||||||
|
enable-raw-pointer-heuristic-for-send
|
||||||
|
enforce-iter-loop-reborrow
|
||||||
|
enforced-import-renames
|
||||||
|
enum-variant-name-threshold
|
||||||
|
enum-variant-size-threshold
|
||||||
|
excessive-nesting-threshold
|
||||||
|
future-size-threshold
|
||||||
|
ignore-interior-mutability
|
||||||
|
large-error-threshold
|
||||||
|
literal-representation-threshold
|
||||||
|
matches-for-let-else
|
||||||
|
max-fn-params-bools
|
||||||
|
max-include-file-size
|
||||||
|
max-struct-bools
|
||||||
|
max-suggested-slice-pattern-length
|
||||||
|
max-trait-bounds
|
||||||
|
min-ident-chars-threshold
|
||||||
|
missing-docs-in-crate-items
|
||||||
|
msrv
|
||||||
|
pass-by-value-size-limit
|
||||||
|
pub-underscore-fields-behavior
|
||||||
|
semicolon-inside-block-ignore-singleline
|
||||||
|
semicolon-outside-block-ignore-multiline
|
||||||
|
single-char-binding-names-threshold
|
||||||
|
stack-size-threshold
|
||||||
|
standard-macro-braces
|
||||||
|
struct-field-name-threshold
|
||||||
|
suppress-restriction-lint-in-const
|
||||||
|
third-party
|
||||||
|
too-large-for-stack
|
||||||
|
too-many-arguments-threshold
|
||||||
|
too-many-lines-threshold
|
||||||
|
trivial-copy-size-limit
|
||||||
|
type-complexity-threshold
|
||||||
|
unnecessary-box-size
|
||||||
|
unreadable-literal-lint-fractions
|
||||||
|
upper-case-acronyms-aggressive
|
||||||
|
vec-box-size-threshold
|
||||||
|
verbose-bit-mask-threshold
|
||||||
|
warn-on-all-wildcard-imports
|
||||||
|
--> $DIR/$DIR/clippy.toml:7:1
|
||||||
|
|
|
||||||
|
LL | allow_mixed_uninlined_format_args = true
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: perhaps you meant: `allow-mixed-uninlined-format-args`
|
||||||
|
|
||||||
|
error: aborting due to 3 previous errors
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ use quote::{quote, quote_spanned};
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::token::Star;
|
use syn::token::Star;
|
||||||
use syn::{
|
use syn::{
|
||||||
parse_macro_input, parse_quote, FnArg, ImplItem, ItemImpl, ItemTrait, Lifetime, Pat, PatIdent, PatType, Signature,
|
parse_macro_input, parse_quote, FnArg, ImplItem, ItemFn, ItemImpl, ItemTrait, Lifetime, Pat, PatIdent, PatType,
|
||||||
TraitItem, Type,
|
Signature, TraitItem, Type,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
|
|
@ -95,3 +95,33 @@ pub fn rename_my_lifetimes(_args: TokenStream, input: TokenStream) -> TokenStrea
|
||||||
|
|
||||||
TokenStream::from(quote!(#item))
|
TokenStream::from(quote!(#item))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn fake_main(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
let mut item = parse_macro_input!(item as ItemFn);
|
||||||
|
let span = item.block.brace_token.span;
|
||||||
|
|
||||||
|
if item.sig.asyncness.is_some() {
|
||||||
|
item.sig.asyncness = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let crate_name = quote! { fake_crate };
|
||||||
|
let block = item.block;
|
||||||
|
item.block = syn::parse_quote_spanned! {
|
||||||
|
span =>
|
||||||
|
{
|
||||||
|
#crate_name::block_on(async {
|
||||||
|
#block
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
mod #crate_name {
|
||||||
|
pub fn block_on<F: ::std::future::Future>(_fut: F) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#item
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,4 +85,18 @@ fn block_in_match_expr(num: i32) -> i32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// issue #12162
|
||||||
|
macro_rules! timed {
|
||||||
|
($name:expr, $body:expr $(,)?) => {{
|
||||||
|
let __scope = ();
|
||||||
|
$body
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn issue_12162() {
|
||||||
|
if timed!("check this!", false) {
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
||||||
|
|
@ -85,4 +85,18 @@ fn block_in_match_expr(num: i32) -> i32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// issue #12162
|
||||||
|
macro_rules! timed {
|
||||||
|
($name:expr, $body:expr $(,)?) => {{
|
||||||
|
let __scope = ();
|
||||||
|
$body
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn issue_12162() {
|
||||||
|
if timed!("check this!", false) {
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,16 @@ fn simple_examples() {
|
||||||
|
|
||||||
// Simple
|
// Simple
|
||||||
if true {
|
if true {
|
||||||
//~^ ERROR: all if blocks contain the same code at the start
|
|
||||||
println!("Hello World!");
|
println!("Hello World!");
|
||||||
println!("I'm branch nr: 1");
|
println!("I'm branch nr: 1");
|
||||||
} else {
|
} else {
|
||||||
println!("Hello World!");
|
println!("Hello World!");
|
||||||
println!("I'm branch nr: 2");
|
println!("I'm branch nr: 2");
|
||||||
}
|
}
|
||||||
|
//~^^^^^^^ ERROR: all if blocks contain the same code at the start
|
||||||
|
|
||||||
// Else if
|
// Else if
|
||||||
if x == 0 {
|
if x == 0 {
|
||||||
//~^ ERROR: all if blocks contain the same code at the start
|
|
||||||
let y = 9;
|
let y = 9;
|
||||||
println!("The value y was set to: `{}`", y);
|
println!("The value y was set to: `{}`", y);
|
||||||
let _z = y;
|
let _z = y;
|
||||||
|
|
@ -38,6 +37,7 @@ fn simple_examples() {
|
||||||
|
|
||||||
println!("Ha, Pascal allows you to start the array where you want")
|
println!("Ha, Pascal allows you to start the array where you want")
|
||||||
}
|
}
|
||||||
|
//~^^^^^^^^^^^^^^^^^^^ ERROR: all if blocks contain the same code at the start
|
||||||
|
|
||||||
// Return a value
|
// Return a value
|
||||||
let _ = if x == 7 {
|
let _ = if x == 7 {
|
||||||
|
|
@ -60,7 +60,6 @@ fn simple_but_suggestion_is_invalid() {
|
||||||
// Can't be automatically moved because used_value_name is getting used again
|
// Can't be automatically moved because used_value_name is getting used again
|
||||||
let used_value_name = 19;
|
let used_value_name = 19;
|
||||||
if x == 10 {
|
if x == 10 {
|
||||||
//~^ ERROR: all if blocks contain the same code at the start
|
|
||||||
let used_value_name = "Different type";
|
let used_value_name = "Different type";
|
||||||
println!("Str: {}", used_value_name);
|
println!("Str: {}", used_value_name);
|
||||||
let _ = 1;
|
let _ = 1;
|
||||||
|
|
@ -69,6 +68,7 @@ fn simple_but_suggestion_is_invalid() {
|
||||||
println!("Str: {}", used_value_name);
|
println!("Str: {}", used_value_name);
|
||||||
let _ = 2;
|
let _ = 2;
|
||||||
}
|
}
|
||||||
|
//~^^^^^^^^^ ERROR: all if blocks contain the same code at the start
|
||||||
let _ = used_value_name;
|
let _ = used_value_name;
|
||||||
|
|
||||||
// This can be automatically moved as `can_be_overridden` is not used again
|
// This can be automatically moved as `can_be_overridden` is not used again
|
||||||
|
|
@ -101,11 +101,11 @@ fn check_if_same_than_else_mask() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if x == 2019 {
|
if x == 2019 {
|
||||||
//~^ ERROR: this `if` has identical blocks
|
|
||||||
println!("This should trigger `IS_SAME_THAN_ELSE` as usual");
|
println!("This should trigger `IS_SAME_THAN_ELSE` as usual");
|
||||||
} else {
|
} else {
|
||||||
println!("This should trigger `IS_SAME_THAN_ELSE` as usual");
|
println!("This should trigger `IS_SAME_THAN_ELSE` as usual");
|
||||||
}
|
}
|
||||||
|
//~^^^^^ ERROR: this `if` has identical blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::vec_init_then_push)]
|
#[allow(clippy::vec_init_then_push)]
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ error: all if blocks contain the same code at the start
|
||||||
--> $DIR/shared_at_top.rs:11:5
|
--> $DIR/shared_at_top.rs:11:5
|
||||||
|
|
|
|
||||||
LL | / if true {
|
LL | / if true {
|
||||||
LL | |
|
|
||||||
LL | | println!("Hello World!");
|
LL | | println!("Hello World!");
|
||||||
| |_________________________________^
|
| |_________________________________^
|
||||||
|
|
|
|
||||||
|
|
@ -21,7 +20,6 @@ error: all if blocks contain the same code at the start
|
||||||
--> $DIR/shared_at_top.rs:21:5
|
--> $DIR/shared_at_top.rs:21:5
|
||||||
|
|
|
|
||||||
LL | / if x == 0 {
|
LL | / if x == 0 {
|
||||||
LL | |
|
|
||||||
LL | | let y = 9;
|
LL | | let y = 9;
|
||||||
LL | | println!("The value y was set to: `{}`", y);
|
LL | | println!("The value y was set to: `{}`", y);
|
||||||
LL | | let _z = y;
|
LL | | let _z = y;
|
||||||
|
|
@ -54,7 +52,6 @@ error: all if blocks contain the same code at the start
|
||||||
--> $DIR/shared_at_top.rs:62:5
|
--> $DIR/shared_at_top.rs:62:5
|
||||||
|
|
|
|
||||||
LL | / if x == 10 {
|
LL | / if x == 10 {
|
||||||
LL | |
|
|
||||||
LL | | let used_value_name = "Different type";
|
LL | | let used_value_name = "Different type";
|
||||||
LL | | println!("Str: {}", used_value_name);
|
LL | | println!("Str: {}", used_value_name);
|
||||||
| |_____________________________________________^
|
| |_____________________________________________^
|
||||||
|
|
@ -105,13 +102,12 @@ error: this `if` has identical blocks
|
||||||
|
|
|
|
||||||
LL | if x == 2019 {
|
LL | if x == 2019 {
|
||||||
| __________________^
|
| __________________^
|
||||||
LL | |
|
|
||||||
LL | | println!("This should trigger `IS_SAME_THAN_ELSE` as usual");
|
LL | | println!("This should trigger `IS_SAME_THAN_ELSE` as usual");
|
||||||
LL | | } else {
|
LL | | } else {
|
||||||
| |_____^
|
| |_____^
|
||||||
|
|
|
|
||||||
note: same as this
|
note: same as this
|
||||||
--> $DIR/shared_at_top.rs:106:12
|
--> $DIR/shared_at_top.rs:105:12
|
||||||
|
|
|
|
||||||
LL | } else {
|
LL | } else {
|
||||||
| ____________^
|
| ____________^
|
||||||
|
|
|
||||||
|
|
@ -107,9 +107,9 @@ fn valid_examples() {
|
||||||
|
|
||||||
// Let's test empty blocks
|
// Let's test empty blocks
|
||||||
if false {
|
if false {
|
||||||
//~^ ERROR: this `if` has identical blocks
|
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
|
//~^^^ ERROR: this `if` has identical blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This makes sure that the `if_same_then_else` masks the `shared_code_in_if_blocks` lint
|
/// This makes sure that the `if_same_then_else` masks the `shared_code_in_if_blocks` lint
|
||||||
|
|
@ -119,7 +119,6 @@ fn trigger_other_lint() {
|
||||||
|
|
||||||
// Same block
|
// Same block
|
||||||
if x == 0 {
|
if x == 0 {
|
||||||
//~^ ERROR: this `if` has identical blocks
|
|
||||||
let u = 19;
|
let u = 19;
|
||||||
println!("How are u today?");
|
println!("How are u today?");
|
||||||
let _ = "This is a string";
|
let _ = "This is a string";
|
||||||
|
|
@ -128,6 +127,7 @@ fn trigger_other_lint() {
|
||||||
println!("How are u today?");
|
println!("How are u today?");
|
||||||
let _ = "This is a string";
|
let _ = "This is a string";
|
||||||
}
|
}
|
||||||
|
//~^^^^^^^^^ ERROR: this `if` has identical blocks
|
||||||
|
|
||||||
// Only same expression
|
// Only same expression
|
||||||
let _ = if x == 6 { 7 } else { 7 };
|
let _ = if x == 6 { 7 } else { 7 };
|
||||||
|
|
@ -138,28 +138,24 @@ fn trigger_other_lint() {
|
||||||
println!("Well I'm the most important block");
|
println!("Well I'm the most important block");
|
||||||
"I'm a pretty string"
|
"I'm a pretty string"
|
||||||
} else if x == 68 {
|
} else if x == 68 {
|
||||||
//~^ ERROR: this `if` has identical blocks
|
|
||||||
println!("I'm a doppelgänger");
|
println!("I'm a doppelgänger");
|
||||||
// Don't listen to my clone below
|
|
||||||
|
|
||||||
if y == 90 { "=^.^=" } else { ":D" }
|
if y == 90 { "=^.^=" } else { ":D" }
|
||||||
} else {
|
} else {
|
||||||
// Don't listen to my clone above
|
|
||||||
println!("I'm a doppelgänger");
|
println!("I'm a doppelgänger");
|
||||||
|
|
||||||
if y == 90 { "=^.^=" } else { ":D" }
|
if y == 90 { "=^.^=" } else { ":D" }
|
||||||
};
|
};
|
||||||
|
//~^^^^^^^^^ ERROR: this `if` has identical blocks
|
||||||
|
|
||||||
if x == 0 {
|
if x == 0 {
|
||||||
println!("I'm single");
|
println!("I'm single");
|
||||||
} else if x == 68 {
|
} else if x == 68 {
|
||||||
//~^ ERROR: this `if` has identical blocks
|
|
||||||
println!("I'm a doppelgänger");
|
println!("I'm a doppelgänger");
|
||||||
// Don't listen to my clone below
|
|
||||||
} else {
|
} else {
|
||||||
// Don't listen to my clone above
|
|
||||||
println!("I'm a doppelgänger");
|
println!("I'm a doppelgänger");
|
||||||
}
|
}
|
||||||
|
//~^^^^^ ERROR: this `if` has identical blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,11 @@ error: this `if` has identical blocks
|
||||||
|
|
|
|
||||||
LL | if false {
|
LL | if false {
|
||||||
| ______________^
|
| ______________^
|
||||||
LL | |
|
|
||||||
LL | | } else {
|
LL | | } else {
|
||||||
| |_____^
|
| |_____^
|
||||||
|
|
|
|
||||||
note: same as this
|
note: same as this
|
||||||
--> $DIR/valid_if_blocks.rs:111:12
|
--> $DIR/valid_if_blocks.rs:110:12
|
||||||
|
|
|
|
||||||
LL | } else {
|
LL | } else {
|
||||||
| ____________^
|
| ____________^
|
||||||
|
|
@ -25,7 +24,6 @@ error: this `if` has identical blocks
|
||||||
|
|
|
|
||||||
LL | if x == 0 {
|
LL | if x == 0 {
|
||||||
| _______________^
|
| _______________^
|
||||||
LL | |
|
|
||||||
LL | | let u = 19;
|
LL | | let u = 19;
|
||||||
LL | | println!("How are u today?");
|
LL | | println!("How are u today?");
|
||||||
LL | | let _ = "This is a string";
|
LL | | let _ = "This is a string";
|
||||||
|
|
@ -33,7 +31,7 @@ LL | | } else {
|
||||||
| |_____^
|
| |_____^
|
||||||
|
|
|
|
||||||
note: same as this
|
note: same as this
|
||||||
--> $DIR/valid_if_blocks.rs:126:12
|
--> $DIR/valid_if_blocks.rs:125:12
|
||||||
|
|
|
|
||||||
LL | } else {
|
LL | } else {
|
||||||
| ____________^
|
| ____________^
|
||||||
|
|
@ -60,20 +58,17 @@ error: this `if` has identical blocks
|
||||||
|
|
|
|
||||||
LL | } else if x == 68 {
|
LL | } else if x == 68 {
|
||||||
| _______________________^
|
| _______________________^
|
||||||
LL | |
|
|
||||||
LL | | println!("I'm a doppelgänger");
|
LL | | println!("I'm a doppelgänger");
|
||||||
LL | | // Don't listen to my clone below
|
|
||||||
LL | |
|
LL | |
|
||||||
LL | | if y == 90 { "=^.^=" } else { ":D" }
|
LL | | if y == 90 { "=^.^=" } else { ":D" }
|
||||||
LL | | } else {
|
LL | | } else {
|
||||||
| |_____^
|
| |_____^
|
||||||
|
|
|
|
||||||
note: same as this
|
note: same as this
|
||||||
--> $DIR/valid_if_blocks.rs:146:12
|
--> $DIR/valid_if_blocks.rs:144:12
|
||||||
|
|
|
|
||||||
LL | } else {
|
LL | } else {
|
||||||
| ____________^
|
| ____________^
|
||||||
LL | | // Don't listen to my clone above
|
|
||||||
LL | | println!("I'm a doppelgänger");
|
LL | | println!("I'm a doppelgänger");
|
||||||
LL | |
|
LL | |
|
||||||
LL | | if y == 90 { "=^.^=" } else { ":D" }
|
LL | | if y == 90 { "=^.^=" } else { ":D" }
|
||||||
|
|
@ -81,22 +76,19 @@ LL | | };
|
||||||
| |_____^
|
| |_____^
|
||||||
|
|
||||||
error: this `if` has identical blocks
|
error: this `if` has identical blocks
|
||||||
--> $DIR/valid_if_blocks.rs:155:23
|
--> $DIR/valid_if_blocks.rs:153:23
|
||||||
|
|
|
|
||||||
LL | } else if x == 68 {
|
LL | } else if x == 68 {
|
||||||
| _______________________^
|
| _______________________^
|
||||||
LL | |
|
|
||||||
LL | | println!("I'm a doppelgänger");
|
LL | | println!("I'm a doppelgänger");
|
||||||
LL | | // Don't listen to my clone below
|
|
||||||
LL | | } else {
|
LL | | } else {
|
||||||
| |_____^
|
| |_____^
|
||||||
|
|
|
|
||||||
note: same as this
|
note: same as this
|
||||||
--> $DIR/valid_if_blocks.rs:159:12
|
--> $DIR/valid_if_blocks.rs:155:12
|
||||||
|
|
|
|
||||||
LL | } else {
|
LL | } else {
|
||||||
| ____________^
|
| ____________^
|
||||||
LL | | // Don't listen to my clone above
|
|
||||||
LL | | println!("I'm a doppelgänger");
|
LL | | println!("I'm a doppelgänger");
|
||||||
LL | | }
|
LL | | }
|
||||||
| |_____^
|
| |_____^
|
||||||
|
|
|
||||||
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