Merge remote-tracking branch 'upstream/master' into rustup

This commit is contained in:
Philipp Krones 2023-10-21 13:22:39 +02:00
commit 5f031561ef
No known key found for this signature in database
GPG key ID: 1CA0DF2AF59D68A5
139 changed files with 2370 additions and 884 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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::*;