Rollup merge of #120345 - flip1995:clippy-subtree-update, r=Manishearth

Clippy subtree update

r? `@Manishearth`

Closes https://github.com/rust-lang/rust-clippy/issues/12148
This commit is contained in:
Matthias Krüger 2024-01-26 14:43:32 +01:00 committed by GitHub
commit 57f63a3a85
158 changed files with 2853 additions and 1013 deletions

View File

@ -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_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_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_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
@ -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-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-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
[`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

View File

@ -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 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:**
@ -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)
## `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`
Whether to recommend using implicit into iter for reborrowed values.
#### Example
```no_run
let mut vec = vec![1, 2, 3];
@ -793,7 +805,7 @@ for _ in &mut *rmvec {}
## `check-private-items`
Whether to also run the listed lints on private items.
**Default Value:** `false`
@ -806,9 +818,10 @@ for _ in &mut *rmvec {}
## `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:** `"PublicallyExported"`
**Default Value:** `"PubliclyExported"`
---
**Affected lints:**

View File

@ -2,7 +2,9 @@ use crate::msrvs::Msrv;
use crate::types::{DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename};
use crate::ClippyConfiguration;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_session::Session;
use rustc_span::edit_distance::edit_distance;
use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
use serde::de::{IgnoredAny, IntoDeserializer, MapAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize};
@ -26,7 +28,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
"NaN", "NaNs",
"OAuth", "GraphQL",
"OCaml",
"OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS",
"OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry",
"WebGL", "WebGL2", "WebGPU",
"TensorFlow",
"TrueType",
@ -59,18 +61,25 @@ impl TryConf {
#[derive(Debug)]
struct ConfError {
message: String,
suggestion: Option<Suggestion>,
span: Span,
}
impl ConfError {
fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self {
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 {
message: message.into(),
suggestion,
span: Span::new(
file.start_pos + BytePos::from_usize(span.start),
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()) {
Err(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) => {
$(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 value_span = raw_value.span();
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 {
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 => {
$name = Some(value);
// $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!(
"duplicate field `", stringify!($new_conf),
"` (provided as `", stringify!($name), "`)"
), name.span())),
), None, name.span())),
None => $new_conf = $name.clone(),
})?
},
@ -523,7 +534,11 @@ define_Conf! {
///
/// Additional dotfiles (files or directories starting with a dot) to allow
(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.
///
@ -543,15 +558,15 @@ define_Conf! {
/// for _ in &mut *rmvec {}
/// ```
(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.
(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
/// 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.
@ -669,10 +684,16 @@ impl Conf {
// all conf errors are non-fatal, we just use the default conf in case of error
for error in errors {
sess.dcx().span_err(
let mut diag = sess.dcx().struct_span_err(
error.span,
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 {
@ -689,19 +710,31 @@ impl Conf {
const SEPARATOR_WIDTH: usize = 4;
#[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 Display for FieldError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.pad(&self.0)
f.pad(&self.error)
}
}
impl serde::de::Error for FieldError {
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 {
@ -723,7 +756,20 @@ impl serde::de::Error for FieldError {
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 }
}
}

View File

@ -11,6 +11,7 @@ extern crate rustc_ast;
extern crate rustc_data_structures;
#[allow(unused_extern_crates)]
extern crate rustc_driver;
extern crate rustc_errors;
extern crate rustc_session;
extern crate rustc_span;

View File

@ -129,6 +129,6 @@ unimplemented_serialize! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum PubUnderscoreFieldsBehaviour {
PublicallyExported,
PubliclyExported,
AllPubFields,
}

View File

@ -4,11 +4,11 @@ version = "0.0.1"
edition = "2021"
[dependencies]
aho-corasick = "0.7"
aho-corasick = "1.0"
clap = "4.1.4"
indoc = "1.0"
itertools = "0.11"
opener = "0.5"
opener = "0.6"
shell-escape = "0.1"
walkdir = "2.3"

View File

@ -504,9 +504,8 @@ fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<S
}
let searcher = AhoCorasickBuilder::new()
.dfa(true)
.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();
let mut result = String::with_capacity(contents.len() + 1024);

View File

@ -10,7 +10,7 @@ edition = "2021"
[dependencies]
arrayvec = { version = "0.7", default-features = false }
cargo_metadata = "0.15.3"
cargo_metadata = "0.18"
clippy_config = { path = "../clippy_config" }
clippy_utils = { path = "../clippy_utils" }
declare_clippy_lint = { path = "../declare_clippy_lint" }

View File

@ -14,10 +14,10 @@ declare_clippy_lint! {
/// This lint warns when you use `Arc` with a type that does not implement `Send` or `Sync`.
///
/// ### Why is this bad?
/// `Arc<T>` is an Atomic `RC<T>` and guarantees that updates to the reference counter are
/// Atomic. This is useful in multiprocessing scenarios. To send an `Arc<T>` across processes
/// 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),
/// either `T` should be made `Send + Sync` or an `Rc` should be used instead of an `Arc`
/// `Arc<T>` is a thread-safe `Rc<T>` and guarantees that updates to the reference counter
/// use atomic operations. To send an `Arc<T>` across thread boundaries and
/// share ownership between multiple threads, `T` must be [both `Send` and `Sync`](https://doc.rust-lang.org/std/sync/struct.Arc.html#thread-safety),
/// so either `T` should be made `Send + Sync` or an `Rc` should be used instead of an `Arc`
///
/// ### Example
/// ```no_run

View File

@ -67,6 +67,11 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInConditions {
);
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.stmts.is_empty() {
if let Some(ex) = &block.expr {

View File

@ -6,6 +6,7 @@ mod wildcard_dependencies;
use cargo_metadata::MetadataCommand;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_lint_allowed;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::hir_id::CRATE_HIR_ID;
use rustc_lint::{LateContext, LateLintPass, Lint};
use rustc_session::impl_lint_pass;
@ -128,6 +129,8 @@ declare_clippy_lint! {
/// ### Known problems
/// Because this can be caused purely by the dependencies
/// 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
/// ```toml
@ -163,6 +166,7 @@ declare_clippy_lint! {
}
pub struct Cargo {
pub allowed_duplicate_crates: FxHashSet<String>,
pub ignore_publish: bool,
}
@ -208,7 +212,7 @@ impl LateLintPass<'_> for Cargo {
{
match MetadataCommand::new().exec() {
Ok(metadata) => {
multiple_crate_versions::check(cx, &metadata);
multiple_crate_versions::check(cx, &metadata, &self.allowed_duplicate_crates);
},
Err(e) => {
for lint in WITH_DEPS_LINTS {

View File

@ -3,27 +3,40 @@
use cargo_metadata::{DependencyKind, Metadata, Node, Package, PackageId};
use clippy_utils::diagnostics::span_lint;
use itertools::Itertools;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_lint::LateContext;
use rustc_span::DUMMY_SP;
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 mut packages = metadata.packages.clone();
packages.sort_by(|a, b| a.name.cmp(&b.name));
if let Some(resolve) = &metadata.resolve
&& 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)
} else {
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();
if group.len() <= 1 {

View File

@ -439,6 +439,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::STR_SPLIT_AT_NEWLINE_INFO,
crate::methods::SUSPICIOUS_COMMAND_ARG_SPACE_INFO,
crate::methods::SUSPICIOUS_MAP_INFO,
crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO,
crate::methods::SUSPICIOUS_SPLITN_INFO,
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
crate::methods::TYPE_ID_ON_BOX_INFO,

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::last_path_segment;
use clippy_utils::source::snippet_with_context;
use clippy_utils::{last_path_segment, std_or_core};
use rustc_errors::Applicability;
use rustc_hir::{def, Expr, ExprKind, GenericArg, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass};
@ -42,12 +42,14 @@ impl<'tcx> LateLintPass<'tcx> for DefaultIterEmpty {
&& ty.span.ctxt() == ctxt
{
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(
cx,
DEFAULT_INSTEAD_OF_ITER_EMPTY,
expr.span,
"`std::iter::empty()` is the more idiomatic way",
&format!("`{path}()` is the more idiomatic way"),
"try",
sugg,
applicability,
@ -61,6 +63,7 @@ fn make_sugg(
ty_path: &rustc_hir::QPath<'_>,
ctxt: SyntaxContext,
applicability: &mut Applicability,
path: &str,
) -> String {
if let Some(last) = last_path_segment(ty_path).args
&& let Some(iter_ty) = last.args.iter().find_map(|arg| match arg {
@ -69,10 +72,10 @@ fn make_sugg(
})
{
format!(
"std::iter::empty::<{}>()",
"{path}::<{}>()",
snippet_with_context(cx, iter_ty.span, ctxt, "..", applicability).0
)
} else {
"std::iter::empty()".to_owned()
format!("{path}()")
}
}

View File

@ -1,10 +1,10 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::numeric_literal;
use clippy_utils::source::snippet_opt;
use clippy_utils::{get_parent_node, numeric_literal};
use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability;
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_middle::lint::in_external_macro;
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 {
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) {
matches!(item.kind, ItemKind::Const(..))
} else {
false
};
let hir = cx.tcx.hir();
let is_parent_const = matches!(
hir.body_const_context(hir.body_owner_def_id(body.id())),
Some(ConstContext::Const { inline: false } | ConstContext::Static(_))
);
let mut visitor = NumericFallbackVisitor::new(cx, is_parent_const);
visitor.visit_body(body);
}

View File

@ -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::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_hir::def_id::DefId;
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(def_id) = trait_ref.trait_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)
&& !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

View File

@ -6,7 +6,7 @@ use clippy_utils::diagnostics::span_lint;
use rustc_ast::{CoroutineKind, Fn, FnRetTy, Item, ItemKind};
use rustc_data_structures::sync::Lrc;
use rustc_errors::emitter::HumanEmitter;
use rustc_errors::DiagCtxt;
use rustc_errors::{DiagCtxt, DiagnosticBuilder};
use rustc_lint::LateContext;
use rustc_parse::maybe_new_parser_from_source_str;
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) {
Ok(p) => p,
Err(errs) => {
errs.into_iter().for_each(|err| err.cancel());
errs.into_iter().for_each(DiagnosticBuilder::cancel);
return (false, test_attr_spans);
},
};

View File

@ -6,8 +6,8 @@ use clippy_utils::source::snippet_opt;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_path, Visitor};
use rustc_hir::{
GenericArg, GenericArgs, HirId, Impl, ImplItemKind, ImplItemRef, Item, ItemKind, PatKind, Path, PathSegment, Ty,
TyKind,
FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItemKind, ImplItemRef, Item, ItemKind, PatKind, Path,
PathSegment, Ty, TyKind,
};
use rustc_lint::{LateContext, LateLintPass};
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
// ~~~~ ~~~~
(self_ident.span, format!("val: {from}")),
];
if let FnRetTy::Return(_) = sig.decl.output {
// 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 {
cx,

View File

@ -53,7 +53,9 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl {
// List of spans to lint. (lint_span, first_span)
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
.tcx
.with_stable_hashing_context(|hcx| impls.inherent_impls.to_sorted(&hcx, true));

View File

@ -574,6 +574,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
warn_on_all_wildcard_imports,
check_private_items,
pub_underscore_fields_behavior,
ref allowed_duplicate_crates,
blacklisted_names: _,
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_borrowed_ref::NeedlessBorrowedRef));
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(move |_| Box::new(transmute::Transmute::new(msrv())));
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 |_| {
Box::new(cargo::Cargo {
ignore_publish: cargo_ignore_publish,
allowed_duplicate_crates: allowed_duplicate_crates.clone(),
})
});
store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef));

View File

@ -31,7 +31,7 @@ pub(super) fn check<'tcx>(
vec.span,
"it looks like the same item is being pushed into this Vec",
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})"),
);
}

View File

@ -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::sugg::Sugg;
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_hir::LangItem::OptionNone;
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()
&& 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;
span_lint_and_sugg(
cx,
@ -136,7 +137,7 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'
"replacing with `mem::MaybeUninit::uninit().assume_init()`",
"consider using",
format!(
"std::ptr::read({})",
"{top_crate}::ptr::read({})",
snippet_with_applicability(cx, dest.span, "", &mut 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()
{
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;
span_lint_and_sugg(
cx,
@ -157,7 +159,7 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'
"replacing with `mem::uninitialized()`",
"consider using",
format!(
"std::ptr::read({})",
"{top_crate}::ptr::read({})",
snippet_with_applicability(cx, dest.span, "", &mut applicability)
),
applicability,
@ -184,14 +186,17 @@ fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<
return;
}
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(
cx,
MEM_REPLACE_WITH_DEFAULT,
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| {
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(
expr_span,

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
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_hir::LangItem::{OptionNone, OptionSome};
@ -58,10 +58,10 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, re
return;
}
let Some(top_crate) = std_or_core(cx) else { return };
if let Some(i) = item {
let sugg = format!(
"{}::iter::once({}{})",
if is_no_std_crate(cx) { "core" } else { "std" },
"{top_crate}::iter::once({}{})",
iter_type.ref_prefix(),
snippet(cx, i.span, "...")
);
@ -81,11 +81,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, re
expr.span,
&format!("`{method_name}` call on an empty collection"),
"try",
if is_no_std_crate(cx) {
"core::iter::empty()".to_string()
} else {
"std::iter::empty()".to_string()
},
format!("{top_crate}::iter::empty()"),
Applicability::MaybeIncorrect,
);
}

View File

@ -42,7 +42,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, join_a
)
.span_suggestion(
expr_span,
"if this is intentional, try using `Path::new` instead",
"if this is intentional, consider using `Path::new`",
format!("PathBuf::from({arg_str})"),
Applicability::Unspecified,
);

View File

@ -50,7 +50,7 @@ pub fn check(
super::MANUAL_SATURATING_ARITHMETIC,
expr.span,
"manual saturating arithmetic",
&format!("try using `saturating_{arith}`"),
&format!("consider using `saturating_{arith}`"),
format!(
"{}.saturating_{arith}({})",
snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability),

View File

@ -113,9 +113,15 @@ fn handle_path(
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)
{
// FIXME: It would be better to infer the type to check if it's copyable or not
// to suggest to use `.copied()` instead of `.cloned()` where applicable.
lint_path(cx, e.span, recv.span);
// The `copied` and `cloned` methods are only available on `&T` and `&mut T` in `Option`
// and `Result`.
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 replacement = if is_copy { "copied" } else { "cloned" };
span_lint_and_sugg(
cx,
MAP_CLONE,
replace,
"you are explicitly cloning with `.map()`",
"consider calling the dedicated `cloned` method",
&format!("consider calling the dedicated `{replacement}` method"),
format!(
"{}.cloned()",
"{}.{replacement}()",
snippet_with_applicability(cx, root, "..", &mut applicability),
),
applicability,

View File

@ -2827,6 +2827,44 @@ declare_clippy_lint! {
"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! {
/// ### What it does
///* 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,
MUT_MUTEX_LOCK,
NONSENSICAL_OPEN_OPTIONS,
SUSPICIOUS_OPEN_OPTIONS,
PATH_BUF_PUSH_OVERWRITE,
RANGE_ZIP_WITH_LEN,
REPEAT_ONCE,

View File

@ -1,46 +1,74 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_data_structures::fx::FxHashMap;
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_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_span::source_map::Spanned;
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<'_>) {
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)
&& 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();
get_open_options(cx, recv, &mut options);
check_open_options(cx, &options, e.span);
if get_open_options(cx, recv, &mut options) {
check_open_options(cx, &options, e.span);
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[derive(Eq, PartialEq, Clone, Debug)]
enum Argument {
True,
False,
Set(bool),
Unknown,
}
#[derive(Debug)]
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
enum OpenOption {
Write,
Append,
Create,
CreateNew,
Read,
Truncate,
Create,
Append,
Write,
}
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)>) {
if let ExprKind::MethodCall(path, receiver, arguments, _) = argument.kind {
/// Collects information about a method call chain on `OpenOptions`.
/// 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();
// 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 {
ExprKind::Lit(span) => {
if let Spanned {
@ -48,11 +76,12 @@ fn get_open_options(cx: &LateContext<'_>, argument: &Expr<'_>, options: &mut Vec
..
} = span
{
if *lit { Argument::True } else { Argument::False }
Argument::Set(*lit)
} else {
// The function is called with a literal which is not a boolean literal.
// This is theoretically possible, but not very likely.
return;
// We'll ignore it for now
return get_open_options(cx, receiver, options);
}
},
_ => Argument::Unknown,
@ -60,106 +89,77 @@ fn get_open_options(cx: &LateContext<'_>, argument: &Expr<'_>, options: &mut Vec
match path.ident.as_str() {
"create" => {
options.push((OpenOption::Create, argument_option));
options.push((OpenOption::Create, argument_option, span));
},
"create_new" => {
options.push((OpenOption::CreateNew, argument_option, span));
},
"append" => {
options.push((OpenOption::Append, argument_option));
options.push((OpenOption::Append, argument_option, span));
},
"truncate" => {
options.push((OpenOption::Truncate, argument_option));
options.push((OpenOption::Truncate, argument_option, span));
},
"read" => {
options.push((OpenOption::Read, argument_option));
options.push((OpenOption::Read, argument_option, span));
},
"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) {
let (mut create, mut append, mut truncate, mut read, mut write) = (false, false, false, false, false);
let (mut create_arg, mut append_arg, mut truncate_arg, mut read_arg, mut write_arg) =
(false, false, false, false, false);
// This code is almost duplicated (oh, the irony), but I haven't found a way to
// unify it.
for option in options {
match *option {
(OpenOption::Create, arg) => {
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);
},
fn check_open_options(cx: &LateContext<'_>, settings: &[(OpenOption, Argument, Span)], span: Span) {
// The args passed to these methods, if they have been called
let mut options = FxHashMap::default();
for (option, arg, sp) in settings {
if let Some((_, prev_span)) = options.insert(option.clone(), (arg.clone(), *sp)) {
span_lint(
cx,
NONSENSICAL_OPEN_OPTIONS,
prev_span,
&format!("the method `{}` is called more than once", &option),
);
}
}
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(
cx,
NONSENSICAL_OPEN_OPTIONS,
@ -167,7 +167,10 @@ fn check_open_options(cx: &LateContext<'_>, options: &[(OpenOption, Argument)],
"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(
cx,
NONSENSICAL_OPEN_OPTIONS,
@ -175,4 +178,29 @@ fn check_open_options(cx: &LateContext<'_>, options: &[(OpenOption, Argument)],
"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");
},
);
}
}

View File

@ -97,7 +97,7 @@ pub(super) fn check(
};
let method_hint = if is_mut { "as_deref_mut" } else { "as_deref" };
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");
span_lint_and_sugg(

View File

@ -33,7 +33,7 @@ pub(super) fn check<'tcx>(
OPTION_MAP_OR_ERR_OK,
expr.span,
msg,
"try using `ok_or` instead",
"consider using `ok_or`",
format!("{self_snippet}.ok_or({err_snippet})"),
Applicability::MachineApplicable,
);

View File

@ -72,7 +72,7 @@ pub(super) fn check<'tcx>(
OPTION_MAP_OR_NONE,
expr.span,
msg,
"try using `map` instead",
"consider using `map`",
format!("{self_snippet}.map({arg_snippet} {func_snippet})"),
Applicability::MachineApplicable,
);
@ -85,7 +85,7 @@ pub(super) fn check<'tcx>(
OPTION_MAP_OR_NONE,
expr.span,
msg,
"try using `and_then` instead",
"consider using `and_then`",
format!("{self_snippet}.and_then({func_snippet})"),
Applicability::MachineApplicable,
);
@ -97,7 +97,7 @@ pub(super) fn check<'tcx>(
RESULT_MAP_OR_INTO_OPTION,
expr.span,
msg,
"try using `ok` instead",
"consider using `ok`",
format!("{self_snippet}.ok()"),
Applicability::MachineApplicable,
);

View File

@ -34,7 +34,7 @@ pub(super) fn check<'tcx>(
RESULT_MAP_OR_INTO_OPTION,
expr.span,
msg,
"try using `ok` instead",
"consider using `ok`",
format!("{self_snippet}.ok()"),
Applicability::MachineApplicable,
);

View File

@ -63,7 +63,7 @@ pub(super) fn check<'tcx>(
SEARCH_IS_SOME,
method_span.with_hi(expr.span.hi()),
&msg,
"use `any()` instead",
"consider using",
format!(
"any({})",
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
@ -77,7 +77,7 @@ pub(super) fn check<'tcx>(
SEARCH_IS_SOME,
expr.span,
&msg,
"use `!_.any()` instead",
"consider using",
format!(
"!{iter}.any({})",
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
@ -118,7 +118,7 @@ pub(super) fn check<'tcx>(
SEARCH_IS_SOME,
method_span.with_hi(expr.span.hi()),
&msg,
"use `contains()` instead",
"consider using",
format!("contains({find_arg})"),
applicability,
);
@ -132,7 +132,7 @@ pub(super) fn check<'tcx>(
SEARCH_IS_SOME,
expr.span,
&msg,
"use `!_.contains()` instead",
"consider using",
format!("!{string}.contains({find_arg})"),
applicability,
);

View File

@ -57,7 +57,7 @@ pub(super) fn check(
SINGLE_CHAR_PATTERN,
arg.span,
"single-character string constant used as pattern",
"try using a `char` instead",
"consider using a `char`",
hint,
applicability,
);

View File

@ -32,7 +32,7 @@ pub(super) fn check<'tcx>(
UNNECESSARY_JOIN,
span.with_hi(expr.span.hi()),
r#"called `.collect::<Vec<String>>().join("")` on an iterator"#,
"try using",
"consider using",
"collect::<String>()".to_owned(),
applicability,
);

View File

@ -203,7 +203,7 @@ pub(super) fn check<'tcx>(
cx,
UNNECESSARY_SORT_BY,
expr.span,
"use Vec::sort_by_key here instead",
"consider using `sort_by_key`",
"try",
format!(
"{}.sort{}_by_key(|{}| {})",
@ -226,7 +226,7 @@ pub(super) fn check<'tcx>(
cx,
UNNECESSARY_SORT_BY,
expr.span,
"use Vec::sort here instead",
"consider using `sort`",
"try",
format!(
"{}.sort{}()",

View File

@ -1,7 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
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_hir as hir;
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 {
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.
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);
match closure_expr.kind {
hir::ExprKind::MethodCall(method, obj, [], _) => {
@ -122,14 +127,17 @@ fn is_calling_clone(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
// no autoderefs
&& !cx.typeck_results().expr_adjustments(obj).iter()
.any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
&& path_to_local_id(obj, local_id)
{
true
} else {
false
}
},
hir::ExprKind::Call(call, [_]) => {
if let hir::ExprKind::Path(qpath) = call.kind {
hir::ExprKind::Call(call, [recv]) => {
if let hir::ExprKind::Path(qpath) = call.kind
&& path_to_local_id(recv, local_id)
{
check_qpath(cx, qpath, call.hir_id)
} else {
false

View File

@ -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 rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_qpath, FnKind, Visitor};
use rustc_hir::intravisit::FnKind;
use rustc_hir::{
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_infer::infer::{InferCtxt, TyCtxtInferExt};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::associated_body;
use rustc_middle::hir::nested_filter::OnlyBodies;
use rustc_middle::mir::FakeReadCause;
use rustc_middle::ty::{self, Ty, TyCtxt, UpvarId, UpvarPath};
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>) {
cx.tcx.hir().visit_all_item_likes_in_crate(&mut FnNeedsMutVisitor {
cx,
used_fn_def_ids: &mut self.used_fn_def_ids,
});
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
// #11182; do not lint if mutability is required elsewhere
if let ExprKind::Path(..) = expr.kind
&& 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
.fn_def_ids_to_maybe_unused_mut
.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);
}
}
}

View File

@ -1,16 +1,18 @@
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
use clippy_utils::source::snippet_opt;
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_hir::def::{DefKind, Res};
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_lint::{LateContext, LateLintPass, LintContext};
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;
declare_clippy_lint! {
@ -74,94 +76,125 @@ declare_clippy_lint! {
"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 {
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;
}
check_unnecessary_operation(cx, stmt);
}
}
fn check_no_effect(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);
fn check_block(&mut self, _: &LateContext<'tcx>, _: &'tcx rustc_hir::Block<'tcx>) {
self.local_bindings.push(Vec::default());
}
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(_, _, 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;
fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx rustc_hir::Block<'tcx>) {
for hir_id in self.local_bindings.pop().unwrap() {
if let Some(span) = self.underscore_bindings.remove(&hir_id) {
span_lint_hir(
cx,
NO_EFFECT_UNDERSCORE_BINDING,
hir_id,
span,
"binding to `_` prefixed variable with no side-effect",
);
}
}
}
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 {

View File

@ -1,13 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
use clippy_utils::std_or_core;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use super::PTR_EQ;
static LINT_MSG: &str = "use `std::ptr::eq` when comparing raw pointers";
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
@ -26,13 +25,14 @@ pub(super) fn check<'tcx>(
&& let Some(left_snip) = snippet_opt(cx, left_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(
cx,
PTR_EQ,
expr.span,
LINT_MSG,
&format!("use `{top_crate}::ptr::eq` when comparing raw pointers"),
"try",
format!("std::ptr::eq({left_snip}, {right_snip})"),
format!("{top_crate}::ptr::eq({left_snip}, {right_snip})"),
Applicability::MachineApplicable,
);
}

View File

@ -54,7 +54,7 @@ impl<'tcx> LateLintPass<'tcx> for PubUnderscoreFields {
};
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 => {
// If there is a visibility span then the field is marked pub in some way.
!field.vis_span.is_empty()

View File

@ -1,11 +1,13 @@
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::source::snippet;
use clippy_utils::visitors::for_each_expr;
use core::ops::ControlFlow;
use hir::{Expr, ExprKind, Local, PatKind, PathSegment, QPath, StmtKind};
use hir::{Expr, ExprKind, HirId, Local, PatKind, PathSegment, QPath, StmtKind};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
@ -49,57 +51,40 @@ declare_lint_pass!(ReadZeroByteVec => [READ_ZERO_BYTE_VEC]);
impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &hir::Block<'tcx>) {
for (idx, stmt) in block.stmts.iter().enumerate() {
if !stmt.span.from_expansion()
// matches `let v = Vec::new();`
&& let StmtKind::Local(local) = stmt.kind
&& let Local { pat, init: Some(init), .. } = local
&& let PatKind::Binding(_, _, ident, _) = pat.kind
for stmt in block.stmts {
if stmt.span.from_expansion() {
return;
}
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 visitor = |expr: &Expr<'_>| {
if let ExprKind::MethodCall(path, _, [arg], _) = expr.kind
&& let PathSegment {
ident: read_or_read_exact,
..
} = *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 mut visitor = ReadVecVisitor {
local_id: id,
read_zero_expr: None,
has_resize: false,
};
let (read_found, next_stmt_span) = if let Some(next_stmt) = block.stmts.get(idx + 1) {
// 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 {
let Some(enclosing_block) = get_enclosing_block(cx, id) else {
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;
match vec_init_kind {
VecInitKind::WithConstCapacity(len) => {
span_lint_and_sugg(
cx,
READ_ZERO_BYTE_VEC,
next_stmt_span,
expr.span,
"reading zero byte data to `Vec`",
"try",
format!(
"{}.resize({len}, 0); {}",
ident.as_str(),
snippet(cx, next_stmt_span, "..")
),
format!("{}.resize({len}, 0); {}", ident.as_str(), snippet(cx, expr.span, "..")),
applicability,
);
},
@ -108,25 +93,20 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
span_lint_and_sugg(
cx,
READ_ZERO_BYTE_VEC,
next_stmt_span,
expr.span,
"reading zero byte data to `Vec`",
"try",
format!(
"{}.resize({}, 0); {}",
ident.as_str(),
snippet(cx, e.span, ".."),
snippet(cx, next_stmt_span, "..")
snippet(cx, expr.span, "..")
),
applicability,
);
},
_ => {
span_lint(
cx,
READ_ZERO_BYTE_VEC,
next_stmt_span,
"reading zero byte data to `Vec`",
);
span_lint(cx, READ_ZERO_BYTE_VEC, expr.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);
}
}
}

View File

@ -5,6 +5,7 @@ use rustc_errors::Applicability;
use rustc_hir::{Block, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::{ExpnKind, MacroKind, Span};
declare_clippy_lint! {
/// ### 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>) {
if !block.span.from_expansion()
&& let Some(expr) = block.expr
&& !from_attr_macro(expr.span)
&& let t_expr = cx.typeck_results().expr_ty(expr)
&& t_expr.is_unit()
&& 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, _))
}

View File

@ -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 rustc_data_structures::fx::FxHashMap;
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);
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;
if let [caller_span] = *usage.1 {
span_lint_and_help(
span_lint_hir_and_then(
cx,
SINGLE_CALL_FN,
cx.tcx.local_def_id_to_hir_id(def_id),
single_call_fn_span,
"this function is only used once",
Some(caller_span),
"used here",
|diag| {
diag.span_help(caller_span, "used here");
},
);
}
}

View File

@ -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
fn into_comparable_trait_ref(trait_ref: &TraitRef<'_>) -> ComparableTraitRef {
ComparableTraitRef(
@ -401,10 +409,8 @@ fn into_comparable_trait_ref(trait_ref: &TraitRef<'_>) -> ComparableTraitRef {
.filter_map(|segment| {
// get trait bound type arguments
Some(segment.args?.args.iter().filter_map(|arg| {
if let GenericArg::Type(ty) = arg
&& let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
{
return Some(path.res);
if let GenericArg::Type(ty) = arg {
return get_ty_res(**ty);
}
None
}))

View File

@ -1,6 +1,6 @@
use super::TRANSMUTE_INT_TO_CHAR;
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_errors::Applicability;
use rustc_hir::Expr;
@ -25,6 +25,7 @@ pub(super) fn check<'tcx>(
e.span,
&format!("transmute from a `{from_ty}` to a `char`"),
|diag| {
let Some(top_crate) = std_or_core(cx) else { return };
let arg = sugg::Sugg::hir(cx, arg, "..");
let arg = if let ty::Int(_) = from_ty.kind() {
arg.as_ty(ast::UintTy::U32.name_str())
@ -34,7 +35,7 @@ pub(super) fn check<'tcx>(
diag.span_suggestion(
e.span,
"consider using",
format!("std::char::from_u32({arg}).unwrap()"),
format!("{top_crate}::char::from_u32({arg}).unwrap()"),
Applicability::Unspecified,
);
},

View File

@ -1,7 +1,7 @@
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::source::snippet;
use clippy_utils::sugg;
use clippy_utils::{std_or_core, sugg};
use rustc_errors::Applicability;
use rustc_hir::{Expr, Mutability};
use rustc_lint::LateContext;
@ -25,6 +25,8 @@ pub(super) fn check<'tcx>(
&& let ty::Uint(ty::UintTy::U8) = slice_ty.kind()
&& 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 snippet = snippet(cx, arg.span, "..");
@ -36,9 +38,9 @@ pub(super) fn check<'tcx>(
&format!("transmute from a `{from_ty}` to a `{to_ty}`"),
"consider using",
if const_context {
format!("std::str::from_utf8_unchecked{postfix}({snippet})")
format!("{top_crate}::str::from_utf8_unchecked{postfix}({snippet})")
} else {
format!("std::str::from_utf8{postfix}({snippet}).unwrap()")
format!("{top_crate}::str::from_utf8{postfix}({snippet}).unwrap()")
},
Applicability::MaybeIncorrect,
);

View File

@ -167,7 +167,15 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
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)
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
&& trait_id == trait_def_id

View File

@ -1,9 +1,10 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use clippy_utils::{is_trait_method, is_try, match_trait_method, paths};
use clippy_utils::diagnostics::span_lint_and_then;
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_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::sym;
use rustc_span::{sym, Span};
declare_clippy_lint! {
/// ### What it does
@ -45,126 +46,219 @@ declare_clippy_lint! {
declare_lint_pass!(UnusedIoAmount => [UNUSED_IO_AMOUNT]);
impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
let (hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr)) = s.kind else {
return;
};
#[derive(Copy, Clone)]
enum IoOp {
AsyncWrite(bool),
AsyncRead(bool),
SyncRead(bool),
SyncWrite(bool),
}
match expr.kind {
hir::ExprKind::Match(res, _, _) if is_try(cx, expr).is_some() => {
if let hir::ExprKind::Call(func, [ref arg_0, ..]) = res.kind {
if matches!(
func.kind,
hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, ..))
) {
check_map_error(cx, arg_0, expr);
}
} else {
check_map_error(cx, res, expr);
}
},
hir::ExprKind::MethodCall(path, arg_0, ..) => match path.ident.as_str() {
"expect" | "unwrap" | "unwrap_or" | "unwrap_or_else" | "is_ok" | "is_err" => {
check_map_error(cx, arg_0, expr);
},
_ => (),
},
_ => (),
impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
/// We perform the check on the block level.
/// If we want to catch match and if expressions that act as returns of the block
/// we need to check them at `check_expr` or `check_block` as they are not stmts
/// but we can't check them at `check_expr` because we need the broader context
/// 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.
///
/// To check for unused io amount in stmts, we only consider `StmtKind::Semi`.
/// `StmtKind::Local` is not considered because it binds values => the io amount is used.
/// `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.
///
/// We then check the individual expressions via `check_expr`. We use the same logic for
/// semi expressions and the final expression as we need to check match and if expressions
/// 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
/// 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::Call(func, [ref arg_0, ..]) = expr.kind {
if matches!(
func.kind,
hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..))
) {
return Some(arg_0);
return arg_0;
}
}
}
None
expr
}
fn check_map_error(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>) {
let mut call = call;
while let hir::ExprKind::MethodCall(path, receiver, ..) = call.kind {
if matches!(path.ident.as_str(), "or" | "or_else" | "ok") {
call = receiver;
} else {
break;
/// Check whether the current expr is a function call for an IO operation
fn check_io_mode(cx: &LateContext<'_>, call: &hir::Expr<'_>) -> Option<IoOp> {
let hir::ExprKind::MethodCall(path, ..) = call.kind else {
return None;
};
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);
}
}
if let Some(call) = try_remove_await(call) {
check_method_call(cx, call, expr, true);
} 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");
},
_ => (),
for span in wild_cards {
diag.span_note(
*span,
"the result is consumed here, but the amount of I/O bytes remains unhandled",
);
}
}
});
}

View File

@ -66,7 +66,7 @@ impl<'tcx> LateLintPass<'tcx> for AlmostStandardFormulation {
ident.span,
"non-standard lint formulation",
None,
&format!("try using `{}` instead", formulation.correction),
&format!("consider using `{}`", formulation.correction),
);
}
return;

View File

@ -71,7 +71,9 @@ pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
SimplifiedType::Str,
]
.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) {
let lang_item_path = cx.get_def_path(item_def_id);
if path_syms.starts_with(&lang_item_path) {

View File

@ -1,5 +1,6 @@
use rustc_ast::{ast, attr};
use rustc_errors::Applicability;
use rustc_middle::ty::{AdtDef, TyCtxt};
use rustc_session::Session;
use rustc_span::sym;
use std::str::FromStr;
@ -159,3 +160,14 @@ pub fn is_doc_hidden(attrs: &[ast::Attribute]) -> bool {
.filter_map(ast::Attribute::meta_item_list)
.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))
}

View File

@ -134,7 +134,7 @@ impl HirEqInterExpr<'_, '_, '_> {
/// Checks whether two blocks are the same.
#[expect(clippy::similar_names)]
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() {
return false;
}
@ -177,7 +177,7 @@ impl HirEqInterExpr<'_, '_, '_> {
return false;
}
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;
}
@ -212,7 +212,7 @@ impl HirEqInterExpr<'_, '_, '_> {
return false;
}
eq_span_tokens(self.inner.cx, lstart..lend, rstart..rend, |t| {
!matches!(t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi)
!matches!(t, Whitespace | Semi)
})
}

View File

@ -536,7 +536,12 @@ fn find_primitive_impls<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator<It
"f32" => SimplifiedType::Float(FloatTy::F32),
"f64" => SimplifiedType::Float(FloatTy::F64),
#[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()
@ -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::Binding(_, _, _, pat) => pat.map_or(false, |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::Or(pats) => {
// 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,
}
}

View File

@ -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 F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "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
pub const FUTURES_IO_ASYNCREADEXT: [&str; 3] = ["futures_util", "io", "AsyncReadExt"];
#[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 LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
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_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"];
@ -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 SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
#[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"];
#[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"];
#[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_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
pub const VEC_DEQUE_ITER: [&str; 5] = ["alloc", "collections", "vec_deque", "VecDeque", "iter"];

View File

@ -219,7 +219,8 @@ pub fn implements_trait<'tcx>(
/// 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>(
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,

View File

@ -1,3 +1,3 @@
[toolchain]
channel = "nightly-2024-01-11"
channel = "nightly-2024-01-25"
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]

View File

@ -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

View File

@ -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"

View File

@ -0,0 +1,3 @@
#![warn(clippy::multiple_crate_versions)]
fn main() {}

View File

@ -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"

View File

@ -0,0 +1 @@
allowed-duplicate-crates = ["winapi"]

View File

@ -0,0 +1,3 @@
#![warn(clippy::multiple_crate_versions)]
fn main() {}

View File

@ -4,7 +4,7 @@ error: non-standard lint formulation
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`
= 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
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try using `Checks for` instead
= help: consider using `Checks for`
error: aborting due to 2 previous errors

View File

@ -1,5 +1,5 @@
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, |_| {});
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -8,10 +8,10 @@ LL | cx.span_lint(lint, span, msg, |_| {});
= help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]`
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, |_| {});
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 2 previous errors

View File

@ -1 +1 @@
pub-underscore-fields-behavior = "PublicallyExported"
pub-underscore-fields-behavior = "PubliclyExported"

View File

@ -3,6 +3,9 @@ foobar = 42
# so is this one
barfoo = 53
# when using underscores instead of dashes, suggest the correct one
allow_mixed_uninlined_format_args = true
# that one is ignored
[third-party]
clippy-feature = "nightly"

View File

@ -1,3 +1,4 @@
//@no-rustfix
//@error-in-other-file: unknown field `foobar`, expected one of
fn main() {}

View File

@ -11,6 +11,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
allow-private-module-inception
allow-unwrap-in-tests
allowed-dotfiles
allowed-duplicate-crates
allowed-idents-below-min-chars
allowed-scripts
arithmetic-side-effects-allowed
@ -87,6 +88,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
allow-private-module-inception
allow-unwrap-in-tests
allowed-dotfiles
allowed-duplicate-crates
allowed-idents-below-min-chars
allowed-scripts
arithmetic-side-effects-allowed
@ -150,5 +152,82 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
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

View File

@ -11,8 +11,8 @@ use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::token::Star;
use syn::{
parse_macro_input, parse_quote, FnArg, ImplItem, ItemImpl, ItemTrait, Lifetime, Pat, PatIdent, PatType, Signature,
TraitItem, Type,
parse_macro_input, parse_quote, FnArg, ImplItem, ItemFn, ItemImpl, ItemTrait, Lifetime, Pat, PatIdent, PatType,
Signature, TraitItem, Type,
};
#[proc_macro_attribute]
@ -95,3 +95,33 @@ pub fn rename_my_lifetimes(_args: TokenStream, input: TokenStream) -> TokenStrea
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()
}

View File

@ -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() {}

View File

@ -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() {}

View File

@ -9,17 +9,16 @@ fn simple_examples() {
// Simple
if true {
//~^ ERROR: all if blocks contain the same code at the start
println!("Hello World!");
println!("I'm branch nr: 1");
} else {
println!("Hello World!");
println!("I'm branch nr: 2");
}
//~^^^^^^^ ERROR: all if blocks contain the same code at the start
// Else if
if x == 0 {
//~^ ERROR: all if blocks contain the same code at the start
let y = 9;
println!("The value y was set to: `{}`", y);
let _z = y;
@ -38,6 +37,7 @@ fn simple_examples() {
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
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
let used_value_name = 19;
if x == 10 {
//~^ ERROR: all if blocks contain the same code at the start
let used_value_name = "Different type";
println!("Str: {}", used_value_name);
let _ = 1;
@ -69,6 +68,7 @@ fn simple_but_suggestion_is_invalid() {
println!("Str: {}", used_value_name);
let _ = 2;
}
//~^^^^^^^^^ ERROR: all if blocks contain the same code at the start
let _ = used_value_name;
// 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 {
//~^ ERROR: this `if` has identical blocks
println!("This should trigger `IS_SAME_THAN_ELSE` as usual");
} else {
println!("This should trigger `IS_SAME_THAN_ELSE` as usual");
}
//~^^^^^ ERROR: this `if` has identical blocks
}
#[allow(clippy::vec_init_then_push)]

View File

@ -2,7 +2,6 @@ error: all if blocks contain the same code at the start
--> $DIR/shared_at_top.rs:11:5
|
LL | / if true {
LL | |
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
|
LL | / if x == 0 {
LL | |
LL | | let y = 9;
LL | | println!("The value y was set to: `{}`", 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
|
LL | / if x == 10 {
LL | |
LL | | let used_value_name = "Different type";
LL | | println!("Str: {}", used_value_name);
| |_____________________________________________^
@ -105,13 +102,12 @@ error: this `if` has identical blocks
|
LL | if x == 2019 {
| __________________^
LL | |
LL | | println!("This should trigger `IS_SAME_THAN_ELSE` as usual");
LL | | } else {
| |_____^
|
note: same as this
--> $DIR/shared_at_top.rs:106:12
--> $DIR/shared_at_top.rs:105:12
|
LL | } else {
| ____________^

View File

@ -107,9 +107,9 @@ fn valid_examples() {
// Let's test empty blocks
if false {
//~^ ERROR: this `if` has identical blocks
} else {
}
//~^^^ ERROR: this `if` has identical blocks
}
/// 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
if x == 0 {
//~^ ERROR: this `if` has identical blocks
let u = 19;
println!("How are u today?");
let _ = "This is a string";
@ -128,6 +127,7 @@ fn trigger_other_lint() {
println!("How are u today?");
let _ = "This is a string";
}
//~^^^^^^^^^ ERROR: this `if` has identical blocks
// Only same expression
let _ = if x == 6 { 7 } else { 7 };
@ -138,28 +138,24 @@ fn trigger_other_lint() {
println!("Well I'm the most important block");
"I'm a pretty string"
} else if x == 68 {
//~^ ERROR: this `if` has identical blocks
println!("I'm a doppelgänger");
// Don't listen to my clone below
if y == 90 { "=^.^=" } else { ":D" }
} else {
// Don't listen to my clone above
println!("I'm a doppelgänger");
if y == 90 { "=^.^=" } else { ":D" }
};
//~^^^^^^^^^ ERROR: this `if` has identical blocks
if x == 0 {
println!("I'm single");
} else if x == 68 {
//~^ ERROR: this `if` has identical blocks
println!("I'm a doppelgänger");
// Don't listen to my clone below
} else {
// Don't listen to my clone above
println!("I'm a doppelgänger");
}
//~^^^^^ ERROR: this `if` has identical blocks
}
fn main() {}

View File

@ -3,12 +3,11 @@ error: this `if` has identical blocks
|
LL | if false {
| ______________^
LL | |
LL | | } else {
| |_____^
|
note: same as this
--> $DIR/valid_if_blocks.rs:111:12
--> $DIR/valid_if_blocks.rs:110:12
|
LL | } else {
| ____________^
@ -25,7 +24,6 @@ error: this `if` has identical blocks
|
LL | if x == 0 {
| _______________^
LL | |
LL | | let u = 19;
LL | | println!("How are u today?");
LL | | let _ = "This is a string";
@ -33,7 +31,7 @@ LL | | } else {
| |_____^
|
note: same as this
--> $DIR/valid_if_blocks.rs:126:12
--> $DIR/valid_if_blocks.rs:125:12
|
LL | } else {
| ____________^
@ -60,20 +58,17 @@ error: this `if` has identical blocks
|
LL | } else if x == 68 {
| _______________________^
LL | |
LL | | println!("I'm a doppelgänger");
LL | | // Don't listen to my clone below
LL | |
LL | | if y == 90 { "=^.^=" } else { ":D" }
LL | | } else {
| |_____^
|
note: same as this
--> $DIR/valid_if_blocks.rs:146:12
--> $DIR/valid_if_blocks.rs:144:12
|
LL | } else {
| ____________^
LL | | // Don't listen to my clone above
LL | | println!("I'm a doppelgänger");
LL | |
LL | | if y == 90 { "=^.^=" } else { ":D" }
@ -81,22 +76,19 @@ LL | | };
| |_____^
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 | |
LL | | println!("I'm a doppelgänger");
LL | | // Don't listen to my clone below
LL | | } else {
| |_____^
|
note: same as this
--> $DIR/valid_if_blocks.rs:159:12
--> $DIR/valid_if_blocks.rs:155:12
|
LL | } else {
| ____________^
LL | | // Don't listen to my clone above
LL | | println!("I'm a doppelgänger");
LL | | }
| |_____^

View File

@ -2,7 +2,7 @@ error: called `is_some()` after searching an `Iterator` with `find`
--> $DIR/ice-9041.rs:5:19
|
LL | things.iter().find(|p| is_thing_ready(p)).is_some()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|p| is_thing_ready(&p))`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|p| is_thing_ready(&p))`
|
= note: `-D clippy::search-is-some` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::search_is_some)]`

View File

@ -0,0 +1,28 @@
#![warn(clippy::default_instead_of_iter_empty)]
#![allow(dead_code)]
#![feature(lang_items)]
#![no_std]
use core::panic::PanicInfo;
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
loop {}
}
#[derive(Default)]
struct Iter {
iter: core::iter::Empty<usize>,
}
fn main() {
// Do lint.
let _ = core::iter::empty::<usize>();
let _foo: core::iter::Empty<usize> = core::iter::empty();
// Do not lint.
let _ = Iter::default();
}

View File

@ -0,0 +1,28 @@
#![warn(clippy::default_instead_of_iter_empty)]
#![allow(dead_code)]
#![feature(lang_items)]
#![no_std]
use core::panic::PanicInfo;
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
loop {}
}
#[derive(Default)]
struct Iter {
iter: core::iter::Empty<usize>,
}
fn main() {
// Do lint.
let _ = core::iter::Empty::<usize>::default();
let _foo: core::iter::Empty<usize> = core::iter::Empty::default();
// Do not lint.
let _ = Iter::default();
}

View File

@ -0,0 +1,17 @@
error: `core::iter::empty()` is the more idiomatic way
--> $DIR/default_instead_of_iter_empty_no_std.rs:23:13
|
LL | let _ = core::iter::Empty::<usize>::default();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `core::iter::empty::<usize>()`
|
= note: `-D clippy::default-instead-of-iter-empty` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::default_instead_of_iter_empty)]`
error: `core::iter::empty()` is the more idiomatic way
--> $DIR/default_instead_of_iter_empty_no_std.rs:24:42
|
LL | let _foo: core::iter::Empty<usize> = core::iter::Empty::default();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `core::iter::empty()`
error: aborting due to 2 previous errors

View File

@ -216,4 +216,44 @@ mod type_already_inferred {
}
}
mod issue12159 {
#![allow(non_upper_case_globals, clippy::exhaustive_structs)]
pub struct Foo;
static F: i32 = 1;
impl Foo {
const LIFE_u8: u8 = 42;
const LIFE_i8: i8 = 42;
const LIFE_u16: u16 = 42;
const LIFE_i16: i16 = 42;
const LIFE_u32: u32 = 42;
const LIFE_i32: i32 = 42;
const LIFE_u64: u64 = 42;
const LIFE_i64: i64 = 42;
const LIFE_u128: u128 = 42;
const LIFE_i128: i128 = 42;
const LIFE_usize: usize = 42;
const LIFE_isize: isize = 42;
const LIFE_f32: f32 = 42.;
const LIFE_f64: f64 = 42.;
const fn consts() {
const LIFE_u8: u8 = 42;
const LIFE_i8: i8 = 42;
const LIFE_u16: u16 = 42;
const LIFE_i16: i16 = 42;
const LIFE_u32: u32 = 42;
const LIFE_i32: i32 = 42;
const LIFE_u64: u64 = 42;
const LIFE_i64: i64 = 42;
const LIFE_u128: u128 = 42;
const LIFE_i128: i128 = 42;
const LIFE_usize: usize = 42;
const LIFE_isize: isize = 42;
const LIFE_f32: f32 = 42.;
const LIFE_f64: f64 = 42.;
}
}
}
fn main() {}

View File

@ -216,4 +216,44 @@ mod type_already_inferred {
}
}
mod issue12159 {
#![allow(non_upper_case_globals, clippy::exhaustive_structs)]
pub struct Foo;
static F: i32 = 1;
impl Foo {
const LIFE_u8: u8 = 42;
const LIFE_i8: i8 = 42;
const LIFE_u16: u16 = 42;
const LIFE_i16: i16 = 42;
const LIFE_u32: u32 = 42;
const LIFE_i32: i32 = 42;
const LIFE_u64: u64 = 42;
const LIFE_i64: i64 = 42;
const LIFE_u128: u128 = 42;
const LIFE_i128: i128 = 42;
const LIFE_usize: usize = 42;
const LIFE_isize: isize = 42;
const LIFE_f32: f32 = 42.;
const LIFE_f64: f64 = 42.;
const fn consts() {
const LIFE_u8: u8 = 42;
const LIFE_i8: i8 = 42;
const LIFE_u16: u16 = 42;
const LIFE_i16: i16 = 42;
const LIFE_u32: u32 = 42;
const LIFE_i32: i32 = 42;
const LIFE_u64: u64 = 42;
const LIFE_i64: i64 = 42;
const LIFE_u128: u128 = 42;
const LIFE_i128: i128 = 42;
const LIFE_usize: usize = 42;
const LIFE_isize: isize = 42;
const LIFE_f32: f32 = 42.;
const LIFE_f64: f64 = 42.;
}
}
}
fn main() {}

View File

@ -121,4 +121,36 @@ pub fn _from_mod() -> _hidden::InPubFn {
#[derive(PartialEq)]
struct InternalTy;
// This is a `non_exhaustive` type so should not warn.
#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub struct MissingEqNonExhaustive {
foo: u32,
bar: String,
}
// This is a `non_exhaustive` type so should not warn.
#[derive(Debug, PartialEq)]
pub struct MissingEqNonExhaustive1 {
foo: u32,
#[non_exhaustive]
bar: String,
}
// This is a `non_exhaustive` type so should not warn.
#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub enum MissingEqNonExhaustive2 {
Foo,
Bar,
}
// This is a `non_exhaustive` type so should not warn.
#[derive(Debug, PartialEq)]
pub enum MissingEqNonExhaustive3 {
Foo,
#[non_exhaustive]
Bar,
}
fn main() {}

View File

@ -121,4 +121,36 @@ pub fn _from_mod() -> _hidden::InPubFn {
#[derive(PartialEq)]
struct InternalTy;
// This is a `non_exhaustive` type so should not warn.
#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub struct MissingEqNonExhaustive {
foo: u32,
bar: String,
}
// This is a `non_exhaustive` type so should not warn.
#[derive(Debug, PartialEq)]
pub struct MissingEqNonExhaustive1 {
foo: u32,
#[non_exhaustive]
bar: String,
}
// This is a `non_exhaustive` type so should not warn.
#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub enum MissingEqNonExhaustive2 {
Foo,
Bar,
}
// This is a `non_exhaustive` type so should not warn.
#[derive(Debug, PartialEq)]
pub enum MissingEqNonExhaustive3 {
Foo,
#[non_exhaustive]
Bar,
}
fn main() {}

View File

@ -64,7 +64,7 @@ fn test_units() {
/// NaN NaNs
/// OAuth GraphQL
/// OCaml
/// OpenGL OpenMP OpenSSH OpenSSL OpenStreetMap OpenDNS
/// OpenDNS OpenGL OpenMP OpenSSH OpenSSL OpenStreetMap OpenTelemetry
/// WebGL WebGL2 WebGPU
/// TensorFlow
/// TrueType

View File

@ -64,7 +64,7 @@ fn test_units() {
/// NaN NaNs
/// OAuth GraphQL
/// OCaml
/// OpenGL OpenMP OpenSSH OpenSSL OpenStreetMap OpenDNS
/// OpenDNS OpenGL OpenMP OpenSSH OpenSSL OpenStreetMap OpenTelemetry
/// WebGL WebGL2 WebGPU
/// TensorFlow
/// TrueType

View File

@ -89,4 +89,12 @@ fn msrv_1_41() {
}
}
fn issue_12138() {
struct Hello;
impl From<Hello> for () {
fn from(val: Hello) {}
}
}
fn main() {}

View File

@ -89,4 +89,12 @@ fn msrv_1_41() {
}
}
fn issue_12138() {
struct Hello;
impl Into<()> for Hello {
fn into(self) {}
}
}
fn main() {}

View File

@ -86,5 +86,19 @@ LL ~ fn from(val: Vec<T>) -> Self {
LL ~ FromOverInto(val)
|
error: aborting due to 6 previous errors
error: an implementation of `From` is preferred since it gives you `Into<_>` for free where the reverse isn't true
--> $DIR/from_over_into.rs:95:5
|
LL | impl Into<()> for Hello {
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= help: `impl From<Local> for Foreign` is allowed by the orphan rules, for more information see
https://doc.rust-lang.org/reference/items/implementations.html#trait-implementation-coherence
help: replace the `Into` implementation with `From<issue_12138::Hello>`
|
LL ~ impl From<Hello> for () {
LL ~ fn from(val: Hello) {}
|
error: aborting due to 7 previous errors

View File

@ -21,7 +21,6 @@ fn foo() -> bool {
fn if_same_then_else() {
if true {
//~^ ERROR: this `if` has identical blocks
Foo { bar: 42 };
0..10;
..;
@ -38,6 +37,7 @@ fn if_same_then_else() {
0..=10;
foo();
}
//~^^^^^^^^^^^^^^^^^ ERROR: this `if` has identical blocks
if true {
Foo { bar: 42 };
@ -64,19 +64,11 @@ fn if_same_then_else() {
foo();
}
let _ = if true {
//~^ ERROR: this `if` has identical blocks
0.0
} else {
0.0
};
let _ = if true { 0.0 } else { 0.0 };
//~^ ERROR: this `if` has identical blocks
let _ = if true {
//~^ ERROR: this `if` has identical blocks
-0.0
} else {
-0.0
};
let _ = if true { -0.0 } else { -0.0 };
//~^ ERROR: this `if` has identical blocks
let _ = if true { 0.0 } else { -0.0 };
@ -87,15 +79,10 @@ fn if_same_then_else() {
foo();
}
let _ = if true {
//~^ ERROR: this `if` has identical blocks
42
} else {
42
};
let _ = if true { 42 } else { 42 };
//~^ ERROR: this `if` has identical blocks
if true {
//~^ ERROR: this `if` has identical blocks
let bar = if true { 42 } else { 43 };
while foo() {
@ -110,6 +97,7 @@ fn if_same_then_else() {
}
bar + 1;
}
//~^^^^^^^^^^^^^^^ ERROR: this `if` has identical blocks
if true {
let _ = match 42 {

View File

@ -3,16 +3,16 @@ error: this `if` has identical blocks
|
LL | if true {
| _____________^
LL | |
LL | | Foo { bar: 42 };
LL | | 0..10;
LL | | ..;
... |
LL | | foo();
LL | | } else {
| |_____^
|
note: same as this
--> $DIR/if_same_then_else.rs:32:12
--> $DIR/if_same_then_else.rs:31:12
|
LL | } else {
| ____________^
@ -29,75 +29,54 @@ LL | | }
error: this `if` has identical blocks
--> $DIR/if_same_then_else.rs:67:21
|
LL | let _ = if true {
| _____________________^
LL | |
LL | | 0.0
LL | | } else {
| |_____^
LL | let _ = if true { 0.0 } else { 0.0 };
| ^^^^^^^
|
note: same as this
--> $DIR/if_same_then_else.rs:70:12
--> $DIR/if_same_then_else.rs:67:34
|
LL | } else {
| ____________^
LL | | 0.0
LL | | };
| |_____^
LL | let _ = if true { 0.0 } else { 0.0 };
| ^^^^^^^
error: this `if` has identical blocks
--> $DIR/if_same_then_else.rs:74:21
--> $DIR/if_same_then_else.rs:70:21
|
LL | let _ = if true {
| _____________________^
LL | |
LL | | -0.0
LL | | } else {
| |_____^
LL | let _ = if true { -0.0 } else { -0.0 };
| ^^^^^^^^
|
note: same as this
--> $DIR/if_same_then_else.rs:77:12
--> $DIR/if_same_then_else.rs:70:35
|
LL | } else {
| ____________^
LL | | -0.0
LL | | };
| |_____^
LL | let _ = if true { -0.0 } else { -0.0 };
| ^^^^^^^^
error: this `if` has identical blocks
--> $DIR/if_same_then_else.rs:90:21
--> $DIR/if_same_then_else.rs:82:21
|
LL | let _ = if true {
| _____________________^
LL | |
LL | | 42
LL | | } else {
| |_____^
LL | let _ = if true { 42 } else { 42 };
| ^^^^^^
|
note: same as this
--> $DIR/if_same_then_else.rs:93:12
--> $DIR/if_same_then_else.rs:82:33
|
LL | } else {
| ____________^
LL | | 42
LL | | };
| |_____^
LL | let _ = if true { 42 } else { 42 };
| ^^^^^^
error: this `if` has identical blocks
--> $DIR/if_same_then_else.rs:97:13
--> $DIR/if_same_then_else.rs:85:13
|
LL | if true {
| _____________^
LL | |
LL | | let bar = if true { 42 } else { 43 };
LL | |
LL | | while foo() {
... |
LL | | bar + 1;
LL | | } else {
| |_____^
|
note: same as this
--> $DIR/if_same_then_else.rs:105:12
--> $DIR/if_same_then_else.rs:92:12
|
LL | } else {
| ____________^
@ -110,7 +89,7 @@ LL | | }
| |_____^
error: this `if` has identical blocks
--> $DIR/if_same_then_else.rs:250:14
--> $DIR/if_same_then_else.rs:238:14
|
LL | if x {
| ______________^
@ -119,7 +98,7 @@ LL | | } else {
| |_________^
|
note: same as this
--> $DIR/if_same_then_else.rs:252:16
--> $DIR/if_same_then_else.rs:240:16
|
LL | } else {
| ________________^

View File

@ -13,7 +13,6 @@
fn if_same_then_else2() -> Result<&'static str, ()> {
if true {
//~^ ERROR: this `if` has identical blocks
for _ in &[42] {
let foo: &Option<_> = &Some::<u8>(42);
if foo.is_some() {
@ -32,20 +31,21 @@ fn if_same_then_else2() -> Result<&'static str, ()> {
}
}
}
//~^^^^^^^^^^^^^^^^^^^ ERROR: this `if` has identical blocks
if true {
//~^ ERROR: this `if` has identical blocks
if let Some(a) = Some(42) {}
} else {
if let Some(a) = Some(42) {}
}
//~^^^^^ ERROR: this `if` has identical blocks
if true {
//~^ ERROR: this `if` has identical blocks
if let (1, .., 3) = (1, 2, 3) {}
} else {
if let (1, .., 3) = (1, 2, 3) {}
}
//~^^^^^ ERROR: this `if` has identical blocks
if true {
if let (1, .., 3) = (1, 2, 3) {}
@ -90,19 +90,15 @@ fn if_same_then_else2() -> Result<&'static str, ()> {
}
// Same NaNs
let _ = if true {
//~^ ERROR: this `if` has identical blocks
f32::NAN
} else {
f32::NAN
};
let _ = if true { f32::NAN } else { f32::NAN };
//~^ ERROR: this `if` has identical blocks
if true {
//~^ ERROR: this `if` has identical blocks
Ok("foo")?;
} else {
Ok("foo")?;
}
//~^^^^^ ERROR: this `if` has identical blocks
if true {
let foo = "";
@ -122,13 +118,13 @@ fn if_same_then_else2() -> Result<&'static str, ()> {
let foo = "bar";
return Ok(&foo[0..]);
} else if true {
//~^ ERROR: this `if` has identical blocks
let foo = "";
return Ok(&foo[0..]);
} else {
let foo = "";
return Ok(&foo[0..]);
}
//~^^^^^^^ ERROR: this `if` has identical blocks
// False positive `if_same_then_else`: `let (x, y)` vs. `let (y, x)`; see issue #3559.
if true {

View File

@ -3,16 +3,16 @@ error: this `if` has identical blocks
|
LL | if true {
| _____________^
LL | |
LL | | for _ in &[42] {
LL | | let foo: &Option<_> = &Some::<u8>(42);
LL | | if foo.is_some() {
... |
LL | | }
LL | | } else {
| |_____^
|
note: same as this
--> $DIR/if_same_then_else2.rs:25:12
--> $DIR/if_same_then_else2.rs:24:12
|
LL | } else {
| ____________^
@ -31,13 +31,12 @@ error: this `if` has identical blocks
|
LL | if true {
| _____________^
LL | |
LL | | if let Some(a) = Some(42) {}
LL | | } else {
| |_____^
|
note: same as this
--> $DIR/if_same_then_else2.rs:39:12
--> $DIR/if_same_then_else2.rs:38:12
|
LL | } else {
| ____________^
@ -50,13 +49,12 @@ error: this `if` has identical blocks
|
LL | if true {
| _____________^
LL | |
LL | | if let (1, .., 3) = (1, 2, 3) {}
LL | | } else {
| |_____^
|
note: same as this
--> $DIR/if_same_then_else2.rs:46:12
--> $DIR/if_same_then_else2.rs:45:12
|
LL | } else {
| ____________^
@ -67,34 +65,26 @@ LL | | }
error: this `if` has identical blocks
--> $DIR/if_same_then_else2.rs:93:21
|
LL | let _ = if true {
| _____________________^
LL | |
LL | | f32::NAN
LL | | } else {
| |_____^
LL | let _ = if true { f32::NAN } else { f32::NAN };
| ^^^^^^^^^^^^
|
note: same as this
--> $DIR/if_same_then_else2.rs:96:12
--> $DIR/if_same_then_else2.rs:93:39
|
LL | } else {
| ____________^
LL | | f32::NAN
LL | | };
| |_____^
LL | let _ = if true { f32::NAN } else { f32::NAN };
| ^^^^^^^^^^^^
error: this `if` has identical blocks
--> $DIR/if_same_then_else2.rs:100:13
--> $DIR/if_same_then_else2.rs:96:13
|
LL | if true {
| _____________^
LL | |
LL | | Ok("foo")?;
LL | | } else {
| |_____^
|
note: same as this
--> $DIR/if_same_then_else2.rs:103:12
--> $DIR/if_same_then_else2.rs:98:12
|
LL | } else {
| ____________^
@ -103,18 +93,17 @@ LL | | }
| |_____^
error: this `if` has identical blocks
--> $DIR/if_same_then_else2.rs:124:20
--> $DIR/if_same_then_else2.rs:120:20
|
LL | } else if true {
| ____________________^
LL | |
LL | | let foo = "";
LL | | return Ok(&foo[0..]);
LL | | } else {
| |_____^
|
note: same as this
--> $DIR/if_same_then_else2.rs:128:12
--> $DIR/if_same_then_else2.rs:123:12
|
LL | } else {
| ____________^

View File

@ -26,16 +26,23 @@ fn main() {
.unwrap();
let file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.append(false)
.open("dump.json")
.unwrap();
let file = OpenOptions::new()
.create(true)
.truncate(true)
.write(false)
.append(false)
.open("dump.json")
.unwrap();
let file = OpenOptions::new().create(true).append(true).open("dump.json").unwrap();
let file = OpenOptions::new().create(true).write(true).open("dump.json").unwrap();
let file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open("dump.json")
.unwrap();
}

View File

@ -26,16 +26,23 @@ fn main() {
.unwrap();
let file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.append(false)
.open("dump.json")
.unwrap();
let file = OpenOptions::new()
.create(true)
.truncate(true)
.write(false)
.append(false)
.open("dump.json")
.unwrap();
let file = OpenOptions::new().create(true).append(true).open("dump.json").unwrap();
let file = OpenOptions::new().create(true).write(true).open("dump.json").unwrap();
let file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open("dump.json")
.unwrap();
}

View File

@ -11,7 +11,7 @@ help: if this is unintentional, try removing the starting separator
|
LL | path.join("sh");
| ~~~~
help: if this is intentional, try using `Path::new` instead
help: if this is intentional, consider using `Path::new`
|
LL | PathBuf::from("/sh");
| ~~~~~~~~~~~~~~~~~~~~
@ -27,7 +27,7 @@ help: if this is unintentional, try removing the starting separator
|
LL | path.join("\user");
| ~~~~~~~
help: if this is intentional, try using `Path::new` instead
help: if this is intentional, consider using `Path::new`
|
LL | PathBuf::from("\\user");
| ~~~~~~~~~~~~~~~~~~~~~~~
@ -43,7 +43,7 @@ help: if this is unintentional, try removing the starting separator
|
LL | path.join("sh");
| ~~~~
help: if this is intentional, try using `Path::new` instead
help: if this is intentional, consider using `Path::new`
|
LL | PathBuf::from("/sh");
| ~~~~~~~~~~~~~~~~~~~~
@ -59,7 +59,7 @@ help: if this is unintentional, try removing the starting separator
|
LL | path.join(r#"sh"#);
| ~~~~~~~
help: if this is intentional, try using `Path::new` instead
help: if this is intentional, consider using `Path::new`
|
LL | PathBuf::from(r#"/sh"#);
| ~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -17,7 +17,7 @@ error: called `map_or(Err(_), Ok)` on an `Option` value
--> $DIR/manual_ok_or.rs:14:5
|
LL | foo.map_or(Err("error"), Ok);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `ok_or` instead: `foo.ok_or("error")`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `ok_or`: `foo.ok_or("error")`
|
= note: `-D clippy::option-map-or-err-ok` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::option_map_or_err_ok)]`

View File

@ -2,7 +2,7 @@ error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:6:13
|
LL | let _ = 1u32.checked_add(1).unwrap_or(u32::max_value());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1u32.saturating_add(1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1u32.saturating_add(1)`
|
= note: `-D clippy::manual-saturating-arithmetic` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::manual_saturating_arithmetic)]`
@ -11,13 +11,13 @@ error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:7:13
|
LL | let _ = 1u32.checked_add(1).unwrap_or(u32::MAX);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1u32.saturating_add(1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1u32.saturating_add(1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:8:13
|
LL | let _ = 1u8.checked_add(1).unwrap_or(255);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1u8.saturating_add(1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1u8.saturating_add(1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:9:13
@ -26,49 +26,49 @@ LL | let _ = 1u128
| _____________^
LL | | .checked_add(1)
LL | | .unwrap_or(340_282_366_920_938_463_463_374_607_431_768_211_455);
| |_______________________________________________________________________^ help: try using `saturating_add`: `1u128.saturating_add(1)`
| |_______________________________________________________________________^ help: consider using `saturating_add`: `1u128.saturating_add(1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:14:13
|
LL | let _ = 1u32.checked_mul(1).unwrap_or(u32::MAX);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_mul`: `1u32.saturating_mul(1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_mul`: `1u32.saturating_mul(1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:16:13
|
LL | let _ = 1u32.checked_sub(1).unwrap_or(u32::min_value());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1u32.saturating_sub(1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1u32.saturating_sub(1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:17:13
|
LL | let _ = 1u32.checked_sub(1).unwrap_or(u32::MIN);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1u32.saturating_sub(1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1u32.saturating_sub(1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:18:13
|
LL | let _ = 1u8.checked_sub(1).unwrap_or(0);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1u8.saturating_sub(1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1u8.saturating_sub(1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:22:13
|
LL | let _ = 1i32.checked_add(1).unwrap_or(i32::max_value());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i32.saturating_add(1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1i32.saturating_add(1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:23:13
|
LL | let _ = 1i32.checked_add(1).unwrap_or(i32::MAX);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i32.saturating_add(1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1i32.saturating_add(1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:24:13
|
LL | let _ = 1i8.checked_add(1).unwrap_or(127);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i8.saturating_add(1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1i8.saturating_add(1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:25:13
@ -77,25 +77,25 @@ LL | let _ = 1i128
| _____________^
LL | | .checked_add(1)
LL | | .unwrap_or(170_141_183_460_469_231_731_687_303_715_884_105_727);
| |_______________________________________________________________________^ help: try using `saturating_add`: `1i128.saturating_add(1)`
| |_______________________________________________________________________^ help: consider using `saturating_add`: `1i128.saturating_add(1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:28:13
|
LL | let _ = 1i32.checked_add(-1).unwrap_or(i32::min_value());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i32.saturating_add(-1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1i32.saturating_add(-1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:29:13
|
LL | let _ = 1i32.checked_add(-1).unwrap_or(i32::MIN);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i32.saturating_add(-1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1i32.saturating_add(-1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:30:13
|
LL | let _ = 1i8.checked_add(-1).unwrap_or(-128);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i8.saturating_add(-1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1i8.saturating_add(-1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:31:13
@ -104,25 +104,25 @@ LL | let _ = 1i128
| _____________^
LL | | .checked_add(-1)
LL | | .unwrap_or(-170_141_183_460_469_231_731_687_303_715_884_105_728);
| |________________________________________________________________________^ help: try using `saturating_add`: `1i128.saturating_add(-1)`
| |________________________________________________________________________^ help: consider using `saturating_add`: `1i128.saturating_add(-1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:38:13
|
LL | let _ = 1i32.checked_sub(1).unwrap_or(i32::min_value());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i32.saturating_sub(1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1i32.saturating_sub(1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:39:13
|
LL | let _ = 1i32.checked_sub(1).unwrap_or(i32::MIN);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i32.saturating_sub(1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1i32.saturating_sub(1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:40:13
|
LL | let _ = 1i8.checked_sub(1).unwrap_or(-128);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i8.saturating_sub(1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1i8.saturating_sub(1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:41:13
@ -131,25 +131,25 @@ LL | let _ = 1i128
| _____________^
LL | | .checked_sub(1)
LL | | .unwrap_or(-170_141_183_460_469_231_731_687_303_715_884_105_728);
| |________________________________________________________________________^ help: try using `saturating_sub`: `1i128.saturating_sub(1)`
| |________________________________________________________________________^ help: consider using `saturating_sub`: `1i128.saturating_sub(1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:44:13
|
LL | let _ = 1i32.checked_sub(-1).unwrap_or(i32::max_value());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i32.saturating_sub(-1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1i32.saturating_sub(-1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:45:13
|
LL | let _ = 1i32.checked_sub(-1).unwrap_or(i32::MAX);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i32.saturating_sub(-1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1i32.saturating_sub(-1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:46:13
|
LL | let _ = 1i8.checked_sub(-1).unwrap_or(127);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i8.saturating_sub(-1)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1i8.saturating_sub(-1)`
error: manual saturating arithmetic
--> $DIR/manual_saturating_arithmetic.rs:47:13
@ -158,7 +158,7 @@ LL | let _ = 1i128
| _____________^
LL | | .checked_sub(-1)
LL | | .unwrap_or(170_141_183_460_469_231_731_687_303_715_884_105_727);
| |_______________________________________________________________________^ help: try using `saturating_sub`: `1i128.saturating_sub(-1)`
| |_______________________________________________________________________^ help: consider using `saturating_sub`: `1i128.saturating_sub(-1)`
error: aborting due to 24 previous errors

View File

@ -69,15 +69,48 @@ fn main() {
//~^ ERROR: you are explicitly cloning with `.map()`
let y = x.cloned();
//~^ ERROR: you are explicitly cloning with `.map()`
//~| HELP: consider calling the dedicated `cloned` method
let y = x.cloned();
//~^ ERROR: you are explicitly cloning with `.map()`
//~| HELP: consider calling the dedicated `cloned` method
let x: Option<u32> = Some(0);
let x = x.as_ref(); // We do this to prevent triggering the `useless_asref` lint.
let y = x.copied();
//~^ ERROR: you are explicitly cloning with `.map()`
//~| HELP: consider calling the dedicated `copied` method
let y = x.copied();
//~^ ERROR: you are explicitly cloning with `.map()`
//~| HELP: consider calling the dedicated `copied` method
// Should not suggest `copied` or `cloned` here since `T` is not a reference.
let x: Option<u32> = Some(0);
let y = x.map(|x| u32::clone(&x));
let y = x.map(|x| Clone::clone(&x));
// Testing with `Result` now.
let x: Result<String, ()> = Ok(String::new());
let x = x.as_ref(); // We do this to prevent triggering the `useless_asref` lint.
let y = x.cloned();
//~^ ERROR: you are explicitly cloning with `.map()`
//~| HELP: consider calling the dedicated `cloned` method
let y = x.cloned();
//~^ ERROR: you are explicitly cloning with `.map()`
//~| HELP: consider calling the dedicated `cloned` method
let x: Result<u32, ()> = Ok(0);
let x = x.as_ref(); // We do this to prevent triggering the `useless_asref` lint.
let y = x.copied();
//~^ ERROR: you are explicitly cloning with `.map()`
//~| HELP: consider calling the dedicated `copied` method
let y = x.copied();
//~^ ERROR: you are explicitly cloning with `.map()`
//~| HELP: consider calling the dedicated `copied` method
// Should not suggest `copied` or `cloned` here since `T` is not a reference.
let x: Result<u32, ()> = Ok(0);
let y = x.map(|x| u32::clone(&x));
let y = x.map(|x| Clone::clone(&x));
// We ensure that no warning is emitted here because `useless_asref` is taking over.
let x = Some(String::new());

View File

@ -69,15 +69,48 @@ fn main() {
//~^ ERROR: you are explicitly cloning with `.map()`
let y = x.map(Clone::clone);
//~^ ERROR: you are explicitly cloning with `.map()`
//~| HELP: consider calling the dedicated `cloned` method
let y = x.map(String::clone);
//~^ ERROR: you are explicitly cloning with `.map()`
//~| HELP: consider calling the dedicated `cloned` method
let x: Option<u32> = Some(0);
let x = x.as_ref(); // We do this to prevent triggering the `useless_asref` lint.
let y = x.map(|x| u32::clone(x));
//~^ ERROR: you are explicitly cloning with `.map()`
//~| HELP: consider calling the dedicated `copied` method
let y = x.map(|x| Clone::clone(x));
//~^ ERROR: you are explicitly cloning with `.map()`
//~| HELP: consider calling the dedicated `copied` method
// Should not suggest `copied` or `cloned` here since `T` is not a reference.
let x: Option<u32> = Some(0);
let y = x.map(|x| u32::clone(&x));
let y = x.map(|x| Clone::clone(&x));
// Testing with `Result` now.
let x: Result<String, ()> = Ok(String::new());
let x = x.as_ref(); // We do this to prevent triggering the `useless_asref` lint.
let y = x.map(|x| String::clone(x));
//~^ ERROR: you are explicitly cloning with `.map()`
let y = x.map(|x| String::clone(x));
//~| HELP: consider calling the dedicated `cloned` method
let y = x.map(|x| Clone::clone(x));
//~^ ERROR: you are explicitly cloning with `.map()`
//~| HELP: consider calling the dedicated `cloned` method
let x: Result<u32, ()> = Ok(0);
let x = x.as_ref(); // We do this to prevent triggering the `useless_asref` lint.
let y = x.map(|x| u32::clone(x));
//~^ ERROR: you are explicitly cloning with `.map()`
//~| HELP: consider calling the dedicated `copied` method
let y = x.map(|x| Clone::clone(x));
//~^ ERROR: you are explicitly cloning with `.map()`
//~| HELP: consider calling the dedicated `copied` method
// Should not suggest `copied` or `cloned` here since `T` is not a reference.
let x: Result<u32, ()> = Ok(0);
let y = x.map(|x| u32::clone(&x));
let y = x.map(|x| Clone::clone(&x));
// We ensure that no warning is emitted here because `useless_asref` is taking over.
let x = Some(String::new());

Some files were not shown because too many files have changed in this diff Show More