diff options
| author | Philipp Krones <hello@philkrones.com> | 2023-10-21 13:22:39 +0200 |
|---|---|---|
| committer | Philipp Krones <hello@philkrones.com> | 2023-10-21 13:41:46 +0200 |
| commit | 5f031561ef3780a0bc63cf14549e6f90bc8bef89 (patch) | |
| tree | d84b45560d1ccf4859c4e23699b4fde6cfe22eea /clippy_utils/src | |
| parent | d9259fdedd96d03a9226e899a853c97d391eb94d (diff) | |
| parent | 23af253face1d23bdd5a51f7f29b251ad7094f8b (diff) | |
Merge remote-tracking branch 'upstream/master' into rustup
Diffstat (limited to 'clippy_utils/src')
| -rw-r--r-- | clippy_utils/src/consts.rs | 2 | ||||
| -rw-r--r-- | clippy_utils/src/diagnostics.rs | 4 | ||||
| -rw-r--r-- | clippy_utils/src/higher.rs | 2 | ||||
| -rw-r--r-- | clippy_utils/src/lib.rs | 114 | ||||
| -rw-r--r-- | clippy_utils/src/msrvs.rs | 90 | ||||
| -rw-r--r-- | clippy_utils/src/paths.rs | 1 | ||||
| -rw-r--r-- | clippy_utils/src/source.rs | 2 | ||||
| -rw-r--r-- | clippy_utils/src/str_utils.rs | 53 |
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::*; |
