about summary refs log tree commit diff
path: root/clippy_utils/src
diff options
context:
space:
mode:
authorPhilipp Krones <hello@philkrones.com>2023-10-21 13:22:39 +0200
committerPhilipp Krones <hello@philkrones.com>2023-10-21 13:41:46 +0200
commit5f031561ef3780a0bc63cf14549e6f90bc8bef89 (patch)
treed84b45560d1ccf4859c4e23699b4fde6cfe22eea /clippy_utils/src
parentd9259fdedd96d03a9226e899a853c97d391eb94d (diff)
parent23af253face1d23bdd5a51f7f29b251ad7094f8b (diff)
Merge remote-tracking branch 'upstream/master' into rustup
Diffstat (limited to 'clippy_utils/src')
-rw-r--r--clippy_utils/src/consts.rs2
-rw-r--r--clippy_utils/src/diagnostics.rs4
-rw-r--r--clippy_utils/src/higher.rs2
-rw-r--r--clippy_utils/src/lib.rs114
-rw-r--r--clippy_utils/src/msrvs.rs90
-rw-r--r--clippy_utils/src/paths.rs1
-rw-r--r--clippy_utils/src/source.rs2
-rw-r--r--clippy_utils/src/str_utils.rs53
8 files changed, 171 insertions, 97 deletions
diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs
index 0bae7056c4f..79c04c7c7f4 100644
--- a/clippy_utils/src/consts.rs
+++ b/clippy_utils/src/consts.rs
@@ -504,7 +504,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
             },
             (Some(Constant::Vec(vec)), _) => {
                 if !vec.is_empty() && vec.iter().all(|x| *x == vec[0]) {
-                    match vec.get(0) {
+                    match vec.first() {
                         Some(Constant::F32(x)) => Some(Constant::F32(*x)),
                         Some(Constant::F64(x)) => Some(Constant::F64(*x)),
                         _ => None,
diff --git a/clippy_utils/src/diagnostics.rs b/clippy_utils/src/diagnostics.rs
index edd87546a5f..210957a916f 100644
--- a/clippy_utils/src/diagnostics.rs
+++ b/clippy_utils/src/diagnostics.rs
@@ -83,9 +83,9 @@ pub fn span_lint_and_help<T: LintContext>(
     cx.struct_span_lint(lint, span, msg.to_string(), |diag| {
         let help = help.to_string();
         if let Some(help_span) = help_span {
-            diag.span_help(help_span, help.to_string());
+            diag.span_help(help_span, help);
         } else {
-            diag.help(help.to_string());
+            diag.help(help);
         }
         docs_link(diag, lint);
         diag
diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs
index 741f9f54883..edea4b3667f 100644
--- a/clippy_utils/src/higher.rs
+++ b/clippy_utils/src/higher.rs
@@ -449,7 +449,7 @@ pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -
                 } else if name.ident.name == symbol::kw::Default {
                     return Some(VecInitKind::Default);
                 } else if name.ident.name.as_str() == "with_capacity" {
-                    let arg = args.get(0)?;
+                    let arg = args.first()?;
                     return match constant_simple(cx, cx.typeck_results(), arg) {
                         Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
                         _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),
diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs
index c2c97259d38..93b37022822 100644
--- a/clippy_utils/src/lib.rs
+++ b/clippy_utils/src/lib.rs
@@ -2027,48 +2027,88 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
     did.map_or(false, |did| cx.tcx.has_attr(did, sym::must_use))
 }
 
-/// Checks if an expression represents the identity function
-/// Only examines closures and `std::convert::identity`
-pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
-    /// Checks if a function's body represents the identity function. Looks for bodies of the form:
-    /// * `|x| x`
-    /// * `|x| return x`
-    /// * `|x| { return x }`
-    /// * `|x| { return x; }`
-    fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
-        let id = if_chain! {
-            if let [param] = func.params;
-            if let PatKind::Binding(_, id, _, _) = param.pat.kind;
-            then {
-                id
-            } else {
-                return false;
-            }
-        };
+/// Checks if a function's body represents the identity function. Looks for bodies of the form:
+/// * `|x| x`
+/// * `|x| return x`
+/// * `|x| { return x }`
+/// * `|x| { return x; }`
+///
+/// Consider calling [`is_expr_untyped_identity_function`] or [`is_expr_identity_function`] instead.
+fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
+    let id = if_chain! {
+        if let [param] = func.params;
+        if let PatKind::Binding(_, id, _, _) = param.pat.kind;
+        then {
+            id
+        } else {
+            return false;
+        }
+    };
 
-        let mut expr = func.value;
-        loop {
-            match expr.kind {
-                #[rustfmt::skip]
-                ExprKind::Block(&Block { stmts: [], expr: Some(e), .. }, _, )
-                | ExprKind::Ret(Some(e)) => expr = e,
-                #[rustfmt::skip]
-                ExprKind::Block(&Block { stmts: [stmt], expr: None, .. }, _) => {
-                    if_chain! {
-                        if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind;
-                        if let ExprKind::Ret(Some(ret_val)) = e.kind;
-                        then {
-                            expr = ret_val;
-                        } else {
-                            return false;
-                        }
-                    }
+    let mut expr = func.value;
+    loop {
+        match expr.kind {
+            ExprKind::Block(
+                &Block {
+                    stmts: [],
+                    expr: Some(e),
+                    ..
                 },
-                _ => return path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty(),
-            }
+                _,
+            )
+            | ExprKind::Ret(Some(e)) => expr = e,
+            ExprKind::Block(
+                &Block {
+                    stmts: [stmt],
+                    expr: None,
+                    ..
+                },
+                _,
+            ) => {
+                if_chain! {
+                    if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind;
+                    if let ExprKind::Ret(Some(ret_val)) = e.kind;
+                    then {
+                        expr = ret_val;
+                    } else {
+                        return false;
+                    }
+                }
+            },
+            _ => return path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty(),
         }
     }
+}
 
+/// This is the same as [`is_expr_identity_function`], but does not consider closures
+/// with type annotations for its bindings (or similar) as identity functions:
+/// * `|x: u8| x`
+/// * `std::convert::identity::<u8>`
+pub fn is_expr_untyped_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+    match expr.kind {
+        ExprKind::Closure(&Closure { body, fn_decl, .. })
+            if fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer)) =>
+        {
+            is_body_identity_function(cx, cx.tcx.hir().body(body))
+        },
+        ExprKind::Path(QPath::Resolved(_, path))
+            if path.segments.iter().all(|seg| seg.infer_args)
+                && let Some(did) = path.res.opt_def_id() => {
+            cx.tcx.is_diagnostic_item(sym::convert_identity, did)
+        },
+        _ => false,
+    }
+}
+
+/// Checks if an expression represents the identity function
+/// Only examines closures and `std::convert::identity`
+///
+/// NOTE: If you want to use this function to find out if a closure is unnecessary, you likely want
+/// to call [`is_expr_untyped_identity_function`] instead, which makes sure that the closure doesn't
+/// have type annotations. This is important because removing a closure with bindings can
+/// remove type information that helped type inference before, which can then lead to compile
+/// errors.
+pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
     match expr.kind {
         ExprKind::Closure(&Closure { body, .. }) => is_body_identity_function(cx, cx.tcx.hir().body(body)),
         _ => path_def_id(cx, expr).map_or(false, |id| cx.tcx.is_diagnostic_item(sym::convert_identity, id)),
diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs
index df839c2106f..c6a48874e09 100644
--- a/clippy_utils/src/msrvs.rs
+++ b/clippy_utils/src/msrvs.rs
@@ -1,9 +1,7 @@
-use std::sync::OnceLock;
-
 use rustc_ast::Attribute;
 use rustc_semver::RustcVersion;
 use rustc_session::Session;
-use rustc_span::Span;
+use serde::Deserialize;
 
 use crate::attrs::get_unique_attr;
 
@@ -53,65 +51,45 @@ msrv_aliases! {
     1,15,0 { MAYBE_BOUND_IN_WHERE }
 }
 
-fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
-    if let Ok(version) = RustcVersion::parse(msrv) {
-        return Some(version);
-    } else if let Some(sess) = sess {
-        if let Some(span) = span {
-            sess.span_err(span, format!("`{msrv}` is not a valid Rust version"));
-        }
-    }
-    None
-}
-
 /// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]`
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone)]
 pub struct Msrv {
     stack: Vec<RustcVersion>,
 }
 
+impl<'de> Deserialize<'de> for Msrv {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let v = String::deserialize(deserializer)?;
+        RustcVersion::parse(&v)
+            .map(|v| Msrv { stack: vec![v] })
+            .map_err(|_| serde::de::Error::custom("not a valid Rust version"))
+    }
+}
+
 impl Msrv {
-    fn new(initial: Option<RustcVersion>) -> Self {
-        Self {
-            stack: Vec::from_iter(initial),
-        }
+    pub fn empty() -> Msrv {
+        Msrv { stack: Vec::new() }
     }
 
-    fn read_inner(conf_msrv: &Option<String>, sess: &Session) -> Self {
+    pub fn read_cargo(&mut self, sess: &Session) {
         let cargo_msrv = std::env::var("CARGO_PKG_RUST_VERSION")
             .ok()
-            .and_then(|v| parse_msrv(&v, None, None));
-        let clippy_msrv = conf_msrv.as_ref().and_then(|s| {
-            parse_msrv(s, None, None).or_else(|| {
-                sess.err(format!(
-                    "error reading Clippy's configuration file. `{s}` is not a valid Rust version"
-                ));
-                None
-            })
-        });
-
-        // if both files have an msrv, let's compare them and emit a warning if they differ
-        if let Some(cargo_msrv) = cargo_msrv
-            && let Some(clippy_msrv) = clippy_msrv
-            && clippy_msrv != cargo_msrv
-        {
-            sess.warn(format!(
-                "the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
-            ));
+            .and_then(|v| RustcVersion::parse(&v).ok());
+
+        match (self.current(), cargo_msrv) {
+            (None, Some(cargo_msrv)) => self.stack = vec![cargo_msrv],
+            (Some(clippy_msrv), Some(cargo_msrv)) => {
+                if clippy_msrv != cargo_msrv {
+                    sess.warn(format!(
+                        "the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
+                    ));
+                }
+            },
+            _ => {},
         }
-
-        Self::new(clippy_msrv.or(cargo_msrv))
-    }
-
-    /// Set the initial MSRV from the Clippy config file or from Cargo due to the `rust-version`
-    /// field in `Cargo.toml`
-    ///
-    /// Returns a `&'static Msrv` as `Copy` types are more easily passed to the
-    /// `register_{late,early}_pass` callbacks
-    pub fn read(conf_msrv: &Option<String>, sess: &Session) -> &'static Self {
-        static PARSED: OnceLock<Msrv> = OnceLock::new();
-
-        PARSED.get_or_init(|| Self::read_inner(conf_msrv, sess))
     }
 
     pub fn current(&self) -> Option<RustcVersion> {
@@ -125,10 +103,14 @@ impl Msrv {
     fn parse_attr(sess: &Session, attrs: &[Attribute]) -> Option<RustcVersion> {
         if let Some(msrv_attr) = get_unique_attr(sess, attrs, "msrv") {
             if let Some(msrv) = msrv_attr.value_str() {
-                return parse_msrv(&msrv.to_string(), Some(sess), Some(msrv_attr.span));
-            }
+                if let Ok(version) = RustcVersion::parse(msrv.as_str()) {
+                    return Some(version);
+                }
 
-            sess.span_err(msrv_attr.span, "bad clippy attribute");
+                sess.span_err(msrv_attr.span, format!("`{msrv}` is not a valid Rust version"));
+            } else {
+                sess.span_err(msrv_attr.span, "bad clippy attribute");
+            }
         }
 
         None
diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs
index 1f2bb16f459..4a20399e364 100644
--- a/clippy_utils/src/paths.rs
+++ b/clippy_utils/src/paths.rs
@@ -98,7 +98,6 @@ pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol",
 pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
 #[cfg(feature = "internal")]
 pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
-pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"];
 #[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
diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs
index 31cb421095e..bb9cdbbcaeb 100644
--- a/clippy_utils/src/source.rs
+++ b/clippy_utils/src/source.rs
@@ -8,7 +8,7 @@ use rustc_hir::{BlockCheckMode, Expr, ExprKind, UnsafeSource};
 use rustc_lint::{LateContext, LintContext};
 use rustc_session::Session;
 use rustc_span::source_map::{original_sp, SourceMap};
-use rustc_span::{hygiene, BytePos, SourceFileAndLine, Pos, SourceFile, Span, SpanData, SyntaxContext, DUMMY_SP};
+use rustc_span::{hygiene, BytePos, Pos, SourceFile, SourceFileAndLine, Span, SpanData, SyntaxContext, DUMMY_SP};
 use std::borrow::Cow;
 use std::ops::Range;
 
diff --git a/clippy_utils/src/str_utils.rs b/clippy_utils/src/str_utils.rs
index 03a9d3c25fd..69c25b427e1 100644
--- a/clippy_utils/src/str_utils.rs
+++ b/clippy_utils/src/str_utils.rs
@@ -236,6 +236,59 @@ pub fn count_match_end(str1: &str, str2: &str) -> StrCount {
         })
 }
 
+/// Returns a `snake_case` version of the input
+/// ```
+/// use clippy_utils::str_utils::to_snake_case;
+/// assert_eq!(to_snake_case("AbcDef"), "abc_def");
+/// assert_eq!(to_snake_case("ABCD"), "a_b_c_d");
+/// assert_eq!(to_snake_case("AbcDD"), "abc_d_d");
+/// assert_eq!(to_snake_case("Abc1DD"), "abc1_d_d");
+/// ```
+pub fn to_snake_case(name: &str) -> String {
+    let mut s = String::new();
+    for (i, c) in name.chars().enumerate() {
+        if c.is_uppercase() {
+            // characters without capitalization are considered lowercase
+            if i != 0 {
+                s.push('_');
+            }
+            s.extend(c.to_lowercase());
+        } else {
+            s.push(c);
+        }
+    }
+    s
+}
+/// Returns a `CamelCase` version of the input
+/// ```
+/// use clippy_utils::str_utils::to_camel_case;
+/// assert_eq!(to_camel_case("abc_def"), "AbcDef");
+/// assert_eq!(to_camel_case("a_b_c_d"), "ABCD");
+/// assert_eq!(to_camel_case("abc_d_d"), "AbcDD");
+/// assert_eq!(to_camel_case("abc1_d_d"), "Abc1DD");
+/// ```
+pub fn to_camel_case(item_name: &str) -> String {
+    let mut s = String::new();
+    let mut up = true;
+    for c in item_name.chars() {
+        if c.is_uppercase() {
+            // we only turn snake case text into CamelCase
+            return item_name.to_string();
+        }
+        if c == '_' {
+            up = true;
+            continue;
+        }
+        if up {
+            up = false;
+            s.extend(c.to_uppercase());
+        } else {
+            s.push(c);
+        }
+    }
+    s
+}
+
 #[cfg(test)]
 mod test {
     use super::*;