Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
5f031561ef
139 changed files with 2370 additions and 884 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
});
|
||||
.and_then(|v| RustcVersion::parse(&v).ok());
|
||||
|
||||
// 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`"
|
||||
));
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue