diff options
| author | The rustc-dev-guide Cronjob Bot <github-actions@github.com> | 2025-02-10 04:02:38 +0000 |
|---|---|---|
| committer | The rustc-dev-guide Cronjob Bot <github-actions@github.com> | 2025-02-10 04:02:38 +0000 |
| commit | 81f21f0cce8be1e4dd1c8f4b9c6d0434029a13be (patch) | |
| tree | 2d6f32e88ad42b1ced7529c15488473bfe8b2abd /src/tools | |
| parent | c2ff52afbd2bbc53e5f83fbce034c805c2c1b3f6 (diff) | |
| parent | 124cc92199ffa924f6b4c7cc819a85b65e0c3984 (diff) | |
| download | rust-81f21f0cce8be1e4dd1c8f4b9c6d0434029a13be.tar.gz rust-81f21f0cce8be1e4dd1c8f4b9c6d0434029a13be.zip | |
Merge from rustc
Diffstat (limited to 'src/tools')
194 files changed, 3979 insertions, 1710 deletions
diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs index feec2a7444f..c21bf82b852 100644 --- a/src/tools/build-manifest/src/main.rs +++ b/src/tools/build-manifest/src/main.rs @@ -603,11 +603,14 @@ impl Builder { }) .collect(); - dst.insert(pkg.manifest_component_name(), Package { - version: version_info.version.unwrap_or_default(), - git_commit_hash: version_info.git_commit, - target: targets, - }); + dst.insert( + pkg.manifest_component_name(), + Package { + version: version_info.version.unwrap_or_default(), + git_commit_hash: version_info.git_commit, + target: targets, + }, + ); } fn url(&self, path: &Path) -> String { diff --git a/src/tools/bump-stage0/src/main.rs b/src/tools/bump-stage0/src/main.rs index b2dc044ec66..f51072718a3 100644 --- a/src/tools/bump-stage0/src/main.rs +++ b/src/tools/bump-stage0/src/main.rs @@ -50,7 +50,7 @@ impl Tool { # # All changes below this comment will be overridden the next time the # tool is executed. - "#; +"#; let mut file_content = String::new(); diff --git a/src/tools/cargo b/src/tools/cargo -Subproject 0e3d73849ab8cbbab3ec5c65cbd555586cb2133 +Subproject 2928e32734b04925ee51e1ae88bea9a83d2fd45 diff --git a/src/tools/clippy/CHANGELOG.md b/src/tools/clippy/CHANGELOG.md index bc42c07224e..fa03c953aa5 100644 --- a/src/tools/clippy/CHANGELOG.md +++ b/src/tools/clippy/CHANGELOG.md @@ -5765,6 +5765,7 @@ Released 2018-09-13 [`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive [`manual_ok_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_err [`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or +[`manual_option_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_option_as_slice [`manual_pattern_char_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_pattern_char_comparison [`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains [`manual_range_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_patterns @@ -5773,6 +5774,7 @@ Released 2018-09-13 [`manual_retain`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain [`manual_rotate`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rotate [`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic +[`manual_slice_fill`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_slice_fill [`manual_slice_size_calculation`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_slice_size_calculation [`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once [`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat @@ -5954,6 +5956,7 @@ Released 2018-09-13 [`positional_named_format_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#positional_named_format_parameters [`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma [`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence +[`precedence_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence_bits [`print_in_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_in_format_impl [`print_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_literal [`print_stderr`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stderr @@ -6026,6 +6029,7 @@ Released 2018-09-13 [`result_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unwrap_or_else [`result_unit_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unit_err [`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used +[`return_and_then`]: https://rust-lang.github.io/rust-clippy/master/index.html#return_and_then [`return_self_not_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#return_self_not_must_use [`reverse_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#reverse_range_loop [`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges diff --git a/src/tools/clippy/book/src/attribs.md b/src/tools/clippy/book/src/attribs.md index cf99497bc0f..9b7f5970504 100644 --- a/src/tools/clippy/book/src/attribs.md +++ b/src/tools/clippy/book/src/attribs.md @@ -5,7 +5,7 @@ To do this, Clippy provides attributes that can be applied to items in the 3rd p ## `#[clippy::format_args]` -_Available since Clippy v1.84_ +_Available since Clippy v1.85_ This attribute can be added to a macro that supports `format!`, `println!`, or similar syntax. It tells Clippy that the macro is a formatting macro, and that the arguments to the macro diff --git a/src/tools/clippy/book/src/lint_configuration.md b/src/tools/clippy/book/src/lint_configuration.md index b8f9fff9c61..dab2630a56f 100644 --- a/src/tools/clippy/book/src/lint_configuration.md +++ b/src/tools/clippy/book/src/lint_configuration.md @@ -752,6 +752,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`manual_rem_euclid`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid) * [`manual_repeat_n`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_repeat_n) * [`manual_retain`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain) +* [`manual_slice_fill`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_slice_fill) * [`manual_split_once`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once) * [`manual_str_repeat`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat) * [`manual_strip`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip) diff --git a/src/tools/clippy/book/src/lints.md b/src/tools/clippy/book/src/lints.md index 442dc63914e..1dd51797660 100644 --- a/src/tools/clippy/book/src/lints.md +++ b/src/tools/clippy/book/src/lints.md @@ -101,5 +101,18 @@ The `clippy::cargo` group gives you suggestions on how to improve your your crate and are not sure if you have all useful information in your `Cargo.toml`. +## Nursery + +The `clippy::nursery` group contains lints which are buggy or need more work. It is **not** +recommended to enable the whole group, but rather cherry-pick lints that are useful for your +code base and your use case. + +## Deprecated + +The `clippy::deprecated` is empty lints that exist to ensure that `#[allow(lintname)]` still +compiles after the lint was deprecated. Deprecation "removes" lints by removing their +functionality and marking them as deprecated, which may cause further warnings but cannot +cause a compiler error. + [Clippy lint documentation]: https://rust-lang.github.io/rust-clippy/ [Clippy 1.0 RFC]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#lint-audit-and-categories diff --git a/src/tools/clippy/clippy_config/src/conf.rs b/src/tools/clippy/clippy_config/src/conf.rs index 552141476f3..a1591188bee 100644 --- a/src/tools/clippy/clippy_config/src/conf.rs +++ b/src/tools/clippy/clippy_config/src/conf.rs @@ -621,6 +621,7 @@ define_Conf! { manual_rem_euclid, manual_repeat_n, manual_retain, + manual_slice_fill, manual_split_once, manual_str_repeat, manual_strip, diff --git a/src/tools/clippy/clippy_dev/src/new_lint.rs b/src/tools/clippy/clippy_dev/src/new_lint.rs index 35dd986ff61..cc4b26867a2 100644 --- a/src/tools/clippy/clippy_dev/src/new_lint.rs +++ b/src/tools/clippy/clippy_dev/src/new_lint.rs @@ -255,8 +255,9 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { let name_camel = to_camel_case(lint.name); let name_upper = lint_name.to_uppercase(); - result.push_str(&if enable_msrv { - formatdoc!( + if enable_msrv { + let _: fmt::Result = writedoc!( + result, r" use clippy_utils::msrvs::{{self, Msrv}}; use clippy_config::Conf; @@ -265,22 +266,24 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { use rustc_session::impl_lint_pass; " - ) + ); } else { - formatdoc!( + let _: fmt::Result = writedoc!( + result, r" {pass_import} use rustc_lint::{{{context_import}, {pass_type}}}; use rustc_session::declare_lint_pass; " - ) - }); + ); + } let _: fmt::Result = writeln!(result, "{}", get_lint_declaration(&name_upper, category)); - result.push_str(&if enable_msrv { - formatdoc!( + if enable_msrv { + let _: fmt::Result = writedoc!( + result, r" pub struct {name_camel} {{ msrv: Msrv, @@ -301,16 +304,17 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { // TODO: Add MSRV level to `clippy_config/src/msrvs.rs` if needed. // TODO: Update msrv config comment in `clippy_config/src/conf.rs` " - ) + ); } else { - formatdoc!( + let _: fmt::Result = writedoc!( + result, r" declare_lint_pass!({name_camel} => [{name_upper}]); impl {pass_type}{pass_lifetimes} for {name_camel} {{}} " - ) - }); + ); + } result } diff --git a/src/tools/clippy/clippy_dev/src/update_lints.rs b/src/tools/clippy/clippy_dev/src/update_lints.rs index fc0780f89a7..b80ee5aac7e 100644 --- a/src/tools/clippy/clippy_dev/src/update_lints.rs +++ b/src/tools/clippy/clippy_dev/src/update_lints.rs @@ -985,17 +985,23 @@ mod tests { Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()), ]; let mut expected: HashMap<String, Vec<Lint>> = HashMap::new(); - expected.insert("group1".to_string(), vec![ - Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()), - Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()), - ]); - expected.insert("group2".to_string(), vec![Lint::new( - "should_assert_eq2", - "group2", - "\"abc\"", - "module_name", - Range::default(), - )]); + expected.insert( + "group1".to_string(), + vec![ + Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()), + Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()), + ], + ); + expected.insert( + "group2".to_string(), + vec![Lint::new( + "should_assert_eq2", + "group2", + "\"abc\"", + "module_name", + Range::default(), + )], + ); assert_eq!(expected, Lint::by_lint_group(lints.into_iter())); } } diff --git a/src/tools/clippy/clippy_lints/src/approx_const.rs b/src/tools/clippy/clippy_lints/src/approx_const.rs index 95c85f250e9..95f64b74044 100644 --- a/src/tools/clippy/clippy_lints/src/approx_const.rs +++ b/src/tools/clippy/clippy_lints/src/approx_const.rs @@ -3,10 +3,10 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::msrvs::{self, Msrv}; use rustc_ast::ast::{FloatTy, LitFloatType, LitKind}; use rustc_attr_parsing::RustcVersion; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{HirId, Lit}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; -use rustc_span::symbol; +use rustc_span::{Span, symbol}; use std::f64::consts as f64; declare_clippy_lint! { @@ -73,22 +73,28 @@ impl ApproxConstant { msrv: conf.msrv.clone(), } } +} - fn check_lit(&self, cx: &LateContext<'_>, lit: &LitKind, e: &Expr<'_>) { - match *lit { +impl<'tcx> LateLintPass<'tcx> for ApproxConstant { + fn check_lit(&mut self, cx: &LateContext<'_>, _hir_id: HirId, lit: &Lit, _negated: bool) { + match lit.node { LitKind::Float(s, LitFloatType::Suffixed(fty)) => match fty { - FloatTy::F16 => self.check_known_consts(cx, e, s, "f16"), - FloatTy::F32 => self.check_known_consts(cx, e, s, "f32"), - FloatTy::F64 => self.check_known_consts(cx, e, s, "f64"), - FloatTy::F128 => self.check_known_consts(cx, e, s, "f128"), + FloatTy::F16 => self.check_known_consts(cx, lit.span, s, "f16"), + FloatTy::F32 => self.check_known_consts(cx, lit.span, s, "f32"), + FloatTy::F64 => self.check_known_consts(cx, lit.span, s, "f64"), + FloatTy::F128 => self.check_known_consts(cx, lit.span, s, "f128"), }, // FIXME(f16_f128): add `f16` and `f128` when these types become stable. - LitKind::Float(s, LitFloatType::Unsuffixed) => self.check_known_consts(cx, e, s, "f{32, 64}"), + LitKind::Float(s, LitFloatType::Unsuffixed) => self.check_known_consts(cx, lit.span, s, "f{32, 64}"), _ => (), } } - fn check_known_consts(&self, cx: &LateContext<'_>, e: &Expr<'_>, s: symbol::Symbol, module: &str) { + extract_msrv_attr!(LateContext); +} + +impl ApproxConstant { + fn check_known_consts(&self, cx: &LateContext<'_>, span: Span, s: symbol::Symbol, module: &str) { let s = s.as_str(); if s.parse::<f64>().is_ok() { for &(constant, name, min_digits, msrv) in &KNOWN_CONSTS { @@ -96,7 +102,7 @@ impl ApproxConstant { span_lint_and_help( cx, APPROX_CONSTANT, - e.span, + span, format!("approximate value of `{module}::consts::{name}` found"), None, "consider using the constant directly", @@ -110,16 +116,6 @@ impl ApproxConstant { impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]); -impl<'tcx> LateLintPass<'tcx> for ApproxConstant { - fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if let ExprKind::Lit(lit) = &e.kind { - self.check_lit(cx, &lit.node, e); - } - } - - extract_msrv_attr!(LateContext); -} - /// Returns `false` if the number of significant figures in `value` are /// less than `min_digits`; otherwise, returns true if `value` is equal /// to `constant`, rounded to the number of digits present in `value`. diff --git a/src/tools/clippy/clippy_lints/src/attrs/mixed_attributes_style.rs b/src/tools/clippy/clippy_lints/src/attrs/mixed_attributes_style.rs index 3e4bcfbfc19..5a26ba8bf93 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/mixed_attributes_style.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/mixed_attributes_style.rs @@ -1,8 +1,8 @@ +use std::sync::Arc; use super::MIXED_ATTRIBUTES_STYLE; use clippy_utils::diagnostics::span_lint; use rustc_ast::{AttrKind, AttrStyle, Attribute}; use rustc_data_structures::fx::FxHashSet; -use rustc_data_structures::sync::Lrc; use rustc_lint::{EarlyContext, LintContext}; use rustc_span::source_map::SourceMap; use rustc_span::{SourceFile, Span, Symbol}; @@ -79,7 +79,7 @@ fn lint_mixed_attrs(cx: &EarlyContext<'_>, attrs: &[Attribute]) { ); } -fn attr_in_same_src_as_item(source_map: &SourceMap, item_src: &Lrc<SourceFile>, attr_span: Span) -> bool { +fn attr_in_same_src_as_item(source_map: &SourceMap, item_src: &Arc<SourceFile>, attr_span: Span) -> bool { let attr_src = source_map.lookup_source_file(attr_span.lo()); - Lrc::ptr_eq(item_src, &attr_src) + Arc::ptr_eq(item_src, &attr_src) } diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs b/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs index 48e9f1d690e..ca973f4bb1a 100644 --- a/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs @@ -4,13 +4,13 @@ use clippy_utils::expr_or_init; use clippy_utils::source::snippet; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize}; +use rustc_abi::IntegerType; use rustc_errors::{Applicability, Diag}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; use rustc_middle::ty::{self, FloatTy, Ty}; use rustc_span::Span; -use rustc_target::abi::IntegerType; use super::{CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION, utils}; diff --git a/src/tools/clippy/clippy_lints/src/declared_lints.rs b/src/tools/clippy/clippy_lints/src/declared_lints.rs index 86bcf8edd57..5c5978b5559 100644 --- a/src/tools/clippy/clippy_lints/src/declared_lints.rs +++ b/src/tools/clippy/clippy_lints/src/declared_lints.rs @@ -144,8 +144,6 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::doc::DOC_NESTED_REFDEFS_INFO, crate::doc::DOC_OVERINDENTED_LIST_ITEMS_INFO, crate::doc::EMPTY_DOCS_INFO, - crate::doc::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO, - crate::doc::EMPTY_LINE_AFTER_OUTER_ATTR_INFO, crate::doc::MISSING_ERRORS_DOC_INFO, crate::doc::MISSING_PANICS_DOC_INFO, crate::doc::MISSING_SAFETY_DOC_INFO, @@ -162,6 +160,8 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::else_if_without_else::ELSE_IF_WITHOUT_ELSE_INFO, crate::empty_drop::EMPTY_DROP_INFO, crate::empty_enum::EMPTY_ENUM_INFO, + crate::empty_line_after::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO, + crate::empty_line_after::EMPTY_LINE_AFTER_OUTER_ATTR_INFO, crate::empty_with_brackets::EMPTY_ENUM_VARIANTS_WITH_BRACKETS_INFO, crate::empty_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS_INFO, crate::endian_bytes::BIG_ENDIAN_BYTES_INFO, @@ -292,6 +292,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::loops::MANUAL_FIND_INFO, crate::loops::MANUAL_FLATTEN_INFO, crate::loops::MANUAL_MEMCPY_INFO, + crate::loops::MANUAL_SLICE_FILL_INFO, crate::loops::MANUAL_WHILE_LET_SOME_INFO, crate::loops::MISSING_SPIN_LOOP_INFO, crate::loops::MUT_RANGE_BOUND_INFO, @@ -321,6 +322,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::manual_let_else::MANUAL_LET_ELSE_INFO, crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO, crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO, + crate::manual_option_as_slice::MANUAL_OPTION_AS_SLICE_INFO, crate::manual_range_patterns::MANUAL_RANGE_PATTERNS_INFO, crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO, crate::manual_retain::MANUAL_RETAIN_INFO, @@ -463,6 +465,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::methods::REPEAT_ONCE_INFO, crate::methods::RESULT_FILTER_MAP_INFO, crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO, + crate::methods::RETURN_AND_THEN_INFO, crate::methods::SEARCH_IS_SOME_INFO, crate::methods::SEEK_FROM_CURRENT_INFO, crate::methods::SEEK_TO_START_INSTEAD_OF_REWIND_INFO, @@ -626,6 +629,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::permissions_set_readonly_false::PERMISSIONS_SET_READONLY_FALSE_INFO, crate::pointers_in_nomem_asm_block::POINTERS_IN_NOMEM_ASM_BLOCK_INFO, crate::precedence::PRECEDENCE_INFO, + crate::precedence::PRECEDENCE_BITS_INFO, crate::ptr::CMP_NULL_INFO, crate::ptr::INVALID_NULL_PTR_USAGE_INFO, crate::ptr::MUT_FROM_REF_INFO, diff --git a/src/tools/clippy/clippy_lints/src/dereference.rs b/src/tools/clippy/clippy_lints/src/dereference.rs index 123e358d7c3..233ebe00d8e 100644 --- a/src/tools/clippy/clippy_lints/src/dereference.rs +++ b/src/tools/clippy/clippy_lints/src/dereference.rs @@ -291,10 +291,13 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { && let Some(ty) = use_node.defined_ty(cx) && TyCoercionStability::for_defined_ty(cx, ty, use_node.is_return()).is_deref_stable() { - self.state = Some((State::ExplicitDeref { mutability: None }, StateData { - first_expr: expr, - adjusted_ty, - })); + self.state = Some(( + State::ExplicitDeref { mutability: None }, + StateData { + first_expr: expr, + adjusted_ty, + }, + )); } }, RefOp::Method { mutbl, is_ufcs } @@ -456,10 +459,13 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { && next_adjust.is_none_or(|a| matches!(a.kind, Adjust::Deref(_) | Adjust::Borrow(_))) && iter.all(|a| matches!(a.kind, Adjust::Deref(_) | Adjust::Borrow(_))) { - self.state = Some((State::Borrow { mutability }, StateData { - first_expr: expr, - adjusted_ty, - })); + self.state = Some(( + State::Borrow { mutability }, + StateData { + first_expr: expr, + adjusted_ty, + }, + )); } }, _ => {}, @@ -503,10 +509,13 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { let stability = state.stability; report(cx, expr, State::DerefedBorrow(state), data, typeck); if stability.is_deref_stable() { - self.state = Some((State::Borrow { mutability }, StateData { - first_expr: expr, - adjusted_ty, - })); + self.state = Some(( + State::Borrow { mutability }, + StateData { + first_expr: expr, + adjusted_ty, + }, + )); } }, (Some((State::DerefedBorrow(state), data)), RefOp::Deref) => { @@ -531,10 +540,13 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { } else if stability.is_deref_stable() && let Some(parent) = get_parent_expr(cx, expr) { - self.state = Some((State::ExplicitDeref { mutability: None }, StateData { - first_expr: parent, - adjusted_ty, - })); + self.state = Some(( + State::ExplicitDeref { mutability: None }, + StateData { + first_expr: parent, + adjusted_ty, + }, + )); } }, @@ -1124,17 +1136,20 @@ impl<'tcx> Dereferencing<'tcx> { if let Some(outer_pat) = self.ref_locals.get_mut(&local) { if let Some(pat) = outer_pat { // Check for auto-deref - if !matches!(cx.typeck_results().expr_adjustments(e), [ - Adjustment { - kind: Adjust::Deref(_), - .. - }, - Adjustment { - kind: Adjust::Deref(_), + if !matches!( + cx.typeck_results().expr_adjustments(e), + [ + Adjustment { + kind: Adjust::Deref(_), + .. + }, + Adjustment { + kind: Adjust::Deref(_), + .. + }, .. - }, - .. - ]) { + ] + ) { match get_parent_expr(cx, e) { // Field accesses are the same no matter the number of references. Some(Expr { diff --git a/src/tools/clippy/clippy_lints/src/doc/empty_line_after.rs b/src/tools/clippy/clippy_lints/src/doc/empty_line_after.rs deleted file mode 100644 index 099194d4e74..00000000000 --- a/src/tools/clippy/clippy_lints/src/doc/empty_line_after.rs +++ /dev/null @@ -1,342 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::{SpanRangeExt, snippet_indent}; -use clippy_utils::tokenize_with_text; -use itertools::Itertools; -use rustc_ast::AttrStyle; -use rustc_ast::token::CommentKind; -use rustc_errors::{Applicability, Diag, SuggestionStyle}; -use rustc_hir::{AttrKind, Attribute, ItemKind, Node}; -use rustc_lexer::TokenKind; -use rustc_lint::LateContext; -use rustc_span::{BytePos, ExpnKind, InnerSpan, Span, SpanData}; - -use super::{EMPTY_LINE_AFTER_DOC_COMMENTS, EMPTY_LINE_AFTER_OUTER_ATTR}; - -#[derive(Debug, PartialEq, Clone, Copy)] -enum StopKind { - Attr, - Doc(CommentKind), -} - -impl StopKind { - fn is_doc(self) -> bool { - matches!(self, StopKind::Doc(_)) - } -} - -#[derive(Debug)] -struct Stop { - span: Span, - kind: StopKind, - first: usize, - last: usize, -} - -impl Stop { - fn convert_to_inner(&self) -> (Span, String) { - let inner = match self.kind { - // #|[...] - StopKind::Attr => InnerSpan::new(1, 1), - // /// or /** - // ^ ^ - StopKind::Doc(_) => InnerSpan::new(2, 3), - }; - (self.span.from_inner(inner), "!".into()) - } - - fn comment_out(&self, cx: &LateContext<'_>, suggestions: &mut Vec<(Span, String)>) { - match self.kind { - StopKind::Attr => { - if cx.tcx.sess.source_map().is_multiline(self.span) { - suggestions.extend([ - (self.span.shrink_to_lo(), "/* ".into()), - (self.span.shrink_to_hi(), " */".into()), - ]); - } else { - suggestions.push((self.span.shrink_to_lo(), "// ".into())); - } - }, - StopKind::Doc(CommentKind::Line) => suggestions.push((self.span.shrink_to_lo(), "// ".into())), - StopKind::Doc(CommentKind::Block) => { - // /** outer */ /*! inner */ - // ^ ^ - let asterisk = self.span.from_inner(InnerSpan::new(1, 2)); - suggestions.push((asterisk, String::new())); - }, - } - } - - fn from_attr(cx: &LateContext<'_>, attr: &Attribute) -> Option<Self> { - let SpanData { lo, hi, .. } = attr.span.data(); - let file = cx.tcx.sess.source_map().lookup_source_file(lo); - - Some(Self { - span: attr.span, - kind: match attr.kind { - AttrKind::Normal(_) => StopKind::Attr, - AttrKind::DocComment(comment_kind, _) => StopKind::Doc(comment_kind), - }, - first: file.lookup_line(file.relative_position(lo))?, - last: file.lookup_line(file.relative_position(hi))?, - }) - } -} - -/// Represents a set of attrs/doc comments separated by 1 or more empty lines -/// -/// ```ignore -/// /// chunk 1 docs -/// // not an empty line so also part of chunk 1 -/// #[chunk_1_attrs] // <-- prev_stop -/// -/// /* gap */ -/// -/// /// chunk 2 docs // <-- next_stop -/// #[chunk_2_attrs] -/// ``` -struct Gap<'a> { - /// The span of individual empty lines including the newline at the end of the line - empty_lines: Vec<Span>, - has_comment: bool, - next_stop: &'a Stop, - prev_stop: &'a Stop, - /// The chunk that includes [`prev_stop`](Self::prev_stop) - prev_chunk: &'a [Stop], -} - -impl<'a> Gap<'a> { - fn new(cx: &LateContext<'_>, prev_chunk: &'a [Stop], next_chunk: &'a [Stop]) -> Option<Self> { - let prev_stop = prev_chunk.last()?; - let next_stop = next_chunk.first()?; - let gap_span = prev_stop.span.between(next_stop.span); - let gap_snippet = gap_span.get_source_text(cx)?; - - let mut has_comment = false; - let mut empty_lines = Vec::new(); - - for (token, source, inner_span) in tokenize_with_text(&gap_snippet) { - match token { - TokenKind::BlockComment { - doc_style: None, - terminated: true, - } - | TokenKind::LineComment { doc_style: None } => has_comment = true, - TokenKind::Whitespace => { - let newlines = source.bytes().positions(|b| b == b'\n'); - empty_lines.extend( - newlines - .tuple_windows() - .map(|(a, b)| InnerSpan::new(inner_span.start + a + 1, inner_span.start + b)) - .map(|inner_span| gap_span.from_inner(inner_span)), - ); - }, - // Ignore cfg_attr'd out attributes as they may contain empty lines, could also be from macro - // shenanigans - _ => return None, - } - } - - (!empty_lines.is_empty()).then_some(Self { - empty_lines, - has_comment, - next_stop, - prev_stop, - prev_chunk, - }) - } - - fn contiguous_empty_lines(&self) -> impl Iterator<Item = Span> + '_ { - self.empty_lines - // The `+ BytePos(1)` means "next line", because each empty line span is "N:1-N:1". - .chunk_by(|a, b| a.hi() + BytePos(1) == b.lo()) - .map(|chunk| { - let first = chunk.first().expect("at least one empty line"); - let last = chunk.last().expect("at least one empty line"); - // The BytePos subtraction here is safe, as before an empty line, there must be at least one - // attribute/comment. The span needs to start at the end of the previous line. - first.with_lo(first.lo() - BytePos(1)).with_hi(last.hi()) - }) - } -} - -/// If the node the attributes/docs apply to is the first in the module/crate suggest converting -/// them to inner attributes/docs -fn suggest_inner(cx: &LateContext<'_>, diag: &mut Diag<'_, ()>, kind: StopKind, gaps: &[Gap<'_>]) { - let Some(owner) = cx.last_node_with_lint_attrs.as_owner() else { - return; - }; - let parent_desc = match cx.tcx.parent_hir_node(owner.into()) { - Node::Item(item) - if let ItemKind::Mod(parent_mod) = item.kind - && let [first, ..] = parent_mod.item_ids - && first.owner_id == owner => - { - "parent module" - }, - Node::Crate(crate_mod) - if let Some(first) = crate_mod - .item_ids - .iter() - .map(|&id| cx.tcx.hir().item(id)) - // skip prelude imports - .find(|item| !matches!(item.span.ctxt().outer_expn_data().kind, ExpnKind::AstPass(_))) - && first.owner_id == owner => - { - "crate" - }, - _ => return, - }; - - diag.multipart_suggestion_verbose( - match kind { - StopKind::Attr => format!("if the attribute should apply to the {parent_desc} use an inner attribute"), - StopKind::Doc(_) => format!("if the comment should document the {parent_desc} use an inner doc comment"), - }, - gaps.iter() - .flat_map(|gap| gap.prev_chunk) - .map(Stop::convert_to_inner) - .collect(), - Applicability::MaybeIncorrect, - ); -} - -fn check_gaps(cx: &LateContext<'_>, gaps: &[Gap<'_>]) -> bool { - let Some(first_gap) = gaps.first() else { - return false; - }; - let empty_lines = || gaps.iter().flat_map(|gap| gap.empty_lines.iter().copied()); - let contiguous_empty_lines = || gaps.iter().flat_map(Gap::contiguous_empty_lines); - let mut has_comment = false; - let mut has_attr = false; - for gap in gaps { - has_comment |= gap.has_comment; - if !has_attr { - has_attr = gap.prev_chunk.iter().any(|stop| stop.kind == StopKind::Attr); - } - } - let kind = first_gap.prev_stop.kind; - let (lint, kind_desc) = match kind { - StopKind::Attr => (EMPTY_LINE_AFTER_OUTER_ATTR, "outer attribute"), - StopKind::Doc(_) => (EMPTY_LINE_AFTER_DOC_COMMENTS, "doc comment"), - }; - let (lines, are, them) = if empty_lines().nth(1).is_some() { - ("lines", "are", "them") - } else { - ("line", "is", "it") - }; - span_lint_and_then( - cx, - lint, - first_gap.prev_stop.span.to(empty_lines().last().unwrap()), - format!("empty {lines} after {kind_desc}"), - |diag| { - if let Some(owner) = cx.last_node_with_lint_attrs.as_owner() { - let def_id = owner.to_def_id(); - let def_descr = cx.tcx.def_descr(def_id); - diag.span_label(cx.tcx.def_span(def_id), match kind { - StopKind::Attr => format!("the attribute applies to this {def_descr}"), - StopKind::Doc(_) => format!("the comment documents this {def_descr}"), - }); - } - - diag.multipart_suggestion_with_style( - format!("if the empty {lines} {are} unintentional remove {them}"), - contiguous_empty_lines() - .map(|empty_lines| (empty_lines, String::new())) - .collect(), - Applicability::MaybeIncorrect, - SuggestionStyle::HideCodeAlways, - ); - - if has_comment && kind.is_doc() { - // Likely doc comments that applied to some now commented out code - // - // /// Old docs for Foo - // // struct Foo; - - let mut suggestions = Vec::new(); - for stop in gaps.iter().flat_map(|gap| gap.prev_chunk) { - stop.comment_out(cx, &mut suggestions); - } - let name = match cx.tcx.hir().opt_name(cx.last_node_with_lint_attrs) { - Some(name) => format!("`{name}`"), - None => "this".into(), - }; - diag.multipart_suggestion_verbose( - format!("if the doc comment should not document {name} comment it out"), - suggestions, - Applicability::MaybeIncorrect, - ); - } else { - suggest_inner(cx, diag, kind, gaps); - } - - if kind == StopKind::Doc(CommentKind::Line) - && gaps - .iter() - .all(|gap| !gap.has_comment && gap.next_stop.kind == StopKind::Doc(CommentKind::Line)) - { - // Commentless empty gaps between line doc comments, possibly intended to be part of the markdown - - let indent = snippet_indent(cx, first_gap.prev_stop.span).unwrap_or_default(); - diag.multipart_suggestion_verbose( - format!("if the documentation should include the empty {lines} include {them} in the comment"), - empty_lines() - .map(|empty_line| (empty_line, format!("{indent}///"))) - .collect(), - Applicability::MaybeIncorrect, - ); - } - }, - ); - kind.is_doc() -} - -/// Returns `true` if [`EMPTY_LINE_AFTER_DOC_COMMENTS`] triggered, used to skip other doc comment -/// lints where they would be confusing -/// -/// [`EMPTY_LINE_AFTER_OUTER_ATTR`] is also here to share an implementation but does not return -/// `true` if it triggers -pub(super) fn check(cx: &LateContext<'_>, attrs: &[Attribute]) -> bool { - let mut outer = attrs - .iter() - .filter(|attr| attr.style == AttrStyle::Outer && !attr.span.from_expansion()) - .map(|attr| Stop::from_attr(cx, attr)) - .collect::<Option<Vec<_>>>() - .unwrap_or_default(); - - if outer.is_empty() { - return false; - } - - // Push a fake attribute Stop for the item itself so we check for gaps between the last outer - // attr/doc comment and the item they apply to - let span = cx.tcx.hir().span(cx.last_node_with_lint_attrs); - if !span.from_expansion() - && let Ok(line) = cx.tcx.sess.source_map().lookup_line(span.lo()) - { - outer.push(Stop { - span, - kind: StopKind::Attr, - first: line.line, - // last doesn't need to be accurate here, we don't compare it with anything - last: line.line, - }); - } - - let mut gaps = Vec::new(); - let mut last = 0; - for pos in outer - .array_windows() - .positions(|[a, b]| b.first.saturating_sub(a.last) > 1) - { - // we want to be after the first stop in the window - let pos = pos + 1; - if let Some(gap) = Gap::new(cx, &outer[last..pos], &outer[pos..]) { - last = pos; - gaps.push(gap); - } - } - - check_gaps(cx, &gaps) -} diff --git a/src/tools/clippy/clippy_lints/src/doc/mod.rs b/src/tools/clippy/clippy_lints/src/doc/mod.rs index 3d8ce7becdb..42e1f7fd950 100644 --- a/src/tools/clippy/clippy_lints/src/doc/mod.rs +++ b/src/tools/clippy/clippy_lints/src/doc/mod.rs @@ -33,7 +33,6 @@ use rustc_span::{Span, sym}; use std::ops::Range; use url::Url; -mod empty_line_after; mod include_in_doc_without_cfg; mod link_with_quotes; mod markdown; @@ -493,82 +492,6 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for empty lines after outer attributes - /// - /// ### Why is this bad? - /// The attribute may have meant to be an inner attribute (`#![attr]`). If - /// it was meant to be an outer attribute (`#[attr]`) then the empty line - /// should be removed - /// - /// ### Example - /// ```no_run - /// #[allow(dead_code)] - /// - /// fn not_quite_good_code() {} - /// ``` - /// - /// Use instead: - /// ```no_run - /// // Good (as inner attribute) - /// #![allow(dead_code)] - /// - /// fn this_is_fine() {} - /// - /// // or - /// - /// // Good (as outer attribute) - /// #[allow(dead_code)] - /// fn this_is_fine_too() {} - /// ``` - #[clippy::version = "pre 1.29.0"] - pub EMPTY_LINE_AFTER_OUTER_ATTR, - suspicious, - "empty line after outer attribute" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for empty lines after doc comments. - /// - /// ### Why is this bad? - /// The doc comment may have meant to be an inner doc comment, regular - /// comment or applied to some old code that is now commented out. If it was - /// intended to be a doc comment, then the empty line should be removed. - /// - /// ### Example - /// ```no_run - /// /// Some doc comment with a blank line after it. - /// - /// fn f() {} - /// - /// /// Docs for `old_code` - /// // fn old_code() {} - /// - /// fn new_code() {} - /// ``` - /// - /// Use instead: - /// ```no_run - /// //! Convert it to an inner doc comment - /// - /// // Or a regular comment - /// - /// /// Or remove the empty line - /// fn f() {} - /// - /// // /// Docs for `old_code` - /// // fn old_code() {} - /// - /// fn new_code() {} - /// ``` - #[clippy::version = "1.70.0"] - pub EMPTY_LINE_AFTER_DOC_COMMENTS, - suspicious, - "empty line after doc comments" -} - -declare_clippy_lint! { - /// ### What it does /// Checks if included files in doc comments are included only for `cfg(doc)`. /// /// ### Why restrict this? @@ -650,8 +573,6 @@ impl_lint_pass!(Documentation => [ EMPTY_DOCS, DOC_LAZY_CONTINUATION, DOC_OVERINDENTED_LIST_ITEMS, - EMPTY_LINE_AFTER_OUTER_ATTR, - EMPTY_LINE_AFTER_DOC_COMMENTS, TOO_LONG_FIRST_DOC_PARAGRAPH, DOC_INCLUDE_WITHOUT_CFG, ]); @@ -784,7 +705,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[ } include_in_doc_without_cfg::check(cx, attrs); - if suspicious_doc_comments::check(cx, attrs) || empty_line_after::check(cx, attrs) || is_doc_hidden(attrs) { + if suspicious_doc_comments::check(cx, attrs) || is_doc_hidden(attrs) { return None; } diff --git a/src/tools/clippy/clippy_lints/src/doc/needless_doctest_main.rs b/src/tools/clippy/clippy_lints/src/doc/needless_doctest_main.rs index 9ba2723157a..ce5beab24bf 100644 --- a/src/tools/clippy/clippy_lints/src/doc/needless_doctest_main.rs +++ b/src/tools/clippy/clippy_lints/src/doc/needless_doctest_main.rs @@ -1,10 +1,10 @@ use std::ops::Range; use std::{io, thread}; +use std::sync::Arc; use crate::doc::{NEEDLESS_DOCTEST_MAIN, TEST_ATTR_IN_DOCTEST}; 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::{Diag, DiagCtxt}; use rustc_lint::LateContext; @@ -46,8 +46,8 @@ pub fn check( rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false); let emitter = HumanEmitter::new(Box::new(io::sink()), fallback_bundle); let dcx = DiagCtxt::new(Box::new(emitter)).disable_warnings(); - #[expect(clippy::arc_with_non_send_sync)] // `Lrc` is expected by with_dcx - let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); + #[expect(clippy::arc_with_non_send_sync)] // `Arc` is expected by with_dcx + let sm = Arc::new(SourceMap::new(FilePathMapping::empty())); let psess = ParseSess::with_dcx(dcx, sm); let mut parser = match new_parser_from_source_str(&psess, filename, code) { diff --git a/src/tools/clippy/clippy_lints/src/empty_line_after.rs b/src/tools/clippy/clippy_lints/src/empty_line_after.rs new file mode 100644 index 00000000000..578ff6e38a6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/empty_line_after.rs @@ -0,0 +1,492 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::{SpanRangeExt, snippet_indent}; +use clippy_utils::tokenize_with_text; +use itertools::Itertools; +use rustc_ast::token::CommentKind; +use rustc_ast::{AssocItemKind, AttrKind, AttrStyle, Attribute, Crate, Item, ItemKind, ModKind, NodeId}; +use rustc_errors::{Applicability, Diag, SuggestionStyle}; +use rustc_lexer::TokenKind; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_session::impl_lint_pass; +use rustc_span::symbol::kw; +use rustc_span::{BytePos, ExpnKind, Ident, InnerSpan, Span, SpanData, Symbol}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for empty lines after outer attributes + /// + /// ### Why is this bad? + /// The attribute may have meant to be an inner attribute (`#![attr]`). If + /// it was meant to be an outer attribute (`#[attr]`) then the empty line + /// should be removed + /// + /// ### Example + /// ```no_run + /// #[allow(dead_code)] + /// + /// fn not_quite_good_code() {} + /// ``` + /// + /// Use instead: + /// ```no_run + /// // Good (as inner attribute) + /// #![allow(dead_code)] + /// + /// fn this_is_fine() {} + /// + /// // or + /// + /// // Good (as outer attribute) + /// #[allow(dead_code)] + /// fn this_is_fine_too() {} + /// ``` + #[clippy::version = "pre 1.29.0"] + pub EMPTY_LINE_AFTER_OUTER_ATTR, + suspicious, + "empty line after outer attribute" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for empty lines after doc comments. + /// + /// ### Why is this bad? + /// The doc comment may have meant to be an inner doc comment, regular + /// comment or applied to some old code that is now commented out. If it was + /// intended to be a doc comment, then the empty line should be removed. + /// + /// ### Example + /// ```no_run + /// /// Some doc comment with a blank line after it. + /// + /// fn f() {} + /// + /// /// Docs for `old_code` + /// // fn old_code() {} + /// + /// fn new_code() {} + /// ``` + /// + /// Use instead: + /// ```no_run + /// //! Convert it to an inner doc comment + /// + /// // Or a regular comment + /// + /// /// Or remove the empty line + /// fn f() {} + /// + /// // /// Docs for `old_code` + /// // fn old_code() {} + /// + /// fn new_code() {} + /// ``` + #[clippy::version = "1.70.0"] + pub EMPTY_LINE_AFTER_DOC_COMMENTS, + suspicious, + "empty line after doc comments" +} + +#[derive(Debug)] +struct ItemInfo { + kind: &'static str, + name: Symbol, + span: Span, + mod_items: Option<NodeId>, +} + +pub struct EmptyLineAfter { + items: Vec<ItemInfo>, +} + +impl_lint_pass!(EmptyLineAfter => [ + EMPTY_LINE_AFTER_OUTER_ATTR, + EMPTY_LINE_AFTER_DOC_COMMENTS, +]); + +impl EmptyLineAfter { + pub fn new() -> Self { + Self { items: Vec::new() } + } +} + +#[derive(Debug, PartialEq, Clone, Copy)] +enum StopKind { + Attr, + Doc(CommentKind), +} + +impl StopKind { + fn is_doc(self) -> bool { + matches!(self, StopKind::Doc(_)) + } +} + +#[derive(Debug)] +struct Stop { + span: Span, + kind: StopKind, + first: usize, + last: usize, +} + +impl Stop { + fn convert_to_inner(&self) -> (Span, String) { + let inner = match self.kind { + // #![...] + StopKind::Attr => InnerSpan::new(1, 1), + // /// or /** + // ^ ^ + StopKind::Doc(_) => InnerSpan::new(2, 3), + }; + (self.span.from_inner(inner), "!".into()) + } + + fn comment_out(&self, cx: &EarlyContext<'_>, suggestions: &mut Vec<(Span, String)>) { + match self.kind { + StopKind::Attr => { + if cx.sess().source_map().is_multiline(self.span) { + suggestions.extend([ + (self.span.shrink_to_lo(), "/* ".into()), + (self.span.shrink_to_hi(), " */".into()), + ]); + } else { + suggestions.push((self.span.shrink_to_lo(), "// ".into())); + } + }, + StopKind::Doc(CommentKind::Line) => suggestions.push((self.span.shrink_to_lo(), "// ".into())), + StopKind::Doc(CommentKind::Block) => { + // /** outer */ /*! inner */ + // ^ ^ + let asterisk = self.span.from_inner(InnerSpan::new(1, 2)); + suggestions.push((asterisk, String::new())); + }, + } + } + + fn from_attr(cx: &EarlyContext<'_>, attr: &Attribute) -> Option<Self> { + let SpanData { lo, hi, .. } = attr.span.data(); + let file = cx.sess().source_map().lookup_source_file(lo); + + Some(Self { + span: attr.span, + kind: match attr.kind { + AttrKind::Normal(_) => StopKind::Attr, + AttrKind::DocComment(comment_kind, _) => StopKind::Doc(comment_kind), + }, + first: file.lookup_line(file.relative_position(lo))?, + last: file.lookup_line(file.relative_position(hi))?, + }) + } +} + +/// Represents a set of attrs/doc comments separated by 1 or more empty lines +/// +/// ```ignore +/// /// chunk 1 docs +/// // not an empty line so also part of chunk 1 +/// #[chunk_1_attrs] // <-- prev_stop +/// +/// /* gap */ +/// +/// /// chunk 2 docs // <-- next_stop +/// #[chunk_2_attrs] +/// ``` +struct Gap<'a> { + /// The span of individual empty lines including the newline at the end of the line + empty_lines: Vec<Span>, + has_comment: bool, + next_stop: &'a Stop, + prev_stop: &'a Stop, + /// The chunk that includes [`prev_stop`](Self::prev_stop) + prev_chunk: &'a [Stop], +} + +impl<'a> Gap<'a> { + fn new(cx: &EarlyContext<'_>, prev_chunk: &'a [Stop], next_chunk: &'a [Stop]) -> Option<Self> { + let prev_stop = prev_chunk.last()?; + let next_stop = next_chunk.first()?; + let gap_span = prev_stop.span.between(next_stop.span); + let gap_snippet = gap_span.get_source_text(cx)?; + + let mut has_comment = false; + let mut empty_lines = Vec::new(); + + for (token, source, inner_span) in tokenize_with_text(&gap_snippet) { + match token { + TokenKind::BlockComment { + doc_style: None, + terminated: true, + } + | TokenKind::LineComment { doc_style: None } => has_comment = true, + TokenKind::Whitespace => { + let newlines = source.bytes().positions(|b| b == b'\n'); + empty_lines.extend( + newlines + .tuple_windows() + .map(|(a, b)| InnerSpan::new(inner_span.start + a + 1, inner_span.start + b)) + .map(|inner_span| gap_span.from_inner(inner_span)), + ); + }, + // Ignore cfg_attr'd out attributes as they may contain empty lines, could also be from macro + // shenanigans + _ => return None, + } + } + + (!empty_lines.is_empty()).then_some(Self { + empty_lines, + has_comment, + next_stop, + prev_stop, + prev_chunk, + }) + } + + fn contiguous_empty_lines(&self) -> impl Iterator<Item = Span> + '_ { + self.empty_lines + // The `+ BytePos(1)` means "next line", because each empty line span is "N:1-N:1". + .chunk_by(|a, b| a.hi() + BytePos(1) == b.lo()) + .map(|chunk| { + let first = chunk.first().expect("at least one empty line"); + let last = chunk.last().expect("at least one empty line"); + // The BytePos subtraction here is safe, as before an empty line, there must be at least one + // attribute/comment. The span needs to start at the end of the previous line. + first.with_lo(first.lo() - BytePos(1)).with_hi(last.hi()) + }) + } +} + +impl EmptyLineAfter { + fn check_gaps(&self, cx: &EarlyContext<'_>, gaps: &[Gap<'_>], id: NodeId) { + let Some(first_gap) = gaps.first() else { + return; + }; + let empty_lines = || gaps.iter().flat_map(|gap| gap.empty_lines.iter().copied()); + let contiguous_empty_lines = || gaps.iter().flat_map(Gap::contiguous_empty_lines); + let mut has_comment = false; + let mut has_attr = false; + for gap in gaps { + has_comment |= gap.has_comment; + if !has_attr { + has_attr = gap.prev_chunk.iter().any(|stop| stop.kind == StopKind::Attr); + } + } + let kind = first_gap.prev_stop.kind; + let (lint, kind_desc) = match kind { + StopKind::Attr => (EMPTY_LINE_AFTER_OUTER_ATTR, "outer attribute"), + StopKind::Doc(_) => (EMPTY_LINE_AFTER_DOC_COMMENTS, "doc comment"), + }; + let (lines, are, them) = if empty_lines().nth(1).is_some() { + ("lines", "are", "them") + } else { + ("line", "is", "it") + }; + span_lint_and_then( + cx, + lint, + first_gap.prev_stop.span.to(empty_lines().last().unwrap()), + format!("empty {lines} after {kind_desc}"), + |diag| { + let info = self.items.last().unwrap(); + diag.span_label(info.span, match kind { + StopKind::Attr => format!("the attribute applies to this {}", info.kind), + StopKind::Doc(_) => format!("the comment documents this {}", info.kind), + }); + + diag.multipart_suggestion_with_style( + format!("if the empty {lines} {are} unintentional, remove {them}"), + contiguous_empty_lines() + .map(|empty_lines| (empty_lines, String::new())) + .collect(), + Applicability::MaybeIncorrect, + SuggestionStyle::HideCodeAlways, + ); + + if has_comment && kind.is_doc() { + // Likely doc comments that applied to some now commented out code + // + // /// Old docs for Foo + // // struct Foo; + + let mut suggestions = Vec::new(); + for stop in gaps.iter().flat_map(|gap| gap.prev_chunk) { + stop.comment_out(cx, &mut suggestions); + } + diag.multipart_suggestion_verbose( + format!("if the doc comment should not document `{}` comment it out", info.name), + suggestions, + Applicability::MaybeIncorrect, + ); + } else { + self.suggest_inner(diag, kind, gaps, id); + } + + if kind == StopKind::Doc(CommentKind::Line) + && gaps + .iter() + .all(|gap| !gap.has_comment && gap.next_stop.kind == StopKind::Doc(CommentKind::Line)) + { + // Commentless empty gaps between line doc comments, possibly intended to be part of the markdown + + let indent = snippet_indent(cx, first_gap.prev_stop.span).unwrap_or_default(); + diag.multipart_suggestion_verbose( + format!("if the documentation should include the empty {lines} include {them} in the comment"), + empty_lines() + .map(|empty_line| (empty_line, format!("{indent}///"))) + .collect(), + Applicability::MaybeIncorrect, + ); + } + }, + ); + } + + /// If the node the attributes/docs apply to is the first in the module/crate suggest converting + /// them to inner attributes/docs + fn suggest_inner(&self, diag: &mut Diag<'_, ()>, kind: StopKind, gaps: &[Gap<'_>], id: NodeId) { + if let Some(parent) = self.items.iter().rev().nth(1) + && (parent.kind == "module" || parent.kind == "crate") + && parent.mod_items == Some(id) + { + let desc = if parent.kind == "module" { + "parent module" + } else { + parent.kind + }; + diag.multipart_suggestion_verbose( + match kind { + StopKind::Attr => format!("if the attribute should apply to the {desc} use an inner attribute"), + StopKind::Doc(_) => format!("if the comment should document the {desc} use an inner doc comment"), + }, + gaps.iter() + .flat_map(|gap| gap.prev_chunk) + .map(Stop::convert_to_inner) + .collect(), + Applicability::MaybeIncorrect, + ); + } + } + + fn check_item_kind( + &mut self, + cx: &EarlyContext<'_>, + kind: &ItemKind, + ident: &Ident, + span: Span, + attrs: &[Attribute], + id: NodeId, + ) { + self.items.push(ItemInfo { + kind: kind.descr(), + name: ident.name, + span: if span.contains(ident.span) { + span.with_hi(ident.span.hi()) + } else { + span.with_hi(span.lo()) + }, + mod_items: match kind { + ItemKind::Mod(_, ModKind::Loaded(items, _, _, _)) => items + .iter() + .filter(|i| !matches!(i.span.ctxt().outer_expn_data().kind, ExpnKind::AstPass(_))) + .map(|i| i.id) + .next(), + _ => None, + }, + }); + + let mut outer = attrs + .iter() + .filter(|attr| attr.style == AttrStyle::Outer && !attr.span.from_expansion()) + .map(|attr| Stop::from_attr(cx, attr)) + .collect::<Option<Vec<_>>>() + .unwrap_or_default(); + + if outer.is_empty() { + return; + } + + // Push a fake attribute Stop for the item itself so we check for gaps between the last outer + // attr/doc comment and the item they apply to + let span = self.items.last().unwrap().span; + if !span.from_expansion() + && let Ok(line) = cx.sess().source_map().lookup_line(span.lo()) + { + outer.push(Stop { + span, + kind: StopKind::Attr, + first: line.line, + // last doesn't need to be accurate here, we don't compare it with anything + last: line.line, + }); + } + + let mut gaps = Vec::new(); + let mut last = 0; + for pos in outer + .array_windows() + .positions(|[a, b]| b.first.saturating_sub(a.last) > 1) + { + // we want to be after the first stop in the window + let pos = pos + 1; + if let Some(gap) = Gap::new(cx, &outer[last..pos], &outer[pos..]) { + last = pos; + gaps.push(gap); + } + } + + self.check_gaps(cx, &gaps, id); + } +} + +impl EarlyLintPass for EmptyLineAfter { + fn check_crate(&mut self, _: &EarlyContext<'_>, krate: &Crate) { + self.items.push(ItemInfo { + kind: "crate", + name: kw::Crate, + span: krate.spans.inner_span.with_hi(krate.spans.inner_span.lo()), + mod_items: krate + .items + .iter() + .filter(|i| !matches!(i.span.ctxt().outer_expn_data().kind, ExpnKind::AstPass(_))) + .map(|i| i.id) + .next(), + }); + } + + fn check_item_post(&mut self, _: &EarlyContext<'_>, _: &Item) { + self.items.pop(); + } + fn check_impl_item_post(&mut self, _: &EarlyContext<'_>, _: &Item<AssocItemKind>) { + self.items.pop(); + } + fn check_trait_item_post(&mut self, _: &EarlyContext<'_>, _: &Item<AssocItemKind>) { + self.items.pop(); + } + + fn check_impl_item(&mut self, cx: &EarlyContext<'_>, item: &Item<AssocItemKind>) { + self.check_item_kind( + cx, + &item.kind.clone().into(), + &item.ident, + item.span, + &item.attrs, + item.id, + ); + } + + fn check_trait_item(&mut self, cx: &EarlyContext<'_>, item: &Item<AssocItemKind>) { + self.check_item_kind( + cx, + &item.kind.clone().into(), + &item.ident, + item.span, + &item.attrs, + item.id, + ); + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + self.check_item_kind(cx, &item.kind, &item.ident, item.span, &item.attrs, item.id); + } +} diff --git a/src/tools/clippy/clippy_lints/src/endian_bytes.rs b/src/tools/clippy/clippy_lints/src/endian_bytes.rs index 29deaaf3bc7..a7670ffce88 100644 --- a/src/tools/clippy/clippy_lints/src/endian_bytes.rs +++ b/src/tools/clippy/clippy_lints/src/endian_bytes.rs @@ -6,6 +6,7 @@ use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::Ty; use rustc_session::declare_lint_pass; use rustc_span::Symbol; +use std::fmt::Write; declare_clippy_lint! { /// ### What it does @@ -183,7 +184,7 @@ fn maybe_lint_endian_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, prefix: Prefix help_str.push_str("either of "); } - help_str.push_str(&format!("`{ty}::{}` ", lint.as_name(prefix))); + write!(help_str, "`{ty}::{}` ", lint.as_name(prefix)).unwrap(); if i != len && !only_one { help_str.push_str("or "); diff --git a/src/tools/clippy/clippy_lints/src/escape.rs b/src/tools/clippy/clippy_lints/src/escape.rs index a89f0d9c432..c55d4387d69 100644 --- a/src/tools/clippy/clippy_lints/src/escape.rs +++ b/src/tools/clippy/clippy_lints/src/escape.rs @@ -10,7 +10,7 @@ use rustc_session::impl_lint_pass; use rustc_span::Span; use rustc_span::def_id::LocalDefId; use rustc_span::symbol::kw; -use rustc_target::spec::abi::Abi; +use rustc_abi::ExternAbi; pub struct BoxedLocal { too_large_for_stack: u64, @@ -73,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal { fn_def_id: LocalDefId, ) { if let Some(header) = fn_kind.header() { - if header.abi != Abi::Rust { + if header.abi != ExternAbi::Rust { return; } } diff --git a/src/tools/clippy/clippy_lints/src/eta_reduction.rs b/src/tools/clippy/clippy_lints/src/eta_reduction.rs index 52b699274bb..ae3acc1c4b1 100644 --- a/src/tools/clippy/clippy_lints/src/eta_reduction.rs +++ b/src/tools/clippy/clippy_lints/src/eta_reduction.rs @@ -1,13 +1,13 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::higher::VecArgs; -use clippy_utils::source::snippet_opt; +use clippy_utils::source::{snippet_opt, snippet_with_applicability}; use clippy_utils::ty::get_type_diagnostic_name; use clippy_utils::usage::{local_used_after_expr, local_used_in}; use clippy_utils::{ get_path_from_caller_to_method_type, is_adjusted, is_no_std_crate, path_to_local, path_to_local_id, }; use rustc_errors::Applicability; -use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, Safety, TyKind}; +use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, GenericArgs, Param, PatKind, QPath, Safety, TyKind}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{ @@ -15,7 +15,7 @@ use rustc_middle::ty::{ }; use rustc_session::declare_lint_pass; use rustc_span::symbol::sym; -use rustc_target::spec::abi::Abi; +use rustc_abi::ExternAbi; use rustc_trait_selection::error_reporting::InferCtxtErrorExt as _; declare_clippy_lint! { @@ -172,7 +172,7 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx && let output = typeck.expr_ty(body.value) && let ty::Tuple(tys) = *subs.type_at(1).kind() { - cx.tcx.mk_fn_sig(tys, output, false, Safety::Safe, Abi::Rust) + cx.tcx.mk_fn_sig(tys, output, false, Safety::Safe, ExternAbi::Rust) } else { return; } @@ -239,6 +239,11 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx && !cx.tcx.has_attr(method_def_id, sym::track_caller) && check_sig(closure_sig, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder()) { + let mut app = Applicability::MachineApplicable; + let generic_args = match path.args.and_then(GenericArgs::span_ext) { + Some(span) => format!("::{}", snippet_with_applicability(cx, span, "<..>", &mut app)), + None => String::new(), + }; span_lint_and_then( cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, @@ -251,8 +256,8 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx diag.span_suggestion( expr.span, "replace the closure with the method itself", - format!("{}::{}", type_name, path.ident.name), - Applicability::MachineApplicable, + format!("{}::{}{}", type_name, path.ident.name, generic_args), + app, ); }, ); diff --git a/src/tools/clippy/clippy_lints/src/excessive_bools.rs b/src/tools/clippy/clippy_lints/src/excessive_bools.rs index 0011da03dda..15afe3e8c14 100644 --- a/src/tools/clippy/clippy_lints/src/excessive_bools.rs +++ b/src/tools/clippy/clippy_lints/src/excessive_bools.rs @@ -7,7 +7,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; use rustc_span::Span; use rustc_span::def_id::LocalDefId; -use rustc_target::spec::abi::Abi; +use rustc_abi::ExternAbi; declare_clippy_lint! { /// ### What it does @@ -145,7 +145,7 @@ impl<'tcx> LateLintPass<'tcx> for ExcessiveBools { fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx TraitItem<'tcx>) { // functions with a body are already checked by `check_fn` if let TraitItemKind::Fn(fn_sig, TraitFn::Required(_)) = &trait_item.kind - && fn_sig.header.abi == Abi::Rust + && fn_sig.header.abi == ExternAbi::Rust && fn_sig.decl.inputs.len() as u64 > self.max_fn_params_bools { check_fn_decl(cx, fn_sig.decl, fn_sig.span, self.max_fn_params_bools); @@ -162,7 +162,7 @@ impl<'tcx> LateLintPass<'tcx> for ExcessiveBools { def_id: LocalDefId, ) { if let Some(fn_header) = fn_kind.header() - && fn_header.abi == Abi::Rust + && fn_header.abi == ExternAbi::Rust && fn_decl.inputs.len() as u64 > self.max_fn_params_bools && get_parent_as_impl(cx.tcx, cx.tcx.local_def_id_to_hir_id(def_id)) .is_none_or(|impl_item| impl_item.of_trait.is_none()) diff --git a/src/tools/clippy/clippy_lints/src/excessive_nesting.rs b/src/tools/clippy/clippy_lints/src/excessive_nesting.rs index 36567b3ded0..1d3ae894944 100644 --- a/src/tools/clippy/clippy_lints/src/excessive_nesting.rs +++ b/src/tools/clippy/clippy_lints/src/excessive_nesting.rs @@ -124,7 +124,9 @@ struct NestingVisitor<'conf, 'cx> { impl NestingVisitor<'_, '_> { fn check_indent(&mut self, span: Span, id: NodeId) -> bool { - if self.nest_level > self.conf.excessive_nesting_threshold && !span.in_external_macro(self.cx.sess().source_map()) { + if self.nest_level > self.conf.excessive_nesting_threshold + && !span.in_external_macro(self.cx.sess().source_map()) + { self.conf.nodes.insert(id); return true; diff --git a/src/tools/clippy/clippy_lints/src/format_push_string.rs b/src/tools/clippy/clippy_lints/src/format_push_string.rs index 8b1f86cbb91..68cc50f3939 100644 --- a/src/tools/clippy/clippy_lints/src/format_push_string.rs +++ b/src/tools/clippy/clippy_lints/src/format_push_string.rs @@ -11,7 +11,7 @@ declare_clippy_lint! { /// Detects cases where the result of a `format!` call is /// appended to an existing `String`. /// - /// ### Why restrict this? + /// ### Why is this bad? /// Introduces an extra, avoidable heap allocation. /// /// ### Known problems @@ -35,7 +35,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.62.0"] pub FORMAT_PUSH_STRING, - restriction, + pedantic, "`format!(..)` appended to existing `String`" } declare_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]); diff --git a/src/tools/clippy/clippy_lints/src/functions/too_many_arguments.rs b/src/tools/clippy/clippy_lints/src/functions/too_many_arguments.rs index e72a2ad49d8..f17f687719f 100644 --- a/src/tools/clippy/clippy_lints/src/functions/too_many_arguments.rs +++ b/src/tools/clippy/clippy_lints/src/functions/too_many_arguments.rs @@ -1,7 +1,7 @@ use rustc_hir::{self as hir, intravisit}; use rustc_lint::LateContext; use rustc_span::Span; -use rustc_target::spec::abi::Abi; +use rustc_abi::ExternAbi; use clippy_utils::diagnostics::span_lint; use clippy_utils::is_trait_impl_item; @@ -23,11 +23,11 @@ pub(super) fn check_fn( intravisit::FnKind::Method( _, &hir::FnSig { - header: hir::FnHeader { abi: Abi::Rust, .. }, + header: hir::FnHeader { abi: ExternAbi::Rust, .. }, .. }, ) - | intravisit::FnKind::ItemFn(_, _, hir::FnHeader { abi: Abi::Rust, .. }) => check_arg_number( + | intravisit::FnKind::ItemFn(_, _, hir::FnHeader { abi: ExternAbi::Rust, .. }) => check_arg_number( cx, decl, span.with_hi(decl.output.span().hi()), @@ -41,7 +41,7 @@ pub(super) fn check_fn( pub(super) fn check_trait_item(cx: &LateContext<'_>, item: &hir::TraitItem<'_>, too_many_arguments_threshold: u64) { if let hir::TraitItemKind::Fn(ref sig, _) = item.kind { // don't lint extern functions decls, it's not their fault - if sig.header.abi == Abi::Rust { + if sig.header.abi == ExternAbi::Rust { check_arg_number( cx, sig.decl, diff --git a/src/tools/clippy/clippy_lints/src/inherent_to_string.rs b/src/tools/clippy/clippy_lints/src/inherent_to_string.rs index 415b47adac5..c58e8773e3a 100644 --- a/src/tools/clippy/clippy_lints/src/inherent_to_string.rs +++ b/src/tools/clippy/clippy_lints/src/inherent_to_string.rs @@ -5,7 +5,7 @@ use rustc_hir::{GenericParamKind, ImplItem, ImplItemKind, LangItem}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::sym; -use rustc_target::spec::abi::Abi; +use rustc_abi::ExternAbi; declare_clippy_lint! { /// ### What it does @@ -96,7 +96,7 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString { // #11201 && let header = signature.header && header.is_safe() - && header.abi == Abi::Rust + && header.abi == ExternAbi::Rust && impl_item.ident.name == sym::to_string && let decl = signature.decl && decl.implicit_self.has_implicit_self() diff --git a/src/tools/clippy/clippy_lints/src/large_futures.rs b/src/tools/clippy/clippy_lints/src/large_futures.rs index 593704f206a..fd7965d564d 100644 --- a/src/tools/clippy/clippy_lints/src/large_futures.rs +++ b/src/tools/clippy/clippy_lints/src/large_futures.rs @@ -2,11 +2,11 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::ty::implements_trait; +use rustc_abi::Size; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; -use rustc_target::abi::Size; declare_clippy_lint! { /// ### What it does diff --git a/src/tools/clippy/clippy_lints/src/large_stack_arrays.rs b/src/tools/clippy/clippy_lints/src/large_stack_arrays.rs index 6f5c5d6b3ea..c68499ce9f7 100644 --- a/src/tools/clippy/clippy_lints/src/large_stack_arrays.rs +++ b/src/tools/clippy/clippy_lints/src/large_stack_arrays.rs @@ -7,8 +7,8 @@ use clippy_utils::macros::macro_backtrace; use clippy_utils::source::snippet; use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty; +use rustc_middle::ty::layout::LayoutOf; use rustc_session::impl_lint_pass; use rustc_span::{Span, sym}; diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index 4b700673d0f..13218331a67 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -126,6 +126,7 @@ mod duplicate_mod; mod else_if_without_else; mod empty_drop; mod empty_enum; +mod empty_line_after; mod empty_with_brackets; mod endian_bytes; mod entry; @@ -217,6 +218,7 @@ mod manual_is_power_of_two; mod manual_let_else; mod manual_main_separator_str; mod manual_non_exhaustive; +mod manual_option_as_slice; mod manual_range_patterns; mod manual_rem_euclid; mod manual_retain; @@ -972,9 +974,11 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf))); store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp)); store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound)); + store.register_early_pass(|| Box::new(empty_line_after::EmptyLineAfter::new())); store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf))); store.register_late_pass(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern)); store.register_late_pass(|_| Box::<unnecessary_semicolon::UnnecessarySemicolon>::default()); store.register_late_pass(move |_| Box::new(non_std_lazy_statics::NonStdLazyStatic::new(conf))); + store.register_late_pass(|_| Box::new(manual_option_as_slice::ManualOptionAsSlice::new(conf))); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/src/tools/clippy/clippy_lints/src/loops/manual_slice_fill.rs b/src/tools/clippy/clippy_lints/src/loops/manual_slice_fill.rs new file mode 100644 index 00000000000..7c656423579 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/manual_slice_fill.rs @@ -0,0 +1,111 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::eager_or_lazy::switch_to_eager_eval; +use clippy_utils::macros::span_is_local; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::source::{HasSession, snippet_with_applicability}; +use clippy_utils::ty::implements_trait; +use clippy_utils::{higher, peel_blocks_with_stmt, span_contains_comment}; +use rustc_ast::ast::LitKind; +use rustc_ast::{RangeLimits, UnOp}; +use rustc_data_structures::packed::Pu128; +use rustc_errors::Applicability; +use rustc_hir::QPath::Resolved; +use rustc_hir::def::Res; +use rustc_hir::{Expr, ExprKind, Pat}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; +use rustc_span::sym; + +use super::MANUAL_SLICE_FILL; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, + msrv: &Msrv, +) { + if !msrv.meets(msrvs::SLICE_FILL) { + return; + } + + // `for _ in 0..slice.len() { slice[_] = value; }` + if let Some(higher::Range { + start: Some(start), + end: Some(end), + limits: RangeLimits::HalfOpen, + }) = higher::Range::hir(arg) + && let ExprKind::Lit(Spanned { + node: LitKind::Int(Pu128(0), _), + .. + }) = start.kind + && let ExprKind::Block(..) = body.kind + // Check if the body is an assignment to a slice element. + && let ExprKind::Assign(assignee, assignval, _) = peel_blocks_with_stmt(body).kind + && let ExprKind::Index(slice, _, _) = assignee.kind + // Check if `len()` is used for the range end. + && let ExprKind::MethodCall(path, recv,..) = end.kind + && path.ident.name == sym::len + // Check if the slice which is being assigned to is the same as the one being iterated over. + && let ExprKind::Path(Resolved(_, recv_path)) = recv.kind + && let ExprKind::Path(Resolved(_, slice_path)) = slice.kind + && recv_path.res == slice_path.res + && !assignval.span.from_expansion() + // It is generally not equivalent to use the `fill` method if `assignval` can have side effects + && switch_to_eager_eval(cx, assignval) + && span_is_local(assignval.span) + // The `fill` method requires that the slice's element type implements the `Clone` trait. + && let Some(clone_trait) = cx.tcx.lang_items().clone_trait() + && implements_trait(cx, cx.typeck_results().expr_ty(slice), clone_trait, &[]) + { + sugg(cx, body, expr, slice.span, assignval.span); + } + // `for _ in &mut slice { *_ = value; }` + else if let ExprKind::AddrOf(_, _, recv) = arg.kind + // Check if the body is an assignment to a slice element. + && let ExprKind::Assign(assignee, assignval, _) = peel_blocks_with_stmt(body).kind + && let ExprKind::Unary(UnOp::Deref, slice_iter) = assignee.kind + && let ExprKind::Path(Resolved(_, recv_path)) = recv.kind + // Check if the slice which is being assigned to is the same as the one being iterated over. + && let ExprKind::Path(Resolved(_, slice_path)) = slice_iter.kind + && let Res::Local(local) = slice_path.res + && local == pat.hir_id + && !assignval.span.from_expansion() + && switch_to_eager_eval(cx, assignval) + && span_is_local(assignval.span) + // The `fill` method cannot be used if the slice's element type does not implement the `Clone` trait. + && let Some(clone_trait) = cx.tcx.lang_items().clone_trait() + && implements_trait(cx, cx.typeck_results().expr_ty(recv), clone_trait, &[]) + { + sugg(cx, body, expr, recv_path.span, assignval.span); + } +} + +fn sugg<'tcx>( + cx: &LateContext<'tcx>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, + slice_span: rustc_span::Span, + assignval_span: rustc_span::Span, +) { + let mut app = if span_contains_comment(cx.sess().source_map(), body.span) { + Applicability::MaybeIncorrect // Comments may be informational. + } else { + Applicability::MachineApplicable + }; + + span_lint_and_sugg( + cx, + MANUAL_SLICE_FILL, + expr.span, + "manually filling a slice", + "try", + format!( + "{}.fill({});", + snippet_with_applicability(cx, slice_span, "..", &mut app), + snippet_with_applicability(cx, assignval_span, "..", &mut app), + ), + app, + ); +} diff --git a/src/tools/clippy/clippy_lints/src/loops/mod.rs b/src/tools/clippy/clippy_lints/src/loops/mod.rs index c5e75af2303..cdc8c18c3b7 100644 --- a/src/tools/clippy/clippy_lints/src/loops/mod.rs +++ b/src/tools/clippy/clippy_lints/src/loops/mod.rs @@ -8,6 +8,7 @@ mod iter_next_loop; mod manual_find; mod manual_flatten; mod manual_memcpy; +mod manual_slice_fill; mod manual_while_let_some; mod missing_spin_loop; mod mut_range_bound; @@ -714,6 +715,31 @@ declare_clippy_lint! { "possibly unintended infinite loop" } +declare_clippy_lint! { + /// ### What it does + /// Checks for manually filling a slice with a value. + /// + /// ### Why is this bad? + /// Using the `fill` method is more idiomatic and concise. + /// + /// ### Example + /// ```no_run + /// let mut some_slice = [1, 2, 3, 4, 5]; + /// for i in 0..some_slice.len() { + /// some_slice[i] = 0; + /// } + /// ``` + /// Use instead: + /// ```no_run + /// let mut some_slice = [1, 2, 3, 4, 5]; + /// some_slice.fill(0); + /// ``` + #[clippy::version = "1.86.0"] + pub MANUAL_SLICE_FILL, + style, + "manually filling a slice with a value" +} + pub struct Loops { msrv: Msrv, enforce_iter_loop_reborrow: bool, @@ -750,6 +776,7 @@ impl_lint_pass!(Loops => [ MANUAL_WHILE_LET_SOME, UNUSED_ENUMERATE_INDEX, INFINITE_LOOP, + MANUAL_SLICE_FILL, ]); impl<'tcx> LateLintPass<'tcx> for Loops { @@ -823,6 +850,7 @@ impl Loops { ) { let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr); if !is_manual_memcpy_triggered { + manual_slice_fill::check(cx, pat, arg, body, expr, &self.msrv); needless_range_loop::check(cx, pat, arg, body, expr); explicit_counter_loop::check(cx, pat, arg, body, expr, label); } diff --git a/src/tools/clippy/clippy_lints/src/manual_div_ceil.rs b/src/tools/clippy/clippy_lints/src/manual_div_ceil.rs index 816ca17b3d2..04357cdd8f6 100644 --- a/src/tools/clippy/clippy_lints/src/manual_div_ceil.rs +++ b/src/tools/clippy/clippy_lints/src/manual_div_ceil.rs @@ -181,13 +181,16 @@ fn build_suggestion( ExprKind::Lit(Spanned { node: LitKind::Int(_, LitIntType::Unsuffixed), .. - }) | ExprKind::Unary(UnOp::Neg, Expr { - kind: ExprKind::Lit(Spanned { - node: LitKind::Int(_, LitIntType::Unsuffixed), + }) | ExprKind::Unary( + UnOp::Neg, + Expr { + kind: ExprKind::Lit(Spanned { + node: LitKind::Int(_, LitIntType::Unsuffixed), + .. + }), .. - }), - .. - }) + } + ) ) { format!("_{}", cx.typeck_results().expr_ty(rhs)) } else { diff --git a/src/tools/clippy/clippy_lints/src/manual_option_as_slice.rs b/src/tools/clippy/clippy_lints/src/manual_option_as_slice.rs new file mode 100644 index 00000000000..5c40c945c69 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/manual_option_as_slice.rs @@ -0,0 +1,225 @@ +use clippy_config::Conf; +use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; +use clippy_utils::{is_none_arm, msrvs, peel_hir_expr_refs}; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{Arm, Expr, ExprKind, LangItem, Pat, PatKind, QPath, is_range_literal}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::impl_lint_pass; +use rustc_span::{Span, Symbol, sym}; + +declare_clippy_lint! { + /// ### What it does + /// This detects various manual reimplementations of `Option::as_slice`. + /// + /// ### Why is this bad? + /// Those implementations are both more complex than calling `as_slice` + /// and unlike that incur a branch, pessimizing performance and leading + /// to more generated code. + /// + /// ### Example + /// ```no_run + ///# let opt = Some(1); + /// _ = opt.as_ref().map_or(&[][..], std::slice::from_ref); + /// _ = match opt.as_ref() { + /// Some(f) => std::slice::from_ref(f), + /// None => &[], + /// }; + /// ``` + /// Use instead: + /// ```no_run + ///# let opt = Some(1); + /// _ = opt.as_slice(); + /// _ = opt.as_slice(); + /// ``` + #[clippy::version = "1.85.0"] + pub MANUAL_OPTION_AS_SLICE, + complexity, + "manual `Option::as_slice`" +} + +pub struct ManualOptionAsSlice { + msrv: msrvs::Msrv, +} + +impl ManualOptionAsSlice { + pub fn new(conf: &Conf) -> Self { + Self { + msrv: conf.msrv.clone(), + } + } +} + +impl_lint_pass!(ManualOptionAsSlice => [MANUAL_OPTION_AS_SLICE]); + +impl LateLintPass<'_> for ManualOptionAsSlice { + extract_msrv_attr!(LateContext); + + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + let span = expr.span; + if span.from_expansion() + || !self.msrv.meets(if clippy_utils::is_in_const_context(cx) { + msrvs::CONST_OPTION_AS_SLICE + } else { + msrvs::OPTION_AS_SLICE + }) + { + return; + } + match expr.kind { + ExprKind::Match(scrutinee, [arm1, arm2], _) => { + if is_none_arm(cx, arm2) && check_arms(cx, arm2, arm1) + || is_none_arm(cx, arm1) && check_arms(cx, arm1, arm2) + { + check_as_ref(cx, scrutinee, span); + } + }, + ExprKind::If(cond, then, Some(other)) => { + if let ExprKind::Let(let_expr) = cond.kind + && let Some(binding) = extract_ident_from_some_pat(cx, let_expr.pat) + && check_some_body(cx, binding, then) + && is_empty_slice(cx, other.peel_blocks()) + { + check_as_ref(cx, let_expr.init, span); + } + }, + ExprKind::MethodCall(seg, callee, [], _) => { + if seg.ident.name.as_str() == "unwrap_or_default" { + check_map(cx, callee, span); + } + }, + ExprKind::MethodCall(seg, callee, [or], _) => match seg.ident.name.as_str() { + "unwrap_or" => { + if is_empty_slice(cx, or) { + check_map(cx, callee, span); + } + }, + "unwrap_or_else" => { + if returns_empty_slice(cx, or) { + check_map(cx, callee, span); + } + }, + _ => {}, + }, + ExprKind::MethodCall(seg, callee, [or_else, map], _) => match seg.ident.name.as_str() { + "map_or" => { + if is_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) { + check_as_ref(cx, callee, span); + } + }, + "map_or_else" => { + if returns_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) { + check_as_ref(cx, callee, span); + } + }, + _ => {}, + }, + _ => {}, + } + } +} + +fn check_map(cx: &LateContext<'_>, map: &Expr<'_>, span: Span) { + if let ExprKind::MethodCall(seg, callee, [mapping], _) = map.kind + && seg.ident.name == sym::map + && is_slice_from_ref(cx, mapping) + { + check_as_ref(cx, callee, span); + } +} + +fn check_as_ref(cx: &LateContext<'_>, expr: &Expr<'_>, span: Span) { + if let ExprKind::MethodCall(seg, callee, [], _) = expr.kind + && seg.ident.name == sym::as_ref + && let ty::Adt(adtdef, ..) = cx.typeck_results().expr_ty(callee).kind() + && cx.tcx.is_diagnostic_item(sym::Option, adtdef.did()) + { + if let Some(snippet) = clippy_utils::source::snippet_opt(cx, callee.span) { + span_lint_and_sugg( + cx, + MANUAL_OPTION_AS_SLICE, + span, + "use `Option::as_slice`", + "use", + format!("{snippet}.as_slice()"), + Applicability::MachineApplicable, + ); + } else { + span_lint(cx, MANUAL_OPTION_AS_SLICE, span, "use `Option_as_slice`"); + } + } +} + +fn extract_ident_from_some_pat(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<Symbol> { + if let PatKind::TupleStruct(QPath::Resolved(None, path), [binding], _) = pat.kind + && let Res::Def(DefKind::Ctor(..), def_id) = path.res + && let PatKind::Binding(_mode, _hir_id, ident, _inner_pat) = binding.kind + && clippy_utils::is_lang_item_or_ctor(cx, def_id, LangItem::OptionSome) + { + Some(ident.name) + } else { + None + } +} + +/// Returns true if `expr` is `std::slice::from_ref(<name>)`. Used in `if let`s. +fn check_some_body(cx: &LateContext<'_>, name: Symbol, expr: &Expr<'_>) -> bool { + if let ExprKind::Call(slice_from_ref, [arg]) = expr.peel_blocks().kind + && is_slice_from_ref(cx, slice_from_ref) + && let ExprKind::Path(QPath::Resolved(None, path)) = arg.kind + && let [seg] = path.segments + { + seg.ident.name == name + } else { + false + } +} + +fn check_arms(cx: &LateContext<'_>, none_arm: &Arm<'_>, some_arm: &Arm<'_>) -> bool { + if none_arm.guard.is_none() + && some_arm.guard.is_none() + && is_empty_slice(cx, none_arm.body) + && let Some(name) = extract_ident_from_some_pat(cx, some_arm.pat) + { + check_some_body(cx, name, some_arm.body) + } else { + false + } +} + +fn returns_empty_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Path(_) => clippy_utils::is_path_diagnostic_item(cx, expr, sym::default_fn), + ExprKind::Closure(cl) => is_empty_slice(cx, cx.tcx.hir().body(cl.body).value), + _ => false, + } +} + +/// Returns if expr returns an empty slice. If: +/// - An indexing operation to an empty array with a built-in range. `[][..]` +/// - An indexing operation with a zero-ended range. `expr[..0]` +/// - A reference to an empty array. `&[]` +/// - Or a call to `Default::default`. +fn is_empty_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let expr = peel_hir_expr_refs(expr.peel_blocks()).0; + match expr.kind { + ExprKind::Index(arr, range, _) => match arr.kind { + ExprKind::Array([]) => is_range_literal(range), + ExprKind::Array(_) => { + let Some(range) = clippy_utils::higher::Range::hir(range) else { + return false; + }; + range.end.is_some_and(|e| clippy_utils::is_integer_const(cx, e, 0)) + }, + _ => false, + }, + ExprKind::Array([]) => true, + ExprKind::Call(def, []) => clippy_utils::is_path_diagnostic_item(cx, def, sym::default_fn), + _ => false, + } +} + +fn is_slice_from_ref(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + clippy_utils::is_expr_path_def_path(cx, expr, &["core", "slice", "raw", "from_ref"]) +} diff --git a/src/tools/clippy/clippy_lints/src/manual_unwrap_or_default.rs b/src/tools/clippy/clippy_lints/src/manual_unwrap_or_default.rs index 7b95399c907..87d2faa225c 100644 --- a/src/tools/clippy/clippy_lints/src/manual_unwrap_or_default.rs +++ b/src/tools/clippy/clippy_lints/src/manual_unwrap_or_default.rs @@ -9,8 +9,8 @@ use rustc_span::sym; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher::IfLetOrMatch; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::implements_trait; -use clippy_utils::{is_default_equivalent, is_in_const_context, peel_blocks, span_contains_comment}; +use clippy_utils::ty::{expr_type_is_certain, implements_trait}; +use clippy_utils::{is_default_equivalent, is_in_const_context, path_res, peel_blocks, span_contains_comment}; declare_clippy_lint! { /// ### What it does @@ -158,6 +158,36 @@ fn handle<'tcx>(cx: &LateContext<'tcx>, if_let_or_match: IfLetOrMatch<'tcx>, exp } else { Applicability::MachineApplicable }; + + // We now check if the condition is a None variant, in which case we need to specify the type + if path_res(cx, condition) + .opt_def_id() + .is_some_and(|id| Some(cx.tcx.parent(id)) == cx.tcx.lang_items().option_none_variant()) + { + return span_lint_and_sugg( + cx, + MANUAL_UNWRAP_OR_DEFAULT, + expr.span, + format!("{expr_name} can be simplified with `.unwrap_or_default()`"), + "replace it with", + format!("{receiver}::<{expr_type}>.unwrap_or_default()"), + applicability, + ); + } + + // We check if the expression type is still uncertain, in which case we ask the user to specify it + if !expr_type_is_certain(cx, condition) { + return span_lint_and_sugg( + cx, + MANUAL_UNWRAP_OR_DEFAULT, + expr.span, + format!("{expr_name} can be simplified with `.unwrap_or_default()`"), + format!("ascribe the type {expr_type} and replace your expression with"), + format!("{receiver}.unwrap_or_default()"), + Applicability::Unspecified, + ); + } + span_lint_and_sugg( cx, MANUAL_UNWRAP_OR_DEFAULT, diff --git a/src/tools/clippy/clippy_lints/src/matches/match_as_ref.rs b/src/tools/clippy/clippy_lints/src/matches/match_as_ref.rs index b1889d26c93..1cb4b512a30 100644 --- a/src/tools/clippy/clippy_lints/src/matches/match_as_ref.rs +++ b/src/tools/clippy/clippy_lints/src/matches/match_as_ref.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{is_res_lang_ctor, path_res, peel_blocks}; +use clippy_utils::{is_none_arm, is_res_lang_ctor, path_res, peel_blocks}; use rustc_errors::Applicability; -use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, LangItem, Mutability, PatExpr, PatExprKind, PatKind, QPath}; +use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, LangItem, Mutability, PatKind, QPath}; use rustc_lint::LateContext; use rustc_middle::ty; @@ -55,14 +55,6 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: } } -// Checks if arm has the form `None => None` -fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { - matches!( - arm.pat.kind, - PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), .. }) if is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), LangItem::OptionNone) - ) -} - // Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<Mutability> { if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind diff --git a/src/tools/clippy/clippy_lints/src/methods/mod.rs b/src/tools/clippy/clippy_lints/src/methods/mod.rs index 2f447775fa5..0a8eafad0e8 100644 --- a/src/tools/clippy/clippy_lints/src/methods/mod.rs +++ b/src/tools/clippy/clippy_lints/src/methods/mod.rs @@ -95,6 +95,7 @@ mod readonly_write_lock; mod redundant_as_str; mod repeat_once; mod result_map_or_else_none; +mod return_and_then; mod search_is_some; mod seek_from_current; mod seek_to_start_instead_of_rewind; @@ -148,6 +149,7 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item}; use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty}; pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES; +use rustc_abi::ExternAbi; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; @@ -3517,7 +3519,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.73.0"] pub FORMAT_COLLECT, - perf, + pedantic, "`format!`ing every element in a collection, then collecting the strings into a new `String`" } @@ -4365,7 +4367,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for string slices immediantly followed by `as_bytes`. + /// Checks for string slices immediately followed by `as_bytes`. /// /// ### Why is this bad? /// It involves doing an unnecessary UTF-8 alignment check which is less efficient, and can cause a panic. @@ -4391,6 +4393,46 @@ declare_clippy_lint! { "slicing a string and immediately calling as_bytes is less efficient and can lead to panics" } +declare_clippy_lint! { + /// ### What it does + /// + /// Detect functions that end with `Option::and_then` or `Result::and_then`, and suggest using a question mark (`?`) instead. + /// + /// ### Why is this bad? + /// + /// The `and_then` method is used to chain a computation that returns an `Option` or a `Result`. + /// This can be replaced with the `?` operator, which is more concise and idiomatic. + /// + /// ### Example + /// + /// ```no_run + /// fn test(opt: Option<i32>) -> Option<i32> { + /// opt.and_then(|n| { + /// if n > 1 { + /// Some(n + 1) + /// } else { + /// None + /// } + /// }) + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn test(opt: Option<i32>) -> Option<i32> { + /// let n = opt?; + /// if n > 1 { + /// Some(n + 1) + /// } else { + /// None + /// } + /// } + /// ``` + #[clippy::version = "1.86.0"] + pub RETURN_AND_THEN, + restriction, + "using `Option::and_then` or `Result::and_then` to chain a computation that returns an `Option` or a `Result`" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -4560,6 +4602,7 @@ impl_lint_pass!(Methods => [ USELESS_NONZERO_NEW_UNCHECKED, MANUAL_REPEAT_N, SLICED_STRING_AS_BYTES, + RETURN_AND_THEN, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4789,7 +4832,10 @@ impl Methods { let biom_option_linted = bind_instead_of_map::check_and_then_some(cx, expr, recv, arg); let biom_result_linted = bind_instead_of_map::check_and_then_ok(cx, expr, recv, arg); if !biom_option_linted && !biom_result_linted { - unnecessary_lazy_eval::check(cx, expr, recv, arg, "and"); + let ule_and_linted = unnecessary_lazy_eval::check(cx, expr, recv, arg, "and"); + if !ule_and_linted { + return_and_then::check(cx, expr, recv, arg); + } } }, ("any", [arg]) => { @@ -5003,7 +5049,9 @@ impl Methods { get_first::check(cx, expr, recv, arg); get_last_with_len::check(cx, expr, recv, arg); }, - ("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"), + ("get_or_insert_with", [arg]) => { + unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"); + }, ("hash", [arg]) => { unit_hash::check(cx, expr, recv, arg); }, @@ -5144,7 +5192,9 @@ impl Methods { }, _ => iter_nth_zero::check(cx, expr, recv, n_arg), }, - ("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"), + ("ok_or_else", [arg]) => { + unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"); + }, ("open", [_]) => { open_options::check(cx, expr, recv); }, @@ -5398,7 +5448,7 @@ const FN_HEADER: hir::FnHeader = hir::FnHeader { safety: hir::HeaderSafety::Normal(hir::Safety::Safe), constness: hir::Constness::NotConst, asyncness: hir::IsAsync::NotAsync, - abi: rustc_target::spec::abi::Abi::Rust, + abi: ExternAbi::Rust, }; struct ShouldImplTraitCase { @@ -5437,9 +5487,12 @@ impl ShouldImplTraitCase { fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool { self.lint_explicit_lifetime || !impl_item.generics.params.iter().any(|p| { - matches!(p.kind, hir::GenericParamKind::Lifetime { - kind: hir::LifetimeParamKind::Explicit - }) + matches!( + p.kind, + hir::GenericParamKind::Lifetime { + kind: hir::LifetimeParamKind::Explicit + } + ) }) } } diff --git a/src/tools/clippy/clippy_lints/src/methods/needless_option_take.rs b/src/tools/clippy/clippy_lints/src/methods/needless_option_take.rs index c41ce2481d7..88b9c69f6f9 100644 --- a/src/tools/clippy/clippy_lints/src/methods/needless_option_take.rs +++ b/src/tools/clippy/clippy_lints/src/methods/needless_option_take.rs @@ -1,5 +1,6 @@ -use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::is_type_diagnostic_item; +use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_span::sym; @@ -10,13 +11,22 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &' // Checks if expression type is equal to sym::Option and if the expr is not a syntactic place if !recv.is_syntactic_place_expr() && is_expr_option(cx, recv) { if let Some(function_name) = source_of_temporary_value(recv) { - span_lint_and_note( + span_lint_and_then( cx, NEEDLESS_OPTION_TAKE, expr.span, "called `Option::take()` on a temporary value", - None, - format!("`{function_name}` creates a temporary value, so calling take() has no effect"), + |diag| { + diag.note(format!( + "`{function_name}` creates a temporary value, so calling take() has no effect" + )); + diag.span_suggestion( + expr.span.with_lo(recv.span.hi()), + "remove", + "", + Applicability::MachineApplicable, + ); + }, ); } } diff --git a/src/tools/clippy/clippy_lints/src/methods/return_and_then.rs b/src/tools/clippy/clippy_lints/src/methods/return_and_then.rs new file mode 100644 index 00000000000..7b1199ad1e2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/return_and_then.rs @@ -0,0 +1,67 @@ +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, GenericArg, Ty}; +use rustc_span::sym; +use std::ops::ControlFlow; + +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_applicability}; +use clippy_utils::ty::get_type_diagnostic_name; +use clippy_utils::visitors::for_each_unconsumed_temporary; +use clippy_utils::{is_expr_final_block_expr, peel_blocks}; + +use super::RETURN_AND_THEN; + +/// lint if `and_then` is the last expression in a block, and +/// there are no references or temporaries in the receiver +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &hir::Expr<'_>, + recv: &'tcx hir::Expr<'tcx>, + arg: &'tcx hir::Expr<'_>, +) { + if !is_expr_final_block_expr(cx.tcx, expr) { + return; + } + + let recv_type = cx.typeck_results().expr_ty(recv); + if !matches!(get_type_diagnostic_name(cx, recv_type), Some(sym::Option | sym::Result)) { + return; + } + + let has_ref_type = matches!(recv_type.kind(), ty::Adt(_, args) if args + .first() + .and_then(|arg0: &GenericArg<'tcx>| GenericArg::as_type(*arg0)) + .is_some_and(Ty::is_ref)); + let has_temporaries = for_each_unconsumed_temporary(cx, recv, |_| ControlFlow::Break(())).is_break(); + if has_ref_type && has_temporaries { + return; + } + + let hir::ExprKind::Closure(&hir::Closure { body, fn_decl, .. }) = arg.kind else { + return; + }; + + let closure_arg = fn_decl.inputs[0]; + let closure_expr = peel_blocks(cx.tcx.hir().body(body).value); + + let mut applicability = Applicability::MachineApplicable; + let arg_snip = snippet_with_applicability(cx, closure_arg.span, "_", &mut applicability); + let recv_snip = snippet_with_applicability(cx, recv.span, "_", &mut applicability); + let body_snip = snippet_with_applicability(cx, closure_expr.span, "..", &mut applicability); + let inner = match body_snip.strip_prefix('{').and_then(|s| s.strip_suffix('}')) { + Some(s) => s.trim_start_matches('\n').trim_end(), + None => &body_snip, + }; + + let msg = "use the question mark operator instead of an `and_then` call"; + let sugg = format!( + "let {} = {}?;\n{}", + arg_snip, + recv_snip, + reindent_multiline(inner.into(), false, indent_of(cx, expr.span)) + ); + + span_lint_and_sugg(cx, RETURN_AND_THEN, expr.span, msg, "try", sugg, applicability); +} diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_fold.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_fold.rs index c27d1fb4903..e7adf3b43ba 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_fold.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_fold.rs @@ -123,32 +123,60 @@ pub(super) fn check( if let hir::ExprKind::Lit(lit) = init.kind { match lit.node { ast::LitKind::Bool(false) => { - check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Or, Replacement { - method_name: "any", - has_args: true, - has_generic_return: false, - }); + check_fold_with_op( + cx, + expr, + acc, + fold_span, + hir::BinOpKind::Or, + Replacement { + method_name: "any", + has_args: true, + has_generic_return: false, + }, + ); }, ast::LitKind::Bool(true) => { - check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::And, Replacement { - method_name: "all", - has_args: true, - has_generic_return: false, - }); + check_fold_with_op( + cx, + expr, + acc, + fold_span, + hir::BinOpKind::And, + Replacement { + method_name: "all", + has_args: true, + has_generic_return: false, + }, + ); }, ast::LitKind::Int(Pu128(0), _) => { - check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Add, Replacement { - method_name: "sum", - has_args: false, - has_generic_return: needs_turbofish(cx, expr), - }); + check_fold_with_op( + cx, + expr, + acc, + fold_span, + hir::BinOpKind::Add, + Replacement { + method_name: "sum", + has_args: false, + has_generic_return: needs_turbofish(cx, expr), + }, + ); }, ast::LitKind::Int(Pu128(1), _) => { - check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Mul, Replacement { - method_name: "product", - has_args: false, - has_generic_return: needs_turbofish(cx, expr), - }); + check_fold_with_op( + cx, + expr, + acc, + fold_span, + hir::BinOpKind::Mul, + Replacement { + method_name: "product", + has_args: false, + has_generic_return: needs_turbofish(cx, expr), + }, + ); }, _ => (), } diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_lazy_eval.rs index 1673a6f8b3a..7af550fa7c6 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_lazy_eval.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_lazy_eval.rs @@ -18,7 +18,7 @@ pub(super) fn check<'tcx>( recv: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>, simplify_using: &str, -) { +) -> bool { let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option); let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result); let is_bool = cx.typeck_results().expr_ty(recv).is_bool(); @@ -29,7 +29,7 @@ pub(super) fn check<'tcx>( let body_expr = &body.value; if usage::BindingUsageFinder::are_params_used(cx, body) || is_from_proc_macro(cx, expr) { - return; + return false; } if eager_or_lazy::switch_to_eager_eval(cx, body_expr) { @@ -71,8 +71,10 @@ pub(super) fn check<'tcx>( applicability, ); }); + return true; } } } } + false } diff --git a/src/tools/clippy/clippy_lints/src/misc.rs b/src/tools/clippy/clippy_lints/src/misc.rs index fa0eb9a94b7..fca416d9e64 100644 --- a/src/tools/clippy/clippy_lints/src/misc.rs +++ b/src/tools/clippy/clippy_lints/src/misc.rs @@ -168,7 +168,7 @@ impl<'tcx> LateLintPass<'tcx> for LintPass { TOPLEVEL_REF_ARG, arg.hir_id, arg.pat.span, - "`ref` directly on a function argument is ignored. \ + "`ref` directly on a function parameter does not prevent taking ownership of the passed argument. \ Consider using a reference type instead", ); } diff --git a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs index 962d85c6a9d..ba4af134ccd 100644 --- a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs +++ b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs @@ -1,3 +1,4 @@ + use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; @@ -12,7 +13,7 @@ use rustc_middle::ty; use rustc_session::impl_lint_pass; use rustc_span::Span; use rustc_span::def_id::LocalDefId; -use rustc_target::spec::abi::Abi; +use rustc_abi::ExternAbi; declare_clippy_lint! { /// ### What it does @@ -183,11 +184,11 @@ fn already_const(header: hir::FnHeader) -> bool { header.constness == Constness::Const } -fn could_be_const_with_abi(msrv: &Msrv, abi: Abi) -> bool { +fn could_be_const_with_abi(msrv: &Msrv, abi: ExternAbi) -> bool { match abi { - Abi::Rust => true, + ExternAbi::Rust => true, // `const extern "C"` was stabilized after 1.62.0 - Abi::C { unwind: false } => msrv.meets(msrvs::CONST_EXTERN_C_FN), + ExternAbi::C { unwind: false } => msrv.meets(msrvs::CONST_EXTERN_C_FN), // Rest ABIs are still unstable and need the `const_extern_fn` feature enabled. _ => msrv.meets(msrvs::CONST_EXTERN_FN), } diff --git a/src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs b/src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs index 302db2c914c..9acede4f32d 100644 --- a/src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs +++ b/src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs @@ -152,16 +152,19 @@ fn collect_unsafe_exprs<'tcx>( ExprKind::AssignOp(_, lhs, rhs) | ExprKind::Assign(lhs, rhs, _) => { if matches!( lhs.kind, - ExprKind::Path(QPath::Resolved(_, hir::Path { - res: Res::Def( - DefKind::Static { - mutability: Mutability::Mut, - .. - }, - _ - ), - .. - })) + ExprKind::Path(QPath::Resolved( + _, + hir::Path { + res: Res::Def( + DefKind::Static { + mutability: Mutability::Mut, + .. + }, + _ + ), + .. + } + )) ) { unsafe_ops.push(("modification of a mutable static occurs here", expr.span)); collect_unsafe_exprs(cx, rhs, unsafe_ops); diff --git a/src/tools/clippy/clippy_lints/src/mutex_atomic.rs b/src/tools/clippy/clippy_lints/src/mutex_atomic.rs index 86c084423b7..49fd29d1dd6 100644 --- a/src/tools/clippy/clippy_lints/src/mutex_atomic.rs +++ b/src/tools/clippy/clippy_lints/src/mutex_atomic.rs @@ -18,11 +18,14 @@ declare_clippy_lint! { /// /// On the other hand, `Mutex`es are, in general, easier to /// verify correctness. An atomic does not behave the same as - /// an equivalent mutex. See [this issue](https://github.com/rust-lang/rust-clippy/issues/4295)'s commentary for more details. + /// an equivalent mutex. See [this issue](https://github.com/rust-lang/rust-clippy/issues/4295)'s + /// commentary for more details. /// /// ### Known problems - /// This lint cannot detect if the mutex is actually used - /// for waiting before a critical section. + /// * This lint cannot detect if the mutex is actually used + /// for waiting before a critical section. + /// * This lint has a false positive that warns without considering the case + /// where `Mutex` is used together with `Condvar`. /// /// ### Example /// ```no_run @@ -48,14 +51,23 @@ declare_clippy_lint! { /// Checks for usage of `Mutex<X>` where `X` is an integral /// type. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Using a mutex just to make access to a plain integer /// sequential is /// shooting flies with cannons. `std::sync::atomic::AtomicUsize` is leaner and faster. /// + /// On the other hand, `Mutex`es are, in general, easier to + /// verify correctness. An atomic does not behave the same as + /// an equivalent mutex. See [this issue](https://github.com/rust-lang/rust-clippy/issues/4295)'s + /// commentary for more details. + /// /// ### Known problems - /// This lint cannot detect if the mutex is actually used - /// for waiting before a critical section. + /// * This lint cannot detect if the mutex is actually used + /// for waiting before a critical section. + /// * This lint has a false positive that warns without considering the case + /// where `Mutex` is used together with `Condvar`. + /// * This lint suggest using `AtomicU64` instead of `Mutex<u64>`, but + /// `AtomicU64` is not available on some 32-bit platforms. /// /// ### Example /// ```no_run @@ -70,7 +82,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "pre 1.29.0"] pub MUTEX_INTEGER, - nursery, + restriction, "using a mutex for an integer type" } @@ -108,7 +120,7 @@ fn get_atomic_name(ty: Ty<'_>) -> Option<&'static str> { UintTy::U32 => Some("AtomicU32"), UintTy::U64 => Some("AtomicU64"), UintTy::Usize => Some("AtomicUsize"), - // There's no `AtomicU128`. + // `AtomicU128` is unstable and only available on a few platforms: https://github.com/rust-lang/rust/issues/99069 UintTy::U128 => None, } }, @@ -119,7 +131,7 @@ fn get_atomic_name(ty: Ty<'_>) -> Option<&'static str> { IntTy::I32 => Some("AtomicI32"), IntTy::I64 => Some("AtomicI64"), IntTy::Isize => Some("AtomicIsize"), - // There's no `AtomicI128`. + // `AtomicU128` is unstable and only available on a few platforms: https://github.com/rust-lang/rust/issues/99069 IntTy::I128 => None, } }, diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs index b7dc269061c..996251fdf16 100644 --- a/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -20,7 +20,7 @@ use rustc_session::impl_lint_pass; use rustc_span::Span; use rustc_span::def_id::LocalDefId; use rustc_span::symbol::kw; -use rustc_target::spec::abi::Abi; +use rustc_abi::ExternAbi; declare_clippy_lint! { /// ### What it does @@ -149,7 +149,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { return; } let attrs = cx.tcx.hir().attrs(hir_id); - if header.abi != Abi::Rust || requires_exact_signature(attrs) { + if header.abi != ExternAbi::Rust || requires_exact_signature(attrs) { return; } header.is_async() diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs index 30846fb46ac..40c65d1ef9e 100644 --- a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs +++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs @@ -19,7 +19,7 @@ use rustc_session::declare_lint_pass; use rustc_span::def_id::LocalDefId; use rustc_span::symbol::kw; use rustc_span::{Span, sym}; -use rustc_target::spec::abi::Abi; +use rustc_abi::ExternAbi; use rustc_trait_selection::traits; use rustc_trait_selection::traits::misc::type_allowed_to_implement_copy; @@ -89,7 +89,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { match kind { FnKind::ItemFn(.., header) => { let attrs = cx.tcx.hir().attrs(hir_id); - if header.abi != Abi::Rust || requires_exact_signature(attrs) { + if header.abi != ExternAbi::Rust || requires_exact_signature(attrs) { return; } }, @@ -181,9 +181,14 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { && !is_copy(cx, ty) && ty.is_sized(cx.tcx, cx.typing_env()) && !allowed_traits.iter().any(|&t| { - implements_trait_with_env_from_iter(cx.tcx, cx.typing_env(), ty, t, None, [None::< - ty::GenericArg<'tcx>, - >]) + implements_trait_with_env_from_iter( + cx.tcx, + cx.typing_env(), + ty, + t, + None, + [None::<ty::GenericArg<'tcx>>], + ) }) && !implements_borrow_trait && !all_borrowable_trait diff --git a/src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs b/src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs index 2e2916c957d..b73b9083a99 100644 --- a/src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs +++ b/src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs @@ -5,7 +5,7 @@ use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::{BytePos, Pos}; -use rustc_target::spec::abi::Abi; +use rustc_abi::ExternAbi; declare_clippy_lint! { /// ### What it does @@ -46,7 +46,7 @@ impl<'tcx> LateLintPass<'tcx> for NoMangleWithRustAbi { for attr in attrs { if let Some(ident) = attr.ident() && ident.name == rustc_span::sym::no_mangle - && fn_sig.header.abi == Abi::Rust + && fn_sig.header.abi == ExternAbi::Rust && let Some((fn_attrs, _)) = fn_snippet.rsplit_once("fn") && !fn_attrs.contains("extern") { diff --git a/src/tools/clippy/clippy_lints/src/non_copy_const.rs b/src/tools/clippy/clippy_lints/src/non_copy_const.rs index 147654675ec..405bbfc9c6f 100644 --- a/src/tools/clippy/clippy_lints/src/non_copy_const.rs +++ b/src/tools/clippy/clippy_lints/src/non_copy_const.rs @@ -5,6 +5,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_in_const_context; use clippy_utils::macros::macro_backtrace; use clippy_utils::ty::{InteriorMut, implements_trait}; +use rustc_abi::VariantIdx; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::{ @@ -16,7 +17,6 @@ use rustc_middle::ty::adjustment::Adjust; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_session::impl_lint_pass; use rustc_span::{DUMMY_SP, Span, sym}; -use rustc_target::abi::VariantIdx; // FIXME: this is a correctness problem but there's no suitable // warn-by-default category. diff --git a/src/tools/clippy/clippy_lints/src/operators/mod.rs b/src/tools/clippy/clippy_lints/src/operators/mod.rs index d9845bc3b0f..9ad32c2bd39 100644 --- a/src/tools/clippy/clippy_lints/src/operators/mod.rs +++ b/src/tools/clippy/clippy_lints/src/operators/mod.rs @@ -265,9 +265,6 @@ declare_clippy_lint! { /// `x.trailing_zeros() >= 4` is much clearer than `x & 15 /// == 0` /// - /// ### Known problems - /// llvm generates better code for `x & 15 == 0` on x86 - /// /// ### Example /// ```no_run /// # let x = 1; diff --git a/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs b/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs index b2089487a9f..a3e89671eec 100644 --- a/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs +++ b/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs @@ -19,7 +19,7 @@ use rustc_middle::ty::{self, RegionKind, TyCtxt}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LocalDefId; use rustc_span::{Span, sym}; -use rustc_target::spec::abi::Abi; +use rustc_abi::ExternAbi; declare_clippy_lint! { /// ### What it does @@ -277,7 +277,7 @@ impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue { let hir_id = cx.tcx.local_def_id_to_hir_id(def_id); match kind { FnKind::ItemFn(.., header) => { - if header.abi != Abi::Rust { + if header.abi != ExternAbi::Rust { return; } let attrs = cx.tcx.hir().attrs(hir_id); diff --git a/src/tools/clippy/clippy_lints/src/precedence.rs b/src/tools/clippy/clippy_lints/src/precedence.rs index 421b2b74755..ec6835db897 100644 --- a/src/tools/clippy/clippy_lints/src/precedence.rs +++ b/src/tools/clippy/clippy_lints/src/precedence.rs @@ -3,17 +3,14 @@ use clippy_utils::source::snippet_with_applicability; use rustc_ast::ast::BinOpKind::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub}; use rustc_ast::ast::{BinOpKind, Expr, ExprKind}; use rustc_errors::Applicability; -use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_lint::{EarlyContext, EarlyLintPass, Lint}; use rustc_session::declare_lint_pass; use rustc_span::source_map::Spanned; declare_clippy_lint! { /// ### What it does - /// Checks for operations where precedence may be unclear - /// and suggests to add parentheses. Currently it catches the following: - /// * mixed usage of arithmetic and bit shifting/combining operators without - /// parentheses - /// * mixed usage of bitmasking and bit shifting operators without parentheses + /// Checks for operations where precedence may be unclear and suggests to add parentheses. + /// It catches a mixed usage of arithmetic and bit shifting/combining operators without parentheses /// /// ### Why is this bad? /// Not everyone knows the precedence of those operators by @@ -21,15 +18,32 @@ declare_clippy_lint! { /// code. /// /// ### Example - /// * `1 << 2 + 3` equals 32, while `(1 << 2) + 3` equals 7 - /// * `0x2345 & 0xF000 >> 12` equals 5, while `(0x2345 & 0xF000) >> 12` equals 2 + /// `1 << 2 + 3` equals 32, while `(1 << 2) + 3` equals 7 #[clippy::version = "pre 1.29.0"] pub PRECEDENCE, complexity, "operations where precedence may be unclear" } -declare_lint_pass!(Precedence => [PRECEDENCE]); +declare_clippy_lint! { + /// ### What it does + /// Checks for bit shifting operations combined with bit masking/combining operators + /// and suggest using parentheses. + /// + /// ### Why restrict this? + /// Not everyone knows the precedence of those operators by + /// heart, so expressions like these may trip others trying to reason about the + /// code. + /// + /// ### Example + /// `0x2345 & 0xF000 >> 12` equals 5, while `(0x2345 & 0xF000) >> 12` equals 2 + #[clippy::version = "1.86.0"] + pub PRECEDENCE_BITS, + restriction, + "operations mixing bit shifting with bit combining/masking" +} + +declare_lint_pass!(Precedence => [PRECEDENCE, PRECEDENCE_BITS]); impl EarlyLintPass for Precedence { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { @@ -38,10 +52,10 @@ impl EarlyLintPass for Precedence { } if let ExprKind::Binary(Spanned { node: op, .. }, ref left, ref right) = expr.kind { - let span_sugg = |expr: &Expr, sugg, appl| { + let span_sugg = |lint: &'static Lint, expr: &Expr, sugg, appl| { span_lint_and_sugg( cx, - PRECEDENCE, + lint, expr.span, "operator precedence might not be obvious", "consider parenthesizing your expression", @@ -57,37 +71,41 @@ impl EarlyLintPass for Precedence { match (op, get_bin_opt(left), get_bin_opt(right)) { ( BitAnd | BitOr | BitXor, - Some(Shl | Shr | Add | Div | Mul | Rem | Sub), - Some(Shl | Shr | Add | Div | Mul | Rem | Sub), + Some(left_op @ (Shl | Shr | Add | Div | Mul | Rem | Sub)), + Some(right_op @ (Shl | Shr | Add | Div | Mul | Rem | Sub)), ) - | (Shl | Shr, Some(Add | Div | Mul | Rem | Sub), Some(Add | Div | Mul | Rem | Sub)) => { + | ( + Shl | Shr, + Some(left_op @ (Add | Div | Mul | Rem | Sub)), + Some(right_op @ (Add | Div | Mul | Rem | Sub)), + ) => { let sugg = format!( "({}) {} ({})", snippet_with_applicability(cx, left.span, "..", &mut applicability), op.as_str(), snippet_with_applicability(cx, right.span, "..", &mut applicability) ); - span_sugg(expr, sugg, applicability); + span_sugg(lint_for(&[op, left_op, right_op]), expr, sugg, applicability); }, - (BitAnd | BitOr | BitXor, Some(Shl | Shr | Add | Div | Mul | Rem | Sub), _) - | (Shl | Shr, Some(Add | Div | Mul | Rem | Sub), _) => { + (BitAnd | BitOr | BitXor, Some(side_op @ (Shl | Shr | Add | Div | Mul | Rem | Sub)), _) + | (Shl | Shr, Some(side_op @ (Add | Div | Mul | Rem | Sub)), _) => { let sugg = format!( "({}) {} {}", snippet_with_applicability(cx, left.span, "..", &mut applicability), op.as_str(), snippet_with_applicability(cx, right.span, "..", &mut applicability) ); - span_sugg(expr, sugg, applicability); + span_sugg(lint_for(&[op, side_op]), expr, sugg, applicability); }, - (BitAnd | BitOr | BitXor, _, Some(Shl | Shr | Add | Div | Mul | Rem | Sub)) - | (Shl | Shr, _, Some(Add | Div | Mul | Rem | Sub)) => { + (BitAnd | BitOr | BitXor, _, Some(side_op @ (Shl | Shr | Add | Div | Mul | Rem | Sub))) + | (Shl | Shr, _, Some(side_op @ (Add | Div | Mul | Rem | Sub))) => { let sugg = format!( "{} {} ({})", snippet_with_applicability(cx, left.span, "..", &mut applicability), op.as_str(), snippet_with_applicability(cx, right.span, "..", &mut applicability) ); - span_sugg(expr, sugg, applicability); + span_sugg(lint_for(&[op, side_op]), expr, sugg, applicability); }, _ => (), } @@ -106,3 +124,11 @@ fn get_bin_opt(expr: &Expr) -> Option<BinOpKind> { fn is_bit_op(op: BinOpKind) -> bool { matches!(op, BitXor | BitAnd | BitOr | Shl | Shr) } + +fn lint_for(ops: &[BinOpKind]) -> &'static Lint { + if ops.iter().all(|op| is_bit_op(*op)) { + PRECEDENCE_BITS + } else { + PRECEDENCE + } +} diff --git a/src/tools/clippy/clippy_lints/src/ptr.rs b/src/tools/clippy/clippy_lints/src/ptr.rs index 506adf0f2cc..7fba4b6a6c8 100644 --- a/src/tools/clippy/clippy_lints/src/ptr.rs +++ b/src/tools/clippy/clippy_lints/src/ptr.rs @@ -1,5 +1,6 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then}; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::source::SpanRangeExt; +use clippy_utils::sugg::Sugg; use clippy_utils::visitors::contains_unsafe_block; use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, std_or_core}; use hir::LifetimeName; @@ -18,7 +19,7 @@ use rustc_middle::ty::{self, Binder, ClauseKind, ExistentialPredicate, List, Pre use rustc_session::declare_lint_pass; use rustc_span::symbol::Symbol; use rustc_span::{Span, sym}; -use rustc_target::spec::abi::Abi; +use rustc_abi::ExternAbi; use rustc_trait_selection::infer::InferCtxtExt as _; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use std::{fmt, iter}; @@ -159,7 +160,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { check_mut_from_ref(cx, sig, None); - if !matches!(sig.header.abi, Abi::Rust) { + if !matches!(sig.header.abi, ExternAbi::Rust) { // Ignore `extern` functions with non-Rust calling conventions return; } @@ -219,7 +220,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { check_mut_from_ref(cx, sig, Some(body)); - if !matches!(sig.header.abi, Abi::Rust) { + if !matches!(sig.header.abi, ExternAbi::Rust) { // Ignore `extern` functions with non-Rust calling conventions return; } @@ -250,15 +251,24 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let ExprKind::Binary(ref op, l, r) = expr.kind { - if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(cx, l) || is_null_path(cx, r)) { - span_lint( - cx, - CMP_NULL, - expr.span, - "comparing with null is better expressed by the `.is_null()` method", - ); - } + if let ExprKind::Binary(op, l, r) = expr.kind + && (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) + { + let non_null_path_snippet = match (is_null_path(cx, l), is_null_path(cx, r)) { + (true, false) if let Some(sugg) = Sugg::hir_opt(cx, r) => sugg.maybe_par(), + (false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_par(), + _ => return, + }; + + span_lint_and_sugg( + cx, + CMP_NULL, + expr.span, + "comparing with null is better expressed by the `.is_null()` method", + "try", + format!("{non_null_path_snippet}.is_null()"), + Applicability::MachineApplicable, + ); } else { check_invalid_ptr_usage(cx, expr); } diff --git a/src/tools/clippy/clippy_lints/src/redundant_clone.rs b/src/tools/clippy/clippy_lints/src/redundant_clone.rs index b9e0106fc86..fb1bc494bd9 100644 --- a/src/tools/clippy/clippy_lints/src/redundant_clone.rs +++ b/src/tools/clippy/clippy_lints/src/redundant_clone.rs @@ -349,10 +349,14 @@ fn visit_clone_usage(cloned: mir::Local, clone: mir::Local, mir: &mir::Body<'_>, local_use_locs: _, local_consume_or_mutate_locs: clone_consume_or_mutate_locs, }, - )) = visit_local_usage(&[cloned, clone], mir, mir::Location { - block: bb, - statement_index: mir.basic_blocks[bb].statements.len(), - }) + )) = visit_local_usage( + &[cloned, clone], + mir, + mir::Location { + block: bb, + statement_index: mir.basic_blocks[bb].statements.len(), + }, + ) .map(|mut vec| (vec.remove(0), vec.remove(0))) { CloneUsage { diff --git a/src/tools/clippy/clippy_lints/src/redundant_else.rs b/src/tools/clippy/clippy_lints/src/redundant_else.rs index 3476f56cf33..a1b5a3aff32 100644 --- a/src/tools/clippy/clippy_lints/src/redundant_else.rs +++ b/src/tools/clippy/clippy_lints/src/redundant_else.rs @@ -1,8 +1,12 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{indent_of, reindent_multiline, snippet}; use rustc_ast::ast::{Block, Expr, ExprKind, Stmt, StmtKind}; use rustc_ast::visit::{Visitor, walk_expr}; +use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_session::declare_lint_pass; +use rustc_span::Span; +use std::borrow::Cow; declare_clippy_lint! { /// ### What it does @@ -75,13 +79,27 @@ impl EarlyLintPass for RedundantElse { _ => break, } } - span_lint_and_help( + + let mut app = Applicability::MachineApplicable; + if let ExprKind::Block(block, _) = &els.kind { + for stmt in &block.stmts { + // If the `else` block contains a local binding or a macro invocation, Clippy shouldn't auto-fix it + if matches!(&stmt.kind, StmtKind::Let(_) | StmtKind::MacCall(_)) { + app = Applicability::Unspecified; + break; + } + } + } + + // FIXME: The indentation of the suggestion would be the same as the one of the macro invocation in this implementation, see https://github.com/rust-lang/rust-clippy/pull/13936#issuecomment-2569548202 + span_lint_and_sugg( cx, REDUNDANT_ELSE, - els.span, + els.span.with_lo(then.span.hi()), "redundant else block", - None, "remove the `else` block and move the contents out", + make_sugg(cx, els.span, "..", Some(expr.span)).to_string(), + app, ); } } @@ -136,3 +154,23 @@ impl BreakVisitor { self.check(stmt, Self::visit_stmt) } } + +// Extract the inner contents of an `else` block str +// e.g. `{ foo(); bar(); }` -> `foo(); bar();` +fn extract_else_block(mut block: &str) -> String { + block = block.strip_prefix("{").unwrap_or(block); + block = block.strip_suffix("}").unwrap_or(block); + block.trim_end().to_string() +} + +fn make_sugg<'a>( + cx: &EarlyContext<'_>, + els_span: Span, + default: &'a str, + indent_relative_to: Option<Span>, +) -> Cow<'a, str> { + let extracted = extract_else_block(&snippet(cx, els_span, default)); + let indent = indent_relative_to.and_then(|s| indent_of(cx, s)); + + reindent_multiline(extracted.into(), false, indent) +} diff --git a/src/tools/clippy/clippy_lints/src/same_name_method.rs b/src/tools/clippy/clippy_lints/src/same_name_method.rs index 8d31641d483..29914d4379f 100644 --- a/src/tools/clippy/clippy_lints/src/same_name_method.rs +++ b/src/tools/clippy/clippy_lints/src/same_name_method.rs @@ -62,10 +62,13 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { && let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind { if !map.contains_key(res) { - map.insert(*res, ExistingName { - impl_methods: BTreeMap::new(), - trait_methods: BTreeMap::new(), - }); + map.insert( + *res, + ExistingName { + impl_methods: BTreeMap::new(), + trait_methods: BTreeMap::new(), + }, + ); } let existing_name = map.get_mut(res).unwrap(); diff --git a/src/tools/clippy/clippy_lints/src/size_of_in_element_count.rs b/src/tools/clippy/clippy_lints/src/size_of_in_element_count.rs index f72ff10dd43..b22c638fc36 100644 --- a/src/tools/clippy/clippy_lints/src/size_of_in_element_count.rs +++ b/src/tools/clippy/clippy_lints/src/size_of_in_element_count.rs @@ -63,8 +63,7 @@ fn get_pointee_ty_and_count_expr<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ) -> Option<(Ty<'tcx>, &'tcx Expr<'tcx>)> { - const METHODS: [&str; 11] = [ - "write_bytes", + const METHODS: [&str; 10] = [ "copy_to", "copy_from", "copy_to_nonoverlapping", @@ -79,7 +78,7 @@ fn get_pointee_ty_and_count_expr<'tcx>( if let ExprKind::Call(func, [.., count]) = expr.kind // Find calls to ptr::{copy, copy_nonoverlapping} - // and ptr::{swap_nonoverlapping, write_bytes}, + // and ptr::swap_nonoverlapping, && let ExprKind::Path(ref func_qpath) = func.kind && let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id() && matches!(cx.tcx.get_diagnostic_name(def_id), Some( @@ -88,7 +87,6 @@ fn get_pointee_ty_and_count_expr<'tcx>( | sym::ptr_slice_from_raw_parts | sym::ptr_slice_from_raw_parts_mut | sym::ptr_swap_nonoverlapping - | sym::ptr_write_bytes | sym::slice_from_raw_parts | sym::slice_from_raw_parts_mut )) @@ -99,7 +97,7 @@ fn get_pointee_ty_and_count_expr<'tcx>( return Some((pointee_ty, count)); } if let ExprKind::MethodCall(method_path, ptr_self, [.., count], _) = expr.kind - // Find calls to copy_{from,to}{,_nonoverlapping} and write_bytes methods + // Find calls to copy_{from,to}{,_nonoverlapping} && let method_ident = method_path.ident.as_str() && METHODS.iter().any(|m| *m == method_ident) @@ -121,6 +119,8 @@ impl<'tcx> LateLintPass<'tcx> for SizeOfInElementCount { instead of a count of elements of `T`"; if let Some((pointee_ty, count_expr)) = get_pointee_ty_and_count_expr(cx, expr) + // Using a number of bytes for a byte type isn't suspicious + && pointee_ty != cx.tcx.types.u8 // Find calls to functions with an element count parameter and get // the pointee type and count parameter expression diff --git a/src/tools/clippy/clippy_lints/src/to_digit_is_some.rs b/src/tools/clippy/clippy_lints/src/to_digit_is_some.rs index 569812d8106..9993e6ae18b 100644 --- a/src/tools/clippy/clippy_lints/src/to_digit_is_some.rs +++ b/src/tools/clippy/clippy_lints/src/to_digit_is_some.rs @@ -55,13 +55,11 @@ impl<'tcx> LateLintPass<'tcx> for ToDigitIsSome { if let hir::ExprKind::Path(to_digits_path) = &to_digits_call.kind && let to_digits_call_res = cx.qpath_res(to_digits_path, to_digits_call.hir_id) && let Some(to_digits_def_id) = to_digits_call_res.opt_def_id() - && match_def_path(cx, to_digits_def_id, &[ - "core", - "char", - "methods", - "<impl char>", - "to_digit", - ]) + && match_def_path( + cx, + to_digits_def_id, + &["core", "char", "methods", "<impl char>", "to_digit"], + ) { Some((false, char_arg, radix_arg)) } else { diff --git a/src/tools/clippy/clippy_lints/src/types/mod.rs b/src/tools/clippy/clippy_lints/src/types/mod.rs index 391c36df492..579cbf447a2 100644 --- a/src/tools/clippy/clippy_lints/src/types/mod.rs +++ b/src/tools/clippy/clippy_lints/src/types/mod.rs @@ -386,22 +386,30 @@ impl<'tcx> LateLintPass<'tcx> for Types { let is_exported = cx.effective_visibilities.is_exported(def_id); - self.check_fn_decl(cx, decl, CheckTyContext { - is_in_trait_impl, - in_body: matches!(fn_kind, FnKind::Closure), - is_exported, - ..CheckTyContext::default() - }); + self.check_fn_decl( + cx, + decl, + CheckTyContext { + is_in_trait_impl, + in_body: matches!(fn_kind, FnKind::Closure), + is_exported, + ..CheckTyContext::default() + }, + ); } fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { let is_exported = cx.effective_visibilities.is_exported(item.owner_id.def_id); match item.kind { - ItemKind::Static(ty, _, _) | ItemKind::Const(ty, _, _) => self.check_ty(cx, ty, CheckTyContext { - is_exported, - ..CheckTyContext::default() - }), + ItemKind::Static(ty, _, _) | ItemKind::Const(ty, _, _) => self.check_ty( + cx, + ty, + CheckTyContext { + is_exported, + ..CheckTyContext::default() + }, + ), // functions, enums, structs, impls and traits are covered _ => (), } @@ -419,10 +427,14 @@ impl<'tcx> LateLintPass<'tcx> for Types { false }; - self.check_ty(cx, ty, CheckTyContext { - is_in_trait_impl, - ..CheckTyContext::default() - }); + self.check_ty( + cx, + ty, + CheckTyContext { + is_in_trait_impl, + ..CheckTyContext::default() + }, + ); }, // Methods are covered by check_fn. // Type aliases are ignored because oftentimes it's impossible to @@ -438,10 +450,14 @@ impl<'tcx> LateLintPass<'tcx> for Types { let is_exported = cx.effective_visibilities.is_exported(field.def_id); - self.check_ty(cx, field.ty, CheckTyContext { - is_exported, - ..CheckTyContext::default() - }); + self.check_ty( + cx, + field.ty, + CheckTyContext { + is_exported, + ..CheckTyContext::default() + }, + ); } fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &TraitItem<'tcx>) { @@ -469,10 +485,14 @@ impl<'tcx> LateLintPass<'tcx> for Types { fn check_local(&mut self, cx: &LateContext<'tcx>, local: &LetStmt<'tcx>) { if let Some(ty) = local.ty { - self.check_ty(cx, ty, CheckTyContext { - in_body: true, - ..CheckTyContext::default() - }); + self.check_ty( + cx, + ty, + CheckTyContext { + in_body: true, + ..CheckTyContext::default() + }, + ); } } } diff --git a/src/tools/clippy/clippy_lints/src/types/type_complexity.rs b/src/tools/clippy/clippy_lints/src/types/type_complexity.rs index 7f51660293b..0704653385f 100644 --- a/src/tools/clippy/clippy_lints/src/types/type_complexity.rs +++ b/src/tools/clippy/clippy_lints/src/types/type_complexity.rs @@ -1,9 +1,9 @@ use clippy_utils::diagnostics::span_lint; +use rustc_abi::ExternAbi; use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_ty}; use rustc_hir::{self as hir, AmbigArg, GenericParamKind, TyKind}; use rustc_lint::LateContext; use rustc_span::Span; -use rustc_target::spec::abi::Abi; use super::TYPE_COMPLEXITY; @@ -50,7 +50,7 @@ impl<'tcx> Visitor<'tcx> for TypeComplexityVisitor { TyKind::Path(..) | TyKind::Slice(..) | TyKind::Tup(..) | TyKind::Array(..) => (10 * self.nest, 1), // function types bring a lot of overhead - TyKind::BareFn(bare) if bare.abi == Abi::Rust => (50 * self.nest, 1), + TyKind::BareFn(bare) if bare.abi == ExternAbi::Rust => (50 * self.nest, 1), TyKind::TraitObject(param_bounds, _) => { let has_lifetime_parameters = param_bounds.iter().any(|bound| { diff --git a/src/tools/clippy/clippy_lints/src/unconditional_recursion.rs b/src/tools/clippy/clippy_lints/src/unconditional_recursion.rs index 3fc08e8192d..207f2ef4563 100644 --- a/src/tools/clippy/clippy_lints/src/unconditional_recursion.rs +++ b/src/tools/clippy/clippy_lints/src/unconditional_recursion.rs @@ -257,10 +257,13 @@ fn is_default_method_on_current_ty<'tcx>(tcx: TyCtxt<'tcx>, qpath: QPath<'tcx>, } if matches!( ty.kind, - TyKind::Path(QPath::Resolved(_, hir::Path { - res: Res::SelfTyAlias { .. }, - .. - },)) + TyKind::Path(QPath::Resolved( + _, + hir::Path { + res: Res::SelfTyAlias { .. }, + .. + }, + )) ) { return true; } diff --git a/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs b/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs index 5e5d6a9e333..b3d26908093 100644 --- a/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -1,3 +1,4 @@ +use std::sync::Arc; use std::ops::ControlFlow; use clippy_config::Conf; @@ -6,7 +7,6 @@ use clippy_utils::is_lint_allowed; use clippy_utils::source::walk_span_to_context; use clippy_utils::visitors::{Descend, for_each_expr}; use hir::HirId; -use rustc_data_structures::sync::Lrc; use rustc_hir as hir; use rustc_hir::{Block, BlockCheckMode, ItemKind, Node, UnsafeSource}; use rustc_lexer::{TokenKind, tokenize}; @@ -480,7 +480,7 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf if let Some(comment_start) = comment_start && let Ok(unsafe_line) = source_map.lookup_line(item.span.lo()) && let Ok(comment_start_line) = source_map.lookup_line(comment_start) - && Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf) + && Arc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf) && let Some(src) = unsafe_line.sf.src.as_deref() { return if comment_start_line.line >= unsafe_line.line { @@ -520,7 +520,7 @@ fn stmt_has_safety_comment(cx: &LateContext<'_>, span: Span, hir_id: HirId) -> H if let Some(comment_start) = comment_start && let Ok(unsafe_line) = source_map.lookup_line(span.lo()) && let Ok(comment_start_line) = source_map.lookup_line(comment_start) - && Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf) + && Arc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf) && let Some(src) = unsafe_line.sf.src.as_deref() { return if comment_start_line.line >= unsafe_line.line { @@ -580,7 +580,7 @@ fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span // ^--------------------------------------------^ if let Ok(unsafe_line) = source_map.lookup_line(span.lo()) && let Ok(macro_line) = source_map.lookup_line(ctxt.outer_expn_data().def_site.lo()) - && Lrc::ptr_eq(&unsafe_line.sf, ¯o_line.sf) + && Arc::ptr_eq(&unsafe_line.sf, ¯o_line.sf) && let Some(src) = unsafe_line.sf.src.as_deref() { if macro_line.line < unsafe_line.line { @@ -641,7 +641,7 @@ fn span_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool { if let Ok(unsafe_line) = source_map.lookup_line(span.lo()) && let Some(body_span) = walk_span_to_context(search_span, SyntaxContext::root()) && let Ok(body_line) = source_map.lookup_line(body_span.lo()) - && Lrc::ptr_eq(&unsafe_line.sf, &body_line.sf) + && Arc::ptr_eq(&unsafe_line.sf, &body_line.sf) && let Some(src) = unsafe_line.sf.src.as_deref() { // Get the text from the start of function body to the unsafe block. diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_semicolon.rs b/src/tools/clippy/clippy_lints/src/unnecessary_semicolon.rs index efbc536dcb4..e5267620c4f 100644 --- a/src/tools/clippy/clippy_lints/src/unnecessary_semicolon.rs +++ b/src/tools/clippy/clippy_lints/src/unnecessary_semicolon.rs @@ -37,7 +37,7 @@ declare_clippy_lint! { #[derive(Default)] pub struct UnnecessarySemicolon { - last_statements: Vec<HirId>, + last_statements: Vec<(HirId, bool)>, } impl_lint_pass!(UnnecessarySemicolon => [UNNECESSARY_SEMICOLON]); @@ -45,27 +45,25 @@ impl_lint_pass!(UnnecessarySemicolon => [UNNECESSARY_SEMICOLON]); impl UnnecessarySemicolon { /// Enter or leave a block, remembering the last statement of the block. fn handle_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>, enter: bool) { - // Up to edition 2021, removing the semicolon of the last statement of a block - // may result in the scrutinee temporary values to live longer than the block - // variables. To avoid this problem, we do not lint the last statement of an - // expressionless block. - if cx.tcx.sess.edition() <= Edition2021 - && block.expr.is_none() + // The last statement of an expressionless block deserves a special treatment. + if block.expr.is_none() && let Some(last_stmt) = block.stmts.last() { if enter { - self.last_statements.push(last_stmt.hir_id); + let block_ty = cx.typeck_results().node_type(block.hir_id); + self.last_statements.push((last_stmt.hir_id, block_ty.is_unit())); } else { self.last_statements.pop(); } } } - /// Checks if `stmt` is the last statement in an expressionless block for edition ≤ 2021. - fn is_last_in_block(&self, stmt: &Stmt<'_>) -> bool { + /// Checks if `stmt` is the last statement in an expressionless block. In this case, + /// return `Some` with a boolean which is `true` if the block type is `()`. + fn is_last_in_block(&self, stmt: &Stmt<'_>) -> Option<bool> { self.last_statements .last() - .is_some_and(|last_stmt_id| last_stmt_id == &stmt.hir_id) + .and_then(|&(stmt_id, is_unit)| (stmt_id == stmt.hir_id).then_some(is_unit)) } } @@ -90,8 +88,22 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessarySemicolon { ) && cx.typeck_results().expr_ty(expr) == cx.tcx.types.unit { - if self.is_last_in_block(stmt) && leaks_droppable_temporary_with_limited_lifetime(cx, expr) { - return; + if let Some(block_is_unit) = self.is_last_in_block(stmt) { + if cx.tcx.sess.edition() <= Edition2021 && leaks_droppable_temporary_with_limited_lifetime(cx, expr) { + // The expression contains temporaries with limited lifetimes in edition lower than 2024. Those may + // survive until after the end of the current scope instead of until the end of the statement, so do + // not lint this situation. + return; + } + + if !block_is_unit { + // Although the expression returns `()`, the block doesn't. This may happen if the expression + // returns early in all code paths, such as a `return value` in the condition of an `if` statement, + // in which case the block type would be `!`. Do not lint in this case, as the statement would + // become the block expression; the block type would become `()` and this may not type correctly + // if the expected type for the block is not `()`. + return; + } } let semi_span = expr.span.shrink_to_hi().to(stmt.span.shrink_to_hi()); diff --git a/src/tools/clippy/clippy_lints/src/unneeded_struct_pattern.rs b/src/tools/clippy/clippy_lints/src/unneeded_struct_pattern.rs index 40ba70d451d..a74eab8b6ae 100644 --- a/src/tools/clippy/clippy_lints/src/unneeded_struct_pattern.rs +++ b/src/tools/clippy/clippy_lints/src/unneeded_struct_pattern.rs @@ -32,7 +32,7 @@ declare_clippy_lint! { /// None => 0, /// }; /// ``` - #[clippy::version = "1.83.0"] + #[clippy::version = "1.86.0"] pub UNNEEDED_STRUCT_PATTERN, style, "using struct pattern to match against unit variant" diff --git a/src/tools/clippy/clippy_lints/src/utils/attr_collector.rs b/src/tools/clippy/clippy_lints/src/utils/attr_collector.rs index 1522553bbf5..169429811d3 100644 --- a/src/tools/clippy/clippy_lints/src/utils/attr_collector.rs +++ b/src/tools/clippy/clippy_lints/src/utils/attr_collector.rs @@ -1,14 +1,13 @@ use std::mem; -use std::sync::OnceLock; +use std::sync::{Arc, OnceLock}; use rustc_ast::{Attribute, Crate}; -use rustc_data_structures::sync::Lrc; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::impl_lint_pass; use rustc_span::Span; #[derive(Clone, Default)] -pub struct AttrStorage(pub Lrc<OnceLock<Vec<Span>>>); +pub struct AttrStorage(pub Arc<OnceLock<Vec<Span>>>); pub struct AttrCollector { storage: AttrStorage, diff --git a/src/tools/clippy/clippy_lints/src/write.rs b/src/tools/clippy/clippy_lints/src/write.rs index 31ae002e47d..11c14c14777 100644 --- a/src/tools/clippy/clippy_lints/src/write.rs +++ b/src/tools/clippy/clippy_lints/src/write.rs @@ -522,7 +522,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { let replacement = match (format_string_is_raw, replace_raw) { (false, false) => Some(replacement), - (false, true) => Some(replacement.replace('"', "\\\"").replace('\\', "\\\\")), + (false, true) => Some(replacement.replace('\\', "\\\\").replace('"', "\\\"")), (true, false) => match conservative_unescape(&replacement) { Ok(unescaped) => Some(unescaped), Err(UnescapeErr::Lint) => None, diff --git a/src/tools/clippy/clippy_utils/README.md b/src/tools/clippy/clippy_utils/README.md index 251e3dfe41b..41f3b1cbd50 100644 --- a/src/tools/clippy/clippy_utils/README.md +++ b/src/tools/clippy/clippy_utils/README.md @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain: <!-- begin autogenerated nightly --> ``` -nightly-2025-01-28 +nightly-2025-02-06 ``` <!-- end autogenerated nightly --> diff --git a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs index 2eb09bac8d8..ab5f97199ce 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs @@ -362,18 +362,21 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { defaultness: ld, sig: lf, generics: lg, + contract: lc, body: lb, }), Fn(box ast::Fn { defaultness: rd, sig: rf, generics: rg, + contract: rc, body: rb, }), ) => { eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) + && eq_opt_fn_contract(lc, rc) && both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r)) }, (Mod(lu, lmk), Mod(ru, rmk)) => { @@ -497,18 +500,21 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool { defaultness: ld, sig: lf, generics: lg, + contract: lc, body: lb, }), Fn(box ast::Fn { defaultness: rd, sig: rf, generics: rg, + contract: rc, body: rb, }), ) => { eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) + && eq_opt_fn_contract(lc, rc) && both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r)) }, ( @@ -559,18 +565,21 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { defaultness: ld, sig: lf, generics: lg, + contract: lc, body: lb, }), Fn(box ast::Fn { defaultness: rd, sig: rf, generics: rg, + contract: rc, body: rb, }), ) => { eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) + && eq_opt_fn_contract(lc, rc) && both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r)) }, ( @@ -653,6 +662,17 @@ pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool { && eq_ext(&l.ext, &r.ext) } +#[expect(clippy::ref_option, reason = "This is the type how it is stored in the AST")] +pub fn eq_opt_fn_contract(l: &Option<P<FnContract>>, r: &Option<P<FnContract>>) -> bool { + match (l, r) { + (Some(l), Some(r)) => { + eq_expr_opt(l.requires.as_ref(), r.requires.as_ref()) && eq_expr_opt(l.ensures.as_ref(), r.ensures.as_ref()) + }, + (None, None) => true, + (Some(_), None) | (None, Some(_)) => false, + } +} + pub fn eq_generics(l: &Generics, r: &Generics) -> bool { over(&l.params, &r.params, eq_generic_param) && over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| { diff --git a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs index 179d42a8b5d..59aaaa3d9fb 100644 --- a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs +++ b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs @@ -12,6 +12,7 @@ //! code was written, and check if the span contains that text. Note this will only work correctly //! if the span is not from a `macro_rules` based macro. +use rustc_abi::ExternAbi; use rustc_ast::AttrStyle; use rustc_ast::ast::{AttrKind, Attribute, IntTy, LitIntType, LitKind, StrStyle, TraitObjectSyntax, UintTy}; use rustc_ast::token::CommentKind; @@ -26,7 +27,6 @@ use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_span::symbol::{Ident, kw}; use rustc_span::{Span, Symbol}; -use rustc_target::spec::abi::Abi; /// The search pattern to look for. Used by `span_matches_pat` #[derive(Clone)] @@ -233,7 +233,7 @@ fn fn_header_search_pat(header: FnHeader) -> Pat { Pat::Str("const") } else if header.is_unsafe() { Pat::Str("unsafe") - } else if header.abi != Abi::Rust { + } else if header.abi != ExternAbi::Rust { Pat::Str("extern") } else { Pat::MultiStr(&["fn", "extern"]) @@ -375,7 +375,7 @@ fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) { TyKind::BareFn(bare_fn) => ( if bare_fn.safety.is_unsafe() { Pat::Str("unsafe") - } else if bare_fn.abi != Abi::Rust { + } else if bare_fn.abi != ExternAbi::Rust { Pat::Str("extern") } else { Pat::MultiStr(&["fn", "extern"]) diff --git a/src/tools/clippy/clippy_utils/src/consts.rs b/src/tools/clippy/clippy_utils/src/consts.rs index a660623f418..db82c458f70 100644 --- a/src/tools/clippy/clippy_utils/src/consts.rs +++ b/src/tools/clippy/clippy_utils/src/consts.rs @@ -4,13 +4,15 @@ //! executable MIR bodies, so we have to do this instead. #![allow(clippy::float_cmp)] +use std::sync::Arc; + use crate::source::{SpanRangeExt, walk_span_to_context}; use crate::{clip, is_direct_expn_of, sext, unsext}; +use rustc_abi::Size; use rustc_apfloat::Float; use rustc_apfloat::ieee::{Half, Quad}; use rustc_ast::ast::{self, LitFloatType, LitKind}; -use rustc_data_structures::sync::Lrc; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{ BinOp, BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, Item, ItemKind, Node, PatExpr, PatExprKind, QPath, UnOp, @@ -24,7 +26,6 @@ use rustc_middle::{bug, mir, span_bug}; use rustc_span::def_id::DefId; use rustc_span::symbol::Ident; use rustc_span::{SyntaxContext, sym}; -use rustc_target::abi::Size; use std::cell::Cell; use std::cmp::Ordering; use std::hash::{Hash, Hasher}; @@ -37,7 +38,7 @@ pub enum Constant<'tcx> { /// A `String` (e.g., "abc"). Str(String), /// A binary string (e.g., `b"abc"`). - Binary(Lrc<[u8]>), + Binary(Arc<[u8]>), /// A single `char` (e.g., `'a'`). Char(char), /// An integer's bit representation. @@ -305,7 +306,7 @@ pub fn lit_to_mir_constant<'tcx>(lit: &LitKind, ty: Option<Ty<'tcx>>) -> Constan match *lit { LitKind::Str(ref is, _) => Constant::Str(is.to_string()), LitKind::Byte(b) => Constant::Int(u128::from(b)), - LitKind::ByteStr(ref s, _) | LitKind::CStr(ref s, _) => Constant::Binary(Lrc::clone(s)), + LitKind::ByteStr(ref s, _) | LitKind::CStr(ref s, _) => Constant::Binary(Arc::clone(s)), LitKind::Char(c) => Constant::Char(c), LitKind::Int(n, _) => Constant::Int(n.get()), LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty { diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index d0eb5318e64..4bbf28115a6 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -10,7 +10,7 @@ use rustc_hir::{ AssocItemConstraint, BinOpKind, BindingMode, Block, BodyId, Closure, ConstArg, ConstArgKind, Expr, ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime, LifetimeName, Pat, PatExpr, PatExprKind, PatField, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, StructTailExpr, - TraitBoundModifiers, Ty, TyKind, + TraitBoundModifiers, Ty, TyKind, TyPat, TyPatKind, }; use rustc_lexer::{TokenKind, tokenize}; use rustc_lint::LateContext; @@ -1102,6 +1102,22 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } } + pub fn hash_ty_pat(&mut self, pat: &TyPat<'_>) { + std::mem::discriminant(&pat.kind).hash(&mut self.s); + match pat.kind { + TyPatKind::Range(s, e, i) => { + if let Some(s) = s { + self.hash_const_arg(s); + } + if let Some(e) = e { + self.hash_const_arg(e); + } + std::mem::discriminant(&i).hash(&mut self.s); + }, + TyPatKind::Err(_) => {}, + } + } + pub fn hash_pat(&mut self, pat: &Pat<'_>) { std::mem::discriminant(&pat.kind).hash(&mut self.s); match pat.kind { @@ -1247,7 +1263,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { }, TyKind::Pat(ty, pat) => { self.hash_ty(ty); - self.hash_pat(pat); + self.hash_ty_pat(pat); }, TyKind::Ptr(mut_ty) => { self.hash_ty(mut_ty.ty); diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index 5a5227af907..79cc5066580 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -29,6 +29,7 @@ // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) +extern crate rustc_abi; extern crate rustc_ast; extern crate rustc_ast_pretty; extern crate rustc_attr_parsing; @@ -48,7 +49,6 @@ extern crate rustc_middle; extern crate rustc_mir_dataflow; extern crate rustc_session; extern crate rustc_span; -extern crate rustc_target; extern crate rustc_trait_selection; extern crate smallvec; @@ -123,7 +123,7 @@ use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::SourceMap; use rustc_span::symbol::{Ident, Symbol, kw}; use rustc_span::{InnerSpan, Span, sym}; -use rustc_target::abi::Integer; +use rustc_abi::Integer; use visitors::{Visitable, for_each_unconsumed_temporary}; use crate::consts::{ConstEvalCtxt, Constant, mir_to_const}; @@ -341,6 +341,15 @@ pub fn is_wild(pat: &Pat<'_>) -> bool { matches!(pat.kind, PatKind::Wild) } +// Checks if arm has the form `None => None` +pub fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { + matches!( + arm.pat.kind, + PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), .. }) + if is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), OptionNone) + ) +} + /// Checks if the given `QPath` belongs to a type alias. pub fn is_ty_alias(qpath: &QPath<'_>) -> bool { match *qpath { diff --git a/src/tools/clippy/clippy_utils/src/macros.rs b/src/tools/clippy/clippy_utils/src/macros.rs index f4c730ef118..30fd48fc060 100644 --- a/src/tools/clippy/clippy_utils/src/macros.rs +++ b/src/tools/clippy/clippy_utils/src/macros.rs @@ -1,12 +1,14 @@ #![allow(clippy::similar_names)] // `expr` and `expn` +use std::sync::Arc; + use crate::get_unique_attr; use crate::visitors::{Descend, for_each_expr_without_closures}; use arrayvec::ArrayVec; use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder}; use rustc_data_structures::fx::FxHashMap; -use rustc_data_structures::sync::{Lrc, OnceLock}; +use rustc_data_structures::sync::OnceLock; use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath}; use rustc_lint::{LateContext, LintContext}; use rustc_span::def_id::DefId; @@ -393,7 +395,7 @@ fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> /// Stores AST [`FormatArgs`] nodes for use in late lint passes, as they are in a desugared form in /// the HIR #[derive(Default, Clone)] -pub struct FormatArgsStorage(Lrc<OnceLock<FxHashMap<Span, FormatArgs>>>); +pub struct FormatArgsStorage(Arc<OnceLock<FxHashMap<Span, FormatArgs>>>); impl FormatArgsStorage { /// Returns an AST [`FormatArgs`] node if a `format_args` expansion is found as a descendant of diff --git a/src/tools/clippy/clippy_utils/src/mir/mod.rs b/src/tools/clippy/clippy_utils/src/mir/mod.rs index ccbbccd0dbf..85250f81dc4 100644 --- a/src/tools/clippy/clippy_utils/src/mir/mod.rs +++ b/src/tools/clippy/clippy_utils/src/mir/mod.rs @@ -112,10 +112,14 @@ pub fn block_in_cycle(body: &Body<'_>, block: BasicBlock) -> bool { /// Convenience wrapper around `visit_local_usage`. pub fn used_exactly_once(mir: &Body<'_>, local: Local) -> Option<bool> { - visit_local_usage(&[local], mir, Location { - block: START_BLOCK, - statement_index: 0, - }) + visit_local_usage( + &[local], + mir, + Location { + block: START_BLOCK, + statement_index: 0, + }, + ) .map(|mut vec| { let LocalUsage { local_use_locs, .. } = vec.remove(0); let mut locations = local_use_locs diff --git a/src/tools/clippy/clippy_utils/src/msrvs.rs b/src/tools/clippy/clippy_utils/src/msrvs.rs index d73cb7e3561..24f73b9df26 100644 --- a/src/tools/clippy/clippy_utils/src/msrvs.rs +++ b/src/tools/clippy/clippy_utils/src/msrvs.rs @@ -1,4 +1,5 @@ use rustc_ast::attr::AttributeExt; + use rustc_attr_parsing::{RustcVersion, parse_version}; use rustc_session::Session; use rustc_span::{Symbol, sym}; @@ -18,12 +19,14 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { + 1,84,0 { CONST_OPTION_AS_SLICE } 1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP } 1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP } 1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE, EXPLICIT_SELF_TYPE_ELISION } 1,80,0 { BOX_INTO_ITER, LAZY_CELL } 1,77,0 { C_STR_LITERALS } 1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT } + 1,75,0 { OPTION_AS_SLICE } 1,74,0 { REPR_RUST } 1,73,0 { MANUAL_DIV_CEIL } 1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE } @@ -40,7 +43,7 @@ msrv_aliases! { 1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR } 1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST } 1,51,0 { BORROW_AS_PTR, SEEK_FROM_CURRENT, UNSIGNED_ABS } - 1,50,0 { BOOL_THEN, CLAMP } + 1,50,0 { BOOL_THEN, CLAMP, SLICE_FILL } 1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN, SATURATING_SUB_CONST } 1,46,0 { CONST_IF_MATCH } 1,45,0 { STR_STRIP_PREFIX } diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index 0aaef91e48a..c7890f33f27 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -179,7 +179,10 @@ fn check_rvalue<'tcx>( )) } }, - Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks, _) + Rvalue::NullaryOp( + NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks | NullOp::ContractChecks, + _, + ) | Rvalue::ShallowInitBox(_, _) => Ok(()), Rvalue::UnaryOp(_, operand) => { let ty = operand.ty(body, tcx); diff --git a/src/tools/clippy/clippy_utils/src/source.rs b/src/tools/clippy/clippy_utils/src/source.rs index eecbfb3936a..c6a038f7e0c 100644 --- a/src/tools/clippy/clippy_utils/src/source.rs +++ b/src/tools/clippy/clippy_utils/src/source.rs @@ -2,8 +2,9 @@ #![allow(clippy::module_name_repetitions)] +use std::sync::Arc; + use rustc_ast::{LitKind, StrStyle}; -use rustc_data_structures::sync::Lrc; use rustc_errors::Applicability; use rustc_hir::{BlockCheckMode, Expr, ExprKind, UnsafeSource}; use rustc_lint::{EarlyContext, LateContext}; @@ -204,7 +205,7 @@ impl fmt::Display for SourceText { fn get_source_range(sm: &SourceMap, sp: Range<BytePos>) -> Option<SourceFileRange> { let start = sm.lookup_byte_offset(sp.start); let end = sm.lookup_byte_offset(sp.end); - if !Lrc::ptr_eq(&start.sf, &end.sf) || start.pos > end.pos { + if !Arc::ptr_eq(&start.sf, &end.sf) || start.pos > end.pos { return None; } sm.ensure_source_file_source_present(&start.sf); @@ -277,7 +278,7 @@ fn trim_start(sm: &SourceMap, sp: Range<BytePos>) -> Range<BytePos> { } pub struct SourceFileRange { - pub sf: Lrc<SourceFile>, + pub sf: Arc<SourceFile>, pub range: Range<usize>, } impl SourceFileRange { @@ -726,12 +727,15 @@ pub fn str_literal_to_char_literal( &snip[1..(snip.len() - 1)] }; - let hint = format!("'{}'", match ch { - "'" => "\\'", - r"\" => "\\\\", - "\\\"" => "\"", // no need to escape `"` in `'"'` - _ => ch, - }); + let hint = format!( + "'{}'", + match ch { + "'" => "\\'", + r"\" => "\\\\", + "\\\"" => "\"", // no need to escape `"` in `'"'` + _ => ch, + } + ); Some(hint) } else { diff --git a/src/tools/clippy/clippy_utils/src/str_utils.rs b/src/tools/clippy/clippy_utils/src/str_utils.rs index 1588ee452da..421b25a77fe 100644 --- a/src/tools/clippy/clippy_utils/src/str_utils.rs +++ b/src/tools/clippy/clippy_utils/src/str_utils.rs @@ -370,11 +370,9 @@ mod test { assert_eq!(camel_case_split("AbcDef"), vec!["Abc", "Def"]); assert_eq!(camel_case_split("Abc"), vec!["Abc"]); assert_eq!(camel_case_split("abcDef"), vec!["abc", "Def"]); - assert_eq!(camel_case_split("\u{f6}\u{f6}AabABcd"), vec![ - "\u{f6}\u{f6}", - "Aab", - "A", - "Bcd" - ]); + assert_eq!( + camel_case_split("\u{f6}\u{f6}AabABcd"), + vec!["\u{f6}\u{f6}", "Aab", "A", "Bcd"] + ); } } diff --git a/src/tools/clippy/clippy_utils/src/ty/mod.rs b/src/tools/clippy/clippy_utils/src/ty/mod.rs index e9a05c45747..a5374f6904e 100644 --- a/src/tools/clippy/clippy_utils/src/ty/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ty/mod.rs @@ -4,6 +4,7 @@ use core::ops::ControlFlow; use itertools::Itertools; +use rustc_abi::VariantIdx; use rustc_ast::ast::Mutability; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; @@ -23,7 +24,6 @@ use rustc_middle::ty::{ }; use rustc_span::symbol::Ident; use rustc_span::{DUMMY_SP, Span, Symbol, sym}; -use rustc_target::abi::VariantIdx; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt; use rustc_trait_selection::traits::{Obligation, ObligationCause}; diff --git a/src/tools/clippy/lintcheck/src/config.rs b/src/tools/clippy/lintcheck/src/config.rs index af243f94274..83c3d7aba02 100644 --- a/src/tools/clippy/lintcheck/src/config.rs +++ b/src/tools/clippy/lintcheck/src/config.rs @@ -2,6 +2,7 @@ use clap::{Parser, Subcommand, ValueEnum}; use std::num::NonZero; use std::path::PathBuf; +#[allow(clippy::struct_excessive_bools)] #[derive(Parser, Clone, Debug)] #[command(args_conflicts_with_subcommands = true)] pub(crate) struct LintcheckConfig { @@ -11,6 +12,9 @@ pub(crate) struct LintcheckConfig { short = 'j', value_name = "N", default_value_t = 0, + default_value_if("perf", "true", Some("1")), // Limit jobs to 1 when benchmarking + conflicts_with("perf"), + required = false, hide_default_value = true )] pub max_jobs: usize, @@ -46,6 +50,11 @@ pub(crate) struct LintcheckConfig { /// Run clippy on the dependencies of crates specified in crates-toml #[clap(long, conflicts_with("max_jobs"))] pub recursive: bool, + /// Also produce a `perf.data` file, implies --jobs=1, + /// the `perf.data` file can be found at + /// `target/lintcheck/sources/<package>-<version>/perf.data` + #[clap(long)] + pub perf: bool, #[command(subcommand)] pub subcommand: Option<Commands>, } diff --git a/src/tools/clippy/lintcheck/src/main.rs b/src/tools/clippy/lintcheck/src/main.rs index e88d9f427be..8d0d41ab945 100644 --- a/src/tools/clippy/lintcheck/src/main.rs +++ b/src/tools/clippy/lintcheck/src/main.rs @@ -116,7 +116,25 @@ impl Crate { clippy_args.extend(lint_levels_args.iter().map(String::as_str)); - let mut cmd = Command::new("cargo"); + let mut cmd; + + if config.perf { + cmd = Command::new("perf"); + cmd.args(&[ + "record", + "-e", + "instructions", // Only count instructions + "-g", // Enable call-graph, useful for flamegraphs and produces richer reports + "--quiet", // Do not tamper with lintcheck's normal output + "-o", + "perf.data", + "--", + "cargo", + ]); + } else { + cmd = Command::new("cargo"); + } + cmd.arg(if config.fix { "fix" } else { "check" }) .arg("--quiet") .current_dir(&self.path) @@ -234,12 +252,22 @@ fn normalize_diag( } /// Builds clippy inside the repo to make sure we have a clippy executable we can use. -fn build_clippy() -> String { - let output = Command::new("cargo") - .args(["run", "--bin=clippy-driver", "--", "--version"]) - .stderr(Stdio::inherit()) - .output() - .unwrap(); +fn build_clippy(release_build: bool) -> String { + let mut build_cmd = Command::new("cargo"); + build_cmd.args([ + "run", + "--bin=clippy-driver", + if release_build { "-r" } else { "" }, + "--", + "--version", + ]); + + if release_build { + build_cmd.env("CARGO_PROFILE_RELEASE_DEBUG", "true"); + } + + let output = build_cmd.stderr(Stdio::inherit()).output().unwrap(); + if !output.status.success() { eprintln!("Error: Failed to compile Clippy!"); std::process::exit(1); @@ -270,13 +298,18 @@ fn main() { #[allow(clippy::too_many_lines)] fn lintcheck(config: LintcheckConfig) { - let clippy_ver = build_clippy(); - let clippy_driver_path = fs::canonicalize(format!("target/debug/clippy-driver{EXE_SUFFIX}")).unwrap(); + let clippy_ver = build_clippy(config.perf); + let clippy_driver_path = fs::canonicalize(format!( + "target/{}/clippy-driver{EXE_SUFFIX}", + if config.perf { "release" } else { "debug" } + )) + .unwrap(); // assert that clippy is found assert!( clippy_driver_path.is_file(), - "target/debug/clippy-driver binary not found! {}", + "target/{}/clippy-driver binary not found! {}", + if config.perf { "release" } else { "debug" }, clippy_driver_path.display() ); diff --git a/src/tools/clippy/rust-toolchain b/src/tools/clippy/rust-toolchain index c15d1fe6cd3..ab760287e83 100644 --- a/src/tools/clippy/rust-toolchain +++ b/src/tools/clippy/rust-toolchain @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-01-28" +channel = "nightly-2025-02-06" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/src/tools/clippy/tests/ui/cmp_null.fixed b/src/tools/clippy/tests/ui/cmp_null.fixed new file mode 100644 index 00000000000..e5ab765bc86 --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_null.fixed @@ -0,0 +1,32 @@ +#![warn(clippy::cmp_null)] +#![allow(unused_mut)] + +use std::ptr; + +fn main() { + let x = 0; + let p: *const usize = &x; + if p.is_null() { + //~^ ERROR: comparing with null is better expressed by the `.is_null()` method + //~| NOTE: `-D clippy::cmp-null` implied by `-D warnings` + println!("This is surprising!"); + } + if p.is_null() { + //~^ ERROR: comparing with null is better expressed by the `.is_null()` method + println!("This is surprising!"); + } + + let mut y = 0; + let mut m: *mut usize = &mut y; + if m.is_null() { + //~^ ERROR: comparing with null is better expressed by the `.is_null()` method + println!("This is surprising, too!"); + } + if m.is_null() { + //~^ ERROR: comparing with null is better expressed by the `.is_null()` method + println!("This is surprising, too!"); + } + + let _ = (x as *const ()).is_null(); + //~^ ERROR: comparing with null is better expressed by the `.is_null()` method +} diff --git a/src/tools/clippy/tests/ui/cmp_null.rs b/src/tools/clippy/tests/ui/cmp_null.rs index ef1d93940aa..257f7ba2662 100644 --- a/src/tools/clippy/tests/ui/cmp_null.rs +++ b/src/tools/clippy/tests/ui/cmp_null.rs @@ -11,10 +11,22 @@ fn main() { //~| NOTE: `-D clippy::cmp-null` implied by `-D warnings` println!("This is surprising!"); } + if ptr::null() == p { + //~^ ERROR: comparing with null is better expressed by the `.is_null()` method + println!("This is surprising!"); + } + let mut y = 0; let mut m: *mut usize = &mut y; if m == ptr::null_mut() { //~^ ERROR: comparing with null is better expressed by the `.is_null()` method println!("This is surprising, too!"); } + if ptr::null_mut() == m { + //~^ ERROR: comparing with null is better expressed by the `.is_null()` method + println!("This is surprising, too!"); + } + + let _ = x as *const () == ptr::null(); + //~^ ERROR: comparing with null is better expressed by the `.is_null()` method } diff --git a/src/tools/clippy/tests/ui/cmp_null.stderr b/src/tools/clippy/tests/ui/cmp_null.stderr index 8362904a5ba..f3b35f3afba 100644 --- a/src/tools/clippy/tests/ui/cmp_null.stderr +++ b/src/tools/clippy/tests/ui/cmp_null.stderr @@ -2,16 +2,34 @@ error: comparing with null is better expressed by the `.is_null()` method --> tests/ui/cmp_null.rs:9:8 | LL | if p == ptr::null() { - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ help: try: `p.is_null()` | = note: `-D clippy::cmp-null` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cmp_null)]` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:16:8 + --> tests/ui/cmp_null.rs:14:8 + | +LL | if ptr::null() == p { + | ^^^^^^^^^^^^^^^^ help: try: `p.is_null()` + +error: comparing with null is better expressed by the `.is_null()` method + --> tests/ui/cmp_null.rs:21:8 | LL | if m == ptr::null_mut() { - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^ help: try: `m.is_null()` + +error: comparing with null is better expressed by the `.is_null()` method + --> tests/ui/cmp_null.rs:25:8 + | +LL | if ptr::null_mut() == m { + | ^^^^^^^^^^^^^^^^^^^^ help: try: `m.is_null()` + +error: comparing with null is better expressed by the `.is_null()` method + --> tests/ui/cmp_null.rs:30:13 + | +LL | let _ = x as *const () == ptr::null(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(x as *const ()).is_null()` -error: aborting due to 2 previous errors +error: aborting due to 5 previous errors diff --git a/src/tools/clippy/tests/ui/doc/unbalanced_ticks.rs b/src/tools/clippy/tests/ui/doc/unbalanced_ticks.rs index 04446787b6c..a065654e319 100644 --- a/src/tools/clippy/tests/ui/doc/unbalanced_ticks.rs +++ b/src/tools/clippy/tests/ui/doc/unbalanced_ticks.rs @@ -66,3 +66,19 @@ fn escape_3() {} /// Backslashes ` \` within code blocks don't count. fn escape_4() {} + +trait Foo { + fn bar(); +} + +struct Bar; +impl Foo for Bar { + // NOTE: false positive + /// Returns an `Option<Month>` from a i64, assuming a 1-index, January = 1. + /// + /// `Month::from_i64(n: i64)`: | `1` | `2` | ... | `12` + /// ---------------------------| -------------------- | --------------------- | ... | ----- + /// ``: | Some(Month::January) | Some(Month::February) | ... | + /// Some(Month::December) + fn bar() {} +} diff --git a/src/tools/clippy/tests/ui/doc/unbalanced_ticks.stderr b/src/tools/clippy/tests/ui/doc/unbalanced_ticks.stderr index 50324010e97..c9fd25eb1a1 100644 --- a/src/tools/clippy/tests/ui/doc/unbalanced_ticks.stderr +++ b/src/tools/clippy/tests/ui/doc/unbalanced_ticks.stderr @@ -94,5 +94,17 @@ LL | /// Escaped \` ` backticks don't count, but unescaped backticks do. | = help: a backtick may be missing a pair -error: aborting due to 10 previous errors +error: backticks are unbalanced + --> tests/ui/doc/unbalanced_ticks.rs:79:9 + | +LL | /// `Month::from_i64(n: i64)`: | `1` | `2` | ... | `12` + | _________^ +LL | | /// ---------------------------| -------------------- | --------------------- | ... | ----- +LL | | /// ``: | Some(Month::January) | Some(Month::February) | ... | +LL | | /// Some(Month::December) + | |_____________________________^ + | + = help: a backtick may be missing a pair + +error: aborting due to 11 previous errors diff --git a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.1.fixed b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.1.fixed index fd6a94b6a80..3772b465fdb 100644 --- a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.1.fixed +++ b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.1.fixed @@ -132,4 +132,13 @@ pub struct BlockComment; ))] fn empty_line_in_cfg_attr() {} +trait Foo { + fn bar(); +} + +impl Foo for LineComment { + /// comment on assoc item + fn bar() {} +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.2.fixed b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.2.fixed index 7a57dcd9233..3028d03b669 100644 --- a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.2.fixed +++ b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.2.fixed @@ -141,4 +141,13 @@ pub struct BlockComment; ))] fn empty_line_in_cfg_attr() {} +trait Foo { + fn bar(); +} + +impl Foo for LineComment { + /// comment on assoc item + fn bar() {} +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.rs b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.rs index 1da761a5c3d..ae4ebc271fa 100644 --- a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.rs +++ b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.rs @@ -144,4 +144,14 @@ pub struct BlockComment; ))] fn empty_line_in_cfg_attr() {} +trait Foo { + fn bar(); +} + +impl Foo for LineComment { + /// comment on assoc item + + fn bar() {} +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.stderr b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.stderr index c5d5f3d3759..7b197ae67e0 100644 --- a/src/tools/clippy/tests/ui/empty_line_after/doc_comments.stderr +++ b/src/tools/clippy/tests/ui/empty_line_after/doc_comments.stderr @@ -5,11 +5,11 @@ LL | / /// for the crate LL | | | |_^ LL | fn first_in_crate() {} - | ------------------- the comment documents this function + | ----------------- the comment documents this function | = note: `-D clippy::empty-line-after-doc-comments` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::empty_line_after_doc_comments)]` - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the comment should document the crate use an inner doc comment | LL ~ //! Meant to be an @@ -24,9 +24,9 @@ LL | / /// for the module LL | | | |_^ LL | fn first_in_module() {} - | -------------------- the comment documents this function + | ------------------ the comment documents this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the comment should document the parent module use an inner doc comment | LL ~ //! Meant to be an @@ -42,9 +42,9 @@ LL | | | |_^ LL | /// Blank line LL | fn indented() {} - | ------------- the comment documents this function + | ----------- the comment documents this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the documentation should include the empty line include it in the comment | LL | /// @@ -57,9 +57,9 @@ LL | / /// This should produce a warning LL | | | |_^ LL | fn with_doc_and_newline() {} - | ------------------------- the comment documents this function + | ----------------------- the comment documents this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it error: empty lines after doc comment --> tests/ui/empty_line_after/doc_comments.rs:44:1 @@ -72,9 +72,9 @@ LL | | | |_^ ... LL | fn three_attributes() {} - | --------------------- the comment documents this function + | ------------------- the comment documents this function | - = help: if the empty lines are unintentional remove them + = help: if the empty lines are unintentional, remove them error: empty line after doc comment --> tests/ui/empty_line_after/doc_comments.rs:56:5 @@ -84,9 +84,9 @@ LL | | // fn old_code() {} LL | | | |_^ LL | fn new_code() {} - | ------------- the comment documents this function + | ----------- the comment documents this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the doc comment should not document `new_code` comment it out | LL | // /// docs for `old_code` @@ -106,7 +106,7 @@ LL | | LL | struct Multiple; | --------------- the comment documents this struct | - = help: if the empty lines are unintentional remove them + = help: if the empty lines are unintentional, remove them help: if the doc comment should not document `Multiple` comment it out | LL ~ // /// Docs @@ -126,9 +126,9 @@ LL | | */ LL | | | |_^ LL | fn first_in_module() {} - | -------------------- the comment documents this function + | ------------------ the comment documents this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the comment should document the parent module use an inner doc comment | LL | /*! @@ -145,9 +145,9 @@ LL | | | |_^ ... LL | fn new_code() {} - | ------------- the comment documents this function + | ----------- the comment documents this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the doc comment should not document `new_code` comment it out | LL - /** @@ -163,13 +163,24 @@ LL | | | |_^ LL | /// Docs for `new_code2` LL | fn new_code2() {} - | -------------- the comment documents this function + | ------------ the comment documents this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the doc comment should not document `new_code2` comment it out | LL | // /// Docs for `old_code2` | ++ -error: aborting due to 10 previous errors +error: empty line after doc comment + --> tests/ui/empty_line_after/doc_comments.rs:152:5 + | +LL | / /// comment on assoc item +LL | | + | |_^ +LL | fn bar() {} + | ------ the comment documents this function + | + = help: if the empty line is unintentional, remove it + +error: aborting due to 11 previous errors diff --git a/src/tools/clippy/tests/ui/empty_line_after/outer_attribute.stderr b/src/tools/clippy/tests/ui/empty_line_after/outer_attribute.stderr index a95306e2fa3..519ba6e6761 100644 --- a/src/tools/clippy/tests/ui/empty_line_after/outer_attribute.stderr +++ b/src/tools/clippy/tests/ui/empty_line_after/outer_attribute.stderr @@ -5,11 +5,11 @@ LL | / #[crate_type = "lib"] LL | | | |_^ LL | fn first_in_crate() {} - | ------------------- the attribute applies to this function + | ----------------- the attribute applies to this function | = note: `-D clippy::empty-line-after-outer-attr` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::empty_line_after_outer_attr)]` - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it help: if the attribute should apply to the crate use an inner attribute | LL | #![crate_type = "lib"] @@ -23,9 +23,9 @@ LL | | | |_^ LL | /// some comment LL | fn with_one_newline_and_comment() {} - | --------------------------------- the attribute applies to this function + | ------------------------------- the attribute applies to this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it error: empty line after outer attribute --> tests/ui/empty_line_after/outer_attribute.rs:23:1 @@ -34,9 +34,9 @@ LL | / #[inline] LL | | | |_^ LL | fn with_one_newline() {} - | --------------------- the attribute applies to this function + | ------------------- the attribute applies to this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it error: empty lines after outer attribute --> tests/ui/empty_line_after/outer_attribute.rs:30:5 @@ -46,9 +46,9 @@ LL | | LL | | | |_^ LL | fn with_two_newlines() {} - | ---------------------- the attribute applies to this function + | -------------------- the attribute applies to this function | - = help: if the empty lines are unintentional remove them + = help: if the empty lines are unintentional, remove them help: if the attribute should apply to the parent module use an inner attribute | LL | #![crate_type = "lib"] @@ -63,7 +63,7 @@ LL | | LL | enum Baz { | -------- the attribute applies to this enum | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it error: empty line after outer attribute --> tests/ui/empty_line_after/outer_attribute.rs:45:1 @@ -74,7 +74,7 @@ LL | | LL | struct Foo { | ---------- the attribute applies to this struct | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it error: empty line after outer attribute --> tests/ui/empty_line_after/outer_attribute.rs:53:1 @@ -85,7 +85,7 @@ LL | | LL | mod foo {} | ------- the attribute applies to this module | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it error: empty line after outer attribute --> tests/ui/empty_line_after/outer_attribute.rs:58:1 @@ -95,9 +95,9 @@ LL | | // Still lint cases where the empty line does not immediately follow the LL | | | |_^ LL | fn comment_before_empty_line() {} - | ------------------------------ the attribute applies to this function + | ---------------------------- the attribute applies to this function | - = help: if the empty line is unintentional remove it + = help: if the empty line is unintentional, remove it error: empty lines after outer attribute --> tests/ui/empty_line_after/outer_attribute.rs:64:1 @@ -107,9 +107,9 @@ LL | / #[allow(unused)] LL | | | |_^ LL | pub fn isolated_comment() {} - | ------------------------- the attribute applies to this function + | ----------------------- the attribute applies to this function | - = help: if the empty lines are unintentional remove them + = help: if the empty lines are unintentional, remove them error: aborting due to 9 previous errors diff --git a/src/tools/clippy/tests/ui/eta.fixed b/src/tools/clippy/tests/ui/eta.fixed index f1baf28200e..abccc30ef87 100644 --- a/src/tools/clippy/tests/ui/eta.fixed +++ b/src/tools/clippy/tests/ui/eta.fixed @@ -116,6 +116,11 @@ fn test_redundant_closures_containing_method_calls() { t.iter().filter(|x| x.trait_foo_ref()); t.iter().map(|x| x.trait_foo_ref()); } + + fn issue14096() { + let x = Some("42"); + let _ = x.map(str::parse::<i16>); + } } struct Thunk<T>(Box<dyn FnMut() -> T>); diff --git a/src/tools/clippy/tests/ui/eta.rs b/src/tools/clippy/tests/ui/eta.rs index c52a51880bf..9bcee4eba34 100644 --- a/src/tools/clippy/tests/ui/eta.rs +++ b/src/tools/clippy/tests/ui/eta.rs @@ -116,6 +116,11 @@ fn test_redundant_closures_containing_method_calls() { t.iter().filter(|x| x.trait_foo_ref()); t.iter().map(|x| x.trait_foo_ref()); } + + fn issue14096() { + let x = Some("42"); + let _ = x.map(|x| x.parse::<i16>()); + } } struct Thunk<T>(Box<dyn FnMut() -> T>); diff --git a/src/tools/clippy/tests/ui/eta.stderr b/src/tools/clippy/tests/ui/eta.stderr index 1731a4377f5..ac58e87bc5e 100644 --- a/src/tools/clippy/tests/ui/eta.stderr +++ b/src/tools/clippy/tests/ui/eta.stderr @@ -71,142 +71,148 @@ LL | let e: std::vec::Vec<char> = vec!['a', 'b', 'c'].iter().map(|c| c.to_as | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `char::to_ascii_uppercase` error: redundant closure - --> tests/ui/eta.rs:169:22 + --> tests/ui/eta.rs:122:23 + | +LL | let _ = x.map(|x| x.parse::<i16>()); + | ^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `str::parse::<i16>` + +error: redundant closure + --> tests/ui/eta.rs:174:22 | LL | requires_fn_once(|| x()); | ^^^^^^ help: replace the closure with the function itself: `x` error: redundant closure - --> tests/ui/eta.rs:176:27 + --> tests/ui/eta.rs:181:27 | LL | let a = Some(1u8).map(|a| foo_ptr(a)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `foo_ptr` error: redundant closure - --> tests/ui/eta.rs:181:27 + --> tests/ui/eta.rs:186:27 | LL | let a = Some(1u8).map(|a| closure(a)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `closure` error: redundant closure - --> tests/ui/eta.rs:213:28 + --> tests/ui/eta.rs:218:28 | LL | x.into_iter().for_each(|x| add_to_res(x)); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut add_to_res` error: redundant closure - --> tests/ui/eta.rs:214:28 + --> tests/ui/eta.rs:219:28 | LL | y.into_iter().for_each(|x| add_to_res(x)); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut add_to_res` error: redundant closure - --> tests/ui/eta.rs:215:28 + --> tests/ui/eta.rs:220:28 | LL | z.into_iter().for_each(|x| add_to_res(x)); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `add_to_res` error: redundant closure - --> tests/ui/eta.rs:222:21 + --> tests/ui/eta.rs:227:21 | LL | Some(1).map(|n| closure(n)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut closure` error: redundant closure - --> tests/ui/eta.rs:226:21 + --> tests/ui/eta.rs:231:21 | LL | Some(1).map(|n| in_loop(n)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `in_loop` error: redundant closure - --> tests/ui/eta.rs:319:18 + --> tests/ui/eta.rs:324:18 | LL | takes_fn_mut(|| f()); | ^^^^^^ help: replace the closure with the function itself: `&mut f` error: redundant closure - --> tests/ui/eta.rs:322:19 + --> tests/ui/eta.rs:327:19 | LL | takes_fn_once(|| f()); | ^^^^^^ help: replace the closure with the function itself: `&mut f` error: redundant closure - --> tests/ui/eta.rs:326:26 + --> tests/ui/eta.rs:331:26 | LL | move || takes_fn_mut(|| f_used_once()) | ^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut f_used_once` error: redundant closure - --> tests/ui/eta.rs:338:19 + --> tests/ui/eta.rs:343:19 | LL | array_opt.map(|a| a.as_slice()); | ^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `<[u8; 3]>::as_slice` error: redundant closure - --> tests/ui/eta.rs:341:19 + --> tests/ui/eta.rs:346:19 | LL | slice_opt.map(|s| s.len()); | ^^^^^^^^^^^ help: replace the closure with the method itself: `<[u8]>::len` error: redundant closure - --> tests/ui/eta.rs:344:17 + --> tests/ui/eta.rs:349:17 | LL | ptr_opt.map(|p| p.is_null()); | ^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `<*const usize>::is_null` error: redundant closure - --> tests/ui/eta.rs:348:17 + --> tests/ui/eta.rs:353:17 | LL | dyn_opt.map(|d| d.method_on_dyn()); | ^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `<dyn TestTrait>::method_on_dyn` error: redundant closure - --> tests/ui/eta.rs:408:19 + --> tests/ui/eta.rs:413:19 | LL | let _ = f(&0, |x, y| f2(x, y)); | ^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `f2` error: redundant closure - --> tests/ui/eta.rs:436:22 + --> tests/ui/eta.rs:441:22 | LL | test.map(|t| t.method()) | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `Test::method` error: redundant closure - --> tests/ui/eta.rs:440:22 + --> tests/ui/eta.rs:445:22 | LL | test.map(|t| t.method()) | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `super::Outer::method` error: redundant closure - --> tests/ui/eta.rs:453:18 + --> tests/ui/eta.rs:458:18 | LL | test.map(|t| t.method()) | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `test_mod::Test::method` error: redundant closure - --> tests/ui/eta.rs:460:30 + --> tests/ui/eta.rs:465:30 | LL | test.map(|t| t.method()) | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `crate::issue_10854::d::Test::method` error: redundant closure - --> tests/ui/eta.rs:479:38 + --> tests/ui/eta.rs:484:38 | LL | let x = Box::new(|| None.map(|x| f(x))); | ^^^^^^^^ help: replace the closure with the function itself: `&f` error: redundant closure - --> tests/ui/eta.rs:483:38 + --> tests/ui/eta.rs:488:38 | LL | let x = Box::new(|| None.map(|x| f(x))); | ^^^^^^^^ help: replace the closure with the function itself: `f` error: redundant closure - --> tests/ui/eta.rs:500:35 + --> tests/ui/eta.rs:505:35 | LL | let _field = bind.or_else(|| get_default()).unwrap(); | ^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `get_default` -error: aborting due to 34 previous errors +error: aborting due to 35 previous errors diff --git a/src/tools/clippy/tests/ui/ignored_unit_patterns.fixed b/src/tools/clippy/tests/ui/ignored_unit_patterns.fixed index fde40437309..118f0b48895 100644 --- a/src/tools/clippy/tests/ui/ignored_unit_patterns.fixed +++ b/src/tools/clippy/tests/ui/ignored_unit_patterns.fixed @@ -21,12 +21,15 @@ fn main() { let _ = foo().map_err(|()| todo!()); //~^ ERROR: matching over `()` is more explicit - println!("{:?}", match foo() { - Ok(()) => {}, - //~^ ERROR: matching over `()` is more explicit - Err(()) => {}, - //~^ ERROR: matching over `()` is more explicit - }); + println!( + "{:?}", + match foo() { + Ok(()) => {}, + //~^ ERROR: matching over `()` is more explicit + Err(()) => {}, + //~^ ERROR: matching over `()` is more explicit + } + ); } // ignored_unit_patterns in derive macro should be ok diff --git a/src/tools/clippy/tests/ui/ignored_unit_patterns.rs b/src/tools/clippy/tests/ui/ignored_unit_patterns.rs index 528844d76e0..92feb9e6c28 100644 --- a/src/tools/clippy/tests/ui/ignored_unit_patterns.rs +++ b/src/tools/clippy/tests/ui/ignored_unit_patterns.rs @@ -21,12 +21,15 @@ fn main() { let _ = foo().map_err(|_| todo!()); //~^ ERROR: matching over `()` is more explicit - println!("{:?}", match foo() { - Ok(_) => {}, - //~^ ERROR: matching over `()` is more explicit - Err(_) => {}, - //~^ ERROR: matching over `()` is more explicit - }); + println!( + "{:?}", + match foo() { + Ok(_) => {}, + //~^ ERROR: matching over `()` is more explicit + Err(_) => {}, + //~^ ERROR: matching over `()` is more explicit + } + ); } // ignored_unit_patterns in derive macro should be ok diff --git a/src/tools/clippy/tests/ui/ignored_unit_patterns.stderr b/src/tools/clippy/tests/ui/ignored_unit_patterns.stderr index 54ff4454d6b..00a254e3919 100644 --- a/src/tools/clippy/tests/ui/ignored_unit_patterns.stderr +++ b/src/tools/clippy/tests/ui/ignored_unit_patterns.stderr @@ -26,31 +26,31 @@ LL | let _ = foo().map_err(|_| todo!()); | ^ help: use `()` instead of `_`: `()` error: matching over `()` is more explicit - --> tests/ui/ignored_unit_patterns.rs:25:12 + --> tests/ui/ignored_unit_patterns.rs:27:16 | -LL | Ok(_) => {}, - | ^ help: use `()` instead of `_`: `()` +LL | Ok(_) => {}, + | ^ help: use `()` instead of `_`: `()` error: matching over `()` is more explicit - --> tests/ui/ignored_unit_patterns.rs:27:13 + --> tests/ui/ignored_unit_patterns.rs:29:17 | -LL | Err(_) => {}, - | ^ help: use `()` instead of `_`: `()` +LL | Err(_) => {}, + | ^ help: use `()` instead of `_`: `()` error: matching over `()` is more explicit - --> tests/ui/ignored_unit_patterns.rs:38:9 + --> tests/ui/ignored_unit_patterns.rs:41:9 | LL | let _ = foo().unwrap(); | ^ help: use `()` instead of `_`: `()` error: matching over `()` is more explicit - --> tests/ui/ignored_unit_patterns.rs:47:13 + --> tests/ui/ignored_unit_patterns.rs:50:13 | LL | (1, _) => unimplemented!(), | ^ help: use `()` instead of `_`: `()` error: matching over `()` is more explicit - --> tests/ui/ignored_unit_patterns.rs:54:13 + --> tests/ui/ignored_unit_patterns.rs:57:13 | LL | for (x, _) in v { | ^ help: use `()` instead of `_`: `()` diff --git a/src/tools/clippy/tests/ui/manual_memcpy/without_loop_counters.rs b/src/tools/clippy/tests/ui/manual_memcpy/without_loop_counters.rs index c917fa7f2d0..2f8640cd3f5 100644 --- a/src/tools/clippy/tests/ui/manual_memcpy/without_loop_counters.rs +++ b/src/tools/clippy/tests/ui/manual_memcpy/without_loop_counters.rs @@ -1,5 +1,10 @@ #![warn(clippy::manual_memcpy)] -#![allow(clippy::assigning_clones, clippy::useless_vec, clippy::needless_range_loop)] +#![allow( + clippy::assigning_clones, + clippy::useless_vec, + clippy::needless_range_loop, + clippy::manual_slice_fill +)] //@no-rustfix const LOOP_OFFSET: usize = 5000; diff --git a/src/tools/clippy/tests/ui/manual_memcpy/without_loop_counters.stderr b/src/tools/clippy/tests/ui/manual_memcpy/without_loop_counters.stderr index 803053b2db2..c881e3fac76 100644 --- a/src/tools/clippy/tests/ui/manual_memcpy/without_loop_counters.stderr +++ b/src/tools/clippy/tests/ui/manual_memcpy/without_loop_counters.stderr @@ -1,5 +1,5 @@ error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:9:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:14:5 | LL | / for i in 0..src.len() { LL | | @@ -12,7 +12,7 @@ LL | | } = help: to override `-D warnings` add `#[allow(clippy::manual_memcpy)]` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:16:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:21:5 | LL | / for i in 0..src.len() { LL | | @@ -21,7 +21,7 @@ LL | | } | |_____^ help: try replacing the loop by: `dst[10..(src.len() + 10)].copy_from_slice(&src[..]);` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:22:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:27:5 | LL | / for i in 0..src.len() { LL | | @@ -30,7 +30,7 @@ LL | | } | |_____^ help: try replacing the loop by: `dst[..src.len()].copy_from_slice(&src[10..(src.len() + 10)]);` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:28:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:33:5 | LL | / for i in 11..src.len() { LL | | @@ -39,7 +39,7 @@ LL | | } | |_____^ help: try replacing the loop by: `dst[11..src.len()].copy_from_slice(&src[(11 - 10)..(src.len() - 10)]);` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:34:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:39:5 | LL | / for i in 0..dst.len() { LL | | @@ -48,7 +48,7 @@ LL | | } | |_____^ help: try replacing the loop by: `dst.copy_from_slice(&src[..dst.len()]);` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:48:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:53:5 | LL | / for i in 10..256 { LL | | @@ -64,7 +64,7 @@ LL + dst2[(10 + 500)..(256 + 500)].copy_from_slice(&src[10..256]); | error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:61:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:66:5 | LL | / for i in 10..LOOP_OFFSET { LL | | @@ -73,7 +73,7 @@ LL | | } | |_____^ help: try replacing the loop by: `dst[(10 + LOOP_OFFSET)..(LOOP_OFFSET + LOOP_OFFSET)].copy_from_slice(&src[(10 - some_var)..(LOOP_OFFSET - some_var)]);` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:75:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:80:5 | LL | / for i in 0..src_vec.len() { LL | | @@ -82,7 +82,7 @@ LL | | } | |_____^ help: try replacing the loop by: `dst_vec[..src_vec.len()].copy_from_slice(&src_vec[..]);` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:105:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:110:5 | LL | / for i in from..from + src.len() { LL | | @@ -91,7 +91,7 @@ LL | | } | |_____^ help: try replacing the loop by: `dst[from..(from + src.len())].copy_from_slice(&src[..(from + src.len() - from)]);` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:110:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:115:5 | LL | / for i in from..from + 3 { LL | | @@ -100,7 +100,7 @@ LL | | } | |_____^ help: try replacing the loop by: `dst[from..(from + 3)].copy_from_slice(&src[..(from + 3 - from)]);` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:116:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:121:5 | LL | / for i in 0..5 { LL | | @@ -109,7 +109,7 @@ LL | | } | |_____^ help: try replacing the loop by: `dst[..5].copy_from_slice(&src);` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:122:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:127:5 | LL | / for i in 0..0 { LL | | @@ -118,7 +118,7 @@ LL | | } | |_____^ help: try replacing the loop by: `dst[..0].copy_from_slice(&src[..0]);` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:146:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:151:5 | LL | / for i in 0..4 { LL | | @@ -127,7 +127,7 @@ LL | | } | |_____^ help: try replacing the loop by: `dst.copy_from_slice(&src[..4]);` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:152:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:157:5 | LL | / for i in 0..5 { LL | | @@ -136,7 +136,7 @@ LL | | } | |_____^ help: try replacing the loop by: `dst[..5].copy_from_slice(&src);` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:158:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:163:5 | LL | / for i in 0..5 { LL | | @@ -145,7 +145,7 @@ LL | | } | |_____^ help: try replacing the loop by: `dst.copy_from_slice(&src);` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:205:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:210:5 | LL | / for i in 0..5 { LL | | @@ -154,7 +154,7 @@ LL | | } | |_____^ help: try replacing the loop by: `dst.copy_from_slice(&src[0]);` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:211:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:216:5 | LL | / for i in 0..5 { LL | | @@ -163,7 +163,7 @@ LL | | } | |_____^ help: try replacing the loop by: `dst.copy_from_slice(&src[0][1]);` error: it looks like you're manually copying between slices - --> tests/ui/manual_memcpy/without_loop_counters.rs:219:5 + --> tests/ui/manual_memcpy/without_loop_counters.rs:224:5 | LL | / for i in 0..src.len() { LL | | diff --git a/src/tools/clippy/tests/ui/manual_option_as_slice.fixed b/src/tools/clippy/tests/ui/manual_option_as_slice.fixed new file mode 100644 index 00000000000..48337d7654d --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_option_as_slice.fixed @@ -0,0 +1,62 @@ +#![warn(clippy::manual_option_as_slice)] +#![allow(clippy::redundant_closure, clippy::unwrap_or_default)] + +fn check(x: Option<u32>) { + _ = x.as_slice(); + + _ = x.as_slice(); + + _ = x.as_slice(); + //~^ manual_option_as_slice + + _ = x.as_slice(); + //~^ manual_option_as_slice + + _ = x.as_slice(); + //~^ manual_option_as_slice + + _ = x.as_slice(); + //~^ manual_option_as_slice + + { + use std::slice::from_ref; + _ = x.as_slice(); + //~^ manual_option_as_slice + } + + // possible false positives + let y = x.as_ref(); + _ = match y { + // as_ref outside + Some(f) => &[f][..], + None => &[][..], + }; + _ = match x.as_ref() { + Some(f) => std::slice::from_ref(f), + None => &[0], + }; + _ = match x.as_ref() { + Some(42) => &[23], + Some(f) => std::slice::from_ref(f), + None => &[], + }; + let b = &[42]; + _ = if let Some(_f) = x.as_ref() { + std::slice::from_ref(b) + } else { + &[] + }; + _ = x.as_ref().map_or(&[42][..], std::slice::from_ref); + _ = x.as_ref().map_or_else(|| &[42][..1], std::slice::from_ref); + _ = x.as_ref().map(|f| std::slice::from_ref(f)).unwrap_or_default(); +} + +#[clippy::msrv = "1.74"] +fn check_msrv(x: Option<u32>) { + _ = x.as_ref().map_or(&[][..], std::slice::from_ref); +} + +fn main() { + check(Some(1)); + check_msrv(Some(175)); +} diff --git a/src/tools/clippy/tests/ui/manual_option_as_slice.rs b/src/tools/clippy/tests/ui/manual_option_as_slice.rs new file mode 100644 index 00000000000..561a8b53401 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_option_as_slice.rs @@ -0,0 +1,71 @@ +#![warn(clippy::manual_option_as_slice)] +#![allow(clippy::redundant_closure, clippy::unwrap_or_default)] + +fn check(x: Option<u32>) { + _ = match x.as_ref() { + //~^ manual_option_as_slice + Some(f) => std::slice::from_ref(f), + None => &[], + }; + + _ = if let Some(f) = x.as_ref() { + //~^ manual_option_as_slice + std::slice::from_ref(f) + } else { + &[] + }; + + _ = x.as_ref().map_or(&[][..], std::slice::from_ref); + //~^ manual_option_as_slice + + _ = x.as_ref().map_or_else(Default::default, std::slice::from_ref); + //~^ manual_option_as_slice + + _ = x.as_ref().map(std::slice::from_ref).unwrap_or_default(); + //~^ manual_option_as_slice + + _ = x.as_ref().map_or_else(|| &[42][..0], std::slice::from_ref); + //~^ manual_option_as_slice + + { + use std::slice::from_ref; + _ = x.as_ref().map_or_else(<&[_]>::default, from_ref); + //~^ manual_option_as_slice + } + + // possible false positives + let y = x.as_ref(); + _ = match y { + // as_ref outside + Some(f) => &[f][..], + None => &[][..], + }; + _ = match x.as_ref() { + Some(f) => std::slice::from_ref(f), + None => &[0], + }; + _ = match x.as_ref() { + Some(42) => &[23], + Some(f) => std::slice::from_ref(f), + None => &[], + }; + let b = &[42]; + _ = if let Some(_f) = x.as_ref() { + std::slice::from_ref(b) + } else { + &[] + }; + _ = x.as_ref().map_or(&[42][..], std::slice::from_ref); + _ = x.as_ref().map_or_else(|| &[42][..1], std::slice::from_ref); + _ = x.as_ref().map(|f| std::slice::from_ref(f)).unwrap_or_default(); +} + +#[clippy::msrv = "1.74"] +fn check_msrv(x: Option<u32>) { + _ = x.as_ref().map_or(&[][..], std::slice::from_ref); +} + +fn main() { + check(Some(1)); + check_msrv(Some(175)); +} diff --git a/src/tools/clippy/tests/ui/manual_option_as_slice.stderr b/src/tools/clippy/tests/ui/manual_option_as_slice.stderr new file mode 100644 index 00000000000..569269d3e2b --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_option_as_slice.stderr @@ -0,0 +1,58 @@ +error: use `Option::as_slice` + --> tests/ui/manual_option_as_slice.rs:5:9 + | +LL | _ = match x.as_ref() { + | _________^ +LL | | +LL | | Some(f) => std::slice::from_ref(f), +LL | | None => &[], +LL | | }; + | |_____^ help: use: `x.as_slice()` + | + = note: `-D clippy::manual-option-as-slice` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_option_as_slice)]` + +error: use `Option::as_slice` + --> tests/ui/manual_option_as_slice.rs:11:9 + | +LL | _ = if let Some(f) = x.as_ref() { + | _________^ +LL | | +LL | | std::slice::from_ref(f) +LL | | } else { +LL | | &[] +LL | | }; + | |_____^ help: use: `x.as_slice()` + +error: use `Option::as_slice` + --> tests/ui/manual_option_as_slice.rs:18:9 + | +LL | _ = x.as_ref().map_or(&[][..], std::slice::from_ref); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + +error: use `Option::as_slice` + --> tests/ui/manual_option_as_slice.rs:21:9 + | +LL | _ = x.as_ref().map_or_else(Default::default, std::slice::from_ref); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + +error: use `Option::as_slice` + --> tests/ui/manual_option_as_slice.rs:24:9 + | +LL | _ = x.as_ref().map(std::slice::from_ref).unwrap_or_default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + +error: use `Option::as_slice` + --> tests/ui/manual_option_as_slice.rs:27:9 + | +LL | _ = x.as_ref().map_or_else(|| &[42][..0], std::slice::from_ref); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + +error: use `Option::as_slice` + --> tests/ui/manual_option_as_slice.rs:32:13 + | +LL | _ = x.as_ref().map_or_else(<&[_]>::default, from_ref); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_slice_fill.fixed b/src/tools/clippy/tests/ui/manual_slice_fill.fixed new file mode 100644 index 00000000000..397a156a2dc --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_slice_fill.fixed @@ -0,0 +1,101 @@ +#![warn(clippy::manual_slice_fill)] +#![allow(clippy::needless_range_loop)] + +macro_rules! assign_element { + ($slice:ident, $index:expr) => { + $slice[$index] = 0; + }; +} + +macro_rules! assign_element_2 { + ($i:expr) => { + $i = 0; + }; +} + +struct NoClone; + +fn num() -> usize { + 5 +} + +fn should_lint() { + let mut some_slice = [1, 2, 3, 4, 5]; + + some_slice.fill(0); + + let x = 5; + some_slice.fill(x); + + some_slice.fill(0); + + // This should trigger `manual_slice_fill`, but the applicability is `MaybeIncorrect` since comments + // within the loop might be purely informational. + some_slice.fill(0); +} + +fn should_not_lint() { + let mut some_slice = [1, 2, 3, 4, 5]; + + // Should not lint because we can't determine if the scope of the loop is intended to access all the + // elements of the slice. + for i in 0..5 { + some_slice[i] = 0; + } + + // Should not lint, as using a function to assign values to elements might be + // intentional. For example, the contents of `num()` could be temporary and subject to change + // later. + for i in 0..some_slice.len() { + some_slice[i] = num(); + } + + // Should not lint because this loop isn't equivalent to `fill`. + for i in 0..some_slice.len() { + some_slice[i] = 0; + println!("foo"); + } + + // Should not lint because it may be intentional to use a macro to perform an operation equivalent + // to `fill`. + for i in 0..some_slice.len() { + assign_element!(some_slice, i); + } + + let another_slice = [1, 2, 3]; + // Should not lint because the range is not for `some_slice`. + for i in 0..another_slice.len() { + some_slice[i] = 0; + } + + let mut vec: Vec<Option<NoClone>> = Vec::with_capacity(5); + // Should not lint because `NoClone` does not have `Clone` trait. + for i in 0..vec.len() { + vec[i] = None; + } + + // Should not lint, as using a function to assign values to elements might be + // intentional. For example, the contents of `num()` could be temporary and subject to change + // later. + for i in &mut some_slice { + *i = num(); + } + + // Should not lint because this loop isn't equivalent to `fill`. + for i in &mut some_slice { + *i = 0; + println!("foo"); + } + + // Should not lint because it may be intentional to use a macro to perform an operation equivalent + // to `fill`. + for i in &mut some_slice { + assign_element_2!(*i); + } + + let mut vec: Vec<Option<NoClone>> = Vec::with_capacity(5); + // Should not lint because `NoClone` does not have `Clone` trait. + for i in &mut vec { + *i = None; + } +} diff --git a/src/tools/clippy/tests/ui/manual_slice_fill.rs b/src/tools/clippy/tests/ui/manual_slice_fill.rs new file mode 100644 index 00000000000..c25127ca613 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_slice_fill.rs @@ -0,0 +1,110 @@ +#![warn(clippy::manual_slice_fill)] +#![allow(clippy::needless_range_loop)] + +macro_rules! assign_element { + ($slice:ident, $index:expr) => { + $slice[$index] = 0; + }; +} + +macro_rules! assign_element_2 { + ($i:expr) => { + $i = 0; + }; +} + +struct NoClone; + +fn num() -> usize { + 5 +} + +fn should_lint() { + let mut some_slice = [1, 2, 3, 4, 5]; + + for i in 0..some_slice.len() { + some_slice[i] = 0; + } + + let x = 5; + for i in 0..some_slice.len() { + some_slice[i] = x; + } + + for i in &mut some_slice { + *i = 0; + } + + // This should trigger `manual_slice_fill`, but the applicability is `MaybeIncorrect` since comments + // within the loop might be purely informational. + for i in 0..some_slice.len() { + some_slice[i] = 0; + // foo + } +} + +fn should_not_lint() { + let mut some_slice = [1, 2, 3, 4, 5]; + + // Should not lint because we can't determine if the scope of the loop is intended to access all the + // elements of the slice. + for i in 0..5 { + some_slice[i] = 0; + } + + // Should not lint, as using a function to assign values to elements might be + // intentional. For example, the contents of `num()` could be temporary and subject to change + // later. + for i in 0..some_slice.len() { + some_slice[i] = num(); + } + + // Should not lint because this loop isn't equivalent to `fill`. + for i in 0..some_slice.len() { + some_slice[i] = 0; + println!("foo"); + } + + // Should not lint because it may be intentional to use a macro to perform an operation equivalent + // to `fill`. + for i in 0..some_slice.len() { + assign_element!(some_slice, i); + } + + let another_slice = [1, 2, 3]; + // Should not lint because the range is not for `some_slice`. + for i in 0..another_slice.len() { + some_slice[i] = 0; + } + + let mut vec: Vec<Option<NoClone>> = Vec::with_capacity(5); + // Should not lint because `NoClone` does not have `Clone` trait. + for i in 0..vec.len() { + vec[i] = None; + } + + // Should not lint, as using a function to assign values to elements might be + // intentional. For example, the contents of `num()` could be temporary and subject to change + // later. + for i in &mut some_slice { + *i = num(); + } + + // Should not lint because this loop isn't equivalent to `fill`. + for i in &mut some_slice { + *i = 0; + println!("foo"); + } + + // Should not lint because it may be intentional to use a macro to perform an operation equivalent + // to `fill`. + for i in &mut some_slice { + assign_element_2!(*i); + } + + let mut vec: Vec<Option<NoClone>> = Vec::with_capacity(5); + // Should not lint because `NoClone` does not have `Clone` trait. + for i in &mut vec { + *i = None; + } +} diff --git a/src/tools/clippy/tests/ui/manual_slice_fill.stderr b/src/tools/clippy/tests/ui/manual_slice_fill.stderr new file mode 100644 index 00000000000..3aa980f6919 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_slice_fill.stderr @@ -0,0 +1,38 @@ +error: manually filling a slice + --> tests/ui/manual_slice_fill.rs:25:5 + | +LL | / for i in 0..some_slice.len() { +LL | | some_slice[i] = 0; +LL | | } + | |_____^ help: try: `some_slice.fill(0);` + | + = note: `-D clippy::manual-slice-fill` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_slice_fill)]` + +error: manually filling a slice + --> tests/ui/manual_slice_fill.rs:30:5 + | +LL | / for i in 0..some_slice.len() { +LL | | some_slice[i] = x; +LL | | } + | |_____^ help: try: `some_slice.fill(x);` + +error: manually filling a slice + --> tests/ui/manual_slice_fill.rs:34:5 + | +LL | / for i in &mut some_slice { +LL | | *i = 0; +LL | | } + | |_____^ help: try: `some_slice.fill(0);` + +error: manually filling a slice + --> tests/ui/manual_slice_fill.rs:40:5 + | +LL | / for i in 0..some_slice.len() { +LL | | some_slice[i] = 0; +LL | | // foo +LL | | } + | |_____^ help: try: `some_slice.fill(0);` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_unwrap_or_default_unfixable.rs b/src/tools/clippy/tests/ui/manual_unwrap_or_default_unfixable.rs new file mode 100644 index 00000000000..acc54b52816 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_unwrap_or_default_unfixable.rs @@ -0,0 +1,15 @@ +//@no-rustfix +fn issue_12670() { + // no auto: type not found + #[allow(clippy::match_result_ok)] + let _ = if let Some(x) = "1".parse().ok() { + x + } else { + i32::default() + }; + let _ = if let Some(x) = None { x } else { i32::default() }; + // auto fix with unwrap_or_default + let a: Option<i32> = None; + let _ = if let Some(x) = a { x } else { i32::default() }; + let _ = if let Some(x) = Some(99) { x } else { i32::default() }; +} diff --git a/src/tools/clippy/tests/ui/manual_unwrap_or_default_unfixable.stderr b/src/tools/clippy/tests/ui/manual_unwrap_or_default_unfixable.stderr new file mode 100644 index 00000000000..3849d33cf25 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_unwrap_or_default_unfixable.stderr @@ -0,0 +1,34 @@ +error: if let can be simplified with `.unwrap_or_default()` + --> tests/ui/manual_unwrap_or_default_unfixable.rs:5:13 + | +LL | let _ = if let Some(x) = "1".parse().ok() { + | _____________^ +LL | | x +LL | | } else { +LL | | i32::default() +LL | | }; + | |_____^ help: ascribe the type i32 and replace your expression with: `"1".parse().ok().unwrap_or_default()` + | + = note: `-D clippy::manual-unwrap-or-default` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_unwrap_or_default)]` + +error: if let can be simplified with `.unwrap_or_default()` + --> tests/ui/manual_unwrap_or_default_unfixable.rs:10:13 + | +LL | let _ = if let Some(x) = None { x } else { i32::default() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `None::<i32>.unwrap_or_default()` + +error: if let can be simplified with `.unwrap_or_default()` + --> tests/ui/manual_unwrap_or_default_unfixable.rs:13:13 + | +LL | let _ = if let Some(x) = a { x } else { i32::default() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `a.unwrap_or_default()` + +error: if let can be simplified with `.unwrap_or_default()` + --> tests/ui/manual_unwrap_or_default_unfixable.rs:14:13 + | +LL | let _ = if let Some(x) = Some(99) { x } else { i32::default() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `Some(99).unwrap_or_default()` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_option_take.fixed b/src/tools/clippy/tests/ui/needless_option_take.fixed new file mode 100644 index 00000000000..6514b67ef7a --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_option_take.fixed @@ -0,0 +1,58 @@ +struct MyStruct; + +impl MyStruct { + pub fn get_option() -> Option<Self> { + todo!() + } +} + +fn return_option() -> Option<i32> { + todo!() +} + +fn main() { + println!("Testing non erroneous option_take_on_temporary"); + let mut option = Some(1); + let _ = Box::new(move || option.take().unwrap()); + + println!("Testing non erroneous option_take_on_temporary"); + let x = Some(3); + x.as_ref(); + + let x = Some(3); + x.as_ref(); + //~^ ERROR: called `Option::take()` on a temporary value + + println!("Testing non erroneous option_take_on_temporary"); + let mut x = Some(3); + let y = x.as_mut(); + + let mut x = Some(3); + let y = x.as_mut(); + //~^ ERROR: called `Option::take()` on a temporary value + let y = x.replace(289); + //~^ ERROR: called `Option::take()` on a temporary value + + let y = Some(3).as_mut(); + //~^ ERROR: called `Option::take()` on a temporary value + + let y = Option::as_mut(&mut x); + //~^ ERROR: called `Option::take()` on a temporary value + + let x = return_option(); + let x = return_option(); + //~^ ERROR: called `Option::take()` on a temporary value + + let x = MyStruct::get_option(); + let x = MyStruct::get_option(); + //~^ ERROR: called `Option::take()` on a temporary value + + let mut my_vec = vec![1, 2, 3]; + my_vec.push(4); + let y = my_vec.first(); + let y = my_vec.first(); + //~^ ERROR: called `Option::take()` on a temporary value + + let y = my_vec.first(); + //~^ ERROR: called `Option::take()` on a temporary value +} diff --git a/src/tools/clippy/tests/ui/needless_option_take.stderr b/src/tools/clippy/tests/ui/needless_option_take.stderr index e036bd53170..3fc339ed79e 100644 --- a/src/tools/clippy/tests/ui/needless_option_take.stderr +++ b/src/tools/clippy/tests/ui/needless_option_take.stderr @@ -2,7 +2,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:23:5 | LL | x.as_ref().take(); - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^------- + | | + | help: remove | = note: `as_ref` creates a temporary value, so calling take() has no effect = note: `-D clippy::needless-option-take` implied by `-D warnings` @@ -12,7 +14,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:31:13 | LL | let y = x.as_mut().take(); - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^------- + | | + | help: remove | = note: `as_mut` creates a temporary value, so calling take() has no effect @@ -20,7 +24,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:33:13 | LL | let y = x.replace(289).take(); - | ^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^------- + | | + | help: remove | = note: `replace` creates a temporary value, so calling take() has no effect @@ -28,7 +34,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:36:13 | LL | let y = Some(3).as_mut().take(); - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^------- + | | + | help: remove | = note: `as_mut` creates a temporary value, so calling take() has no effect @@ -36,7 +44,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:39:13 | LL | let y = Option::as_mut(&mut x).take(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^------- + | | + | help: remove | = note: `as_mut` creates a temporary value, so calling take() has no effect @@ -44,7 +54,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:43:13 | LL | let x = return_option().take(); - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^------- + | | + | help: remove | = note: `return_option` creates a temporary value, so calling take() has no effect @@ -52,7 +64,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:47:13 | LL | let x = MyStruct::get_option().take(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^------- + | | + | help: remove | = note: `get_option` creates a temporary value, so calling take() has no effect @@ -60,7 +74,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:53:13 | LL | let y = my_vec.first().take(); - | ^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^------- + | | + | help: remove | = note: `first` creates a temporary value, so calling take() has no effect @@ -68,7 +84,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:56:13 | LL | let y = my_vec.first().take(); - | ^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^------- + | | + | help: remove | = note: `first` creates a temporary value, so calling take() has no effect diff --git a/src/tools/clippy/tests/ui/needless_range_loop.rs b/src/tools/clippy/tests/ui/needless_range_loop.rs index 3f242195330..75f1896eded 100644 --- a/src/tools/clippy/tests/ui/needless_range_loop.rs +++ b/src/tools/clippy/tests/ui/needless_range_loop.rs @@ -2,7 +2,8 @@ #![allow( clippy::uninlined_format_args, clippy::unnecessary_literal_unwrap, - clippy::useless_vec + clippy::useless_vec, + clippy::manual_slice_fill )] //@no-rustfix static STATIC: [usize; 4] = [0, 1, 8, 16]; diff --git a/src/tools/clippy/tests/ui/needless_range_loop.stderr b/src/tools/clippy/tests/ui/needless_range_loop.stderr index dc2cf437e02..503d796e5e8 100644 --- a/src/tools/clippy/tests/ui/needless_range_loop.stderr +++ b/src/tools/clippy/tests/ui/needless_range_loop.stderr @@ -1,5 +1,5 @@ error: the loop variable `i` is only used to index `vec` - --> tests/ui/needless_range_loop.rs:15:14 + --> tests/ui/needless_range_loop.rs:16:14 | LL | for i in 0..vec.len() { | ^^^^^^^^^^^^ @@ -12,7 +12,7 @@ LL | for <item> in &vec { | ~~~~~~ ~~~~ error: the loop variable `i` is only used to index `vec` - --> tests/ui/needless_range_loop.rs:26:14 + --> tests/ui/needless_range_loop.rs:27:14 | LL | for i in 0..vec.len() { | ^^^^^^^^^^^^ @@ -23,7 +23,7 @@ LL | for <item> in &vec { | ~~~~~~ ~~~~ error: the loop variable `j` is only used to index `STATIC` - --> tests/ui/needless_range_loop.rs:32:14 + --> tests/ui/needless_range_loop.rs:33:14 | LL | for j in 0..4 { | ^^^^ @@ -34,7 +34,7 @@ LL | for <item> in &STATIC { | ~~~~~~ ~~~~~~~ error: the loop variable `j` is only used to index `CONST` - --> tests/ui/needless_range_loop.rs:37:14 + --> tests/ui/needless_range_loop.rs:38:14 | LL | for j in 0..4 { | ^^^^ @@ -45,7 +45,7 @@ LL | for <item> in &CONST { | ~~~~~~ ~~~~~~ error: the loop variable `i` is used to index `vec` - --> tests/ui/needless_range_loop.rs:42:14 + --> tests/ui/needless_range_loop.rs:43:14 | LL | for i in 0..vec.len() { | ^^^^^^^^^^^^ @@ -56,7 +56,7 @@ LL | for (i, <item>) in vec.iter().enumerate() { | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~ error: the loop variable `i` is only used to index `vec2` - --> tests/ui/needless_range_loop.rs:51:14 + --> tests/ui/needless_range_loop.rs:52:14 | LL | for i in 0..vec.len() { | ^^^^^^^^^^^^ @@ -67,7 +67,7 @@ LL | for <item> in vec2.iter().take(vec.len()) { | ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: the loop variable `i` is only used to index `vec` - --> tests/ui/needless_range_loop.rs:56:14 + --> tests/ui/needless_range_loop.rs:57:14 | LL | for i in 5..vec.len() { | ^^^^^^^^^^^^ @@ -78,7 +78,7 @@ LL | for <item> in vec.iter().skip(5) { | ~~~~~~ ~~~~~~~~~~~~~~~~~~ error: the loop variable `i` is only used to index `vec` - --> tests/ui/needless_range_loop.rs:61:14 + --> tests/ui/needless_range_loop.rs:62:14 | LL | for i in 0..MAX_LEN { | ^^^^^^^^^^ @@ -89,7 +89,7 @@ LL | for <item> in vec.iter().take(MAX_LEN) { | ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~ error: the loop variable `i` is only used to index `vec` - --> tests/ui/needless_range_loop.rs:66:14 + --> tests/ui/needless_range_loop.rs:67:14 | LL | for i in 0..=MAX_LEN { | ^^^^^^^^^^^ @@ -100,7 +100,7 @@ LL | for <item> in vec.iter().take(MAX_LEN + 1) { | ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: the loop variable `i` is only used to index `vec` - --> tests/ui/needless_range_loop.rs:71:14 + --> tests/ui/needless_range_loop.rs:72:14 | LL | for i in 5..10 { | ^^^^^ @@ -111,7 +111,7 @@ LL | for <item> in vec.iter().take(10).skip(5) { | ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: the loop variable `i` is only used to index `vec` - --> tests/ui/needless_range_loop.rs:76:14 + --> tests/ui/needless_range_loop.rs:77:14 | LL | for i in 5..=10 { | ^^^^^^ @@ -122,7 +122,7 @@ LL | for <item> in vec.iter().take(10 + 1).skip(5) { | ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: the loop variable `i` is used to index `vec` - --> tests/ui/needless_range_loop.rs:81:14 + --> tests/ui/needless_range_loop.rs:82:14 | LL | for i in 5..vec.len() { | ^^^^^^^^^^^^ @@ -133,7 +133,7 @@ LL | for (i, <item>) in vec.iter().enumerate().skip(5) { | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: the loop variable `i` is used to index `vec` - --> tests/ui/needless_range_loop.rs:86:14 + --> tests/ui/needless_range_loop.rs:87:14 | LL | for i in 5..10 { | ^^^^^ @@ -144,7 +144,7 @@ LL | for (i, <item>) in vec.iter().enumerate().take(10).skip(5) { | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: the loop variable `i` is used to index `vec` - --> tests/ui/needless_range_loop.rs:92:14 + --> tests/ui/needless_range_loop.rs:93:14 | LL | for i in 0..vec.len() { | ^^^^^^^^^^^^ diff --git a/src/tools/clippy/tests/ui/precedence.fixed b/src/tools/clippy/tests/ui/precedence.fixed index 9864dd2550b..52144a18bac 100644 --- a/src/tools/clippy/tests/ui/precedence.fixed +++ b/src/tools/clippy/tests/ui/precedence.fixed @@ -20,10 +20,10 @@ fn main() { 1 ^ (1 - 1); 3 | (2 - 1); 3 & (5 - 2); - 0x0F00 & (0x00F0 << 4); - 0x0F00 & (0xF000 >> 4); - (0x0F00 << 1) ^ 3; - (0x0F00 << 1) | 2; + 0x0F00 & 0x00F0 << 4; + 0x0F00 & 0xF000 >> 4; + 0x0F00 << 1 ^ 3; + 0x0F00 << 1 | 2; let b = 3; trip!(b * 8); diff --git a/src/tools/clippy/tests/ui/precedence.stderr b/src/tools/clippy/tests/ui/precedence.stderr index 329422cb8a6..68ad5cb4829 100644 --- a/src/tools/clippy/tests/ui/precedence.stderr +++ b/src/tools/clippy/tests/ui/precedence.stderr @@ -43,29 +43,5 @@ error: operator precedence might not be obvious LL | 3 & 5 - 2; | ^^^^^^^^^ help: consider parenthesizing your expression: `3 & (5 - 2)` -error: operator precedence might not be obvious - --> tests/ui/precedence.rs:23:5 - | -LL | 0x0F00 & 0x00F0 << 4; - | ^^^^^^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `0x0F00 & (0x00F0 << 4)` - -error: operator precedence might not be obvious - --> tests/ui/precedence.rs:24:5 - | -LL | 0x0F00 & 0xF000 >> 4; - | ^^^^^^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `0x0F00 & (0xF000 >> 4)` - -error: operator precedence might not be obvious - --> tests/ui/precedence.rs:25:5 - | -LL | 0x0F00 << 1 ^ 3; - | ^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `(0x0F00 << 1) ^ 3` - -error: operator precedence might not be obvious - --> tests/ui/precedence.rs:26:5 - | -LL | 0x0F00 << 1 | 2; - | ^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `(0x0F00 << 1) | 2` - -error: aborting due to 11 previous errors +error: aborting due to 7 previous errors diff --git a/src/tools/clippy/tests/ui/precedence_bits.fixed b/src/tools/clippy/tests/ui/precedence_bits.fixed new file mode 100644 index 00000000000..82fea0d14e4 --- /dev/null +++ b/src/tools/clippy/tests/ui/precedence_bits.fixed @@ -0,0 +1,35 @@ +#![warn(clippy::precedence_bits)] +#![allow( + unused_must_use, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::precedence +)] +#![allow(clippy::identity_op)] +#![allow(clippy::eq_op)] + +macro_rules! trip { + ($a:expr) => { + match $a & 0b1111_1111u8 { + 0 => println!("a is zero ({})", $a), + _ => println!("a is {}", $a), + } + }; +} + +fn main() { + 1 << 2 + 3; + 1 + 2 << 3; + 4 >> 1 + 1; + 1 + 3 >> 2; + 1 ^ 1 - 1; + 3 | 2 - 1; + 3 & 5 - 2; + 0x0F00 & (0x00F0 << 4); + 0x0F00 & (0xF000 >> 4); + (0x0F00 << 1) ^ 3; + (0x0F00 << 1) | 2; + + let b = 3; + trip!(b * 8); +} diff --git a/src/tools/clippy/tests/ui/precedence_bits.rs b/src/tools/clippy/tests/ui/precedence_bits.rs new file mode 100644 index 00000000000..9b353308b6e --- /dev/null +++ b/src/tools/clippy/tests/ui/precedence_bits.rs @@ -0,0 +1,35 @@ +#![warn(clippy::precedence_bits)] +#![allow( + unused_must_use, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::precedence +)] +#![allow(clippy::identity_op)] +#![allow(clippy::eq_op)] + +macro_rules! trip { + ($a:expr) => { + match $a & 0b1111_1111u8 { + 0 => println!("a is zero ({})", $a), + _ => println!("a is {}", $a), + } + }; +} + +fn main() { + 1 << 2 + 3; + 1 + 2 << 3; + 4 >> 1 + 1; + 1 + 3 >> 2; + 1 ^ 1 - 1; + 3 | 2 - 1; + 3 & 5 - 2; + 0x0F00 & 0x00F0 << 4; + 0x0F00 & 0xF000 >> 4; + 0x0F00 << 1 ^ 3; + 0x0F00 << 1 | 2; + + let b = 3; + trip!(b * 8); +} diff --git a/src/tools/clippy/tests/ui/precedence_bits.stderr b/src/tools/clippy/tests/ui/precedence_bits.stderr new file mode 100644 index 00000000000..f468186b363 --- /dev/null +++ b/src/tools/clippy/tests/ui/precedence_bits.stderr @@ -0,0 +1,29 @@ +error: operator precedence might not be obvious + --> tests/ui/precedence_bits.rs:28:5 + | +LL | 0x0F00 & 0x00F0 << 4; + | ^^^^^^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `0x0F00 & (0x00F0 << 4)` + | + = note: `-D clippy::precedence-bits` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::precedence_bits)]` + +error: operator precedence might not be obvious + --> tests/ui/precedence_bits.rs:29:5 + | +LL | 0x0F00 & 0xF000 >> 4; + | ^^^^^^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `0x0F00 & (0xF000 >> 4)` + +error: operator precedence might not be obvious + --> tests/ui/precedence_bits.rs:30:5 + | +LL | 0x0F00 << 1 ^ 3; + | ^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `(0x0F00 << 1) ^ 3` + +error: operator precedence might not be obvious + --> tests/ui/precedence_bits.rs:31:5 + | +LL | 0x0F00 << 1 | 2; + | ^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `(0x0F00 << 1) | 2` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/print_literal.fixed b/src/tools/clippy/tests/ui/print_literal.fixed index 1705a7ff01b..328e9a9b999 100644 --- a/src/tools/clippy/tests/ui/print_literal.fixed +++ b/src/tools/clippy/tests/ui/print_literal.fixed @@ -66,3 +66,17 @@ fn main() { println!("mixed: {{hello}} {world}"); } + +fn issue_13959() { + println!("\""); + println!( + " + foo + \\ + \\\\ + \" + \\\" + bar +" + ); +} diff --git a/src/tools/clippy/tests/ui/print_literal.rs b/src/tools/clippy/tests/ui/print_literal.rs index d10b26b5887..3130d0b6998 100644 --- a/src/tools/clippy/tests/ui/print_literal.rs +++ b/src/tools/clippy/tests/ui/print_literal.rs @@ -66,3 +66,18 @@ fn main() { println!("mixed: {} {world}", "{hello}"); } + +fn issue_13959() { + println!("{}", r#"""#); + println!( + "{}", + r#" + foo + \ + \\ + " + \" + bar +"# + ); +} diff --git a/src/tools/clippy/tests/ui/print_literal.stderr b/src/tools/clippy/tests/ui/print_literal.stderr index c4cbb8bed70..d967b7c2407 100644 --- a/src/tools/clippy/tests/ui/print_literal.stderr +++ b/src/tools/clippy/tests/ui/print_literal.stderr @@ -192,5 +192,41 @@ LL - println!("mixed: {} {world}", "{hello}"); LL + println!("mixed: {{hello}} {world}"); | -error: aborting due to 16 previous errors +error: literal with an empty format string + --> tests/ui/print_literal.rs:71:20 + | +LL | println!("{}", r#"""#); + | ^^^^^^ + | +help: try + | +LL - println!("{}", r#"""#); +LL + println!("\""); + | + +error: literal with an empty format string + --> tests/ui/print_literal.rs:74:9 + | +LL | / r#" +LL | | foo +LL | | \ +LL | | \\ +... | +LL | | bar +LL | | "# + | |__^ + | +help: try + | +LL ~ " +LL + foo +LL + \\ +LL + \\\\ +LL + \" +LL + \\\" +LL + bar +LL ~ " + | + +error: aborting due to 18 previous errors diff --git a/src/tools/clippy/tests/ui/redundant_else.fixed b/src/tools/clippy/tests/ui/redundant_else.fixed new file mode 100644 index 00000000000..47aa79302d2 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_else.fixed @@ -0,0 +1,154 @@ +#![warn(clippy::redundant_else)] +#![allow(clippy::needless_return, clippy::if_same_then_else, clippy::needless_late_init)] + +fn main() { + loop { + // break + if foo() { + println!("Love your neighbor;"); + break; + } + //~^ ERROR: redundant else block + println!("yet don't pull down your hedge."); + // continue + if foo() { + println!("He that lies down with Dogs,"); + continue; + } + //~^ ERROR: redundant else block + println!("shall rise up with fleas."); + // match block + if foo() { + match foo() { + 1 => break, + _ => return, + } + } + //~^ ERROR: redundant else block + println!("You may delay, but time will not."); + } + // else if + if foo() { + return; + } else if foo() { + return; + } + //~^ ERROR: redundant else block + println!("A fat kitchen makes a lean will."); + // let binding outside of block + let _ = { + if foo() { + return; + } + //~^ ERROR: redundant else block + 1 + }; + // else if with let binding outside of block + let _ = { + if foo() { + return; + } else if foo() { + return; + } + //~^ ERROR: redundant else block + 2 + }; + // inside if let + let _ = if let Some(1) = foo() { + let _ = 1; + if foo() { + return; + } + //~^ ERROR: redundant else block + 1 + } else { + 1 + }; + + // + // non-lint cases + // + + // sanity check + if foo() { + let _ = 1; + } else { + println!("Who is wise? He that learns from every one."); + } + // else if without else + if foo() { + return; + } else if foo() { + foo() + }; + // nested if return + if foo() { + if foo() { + return; + } + } else { + foo() + }; + // match with non-breaking branch + if foo() { + match foo() { + 1 => foo(), + _ => return, + } + } else { + println!("Three may keep a secret, if two of them are dead."); + } + // let binding + let _ = if foo() { + return; + } else { + 1 + }; + // assign + let mut a; + a = if foo() { + return; + } else { + 1 + }; + // assign-op + a += if foo() { + return; + } else { + 1 + }; + // if return else if else + if foo() { + return; + } else if foo() { + 1 + } else { + 2 + }; + // if else if return else + if foo() { + 1 + } else if foo() { + return; + } else { + 2 + }; + // else if with let binding + let _ = if foo() { + return; + } else if foo() { + return; + } else { + 2 + }; + // inside function call + Box::new(if foo() { + return; + } else { + 1 + }); +} + +fn foo<T>() -> T { + unimplemented!("I'm not Santa Claus") +} diff --git a/src/tools/clippy/tests/ui/redundant_else.stderr b/src/tools/clippy/tests/ui/redundant_else.stderr index b649a210b5f..ecc16f7cda5 100644 --- a/src/tools/clippy/tests/ui/redundant_else.stderr +++ b/src/tools/clippy/tests/ui/redundant_else.stderr @@ -1,88 +1,123 @@ error: redundant else block - --> tests/ui/redundant_else.rs:10:16 + --> tests/ui/redundant_else.rs:10:10 | LL | } else { - | ________________^ + | __________^ LL | | LL | | println!("yet don't pull down your hedge."); LL | | } | |_________^ | - = help: remove the `else` block and move the contents out = note: `-D clippy::redundant-else` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::redundant_else)]` +help: remove the `else` block and move the contents out + | +LL ~ } +LL + +LL + println!("yet don't pull down your hedge."); + | error: redundant else block - --> tests/ui/redundant_else.rs:18:16 + --> tests/ui/redundant_else.rs:18:10 | LL | } else { - | ________________^ + | __________^ LL | | LL | | println!("shall rise up with fleas."); LL | | } | |_________^ | - = help: remove the `else` block and move the contents out +help: remove the `else` block and move the contents out + | +LL ~ } +LL + +LL + println!("shall rise up with fleas."); + | error: redundant else block - --> tests/ui/redundant_else.rs:28:16 + --> tests/ui/redundant_else.rs:28:10 | LL | } else { - | ________________^ + | __________^ LL | | LL | | println!("You may delay, but time will not."); LL | | } | |_________^ | - = help: remove the `else` block and move the contents out +help: remove the `else` block and move the contents out + | +LL ~ } +LL + +LL + println!("You may delay, but time will not."); + | error: redundant else block - --> tests/ui/redundant_else.rs:38:12 + --> tests/ui/redundant_else.rs:38:6 | LL | } else { - | ____________^ + | ______^ LL | | LL | | println!("A fat kitchen makes a lean will."); LL | | } | |_____^ | - = help: remove the `else` block and move the contents out +help: remove the `else` block and move the contents out + | +LL ~ } +LL + +LL + println!("A fat kitchen makes a lean will."); + | error: redundant else block - --> tests/ui/redundant_else.rs:46:16 + --> tests/ui/redundant_else.rs:46:10 | LL | } else { - | ________________^ + | __________^ LL | | LL | | 1 LL | | } | |_________^ | - = help: remove the `else` block and move the contents out +help: remove the `else` block and move the contents out + | +LL ~ } +LL + +LL + 1 + | error: redundant else block - --> tests/ui/redundant_else.rs:57:16 + --> tests/ui/redundant_else.rs:57:10 | LL | } else { - | ________________^ + | __________^ LL | | LL | | 2 LL | | } | |_________^ | - = help: remove the `else` block and move the contents out +help: remove the `else` block and move the contents out + | +LL ~ } +LL + +LL + 2 + | error: redundant else block - --> tests/ui/redundant_else.rs:67:16 + --> tests/ui/redundant_else.rs:67:10 | LL | } else { - | ________________^ + | __________^ LL | | LL | | 1 LL | | } | |_________^ | - = help: remove the `else` block and move the contents out +help: remove the `else` block and move the contents out + | +LL ~ } +LL + +LL + 1 + | error: aborting due to 7 previous errors diff --git a/src/tools/clippy/tests/ui/return_and_then.fixed b/src/tools/clippy/tests/ui/return_and_then.fixed new file mode 100644 index 00000000000..9736a51ac86 --- /dev/null +++ b/src/tools/clippy/tests/ui/return_and_then.fixed @@ -0,0 +1,67 @@ +#![warn(clippy::return_and_then)] + +fn main() { + fn test_opt_block(opt: Option<i32>) -> Option<i32> { + let n = opt?; + let mut ret = n + 1; + ret += n; + if n > 1 { Some(ret) } else { None } + } + + fn test_opt_func(opt: Option<i32>) -> Option<i32> { + let n = opt?; + test_opt_block(Some(n)) + } + + fn test_call_chain() -> Option<i32> { + let n = gen_option(1)?; + test_opt_block(Some(n)) + } + + fn test_res_block(opt: Result<i32, i32>) -> Result<i32, i32> { + let n = opt?; + if n > 1 { Ok(n + 1) } else { Err(n) } + } + + fn test_res_func(opt: Result<i32, i32>) -> Result<i32, i32> { + let n = opt?; + test_res_block(Ok(n)) + } + + fn test_ref_only() -> Option<i32> { + // ref: empty string + let x = Some("")?; + if x.len() > 2 { Some(3) } else { None } + } + + fn test_tmp_only() -> Option<i32> { + // unused temporary: vec![1, 2, 4] + let x = Some(match (vec![1, 2, 3], vec![1, 2, 4]) { + (a, _) if a.len() > 1 => a, + (_, b) => b, + })?; + if x.len() > 2 { Some(3) } else { None } + } + + // should not lint + fn test_tmp_ref() -> Option<String> { + String::from("<BOOM>") + .strip_prefix("<") + .and_then(|s| s.strip_suffix(">").map(String::from)) + } + + // should not lint + fn test_unconsumed_tmp() -> Option<i32> { + [1, 2, 3] + .iter() + .map(|x| x + 1) + .collect::<Vec<_>>() // temporary Vec created here + .as_slice() // creates temporary slice + .first() // creates temporary reference + .and_then(|x| test_opt_block(Some(*x))) + } +} + +fn gen_option(n: i32) -> Option<i32> { + Some(n) +} diff --git a/src/tools/clippy/tests/ui/return_and_then.rs b/src/tools/clippy/tests/ui/return_and_then.rs new file mode 100644 index 00000000000..8bcbdfc3a63 --- /dev/null +++ b/src/tools/clippy/tests/ui/return_and_then.rs @@ -0,0 +1,63 @@ +#![warn(clippy::return_and_then)] + +fn main() { + fn test_opt_block(opt: Option<i32>) -> Option<i32> { + opt.and_then(|n| { + let mut ret = n + 1; + ret += n; + if n > 1 { Some(ret) } else { None } + }) + } + + fn test_opt_func(opt: Option<i32>) -> Option<i32> { + opt.and_then(|n| test_opt_block(Some(n))) + } + + fn test_call_chain() -> Option<i32> { + gen_option(1).and_then(|n| test_opt_block(Some(n))) + } + + fn test_res_block(opt: Result<i32, i32>) -> Result<i32, i32> { + opt.and_then(|n| if n > 1 { Ok(n + 1) } else { Err(n) }) + } + + fn test_res_func(opt: Result<i32, i32>) -> Result<i32, i32> { + opt.and_then(|n| test_res_block(Ok(n))) + } + + fn test_ref_only() -> Option<i32> { + // ref: empty string + Some("").and_then(|x| if x.len() > 2 { Some(3) } else { None }) + } + + fn test_tmp_only() -> Option<i32> { + // unused temporary: vec![1, 2, 4] + Some(match (vec![1, 2, 3], vec![1, 2, 4]) { + (a, _) if a.len() > 1 => a, + (_, b) => b, + }) + .and_then(|x| if x.len() > 2 { Some(3) } else { None }) + } + + // should not lint + fn test_tmp_ref() -> Option<String> { + String::from("<BOOM>") + .strip_prefix("<") + .and_then(|s| s.strip_suffix(">").map(String::from)) + } + + // should not lint + fn test_unconsumed_tmp() -> Option<i32> { + [1, 2, 3] + .iter() + .map(|x| x + 1) + .collect::<Vec<_>>() // temporary Vec created here + .as_slice() // creates temporary slice + .first() // creates temporary reference + .and_then(|x| test_opt_block(Some(*x))) + } +} + +fn gen_option(n: i32) -> Option<i32> { + Some(n) +} diff --git a/src/tools/clippy/tests/ui/return_and_then.stderr b/src/tools/clippy/tests/ui/return_and_then.stderr new file mode 100644 index 00000000000..b2e8bf2ca45 --- /dev/null +++ b/src/tools/clippy/tests/ui/return_and_then.stderr @@ -0,0 +1,101 @@ +error: use the question mark operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:5:9 + | +LL | / opt.and_then(|n| { +LL | | let mut ret = n + 1; +LL | | ret += n; +LL | | if n > 1 { Some(ret) } else { None } +LL | | }) + | |__________^ + | + = note: `-D clippy::return-and-then` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::return_and_then)]` +help: try + | +LL ~ let n = opt?; +LL + let mut ret = n + 1; +LL + ret += n; +LL + if n > 1 { Some(ret) } else { None } + | + +error: use the question mark operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:13:9 + | +LL | opt.and_then(|n| test_opt_block(Some(n))) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ let n = opt?; +LL + test_opt_block(Some(n)) + | + +error: use the question mark operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:17:9 + | +LL | gen_option(1).and_then(|n| test_opt_block(Some(n))) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ let n = gen_option(1)?; +LL + test_opt_block(Some(n)) + | + +error: use the question mark operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:21:9 + | +LL | opt.and_then(|n| if n > 1 { Ok(n + 1) } else { Err(n) }) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ let n = opt?; +LL + if n > 1 { Ok(n + 1) } else { Err(n) } + | + +error: use the question mark operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:25:9 + | +LL | opt.and_then(|n| test_res_block(Ok(n))) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ let n = opt?; +LL + test_res_block(Ok(n)) + | + +error: use the question mark operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:30:9 + | +LL | Some("").and_then(|x| if x.len() > 2 { Some(3) } else { None }) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ let x = Some("")?; +LL + if x.len() > 2 { Some(3) } else { None } + | + +error: use the question mark operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:35:9 + | +LL | / Some(match (vec![1, 2, 3], vec![1, 2, 4]) { +LL | | (a, _) if a.len() > 1 => a, +LL | | (_, b) => b, +LL | | }) +LL | | .and_then(|x| if x.len() > 2 { Some(3) } else { None }) + | |_______________________________________________________________^ + | +help: try + | +LL ~ let x = Some(match (vec![1, 2, 3], vec![1, 2, 4]) { +LL + (a, _) if a.len() > 1 => a, +LL + (_, b) => b, +LL + })?; +LL + if x.len() > 2 { Some(3) } else { None } + | + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/size_of_in_element_count/expressions.rs b/src/tools/clippy/tests/ui/size_of_in_element_count/expressions.rs index 91b7ea3922c..f405ba200ac 100644 --- a/src/tools/clippy/tests/ui/size_of_in_element_count/expressions.rs +++ b/src/tools/clippy/tests/ui/size_of_in_element_count/expressions.rs @@ -8,11 +8,11 @@ fn main() { const SIZE: usize = 128; const HALF_SIZE: usize = SIZE / 2; const DOUBLE_SIZE: usize = SIZE * 2; - let mut x = [2u8; SIZE]; - let mut y = [2u8; SIZE]; + let mut x = [2u16; SIZE]; + let mut y = [2u16; SIZE]; // Count expression involving multiplication of size_of (Should trigger the lint) - unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::<u8>() * SIZE) }; + unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::<u16>() * SIZE) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` // Count expression involving nested multiplications of size_of (Should trigger the lint) @@ -20,22 +20,19 @@ fn main() { //~^ ERROR: found a count of bytes instead of a count of elements of `T` // Count expression involving divisions of size_of (Should trigger the lint) - unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE * size_of::<u8>() / 2) }; + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE * size_of::<u16>() / 2) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` // Count expression involving divisions by size_of (Should not trigger the lint) - unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / size_of::<u8>()) }; + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / size_of::<u16>()) }; // Count expression involving divisions by multiple size_of (Should not trigger the lint) - unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / (2 * size_of::<u8>())) }; + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / (2 * size_of::<u16>())) }; // Count expression involving recursive divisions by size_of (Should trigger the lint) - unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / (2 / size_of::<u8>())) }; + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / (2 / size_of::<u16>())) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` // No size_of calls (Should not trigger the lint) unsafe { copy(x.as_ptr(), y.as_mut_ptr(), SIZE) }; - - // Different types for pointee and size_of (Should not trigger the lint) - unsafe { y.as_mut_ptr().write_bytes(0u8, size_of::<u16>() / 2 * SIZE) }; } diff --git a/src/tools/clippy/tests/ui/size_of_in_element_count/expressions.stderr b/src/tools/clippy/tests/ui/size_of_in_element_count/expressions.stderr index 6396afd7f39..74be0d7773d 100644 --- a/src/tools/clippy/tests/ui/size_of_in_element_count/expressions.stderr +++ b/src/tools/clippy/tests/ui/size_of_in_element_count/expressions.stderr @@ -1,8 +1,8 @@ error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/expressions.rs:15:62 | -LL | unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::<u8>() * SIZE) }; - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::<u16>() * SIZE) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type = note: `-D clippy::size-of-in-element-count` implied by `-D warnings` @@ -19,16 +19,16 @@ LL | unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), HALF_SIZE * si error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/expressions.rs:23:47 | -LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE * size_of::<u8>() / 2) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE * size_of::<u16>() / 2) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/expressions.rs:33:47 | -LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / (2 / size_of::<u8>())) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / (2 / size_of::<u16>())) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type diff --git a/src/tools/clippy/tests/ui/size_of_in_element_count/functions.rs b/src/tools/clippy/tests/ui/size_of_in_element_count/functions.rs index 3501cbdf81c..af18136a1db 100644 --- a/src/tools/clippy/tests/ui/size_of_in_element_count/functions.rs +++ b/src/tools/clippy/tests/ui/size_of_in_element_count/functions.rs @@ -11,57 +11,52 @@ fn main() { const SIZE: usize = 128; const HALF_SIZE: usize = SIZE / 2; const DOUBLE_SIZE: usize = SIZE * 2; - let mut x = [2u8; SIZE]; - let mut y = [2u8; SIZE]; + let mut x = [2u16; SIZE]; + let mut y = [2u16; SIZE]; // Count is size_of (Should trigger the lint) - unsafe { copy_nonoverlapping::<u8>(x.as_ptr(), y.as_mut_ptr(), size_of::<u8>()) }; + unsafe { copy_nonoverlapping::<u16>(x.as_ptr(), y.as_mut_ptr(), size_of::<u16>()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of_val(&x[0])) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { x.as_ptr().copy_to(y.as_mut_ptr(), size_of::<u8>()) }; + unsafe { x.as_ptr().copy_to(y.as_mut_ptr(), size_of::<u16>()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { x.as_ptr().copy_to_nonoverlapping(y.as_mut_ptr(), size_of::<u8>()) }; + unsafe { x.as_ptr().copy_to_nonoverlapping(y.as_mut_ptr(), size_of::<u16>()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { y.as_mut_ptr().copy_from(x.as_ptr(), size_of::<u8>()) }; + unsafe { y.as_mut_ptr().copy_from(x.as_ptr(), size_of::<u16>()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { y.as_mut_ptr().copy_from_nonoverlapping(x.as_ptr(), size_of::<u8>()) }; + unsafe { y.as_mut_ptr().copy_from_nonoverlapping(x.as_ptr(), size_of::<u16>()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of::<u8>()) }; + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of::<u16>()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of_val(&x[0])) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { y.as_mut_ptr().write_bytes(0u8, size_of::<u8>() * SIZE) }; - //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { write_bytes(y.as_mut_ptr(), 0u8, size_of::<u8>() * SIZE) }; - //~^ ERROR: found a count of bytes instead of a count of elements of `T` - - unsafe { swap_nonoverlapping(y.as_mut_ptr(), x.as_mut_ptr(), size_of::<u8>() * SIZE) }; + unsafe { swap_nonoverlapping(y.as_mut_ptr(), x.as_mut_ptr(), size_of::<u16>() * SIZE) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - slice_from_raw_parts_mut(y.as_mut_ptr(), size_of::<u8>() * SIZE); + slice_from_raw_parts_mut(y.as_mut_ptr(), size_of::<u16>() * SIZE); //~^ ERROR: found a count of bytes instead of a count of elements of `T` - slice_from_raw_parts(y.as_ptr(), size_of::<u8>() * SIZE); + slice_from_raw_parts(y.as_ptr(), size_of::<u16>() * SIZE); //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { from_raw_parts_mut(y.as_mut_ptr(), size_of::<u8>() * SIZE) }; + unsafe { from_raw_parts_mut(y.as_mut_ptr(), size_of::<u16>() * SIZE) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { from_raw_parts(y.as_ptr(), size_of::<u8>() * SIZE) }; + unsafe { from_raw_parts(y.as_ptr(), size_of::<u16>() * SIZE) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { y.as_mut_ptr().sub(size_of::<u8>()) }; + unsafe { y.as_mut_ptr().sub(size_of::<u16>()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - y.as_ptr().wrapping_sub(size_of::<u8>()); + y.as_ptr().wrapping_sub(size_of::<u16>()); //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { y.as_ptr().add(size_of::<u8>()) }; + unsafe { y.as_ptr().add(size_of::<u16>()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - y.as_mut_ptr().wrapping_add(size_of::<u8>()); + y.as_mut_ptr().wrapping_add(size_of::<u16>()); //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { y.as_ptr().offset(size_of::<u8>() as isize) }; + unsafe { y.as_ptr().offset(size_of::<u16>() as isize) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - y.as_mut_ptr().wrapping_offset(size_of::<u8>() as isize); + y.as_mut_ptr().wrapping_offset(size_of::<u16>() as isize); //~^ ERROR: found a count of bytes instead of a count of elements of `T` } diff --git a/src/tools/clippy/tests/ui/size_of_in_element_count/functions.stderr b/src/tools/clippy/tests/ui/size_of_in_element_count/functions.stderr index abde7dc7cd2..de54789b225 100644 --- a/src/tools/clippy/tests/ui/size_of_in_element_count/functions.stderr +++ b/src/tools/clippy/tests/ui/size_of_in_element_count/functions.stderr @@ -1,8 +1,8 @@ error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:18:68 + --> tests/ui/size_of_in_element_count/functions.rs:18:69 | -LL | unsafe { copy_nonoverlapping::<u8>(x.as_ptr(), y.as_mut_ptr(), size_of::<u8>()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { copy_nonoverlapping::<u16>(x.as_ptr(), y.as_mut_ptr(), size_of::<u16>()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type = note: `-D clippy::size-of-in-element-count` implied by `-D warnings` @@ -19,40 +19,40 @@ LL | unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of_val(&x error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/functions.rs:23:49 | -LL | unsafe { x.as_ptr().copy_to(y.as_mut_ptr(), size_of::<u8>()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { x.as_ptr().copy_to(y.as_mut_ptr(), size_of::<u16>()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/functions.rs:25:64 | -LL | unsafe { x.as_ptr().copy_to_nonoverlapping(y.as_mut_ptr(), size_of::<u8>()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { x.as_ptr().copy_to_nonoverlapping(y.as_mut_ptr(), size_of::<u16>()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/functions.rs:27:51 | -LL | unsafe { y.as_mut_ptr().copy_from(x.as_ptr(), size_of::<u8>()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { y.as_mut_ptr().copy_from(x.as_ptr(), size_of::<u16>()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/functions.rs:29:66 | -LL | unsafe { y.as_mut_ptr().copy_from_nonoverlapping(x.as_ptr(), size_of::<u8>()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { y.as_mut_ptr().copy_from_nonoverlapping(x.as_ptr(), size_of::<u16>()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/functions.rs:32:47 | -LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of::<u8>()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of::<u16>()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type @@ -65,108 +65,92 @@ LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of_val(&x[0])) }; = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:37:46 + --> tests/ui/size_of_in_element_count/functions.rs:37:66 | -LL | unsafe { y.as_mut_ptr().write_bytes(0u8, size_of::<u8>() * SIZE) }; - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { swap_nonoverlapping(y.as_mut_ptr(), x.as_mut_ptr(), size_of::<u16>() * SIZE) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:39:47 + --> tests/ui/size_of_in_element_count/functions.rs:40:46 | -LL | unsafe { write_bytes(y.as_mut_ptr(), 0u8, size_of::<u8>() * SIZE) }; - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | slice_from_raw_parts_mut(y.as_mut_ptr(), size_of::<u16>() * SIZE); + | ^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:42:66 + --> tests/ui/size_of_in_element_count/functions.rs:42:38 | -LL | unsafe { swap_nonoverlapping(y.as_mut_ptr(), x.as_mut_ptr(), size_of::<u8>() * SIZE) }; - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | slice_from_raw_parts(y.as_ptr(), size_of::<u16>() * SIZE); + | ^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:45:46 + --> tests/ui/size_of_in_element_count/functions.rs:45:49 | -LL | slice_from_raw_parts_mut(y.as_mut_ptr(), size_of::<u8>() * SIZE); - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { from_raw_parts_mut(y.as_mut_ptr(), size_of::<u16>() * SIZE) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:47:38 + --> tests/ui/size_of_in_element_count/functions.rs:47:41 | -LL | slice_from_raw_parts(y.as_ptr(), size_of::<u8>() * SIZE); - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { from_raw_parts(y.as_ptr(), size_of::<u16>() * SIZE) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:50:49 + --> tests/ui/size_of_in_element_count/functions.rs:50:33 | -LL | unsafe { from_raw_parts_mut(y.as_mut_ptr(), size_of::<u8>() * SIZE) }; - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { y.as_mut_ptr().sub(size_of::<u16>()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:52:41 + --> tests/ui/size_of_in_element_count/functions.rs:52:29 | -LL | unsafe { from_raw_parts(y.as_ptr(), size_of::<u8>() * SIZE) }; - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | y.as_ptr().wrapping_sub(size_of::<u16>()); + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:55:33 + --> tests/ui/size_of_in_element_count/functions.rs:54:29 | -LL | unsafe { y.as_mut_ptr().sub(size_of::<u8>()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { y.as_ptr().add(size_of::<u16>()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:57:29 + --> tests/ui/size_of_in_element_count/functions.rs:56:33 | -LL | y.as_ptr().wrapping_sub(size_of::<u8>()); - | ^^^^^^^^^^^^^^^ +LL | y.as_mut_ptr().wrapping_add(size_of::<u16>()); + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:59:29 + --> tests/ui/size_of_in_element_count/functions.rs:58:32 | -LL | unsafe { y.as_ptr().add(size_of::<u8>()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { y.as_ptr().offset(size_of::<u16>() as isize) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:61:33 + --> tests/ui/size_of_in_element_count/functions.rs:60:36 | -LL | y.as_mut_ptr().wrapping_add(size_of::<u8>()); - | ^^^^^^^^^^^^^^^ +LL | y.as_mut_ptr().wrapping_offset(size_of::<u16>() as isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type -error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:63:32 - | -LL | unsafe { y.as_ptr().offset(size_of::<u8>() as isize) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type - -error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:65:36 - | -LL | y.as_mut_ptr().wrapping_offset(size_of::<u8>() as isize); - | ^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type - -error: aborting due to 21 previous errors +error: aborting due to 19 previous errors diff --git a/src/tools/clippy/tests/ui/suspicious_doc_comments.fixed b/src/tools/clippy/tests/ui/suspicious_doc_comments.fixed index 614fc03571e..d3df6a41cb1 100644 --- a/src/tools/clippy/tests/ui/suspicious_doc_comments.fixed +++ b/src/tools/clippy/tests/ui/suspicious_doc_comments.fixed @@ -1,5 +1,6 @@ #![allow(unused)] #![warn(clippy::suspicious_doc_comments)] +#![allow(clippy::empty_line_after_doc_comments)] //! Real module documentation. //! Fake module documentation. diff --git a/src/tools/clippy/tests/ui/suspicious_doc_comments.rs b/src/tools/clippy/tests/ui/suspicious_doc_comments.rs index 7dcba0fefc9..04db2b199c0 100644 --- a/src/tools/clippy/tests/ui/suspicious_doc_comments.rs +++ b/src/tools/clippy/tests/ui/suspicious_doc_comments.rs @@ -1,5 +1,6 @@ #![allow(unused)] #![warn(clippy::suspicious_doc_comments)] +#![allow(clippy::empty_line_after_doc_comments)] //! Real module documentation. ///! Fake module documentation. diff --git a/src/tools/clippy/tests/ui/suspicious_doc_comments.stderr b/src/tools/clippy/tests/ui/suspicious_doc_comments.stderr index f12053b1595..c34e39cd0fc 100644 --- a/src/tools/clippy/tests/ui/suspicious_doc_comments.stderr +++ b/src/tools/clippy/tests/ui/suspicious_doc_comments.stderr @@ -1,5 +1,5 @@ error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:5:1 + --> tests/ui/suspicious_doc_comments.rs:6:1 | LL | ///! Fake module documentation. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -12,7 +12,7 @@ LL | //! Fake module documentation. | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:9:5 + --> tests/ui/suspicious_doc_comments.rs:10:5 | LL | ///! This module contains useful functions. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -23,7 +23,7 @@ LL | //! This module contains useful functions. | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:21:5 + --> tests/ui/suspicious_doc_comments.rs:22:5 | LL | / /**! This module contains useful functions. LL | | */ @@ -36,7 +36,7 @@ LL + */ | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:35:5 + --> tests/ui/suspicious_doc_comments.rs:36:5 | LL | / ///! This module LL | | ///! contains @@ -51,7 +51,7 @@ LL ~ //! useful functions. | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:43:5 + --> tests/ui/suspicious_doc_comments.rs:44:5 | LL | / ///! a LL | | ///! b @@ -64,7 +64,7 @@ LL ~ //! b | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:51:5 + --> tests/ui/suspicious_doc_comments.rs:52:5 | LL | ///! a | ^^^^^^ @@ -75,7 +75,7 @@ LL | //! a | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:57:5 + --> tests/ui/suspicious_doc_comments.rs:58:5 | LL | / ///! a LL | | @@ -90,7 +90,7 @@ LL ~ //! b | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:69:5 + --> tests/ui/suspicious_doc_comments.rs:70:5 | LL | ///! Very cool macro | ^^^^^^^^^^^^^^^^^^^^ @@ -101,7 +101,7 @@ LL | //! Very cool macro | error: this is an outer doc comment and does not apply to the parent module or crate - --> tests/ui/suspicious_doc_comments.rs:76:5 + --> tests/ui/suspicious_doc_comments.rs:77:5 | LL | ///! Huh. | ^^^^^^^^^ diff --git a/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.stderr b/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.stderr index fb8fb1a0090..26166e2fc8d 100644 --- a/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.stderr +++ b/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.stderr @@ -1,4 +1,4 @@ -error: `ref` directly on a function argument is ignored. Consider using a reference type instead +error: `ref` directly on a function parameter does not prevent taking ownership of the passed argument. Consider using a reference type instead --> tests/ui/toplevel_ref_arg_non_rustfix.rs:9:15 | LL | fn the_answer(ref mut x: u8) { @@ -7,7 +7,7 @@ LL | fn the_answer(ref mut x: u8) { = note: `-D clippy::toplevel-ref-arg` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::toplevel_ref_arg)]` -error: `ref` directly on a function argument is ignored. Consider using a reference type instead +error: `ref` directly on a function parameter does not prevent taking ownership of the passed argument. Consider using a reference type instead --> tests/ui/toplevel_ref_arg_non_rustfix.rs:20:24 | LL | fn fun_example(ref _x: usize) {} diff --git a/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2021.fixed b/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2021.fixed index 7a3b79553de..343c88b9815 100644 --- a/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2021.fixed +++ b/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2021.fixed @@ -55,3 +55,9 @@ fn no_borrow_issue(a: u32, b: u32) { None => {}, } } + +fn issue14100() -> bool { + // Removing the `;` would make the block type be `()` instead of `!`, and this could no longer be + // cast into the `bool` function return type. + if return true {}; +} diff --git a/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2024.fixed b/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2024.fixed index d186d5e7ebc..1cba5760eb0 100644 --- a/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2024.fixed +++ b/src/tools/clippy/tests/ui/unnecessary_semicolon.edition2024.fixed @@ -55,3 +55,9 @@ fn no_borrow_issue(a: u32, b: u32) { None => {}, } } + +fn issue14100() -> bool { + // Removing the `;` would make the block type be `()` instead of `!`, and this could no longer be + // cast into the `bool` function return type. + if return true {}; +} diff --git a/src/tools/clippy/tests/ui/unnecessary_semicolon.rs b/src/tools/clippy/tests/ui/unnecessary_semicolon.rs index 3028c5b27b3..6abbbd79aaf 100644 --- a/src/tools/clippy/tests/ui/unnecessary_semicolon.rs +++ b/src/tools/clippy/tests/ui/unnecessary_semicolon.rs @@ -55,3 +55,9 @@ fn no_borrow_issue(a: u32, b: u32) { None => {}, }; } + +fn issue14100() -> bool { + // Removing the `;` would make the block type be `()` instead of `!`, and this could no longer be + // cast into the `bool` function return type. + if return true {}; +} diff --git a/src/tools/clippy/tests/ui/write_literal.fixed b/src/tools/clippy/tests/ui/write_literal.fixed index 3d216b76cbf..f1def776e1b 100644 --- a/src/tools/clippy/tests/ui/write_literal.fixed +++ b/src/tools/clippy/tests/ui/write_literal.fixed @@ -62,3 +62,19 @@ fn main() { writeln!(v, "hello {0} {1}, world {2}", 2, 3, 4); //~^ ERROR: literal with an empty format string } + +fn issue_13959() { + let mut v = Vec::new(); + writeln!(v, "\""); + writeln!( + v, + " + foo + \\ + \\\\ + \" + \\\" + bar +" + ); +} diff --git a/src/tools/clippy/tests/ui/write_literal.rs b/src/tools/clippy/tests/ui/write_literal.rs index 79d6daa2e3b..1b7df91b47e 100644 --- a/src/tools/clippy/tests/ui/write_literal.rs +++ b/src/tools/clippy/tests/ui/write_literal.rs @@ -62,3 +62,20 @@ fn main() { writeln!(v, "{0} {1} {2}, {3} {4}", "hello", 2, 3, "world", 4); //~^ ERROR: literal with an empty format string } + +fn issue_13959() { + let mut v = Vec::new(); + writeln!(v, "{}", r#"""#); + writeln!( + v, + "{}", + r#" + foo + \ + \\ + " + \" + bar +"# + ); +} diff --git a/src/tools/clippy/tests/ui/write_literal.stderr b/src/tools/clippy/tests/ui/write_literal.stderr index 9f4cdfd91e8..35c93d567cd 100644 --- a/src/tools/clippy/tests/ui/write_literal.stderr +++ b/src/tools/clippy/tests/ui/write_literal.stderr @@ -144,5 +144,41 @@ LL - writeln!(v, "{0} {1} {2}, {3} {4}", "hello", 2, 3, "world", 4); LL + writeln!(v, "hello {0} {1}, world {2}", 2, 3, 4); | -error: aborting due to 12 previous errors +error: literal with an empty format string + --> tests/ui/write_literal.rs:68:23 + | +LL | writeln!(v, "{}", r#"""#); + | ^^^^^^ + | +help: try + | +LL - writeln!(v, "{}", r#"""#); +LL + writeln!(v, "\""); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:72:9 + | +LL | / r#" +LL | | foo +LL | | \ +LL | | \\ +... | +LL | | bar +LL | | "# + | |__^ + | +help: try + | +LL ~ " +LL + foo +LL + \\ +LL + \\\\ +LL + \" +LL + \\\" +LL + bar +LL ~ " + | + +error: aborting due to 14 previous errors diff --git a/src/tools/clippy/util/gh-pages/index_template.html b/src/tools/clippy/util/gh-pages/index_template.html index deb0ef0b499..a9b64628003 100644 --- a/src/tools/clippy/util/gh-pages/index_template.html +++ b/src/tools/clippy/util/gh-pages/index_template.html @@ -24,14 +24,16 @@ Otherwise, have a great day =^.^= <link id="styleNight" rel="stylesheet" href="https://rust-lang.github.io/mdBook/tomorrow-night.css" disabled="true"> {# #} <link id="styleAyu" rel="stylesheet" href="https://rust-lang.github.io/mdBook/ayu-highlight.css" disabled="true"> {# #} <link rel="stylesheet" href="style.css"> {# #} + <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js" defer></script> {# #} + <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/languages/rust.min.js" defer></script> {# #} + <script src="script.js" defer></script> {# #} </head> {# #} <body> {# #} - <script src="theme.js"></script> {# #} <div id="settings-dropdown"> {# #} <button class="settings-icon" tabindex="-1"></button> {# #} <div class="settings-menu" tabindex="-1"> {# #} <div class="setting-radio-name">Theme</div> {# #} - <select id="theme-choice" onchange="setTheme(this.value, true)"> {# #} + <select id="theme-choice"> {# #} <option value="ayu">Ayu</option> {# #} <option value="coal">Coal</option> {# #} <option value="light">Light</option> {# #} @@ -39,11 +41,12 @@ Otherwise, have a great day =^.^= <option value="rust">Rust</option> {# #} </select> {# #} <label> {# #} - <input type="checkbox" id="disable-shortcuts" onchange="changeSetting(this)"> {#+ #} + <input type="checkbox" id="disable-shortcuts"> {#+ #} <span>Disable keyboard shortcuts</span> {# #} </label> {# #} </div> {# #} </div> {# #} + <script src="theme.js"></script> {# #} <div class="container"> {# #} <div class="page-header"> {# #} @@ -133,10 +136,10 @@ Otherwise, have a great day =^.^= </div> {# #} </div> {# #} <div class="col-12 col-md-2 btn-group expansion-group"> {# #} - <button title="Collapse All" class="btn btn-default expansion-control" type="button" onclick="toggleExpansion(false)"> {# #} + <button title="Collapse All" class="btn btn-default expansion-control" type="button" id="collapse-all"> {# #} <span class="glyphicon glyphicon-collapse-up"></span> {# #} </button> {# #} - <button title="Expand All" class="btn btn-default expansion-control" type="button" onclick="toggleExpansion(true)"> {# #} + <button title="Expand All" class="btn btn-default expansion-control" type="button" id="expand-all"> {# #} <span class="glyphicon glyphicon-collapse-down"></span> {# #} </button> {# #} </div> {# #} @@ -145,13 +148,13 @@ Otherwise, have a great day =^.^= {% for lint in lints %} <article class="panel panel-default" id="{{lint.id}}"> {# #} <input id="label-{{lint.id}}" type="checkbox"> {# #} - <label for="label-{{lint.id}}" onclick="highlightIfNeeded('{{lint.id}}')"> {# #} + <label for="label-{{lint.id}}"> {# #} <header class="panel-heading"> {# #} <h2 class="panel-title"> {# #} <div class="panel-title-name" id="lint-{{lint.id}}"> {# #} <span>{{lint.id}}</span> {#+ #} - <a href="#{{lint.id}}" onclick="lintAnchor(event)" class="anchor label label-default">¶</a> {#+ #} - <a href="" class="anchor label label-default" onclick="copyToClipboard(event)"> {# #} + <a href="#{{lint.id}}" class="lint-anchor anchor label label-default">¶</a> {#+ #} + <a href="" class="copy-to-clipboard anchor label label-default"> {# #} 📋 {# #} </a> {# #} </div> {# #} @@ -227,9 +230,5 @@ Otherwise, have a great day =^.^= ></path> {# #} </svg> {# #} </a> {# #} - - <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js"></script> {# #} - <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/languages/rust.min.js"></script> {# #} - <script src="script.js"></script> {# #} </body> {# #} </html> {# #} diff --git a/src/tools/clippy/util/gh-pages/script.js b/src/tools/clippy/util/gh-pages/script.js index 34d76ad642e..c942a6a05a1 100644 --- a/src/tools/clippy/util/gh-pages/script.js +++ b/src/tools/clippy/util/gh-pages/script.js @@ -1,3 +1,5 @@ +"use strict"; + window.searchState = { timeout: null, inputElem: document.getElementById("search-input"), @@ -124,13 +126,6 @@ function toggleElements(filter, value) { } } -function changeSetting(elem) { - if (elem.id === "disable-shortcuts") { - disableShortcuts = elem.checked; - storeValue(elem.id, elem.checked); - } -} - function onEachLazy(lazyArray, func) { const arr = Array.prototype.slice.call(lazyArray); for (const el of arr) { @@ -138,17 +133,9 @@ function onEachLazy(lazyArray, func) { } } -function highlightIfNeeded(lintId) { - onEachLazy(document.querySelectorAll(`#${lintId} pre > code:not(.hljs)`), el => { - hljs.highlightElement(el.parentElement) - el.classList.add("highlighted"); - }); -} - function expandLint(lintId) { const elem = document.querySelector(`#${lintId} > input[type="checkbox"]`); elem.checked = true; - highlightIfNeeded(lintId); } function lintAnchor(event) { @@ -194,13 +181,9 @@ function handleBlur(event, elementId) { } function toggleExpansion(expand) { - onEachLazy( - document.querySelectorAll("article"), - expand ? el => { - el.classList.remove("collapsed"); - highlightIfNeeded(el); - } : el => el.classList.add("collapsed"), - ); + for (const checkbox of document.querySelectorAll("article input[type=checkbox]")) { + checkbox.checked = expand; + } } // Returns the current URL without any query parameter or hash. @@ -535,7 +518,7 @@ function parseURLFilters() { for (const [corres_key, corres_value] of Object.entries(URL_PARAMS_CORRESPONDENCE)) { if (corres_value === key) { if (key !== "versions") { - const settings = new Set(value.split(",")); + const settings = new Set(value.split(",")); onEachLazy(document.querySelectorAll(`#lint-${key} ul input`), elem => { elem.checked = settings.has(elem.getAttribute("data-value")); updateFilter(elem, corres_key, true); @@ -555,12 +538,60 @@ function parseURLFilters() { } } -document.getElementById(`theme-choice`).value = loadValue("theme"); -let disableShortcuts = loadValue('disable-shortcuts') === "true"; -document.getElementById("disable-shortcuts").checked = disableShortcuts; +function addListeners() { + disableShortcutsButton.addEventListener("change", () => { + disableShortcuts = disableShortcutsButton.checked; + storeValue("disable-shortcuts", disableShortcuts); + }); + + document.getElementById("expand-all").addEventListener("click", () => toggleExpansion(true)); + document.getElementById("collapse-all").addEventListener("click", () => toggleExpansion(false)); + + // A delegated listener to avoid the upfront cost of >1000 listeners + document.addEventListener("click", event => { + if (!event.target instanceof HTMLAnchorElement) { + return; + } + + if (event.target.classList.contains("lint-anchor")) { + lintAnchor(event); + } else if (event.target.classList.contains("copy-to-clipboard")) { + copyToClipboard(event); + } + }); + + document.addEventListener("keypress", handleShortcut); + document.addEventListener("keydown", handleShortcut); +} + +// Highlight code blocks only when they approach the viewport so that clicking the "Expand All" +// button doesn't take a long time +function highlightLazily() { + if (!'IntersectionObserver' in window) { + return; + } + const observer = new IntersectionObserver((entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + observer.unobserve(entry.target); + for (const code of entry.target.querySelectorAll("pre code")) { + hljs.highlightElement(code); + } + } + } + }); + for (const docs of document.querySelectorAll(".lint-docs")) { + observer.observe(docs); + } +} + +let disableShortcuts = loadValue("disable-shortcuts") === "true"; + +const disableShortcutsButton = document.getElementById("disable-shortcuts"); +disableShortcutsButton.checked = disableShortcuts; -document.addEventListener("keypress", handleShortcut); -document.addEventListener("keydown", handleShortcut); +addListeners(); +highlightLazily(); generateSettings(); generateSearch(); diff --git a/src/tools/clippy/util/gh-pages/style.css b/src/tools/clippy/util/gh-pages/style.css index 896f2fdac76..3cc7a919c23 100644 --- a/src/tools/clippy/util/gh-pages/style.css +++ b/src/tools/clippy/util/gh-pages/style.css @@ -1,9 +1,5 @@ blockquote { font-size: 1em; } -[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { - display: none !important; -} - .dropdown-menu { color: var(--fg); background: var(--theme-popup-bg); @@ -188,8 +184,8 @@ details { padding: .5em .5em 0; } -code { - white-space: pre !important; +pre { + padding: 0; } summary { diff --git a/src/tools/clippy/util/gh-pages/theme.js b/src/tools/clippy/util/gh-pages/theme.js index 90f57d4469d..a5dfeed9e8c 100644 --- a/src/tools/clippy/util/gh-pages/theme.js +++ b/src/tools/clippy/util/gh-pages/theme.js @@ -1,3 +1,5 @@ +"use strict"; + function storeValue(settingName, value) { try { localStorage.setItem(`clippy-lint-list-${settingName}`, value); @@ -57,4 +59,11 @@ function setTheme(theme, store) { } else { setTheme(theme, false); } + + const themeChoice = document.getElementById("theme-choice"); + + themeChoice.value = loadValue("theme"); + document.getElementById("theme-choice").addEventListener("change", (e) => { + setTheme(themeChoice.value, true); + }); })(); diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index 8c96554738e..452a2e9a9d5 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -882,14 +882,6 @@ fn iter_header( } let ln = ln.trim(); - // Assume that any directives will be found before the first module or function. This - // doesn't seem to be an optimization with a warm page cache. Maybe with a cold one. - // FIXME(jieyouxu): this will cause `//@` directives in the rest of the test file to - // not be checked. - if ln.starts_with("fn") || ln.starts_with("mod") { - return; - } - let Some(directive_line) = line_directive(line_number, comment, ln) else { continue; }; diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs index ebba16d41f9..023658a3dd4 100644 --- a/src/tools/compiletest/src/header/tests.rs +++ b/src/tools/compiletest/src/header/tests.rs @@ -251,9 +251,10 @@ fn revisions() { let config: Config = cfg().build(); assert_eq!(parse_rs(&config, "//@ revisions: a b c").revisions, vec!["a", "b", "c"],); - assert_eq!(parse_makefile(&config, "# revisions: hello there").revisions, vec![ - "hello", "there" - ],); + assert_eq!( + parse_makefile(&config, "# revisions: hello there").revisions, + vec!["hello", "there"], + ); } #[test] diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index ca48abda5fc..0e2da2b02ca 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -1417,9 +1417,7 @@ impl<'test> TestCx<'test> { } fn is_rustdoc(&self) -> bool { - self.config.src_base.ends_with("rustdoc-ui") - || self.config.src_base.ends_with("rustdoc-js") - || self.config.src_base.ends_with("rustdoc-json") + matches!(self.config.suite.as_str(), "rustdoc-ui" | "rustdoc-js" | "rustdoc-json") } fn get_mir_dump_dir(&self) -> PathBuf { diff --git a/src/tools/compiletest/src/runtest/rustdoc_json.rs b/src/tools/compiletest/src/runtest/rustdoc_json.rs index 31fdb0a5d13..bf7eb2e109a 100644 --- a/src/tools/compiletest/src/runtest/rustdoc_json.rs +++ b/src/tools/compiletest/src/runtest/rustdoc_json.rs @@ -16,13 +16,12 @@ impl TestCx<'_> { self.fatal_proc_rec("rustdoc failed!", &proc_res); } - let root = self.config.find_rust_src_root().unwrap(); let mut json_out = out_dir.join(self.testpaths.file.file_stem().unwrap()); json_out.set_extension("json"); let res = self.run_command_to_procres( Command::new(self.config.jsondocck_path.as_ref().unwrap()) .arg("--doc-dir") - .arg(root.join(&out_dir)) + .arg(&out_dir) .arg("--template") .arg(&self.testpaths.file), ); diff --git a/src/tools/coverage-dump/src/covfun.rs b/src/tools/coverage-dump/src/covfun.rs index 33fac3edccd..82ebd33d0d1 100644 --- a/src/tools/coverage-dump/src/covfun.rs +++ b/src/tools/coverage-dump/src/covfun.rs @@ -95,10 +95,13 @@ pub(crate) fn dump_covfun_mappings( // has increased or decreased the number of physical counters needed. // (It's possible for the generated code to have more counters that // aren't used by any mappings, but that should hopefully be rare.) - println!("Highest counter ID seen: {}", match max_counter { - Some(id) => format!("c{id}"), - None => "(none)".to_owned(), - }); + println!( + "Highest counter ID seen: {}", + match max_counter { + Some(id) => format!("c{id}"), + None => "(none)".to_owned(), + } + ); println!(); } Ok(()) diff --git a/src/tools/features-status-dump/Cargo.toml b/src/tools/features-status-dump/Cargo.toml new file mode 100644 index 00000000000..35be71a46e5 --- /dev/null +++ b/src/tools/features-status-dump/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "features-status-dump" +version = "0.1.0" +license = "MIT OR Apache-2.0" +edition = "2021" + +[dependencies] +anyhow = { version = "1", features = ["backtrace"] } +clap = { version = "4", features = ["derive"] } +serde = { version = "1.0.125", features = [ "derive" ] } +serde_json = "1.0.59" +tidy = { path = "../tidy", features = ["build-metrics"] } diff --git a/src/tools/features-status-dump/src/main.rs b/src/tools/features-status-dump/src/main.rs new file mode 100644 index 00000000000..1ce98d1506d --- /dev/null +++ b/src/tools/features-status-dump/src/main.rs @@ -0,0 +1,53 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::BufWriter; +use std::path::PathBuf; + +use anyhow::{Context, Result}; +use clap::Parser; +use tidy::features::{Feature, collect_lang_features, collect_lib_features}; + +#[derive(Debug, Parser)] +struct Cli { + /// Path to `library/` directory. + #[arg(long)] + library_path: PathBuf, + /// Path to `compiler/` directory. + #[arg(long)] + compiler_path: PathBuf, + /// Path to `output/` directory. + #[arg(long)] + output_path: PathBuf, +} + +#[derive(Debug, serde::Serialize)] +struct FeaturesStatus { + lang_features_status: HashMap<String, Feature>, + lib_features_status: HashMap<String, Feature>, +} + +fn main() -> Result<()> { + let Cli { compiler_path, library_path, output_path } = Cli::parse(); + + let lang_features_status = collect_lang_features(&compiler_path, &mut false); + let lib_features_status = collect_lib_features(&library_path) + .into_iter() + .filter(|&(ref name, _)| !lang_features_status.contains_key(name)) + .collect(); + let features_status = FeaturesStatus { lang_features_status, lib_features_status }; + + let output_dir = output_path.parent().with_context(|| { + format!("failed to get parent dir of output path `{}`", output_path.display()) + })?; + std::fs::create_dir_all(output_dir).with_context(|| { + format!("failed to create output directory at `{}`", output_dir.display()) + })?; + + let output_file = File::create(&output_path).with_context(|| { + format!("failed to create file at given output path `{}`", output_path.display()) + })?; + let writer = BufWriter::new(output_file); + serde_json::to_writer_pretty(writer, &features_status) + .context("failed to write json output")?; + Ok(()) +} diff --git a/src/tools/generate-copyright/src/cargo_metadata.rs b/src/tools/generate-copyright/src/cargo_metadata.rs index 420579372ac..51e353e9b22 100644 --- a/src/tools/generate-copyright/src/cargo_metadata.rs +++ b/src/tools/generate-copyright/src/cargo_metadata.rs @@ -98,12 +98,15 @@ pub fn get_metadata( } // otherwise it's an out-of-tree dependency let package_id = Package { name: package.name, version: package.version.to_string() }; - output.insert(package_id, PackageMetadata { - license: package.license.unwrap_or_else(|| String::from("Unspecified")), - authors: package.authors, - notices: BTreeMap::new(), - is_in_libstd: None, - }); + output.insert( + package_id, + PackageMetadata { + license: package.license.unwrap_or_else(|| String::from("Unspecified")), + authors: package.authors, + notices: BTreeMap::new(), + is_in_libstd: None, + }, + ); } } diff --git a/src/tools/generate-copyright/src/main.rs b/src/tools/generate-copyright/src/main.rs index 0a446ecff5b..7a014989e68 100644 --- a/src/tools/generate-copyright/src/main.rs +++ b/src/tools/generate-copyright/src/main.rs @@ -24,12 +24,16 @@ fn main() -> Result<(), Error> { let root_path = std::path::absolute(".")?; // Scan Cargo dependencies - let mut collected_cargo_metadata = - cargo_metadata::get_metadata_and_notices(&cargo, &out_dir.join("vendor"), &root_path, &[ + let mut collected_cargo_metadata = cargo_metadata::get_metadata_and_notices( + &cargo, + &out_dir.join("vendor"), + &root_path, + &[ Path::new("./Cargo.toml"), Path::new("./src/tools/cargo/Cargo.toml"), Path::new("./library/Cargo.toml"), - ])?; + ], + )?; let library_collected_cargo_metadata = cargo_metadata::get_metadata_and_notices( &cargo, diff --git a/src/tools/jsondoclint/src/validator/tests.rs b/src/tools/jsondoclint/src/validator/tests.rs index a37e6c2eb5c..28deb7e7cee 100644 --- a/src/tools/jsondoclint/src/validator/tests.rs +++ b/src/tools/jsondoclint/src/validator/tests.rs @@ -21,32 +21,42 @@ fn errors_on_missing_links() { root: Id(0), crate_version: None, includes_private: false, - index: FxHashMap::from_iter([(Id(0), Item { - name: Some("root".to_owned()), - id: Id(0), - crate_id: 0, - span: None, - visibility: Visibility::Public, - docs: None, - links: FxHashMap::from_iter([("Not Found".to_owned(), Id(1))]), - attrs: vec![], - deprecation: None, - inner: ItemEnum::Module(Module { is_crate: true, items: vec![], is_stripped: false }), - })]), + index: FxHashMap::from_iter([( + Id(0), + Item { + name: Some("root".to_owned()), + id: Id(0), + crate_id: 0, + span: None, + visibility: Visibility::Public, + docs: None, + links: FxHashMap::from_iter([("Not Found".to_owned(), Id(1))]), + attrs: vec![], + deprecation: None, + inner: ItemEnum::Module(Module { + is_crate: true, + items: vec![], + is_stripped: false, + }), + }, + )]), paths: FxHashMap::default(), external_crates: FxHashMap::default(), format_version: rustdoc_json_types::FORMAT_VERSION, }; - check(&k, &[Error { - kind: ErrorKind::NotFound(vec![vec![ - SelectorPart::Field("index".to_owned()), - SelectorPart::Field("0".to_owned()), - SelectorPart::Field("links".to_owned()), - SelectorPart::Field("Not Found".to_owned()), - ]]), - id: Id(1), - }]); + check( + &k, + &[Error { + kind: ErrorKind::NotFound(vec![vec![ + SelectorPart::Field("index".to_owned()), + SelectorPart::Field("0".to_owned()), + SelectorPart::Field("links".to_owned()), + SelectorPart::Field("Not Found".to_owned()), + ]]), + id: Id(1), + }], + ); } // Test we would catch @@ -58,48 +68,60 @@ fn errors_on_local_in_paths_and_not_index() { crate_version: None, includes_private: false, index: FxHashMap::from_iter([ - (Id(0), Item { - id: Id(0), - crate_id: 0, - name: Some("microcore".to_owned()), - span: None, - visibility: Visibility::Public, - docs: None, - links: FxHashMap::from_iter([(("prim@i32".to_owned(), Id(2)))]), - attrs: Vec::new(), - deprecation: None, - inner: ItemEnum::Module(Module { - is_crate: true, - items: vec![Id(1)], - is_stripped: false, - }), - }), - (Id(1), Item { - id: Id(1), - crate_id: 0, - name: Some("i32".to_owned()), - span: None, - visibility: Visibility::Public, - docs: None, - links: FxHashMap::default(), - attrs: Vec::new(), - deprecation: None, - inner: ItemEnum::Primitive(Primitive { name: "i32".to_owned(), impls: vec![] }), - }), + ( + Id(0), + Item { + id: Id(0), + crate_id: 0, + name: Some("microcore".to_owned()), + span: None, + visibility: Visibility::Public, + docs: None, + links: FxHashMap::from_iter([(("prim@i32".to_owned(), Id(2)))]), + attrs: Vec::new(), + deprecation: None, + inner: ItemEnum::Module(Module { + is_crate: true, + items: vec![Id(1)], + is_stripped: false, + }), + }, + ), + ( + Id(1), + Item { + id: Id(1), + crate_id: 0, + name: Some("i32".to_owned()), + span: None, + visibility: Visibility::Public, + docs: None, + links: FxHashMap::default(), + attrs: Vec::new(), + deprecation: None, + inner: ItemEnum::Primitive(Primitive { name: "i32".to_owned(), impls: vec![] }), + }, + ), ]), - paths: FxHashMap::from_iter([(Id(2), ItemSummary { - crate_id: 0, - path: vec!["microcore".to_owned(), "i32".to_owned()], - kind: ItemKind::Primitive, - })]), + paths: FxHashMap::from_iter([( + Id(2), + ItemSummary { + crate_id: 0, + path: vec!["microcore".to_owned(), "i32".to_owned()], + kind: ItemKind::Primitive, + }, + )]), external_crates: FxHashMap::default(), format_version: rustdoc_json_types::FORMAT_VERSION, }; - check(&krate, &[Error { - id: Id(2), - kind: ErrorKind::Custom("Id for local item in `paths` but not in `index`".to_owned()), - }]); + check( + &krate, + &[Error { + id: Id(2), + kind: ErrorKind::Custom("Id for local item in `paths` but not in `index`".to_owned()), + }], + ); } #[test] @@ -117,84 +139,96 @@ fn errors_on_missing_path() { crate_version: None, includes_private: false, index: FxHashMap::from_iter([ - (Id(0), Item { - id: Id(0), - crate_id: 0, - name: Some("foo".to_owned()), - span: None, - visibility: Visibility::Public, - docs: None, - links: FxHashMap::default(), - attrs: Vec::new(), - deprecation: None, - inner: ItemEnum::Module(Module { - is_crate: true, - items: vec![Id(1), Id(2)], - is_stripped: false, - }), - }), - (Id(1), Item { - id: Id(0), - crate_id: 0, - name: Some("Bar".to_owned()), - span: None, - visibility: Visibility::Public, - docs: None, - links: FxHashMap::default(), - attrs: Vec::new(), - deprecation: None, - inner: ItemEnum::Struct(Struct { - kind: StructKind::Unit, - generics: generics.clone(), - impls: vec![], - }), - }), - (Id(2), Item { - id: Id(0), - crate_id: 0, - name: Some("mk_bar".to_owned()), - span: None, - visibility: Visibility::Public, - docs: None, - links: FxHashMap::default(), - attrs: Vec::new(), - deprecation: None, - inner: ItemEnum::Function(Function { - sig: FunctionSignature { - inputs: vec![], - output: Some(Type::ResolvedPath(Path { - path: "Bar".to_owned(), - id: Id(1), - args: None, - })), - is_c_variadic: false, - }, - generics, - header: FunctionHeader { - is_const: false, - is_unsafe: false, - is_async: false, - abi: Abi::Rust, - }, - has_body: true, - }), - }), + ( + Id(0), + Item { + id: Id(0), + crate_id: 0, + name: Some("foo".to_owned()), + span: None, + visibility: Visibility::Public, + docs: None, + links: FxHashMap::default(), + attrs: Vec::new(), + deprecation: None, + inner: ItemEnum::Module(Module { + is_crate: true, + items: vec![Id(1), Id(2)], + is_stripped: false, + }), + }, + ), + ( + Id(1), + Item { + id: Id(0), + crate_id: 0, + name: Some("Bar".to_owned()), + span: None, + visibility: Visibility::Public, + docs: None, + links: FxHashMap::default(), + attrs: Vec::new(), + deprecation: None, + inner: ItemEnum::Struct(Struct { + kind: StructKind::Unit, + generics: generics.clone(), + impls: vec![], + }), + }, + ), + ( + Id(2), + Item { + id: Id(0), + crate_id: 0, + name: Some("mk_bar".to_owned()), + span: None, + visibility: Visibility::Public, + docs: None, + links: FxHashMap::default(), + attrs: Vec::new(), + deprecation: None, + inner: ItemEnum::Function(Function { + sig: FunctionSignature { + inputs: vec![], + output: Some(Type::ResolvedPath(Path { + path: "Bar".to_owned(), + id: Id(1), + args: None, + })), + is_c_variadic: false, + }, + generics, + header: FunctionHeader { + is_const: false, + is_unsafe: false, + is_async: false, + abi: Abi::Rust, + }, + has_body: true, + }), + }, + ), ]), - paths: FxHashMap::from_iter([(Id(0), ItemSummary { - crate_id: 0, - path: vec!["foo".to_owned()], - kind: ItemKind::Module, - })]), + paths: FxHashMap::from_iter([( + Id(0), + ItemSummary { crate_id: 0, path: vec!["foo".to_owned()], kind: ItemKind::Module }, + )]), external_crates: FxHashMap::default(), format_version: rustdoc_json_types::FORMAT_VERSION, }; - check(&krate, &[Error { - kind: ErrorKind::Custom( - r#"No entry in '$.paths' for Path { path: "Bar", id: Id(1), args: None }"#.to_owned(), - ), - id: Id(1), - }]); + check( + &krate, + &[Error { + kind: ErrorKind::Custom( + r#"No entry in '$.paths' for Path { path: "Bar", id: Id(1), args: None }"# + .to_owned(), + ), + id: Id(1), + }], + ); } #[test] @@ -204,18 +238,25 @@ fn checks_local_crate_id_is_correct() { root: Id(0), crate_version: None, includes_private: false, - index: FxHashMap::from_iter([(Id(0), Item { - id: Id(0), - crate_id: LOCAL_CRATE_ID.wrapping_add(1), - name: Some("irrelavent".to_owned()), - span: None, - visibility: Visibility::Public, - docs: None, - links: FxHashMap::default(), - attrs: Vec::new(), - deprecation: None, - inner: ItemEnum::Module(Module { is_crate: true, items: vec![], is_stripped: false }), - })]), + index: FxHashMap::from_iter([( + Id(0), + Item { + id: Id(0), + crate_id: LOCAL_CRATE_ID.wrapping_add(1), + name: Some("irrelavent".to_owned()), + span: None, + visibility: Visibility::Public, + docs: None, + links: FxHashMap::default(), + attrs: Vec::new(), + deprecation: None, + inner: ItemEnum::Module(Module { + is_crate: true, + items: vec![], + is_stripped: false, + }), + }, + )]), paths: FxHashMap::default(), external_crates: FxHashMap::default(), format_version: FORMAT_VERSION, diff --git a/src/tools/lint-docs/src/lib.rs b/src/tools/lint-docs/src/lib.rs index f6e84465780..9fd33e23204 100644 --- a/src/tools/lint-docs/src/lib.rs +++ b/src/tools/lint-docs/src/lib.rs @@ -19,24 +19,30 @@ mod groups; /// level of the lint, which will be more difficult to support, since rustc /// currently does not track that historical information. static RENAMES: &[(Level, &[(&str, &str)])] = &[ - (Level::Allow, &[ - ("single-use-lifetime", "single-use-lifetimes"), - ("elided-lifetime-in-path", "elided-lifetimes-in-paths"), - ("async-idents", "keyword-idents"), - ("disjoint-capture-migration", "rust-2021-incompatible-closure-captures"), - ("keyword-idents", "keyword-idents-2018"), - ("or-patterns-back-compat", "rust-2021-incompatible-or-patterns"), - ]), - (Level::Warn, &[ - ("bare-trait-object", "bare-trait-objects"), - ("unstable-name-collision", "unstable-name-collisions"), - ("unused-doc-comment", "unused-doc-comments"), - ("redundant-semicolon", "redundant-semicolons"), - ("overlapping-patterns", "overlapping-range-endpoints"), - ("non-fmt-panic", "non-fmt-panics"), - ("unused-tuple-struct-fields", "dead-code"), - ("static-mut-ref", "static-mut-refs"), - ]), + ( + Level::Allow, + &[ + ("single-use-lifetime", "single-use-lifetimes"), + ("elided-lifetime-in-path", "elided-lifetimes-in-paths"), + ("async-idents", "keyword-idents"), + ("disjoint-capture-migration", "rust-2021-incompatible-closure-captures"), + ("keyword-idents", "keyword-idents-2018"), + ("or-patterns-back-compat", "rust-2021-incompatible-or-patterns"), + ], + ), + ( + Level::Warn, + &[ + ("bare-trait-object", "bare-trait-objects"), + ("unstable-name-collision", "unstable-name-collisions"), + ("unused-doc-comment", "unused-doc-comments"), + ("redundant-semicolon", "redundant-semicolons"), + ("overlapping-patterns", "overlapping-range-endpoints"), + ("non-fmt-panic", "non-fmt-panics"), + ("unused-tuple-struct-fields", "dead-code"), + ("static-mut-ref", "static-mut-refs"), + ], + ), (Level::Deny, &[("exceeding-bitshifts", "arithmetic-overflow")]), ]; diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 685f5670ab4..6b051da1b5a 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -29,7 +29,7 @@ use std::num::NonZero; use std::ops::Range; use std::path::PathBuf; use std::str::FromStr; -use std::sync::Once; +use std::sync::{Arc, Once}; use std::sync::atomic::{AtomicI32, AtomicU32, Ordering}; use miri::{ @@ -38,7 +38,6 @@ use miri::{ }; use rustc_abi::ExternAbi; use rustc_data_structures::sync; -use rustc_data_structures::sync::Lrc; use rustc_driver::Compilation; use rustc_hir::def_id::LOCAL_CRATE; use rustc_hir::{self as hir, Node}; @@ -134,7 +133,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls { // HACK: rustc will emit "crate ... required to be available in rlib format, but // was not found in this form" errors once we use `tcx.dependency_formats()` if // there's no rlib provided, so setting a dummy path here to workaround those errors. - Lrc::make_mut(&mut crate_source).rlib = Some((PathBuf::new(), PathKind::All)); + Arc::make_mut(&mut crate_source).rlib = Some((PathBuf::new(), PathKind::All)); crate_source }; }); diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 45054c37c40..a717d8ccf28 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(bootstrap, feature(trait_upcasting))] #![feature(rustc_private)] #![feature(cell_update)] #![feature(float_gamma)] @@ -9,7 +10,6 @@ #![feature(yeet_expr)] #![feature(nonzero_ops)] #![feature(let_chains)] -#![feature(trait_upcasting)] #![feature(strict_overflow_ops)] #![feature(pointer_is_aligned_to)] #![feature(unqualified_local_imports)] diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 4735db48e81..6bd1076a8a8 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -1151,6 +1151,11 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { } #[inline(always)] + fn contract_checks(ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool> { + interp_ok(ecx.tcx.sess.contract_checks()) + } + + #[inline(always)] fn thread_local_static_pointer( ecx: &mut MiriInterpCx<'tcx>, def_id: DefId, diff --git a/src/tools/miri/tests/fail/dyn-upcast-trait-mismatch.rs b/src/tools/miri/tests/fail/dyn-upcast-trait-mismatch.rs index f450e7e652c..1fd791a91f0 100644 --- a/src/tools/miri/tests/fail/dyn-upcast-trait-mismatch.rs +++ b/src/tools/miri/tests/fail/dyn-upcast-trait-mismatch.rs @@ -1,9 +1,6 @@ // Validation stops this too early. //@compile-flags: -Zmiri-disable-validation -#![feature(trait_upcasting)] -#![allow(incomplete_features)] - trait Foo: PartialEq<i32> + std::fmt::Debug + Send + Sync { #[allow(dead_code)] fn a(&self) -> i32 { diff --git a/src/tools/miri/tests/fail/intrinsics/disjoint_bitor.rs b/src/tools/miri/tests/fail/intrinsics/disjoint_bitor.rs new file mode 100644 index 00000000000..a7127143330 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/disjoint_bitor.rs @@ -0,0 +1,5 @@ +#![feature(core_intrinsics)] +fn main() { + // one bit in common + unsafe { std::intrinsics::disjoint_bitor(0b01101001_u8, 0b10001110) }; //~ ERROR: Undefined Behavior +} diff --git a/src/tools/miri/tests/fail/intrinsics/disjoint_bitor.stderr b/src/tools/miri/tests/fail/intrinsics/disjoint_bitor.stderr new file mode 100644 index 00000000000..82502953118 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/disjoint_bitor.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `assume` called with `false` + --> tests/fail/intrinsics/disjoint_bitor.rs:LL:CC + | +LL | unsafe { std::intrinsics::disjoint_bitor(0b01101001_u8, 0b10001110) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `assume` called with `false` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at tests/fail/intrinsics/disjoint_bitor.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_allocs.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_allocs.rs new file mode 100644 index 00000000000..5a997ad8ec4 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_allocs.rs @@ -0,0 +1,5 @@ +fn main() { + unsafe { + (&1_u8 as *const u8).offset_from(&2_u8); //~ERROR: not both derived from the same allocation + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_allocs.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_allocs.stderr new file mode 100644 index 00000000000..34d7c6a8021 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_allocs.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `ptr_offset_from` called on two different pointers that are not both derived from the same allocation + --> tests/fail/intrinsics/ptr_offset_from_different_allocs.rs:LL:CC + | +LL | (&1_u8 as *const u8).offset_from(&2_u8); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `ptr_offset_from` called on two different pointers that are not both derived from the same allocation + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at tests/fail/intrinsics/ptr_offset_from_different_allocs.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_ints.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_ints.rs index 0acda559d3a..0d34e711ca7 100644 --- a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_ints.rs +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_ints.rs @@ -15,6 +15,6 @@ fn main() { let _ = p1.byte_offset_from(p1); // UB because different pointers. - let _ = p1.byte_offset_from(p2); //~ERROR: no provenance + let _ = p1.byte_offset_from(p2); //~ERROR: not both derived from the same allocation } } diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_ints.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_ints.stderr index 7ef66390fcd..897945d6d5d 100644 --- a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_ints.stderr +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_ints.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: out-of-bounds `offset_from` origin: expected a pointer to the end of 1 byte of memory, but got 0xb[noalloc] which is a dangling pointer (it has no provenance) +error: Undefined Behavior: `ptr_offset_from` called on two different pointers that are not both derived from the same allocation --> tests/fail/intrinsics/ptr_offset_from_different_ints.rs:LL:CC | LL | let _ = p1.byte_offset_from(p2); - | ^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds `offset_from` origin: expected a pointer to the end of 1 byte of memory, but got 0xb[noalloc] which is a dangling pointer (it has no provenance) + | ^^^^^^^^^^^^^^^^^^^^^^^ `ptr_offset_from` called on two different pointers that are not both derived from the same allocation | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.rs new file mode 100644 index 00000000000..06f6b7a0117 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.rs @@ -0,0 +1,7 @@ +fn main() { + let mem = [0u8; 1]; + let ptr = mem.as_ptr(); + unsafe { + ptr.wrapping_add(4).offset_from(ptr); //~ERROR: the memory range between them is not in-bounds of an allocation + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.stderr new file mode 100644 index 00000000000..67df633bef5 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `ptr_offset_from` called on two different pointers where the memory range between them is not in-bounds of an allocation + --> tests/fail/intrinsics/ptr_offset_from_oob.rs:LL:CC + | +LL | ptr.wrapping_add(4).offset_from(ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `ptr_offset_from` called on two different pointers where the memory range between them is not in-bounds of an allocation + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at tests/fail/intrinsics/ptr_offset_from_oob.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/pass/binops.rs b/src/tools/miri/tests/pass/binops.rs index 0988d7ccc4c..0aff7acb29d 100644 --- a/src/tools/miri/tests/pass/binops.rs +++ b/src/tools/miri/tests/pass/binops.rs @@ -2,23 +2,23 @@ fn test_nil() { assert_eq!((), ()); - assert!((!(() != ()))); - assert!((!(() < ()))); - assert!((() <= ())); - assert!((!(() > ()))); - assert!((() >= ())); + assert!(!(() != ())); + assert!(!(() < ())); + assert!(() <= ()); + assert!(!(() > ())); + assert!(() >= ()); } fn test_bool() { - assert!((!(true < false))); - assert!((!(true <= false))); - assert!((true > false)); - assert!((true >= false)); + assert!(!(true < false)); + assert!(!(true <= false)); + assert!(true > false); + assert!(true >= false); - assert!((false < true)); - assert!((false <= true)); - assert!((!(false > true))); - assert!((!(false >= true))); + assert!(false < true); + assert!(false <= true); + assert!(!(false > true)); + assert!(!(false >= true)); // Bools support bitwise binops assert_eq!(false & false, false); @@ -65,9 +65,9 @@ fn test_class() { assert_eq!(q, r); r.y = 17; - assert!((r.y != q.y)); + assert!(r.y != q.y); assert_eq!(r.y, 17); - assert!((q != r)); + assert!(q != r); } pub fn main() { diff --git a/src/tools/miri/tests/pass/box-custom-alloc.rs b/src/tools/miri/tests/pass/box-custom-alloc.rs index 71ce019187c..f0614313e50 100644 --- a/src/tools/miri/tests/pass/box-custom-alloc.rs +++ b/src/tools/miri/tests/pass/box-custom-alloc.rs @@ -1,7 +1,6 @@ //@revisions: stack tree //@[tree]compile-flags: -Zmiri-tree-borrows -#![allow(incomplete_features)] // for trait upcasting -#![feature(allocator_api, trait_upcasting)] +#![feature(allocator_api)] use std::alloc::{AllocError, Allocator, Layout}; use std::cell::Cell; diff --git a/src/tools/miri/tests/pass/dyn-upcast.rs b/src/tools/miri/tests/pass/dyn-upcast.rs index f100c4d6a86..6f8adc09640 100644 --- a/src/tools/miri/tests/pass/dyn-upcast.rs +++ b/src/tools/miri/tests/pass/dyn-upcast.rs @@ -1,6 +1,3 @@ -#![feature(trait_upcasting)] -#![allow(incomplete_features)] - use std::fmt; fn main() { diff --git a/src/tools/publish_toolstate.py b/src/tools/publish_toolstate.py index a639dc20a60..3688513987d 100755 --- a/src/tools/publish_toolstate.py +++ b/src/tools/publish_toolstate.py @@ -30,13 +30,18 @@ except ImportError: # These should be collaborators of the rust-lang/rust repository (with at least # read privileges on it). CI will fail otherwise. MAINTAINERS = { - "book": {"carols10cents"}, - "nomicon": {"frewsxcv", "Gankra", "JohnTitor"}, - "reference": {"Havvy", "matthewjasper", "ehuss"}, - "rust-by-example": {"marioidival"}, - "embedded-book": {"adamgreig", "andre-richter", "jamesmunns", "therealprof"}, + "book": {"ehuss", "chriskrycho", "carols10cents"}, + "nomicon": {"ehuss", "JohnTitor"}, + "reference": {"ehuss"}, + "rust-by-example": {"ehuss", "marioidival"}, + "embedded-book": { + "ehuss", + "adamgreig", + "andre-richter", + "jamesmunns", + "therealprof", + }, "edition-guide": {"ehuss"}, - "rustc-dev-guide": {"spastorino", "amanjeev", "JohnTitor"}, } LABELS = { @@ -46,7 +51,6 @@ LABELS = { "rust-by-example": ["C-bug"], "embedded-book": ["C-bug"], "edition-guide": ["C-bug"], - "rustc-dev-guide": ["C-bug"], } REPOS = { @@ -56,7 +60,6 @@ REPOS = { "rust-by-example": "https://github.com/rust-lang/rust-by-example", "embedded-book": "https://github.com/rust-embedded/book", "edition-guide": "https://github.com/rust-lang/edition-guide", - "rustc-dev-guide": "https://github.com/rust-lang/rustc-dev-guide", } diff --git a/src/tools/run-make-support/src/external_deps/rustc.rs b/src/tools/run-make-support/src/external_deps/rustc.rs index 8d99924a2d1..33dc57cbc07 100644 --- a/src/tools/run-make-support/src/external_deps/rustc.rs +++ b/src/tools/run-make-support/src/external_deps/rustc.rs @@ -338,6 +338,18 @@ impl Rustc { self } + /// Specify `-C debuginfo=...`. + pub fn debuginfo(&mut self, level: &str) -> &mut Self { + self.cmd.arg(format!("-Cdebuginfo={level}")); + self + } + + /// Specify `-C split-debuginfo={packed,unpacked,off}`. + pub fn split_debuginfo(&mut self, split_kind: &str) -> &mut Self { + self.cmd.arg(format!("-Csplit-debuginfo={split_kind}")); + self + } + /// Pass the `--verbose` flag. pub fn verbose(&mut self) -> &mut Self { self.cmd.arg("--verbose"); diff --git a/src/tools/run-make-support/src/fs.rs b/src/tools/run-make-support/src/fs.rs index 7ebe4a9ca13..ceb4ebbaca5 100644 --- a/src/tools/run-make-support/src/fs.rs +++ b/src/tools/run-make-support/src/fs.rs @@ -238,9 +238,7 @@ pub fn set_permissions<P: AsRef<Path>>(path: P, perm: std::fs::Permissions) { )); } -/// A function which prints all file names in the directory `dir` similarly to Unix's `ls`. -/// Useful for debugging. -/// Usage: `eprintln!("{:#?}", shallow_find_dir_entries(some_dir));` +/// List directory entries immediately under the given `dir`. #[track_caller] pub fn shallow_find_dir_entries<P: AsRef<Path>>(dir: P) -> Vec<PathBuf> { let paths = read_dir(dir); diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs index a8c9bec57fd..7e63ab3159a 100644 --- a/src/tools/run-make-support/src/lib.rs +++ b/src/tools/run-make-support/src/lib.rs @@ -94,8 +94,8 @@ pub use artifact_names::{ /// Path-related helpers. pub use path_helpers::{ - cwd, filename_contains, filename_not_in_denylist, has_extension, has_prefix, has_suffix, - not_contains, path, shallow_find_files, build_root, source_root, + build_root, cwd, filename_contains, filename_not_in_denylist, has_extension, has_prefix, + has_suffix, not_contains, path, shallow_find_directories, shallow_find_files, source_root, }; /// Helpers for scoped test execution where certain properties are attempted to be maintained. diff --git a/src/tools/run-make-support/src/path_helpers.rs b/src/tools/run-make-support/src/path_helpers.rs index 1c59f2feea4..b766e50e523 100644 --- a/src/tools/run-make-support/src/path_helpers.rs +++ b/src/tools/run-make-support/src/path_helpers.rs @@ -59,6 +59,25 @@ pub fn shallow_find_files<P: AsRef<Path>, F: Fn(&PathBuf) -> bool>( matching_files } +/// Browse the directory `path` non-recursively and return all directories which respect the +/// parameters outlined by `closure`. +#[track_caller] +pub fn shallow_find_directories<P: AsRef<Path>, F: Fn(&PathBuf) -> bool>( + path: P, + filter: F, +) -> Vec<PathBuf> { + let mut matching_files = Vec::new(); + for entry in rfs::read_dir(path) { + let entry = entry.expect("failed to read directory entry."); + let path = entry.path(); + + if path.is_dir() && filter(&path) { + matching_files.push(path); + } + } + matching_files +} + /// Returns true if the filename at `path` does not contain `expected`. pub fn not_contains<P: AsRef<Path>>(path: P, expected: &str) -> bool { !path.as_ref().file_name().is_some_and(|name| name.to_str().unwrap().contains(expected)) diff --git a/src/tools/rust-installer/src/compression.rs b/src/tools/rust-installer/src/compression.rs index 96c48657c46..df3a98ae789 100644 --- a/src/tools/rust-installer/src/compression.rs +++ b/src/tools/rust-installer/src/compression.rs @@ -81,14 +81,17 @@ impl CompressionFormat { let file = crate::util::create_new_file(path)?; Ok(match self { - CompressionFormat::Gz => Box::new(GzEncoder::new(file, match profile { - CompressionProfile::Fast => flate2::Compression::fast(), - CompressionProfile::Balanced => flate2::Compression::new(6), - CompressionProfile::Best => flate2::Compression::best(), - CompressionProfile::NoOp => panic!( - "compression profile 'no-op' should not call `CompressionFormat::encode`." - ), - })), + CompressionFormat::Gz => Box::new(GzEncoder::new( + file, + match profile { + CompressionProfile::Fast => flate2::Compression::fast(), + CompressionProfile::Balanced => flate2::Compression::new(6), + CompressionProfile::Best => flate2::Compression::best(), + CompressionProfile::NoOp => panic!( + "compression profile 'no-op' should not call `CompressionFormat::encode`." + ), + }, + )), CompressionFormat::Xz => { let encoder = match profile { CompressionProfile::NoOp => panic!( diff --git a/src/tools/rustc-perf-wrapper/Cargo.toml b/src/tools/rustc-perf-wrapper/Cargo.toml deleted file mode 100644 index 416bfef41d7..00000000000 --- a/src/tools/rustc-perf-wrapper/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "rustc-perf-wrapper" -version = "0.1.0" -edition = "2021" - -[dependencies] -clap = { version = "4.5.7", features = ["derive", "env"] } diff --git a/src/tools/rustc-perf-wrapper/README.md b/src/tools/rustc-perf-wrapper/README.md deleted file mode 100644 index d7655459a2f..00000000000 --- a/src/tools/rustc-perf-wrapper/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# rustc-perf wrapper -Utility tool for invoking [`rustc-perf`](https://github.com/rust-lang/rustc-perf) for benchmarking/profiling -a stage1/2 compiler built by bootstrap using `x perf -- <command>`. diff --git a/src/tools/rustc-perf-wrapper/src/config.rs b/src/tools/rustc-perf-wrapper/src/config.rs deleted file mode 100644 index a88abfe4723..00000000000 --- a/src/tools/rustc-perf-wrapper/src/config.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::fmt::{Display, Formatter}; - -#[derive(Clone, Copy, Debug, clap::ValueEnum)] -#[value(rename_all = "PascalCase")] -pub enum Profile { - Check, - Debug, - Doc, - Opt, - Clippy, -} - -impl Display for Profile { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let name = match self { - Profile::Check => "Check", - Profile::Debug => "Debug", - Profile::Doc => "Doc", - Profile::Opt => "Opt", - Profile::Clippy => "Clippy", - }; - f.write_str(name) - } -} - -#[derive(Clone, Copy, Debug, clap::ValueEnum)] -#[value(rename_all = "PascalCase")] -pub enum Scenario { - Full, - IncrFull, - IncrUnchanged, - IncrPatched, -} - -impl Display for Scenario { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let name = match self { - Scenario::Full => "Full", - Scenario::IncrFull => "IncrFull", - Scenario::IncrUnchanged => "IncrUnchanged", - Scenario::IncrPatched => "IncrPatched", - }; - f.write_str(name) - } -} diff --git a/src/tools/rustc-perf-wrapper/src/main.rs b/src/tools/rustc-perf-wrapper/src/main.rs deleted file mode 100644 index e6c885e23de..00000000000 --- a/src/tools/rustc-perf-wrapper/src/main.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::fs::create_dir_all; -use std::path::{Path, PathBuf}; -use std::process::Command; - -use clap::Parser; - -use crate::config::{Profile, Scenario}; - -mod config; - -/// Performs profiling or benchmarking with [`rustc-perf`](https://github.com/rust-lang/rustc-perf) -/// using a locally built compiler. -#[derive(Debug, clap::Parser)] -// Hide arguments from BuildContext in the default usage string. -// Clap does not seem to have a way of disabling the usage of these arguments. -#[clap(override_usage = "rustc-perf-wrapper [OPTIONS] <COMMAND>")] -pub struct Args { - #[clap(subcommand)] - cmd: PerfCommand, - - #[clap(flatten)] - ctx: BuildContext, -} - -#[derive(Debug, clap::Parser)] -enum PerfCommand { - /// Run `profile_local eprintln`. - /// This executes the compiler on the given benchmarks and stores its stderr output. - Eprintln { - #[clap(flatten)] - opts: SharedOpts, - }, - /// Run `profile_local samply` - /// This executes the compiler on the given benchmarks and profiles it with `samply`. - /// You need to install `samply`, e.g. using `cargo install samply`. - Samply { - #[clap(flatten)] - opts: SharedOpts, - }, - /// Run `profile_local cachegrind`. - /// This executes the compiler on the given benchmarks under `Cachegrind`. - Cachegrind { - #[clap(flatten)] - opts: SharedOpts, - }, - Benchmark { - /// Identifier to associate benchmark results with - id: String, - - #[clap(flatten)] - opts: SharedOpts, - }, - Compare { - /// The name of the base artifact to be compared. - base: String, - - /// The name of the modified artifact to be compared. - modified: String, - }, -} - -#[derive(Debug, clap::Parser)] -struct SharedOpts { - /// Select the benchmarks that you want to run (separated by commas). - /// If unspecified, all benchmarks will be executed. - #[clap(long, global = true, value_delimiter = ',')] - include: Vec<String>, - - /// Select the benchmarks matching a prefix in this comma-separated list that you don't want to run. - #[clap(long, global = true, value_delimiter = ',')] - exclude: Vec<String>, - - /// Select the scenarios that should be benchmarked. - #[clap( - long, - global = true, - value_delimiter = ',', - default_value = "Full,IncrFull,IncrUnchanged,IncrPatched" - )] - scenarios: Vec<Scenario>, - /// Select the profiles that should be benchmarked. - #[clap(long, global = true, value_delimiter = ',', default_value = "Check,Debug,Opt")] - profiles: Vec<Profile>, -} - -/// These arguments are mostly designed to be passed from bootstrap, not by users -/// directly. -#[derive(Debug, clap::Parser)] -struct BuildContext { - /// Compiler binary that will be benchmarked/profiled. - #[clap(long, hide = true, env = "RUSTC_REAL")] - compiler: PathBuf, - /// rustc-perf collector binary that will be used for running benchmarks/profilers. - #[clap(long, hide = true, env = "PERF_COLLECTOR")] - collector: PathBuf, - /// Directory where to store results. - #[clap(long, hide = true, env = "PERF_RESULT_DIR")] - results_dir: PathBuf, -} - -fn main() { - let args = Args::parse(); - run(args); -} - -fn run(args: Args) { - let mut cmd = Command::new(args.ctx.collector); - let db_path = args.ctx.results_dir.join("results.db"); - - match &args.cmd { - PerfCommand::Eprintln { opts } - | PerfCommand::Samply { opts } - | PerfCommand::Cachegrind { opts } => { - cmd.arg("profile_local"); - cmd.arg(match &args.cmd { - PerfCommand::Eprintln { .. } => "eprintln", - PerfCommand::Samply { .. } => "samply", - PerfCommand::Cachegrind { .. } => "cachegrind", - _ => unreachable!(), - }); - - cmd.arg("--out-dir").arg(&args.ctx.results_dir); - - apply_shared_opts(&mut cmd, opts); - execute_benchmark(&mut cmd, &args.ctx.compiler); - - println!("You can find the results at `{}`", args.ctx.results_dir.display()); - } - PerfCommand::Benchmark { id, opts } => { - cmd.arg("bench_local"); - cmd.arg("--db").arg(&db_path); - cmd.arg("--id").arg(id); - - apply_shared_opts(&mut cmd, opts); - create_dir_all(&args.ctx.results_dir).unwrap(); - execute_benchmark(&mut cmd, &args.ctx.compiler); - } - PerfCommand::Compare { base, modified } => { - cmd.arg("bench_cmp"); - cmd.arg("--db").arg(&db_path); - cmd.arg(base).arg(modified); - - create_dir_all(&args.ctx.results_dir).unwrap(); - cmd.status().expect("error while running rustc-perf bench_cmp"); - } - } -} - -fn apply_shared_opts(cmd: &mut Command, opts: &SharedOpts) { - if !opts.include.is_empty() { - cmd.arg("--include").arg(opts.include.join(",")); - } - if !opts.exclude.is_empty() { - cmd.arg("--exclude").arg(opts.exclude.join(",")); - } - if !opts.profiles.is_empty() { - cmd.arg("--profiles") - .arg(opts.profiles.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(",")); - } - if !opts.scenarios.is_empty() { - cmd.arg("--scenarios") - .arg(opts.scenarios.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(",")); - } -} - -fn execute_benchmark(cmd: &mut Command, compiler: &Path) { - cmd.arg(compiler); - println!("Running `rustc-perf` using `{}`", compiler.display()); - - const MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR"); - - let rustc_perf_dir = Path::new(MANIFEST_DIR).join("../rustc-perf"); - - // We need to set the working directory to `src/tools/perf`, so that it can find the directory - // with compile-time benchmarks. - let cmd = cmd.current_dir(rustc_perf_dir); - cmd.status().expect("error while running rustc-perf collector"); -} diff --git a/src/tools/rustdoc-gui/tester.js b/src/tools/rustdoc-gui/tester.js index c5874b4ee02..ff87b76aa45 100644 --- a/src/tools/rustdoc-gui/tester.js +++ b/src/tools/rustdoc-gui/tester.js @@ -197,7 +197,6 @@ async function main(argv) { const args = [ "--variable", "DOC_PATH", opts["doc_folder"].split("\\").join("/"), "--enable-fail-on-js-error", "--allow-file-access-from-files", - "--no-sandbox", ]; if (opts["debug"]) { debug = true; diff --git a/src/tools/rustfmt/src/config/file_lines.rs b/src/tools/rustfmt/src/config/file_lines.rs index c53ec6371e9..2f2a6c8d552 100644 --- a/src/tools/rustfmt/src/config/file_lines.rs +++ b/src/tools/rustfmt/src/config/file_lines.rs @@ -3,9 +3,9 @@ use itertools::Itertools; use std::collections::HashMap; use std::path::PathBuf; +use std::sync::Arc; use std::{cmp, fmt, iter, str}; -use rustc_data_structures::sync::Lrc; use rustc_span::SourceFile; use serde::{Deserialize, Deserializer, Serialize, Serializer, ser}; use serde_json as json; @@ -13,7 +13,7 @@ use thiserror::Error; /// A range of lines in a file, inclusive of both ends. pub struct LineRange { - pub(crate) file: Lrc<SourceFile>, + pub(crate) file: Arc<SourceFile>, pub(crate) lo: usize, pub(crate) hi: usize, } diff --git a/src/tools/rustfmt/src/parse/session.rs b/src/tools/rustfmt/src/parse/session.rs index 34077c5f866..afd847f9515 100644 --- a/src/tools/rustfmt/src/parse/session.rs +++ b/src/tools/rustfmt/src/parse/session.rs @@ -1,7 +1,8 @@ use std::path::Path; +use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; -use rustc_data_structures::sync::{IntoDynSyncSend, Lrc}; +use rustc_data_structures::sync::IntoDynSyncSend; use rustc_errors::emitter::{DynEmitter, Emitter, HumanEmitter, SilentEmitter, stderr_destination}; use rustc_errors::registry::Registry; use rustc_errors::translation::Translate; @@ -25,17 +26,17 @@ use crate::{Config, ErrorKind, FileName}; /// ParseSess holds structs necessary for constructing a parser. pub(crate) struct ParseSess { raw_psess: RawParseSess, - ignore_path_set: Lrc<IgnorePathSet>, - can_reset_errors: Lrc<AtomicBool>, + ignore_path_set: Arc<IgnorePathSet>, + can_reset_errors: Arc<AtomicBool>, } /// Emit errors against every files expect ones specified in the `ignore_path_set`. struct SilentOnIgnoredFilesEmitter { - ignore_path_set: IntoDynSyncSend<Lrc<IgnorePathSet>>, - source_map: Lrc<SourceMap>, + ignore_path_set: IntoDynSyncSend<Arc<IgnorePathSet>>, + source_map: Arc<SourceMap>, emitter: Box<DynEmitter>, has_non_ignorable_parser_errors: bool, - can_reset: Lrc<AtomicBool>, + can_reset: Arc<AtomicBool>, } impl SilentOnIgnoredFilesEmitter { @@ -96,9 +97,9 @@ impl From<Color> for ColorConfig { } fn default_dcx( - source_map: Lrc<SourceMap>, - ignore_path_set: Lrc<IgnorePathSet>, - can_reset: Lrc<AtomicBool>, + source_map: Arc<SourceMap>, + ignore_path_set: Arc<IgnorePathSet>, + can_reset: Arc<AtomicBool>, show_parse_errors: bool, color: Color, ) -> DiagCtxt { @@ -139,16 +140,16 @@ fn default_dcx( impl ParseSess { pub(crate) fn new(config: &Config) -> Result<ParseSess, ErrorKind> { let ignore_path_set = match IgnorePathSet::from_ignore_list(&config.ignore()) { - Ok(ignore_path_set) => Lrc::new(ignore_path_set), + Ok(ignore_path_set) => Arc::new(ignore_path_set), Err(e) => return Err(ErrorKind::InvalidGlobPattern(e)), }; - let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty())); - let can_reset_errors = Lrc::new(AtomicBool::new(false)); + let source_map = Arc::new(SourceMap::new(FilePathMapping::empty())); + let can_reset_errors = Arc::new(AtomicBool::new(false)); let dcx = default_dcx( - Lrc::clone(&source_map), - Lrc::clone(&ignore_path_set), - Lrc::clone(&can_reset_errors), + Arc::clone(&source_map), + Arc::clone(&ignore_path_set), + Arc::clone(&can_reset_errors), config.show_parse_errors(), config.color(), ); @@ -211,7 +212,7 @@ impl ParseSess { self.raw_psess.source_map().span_to_filename(span).into() } - pub(crate) fn span_to_file_contents(&self, span: Span) -> Lrc<rustc_span::SourceFile> { + pub(crate) fn span_to_file_contents(&self, span: Span) -> Arc<rustc_span::SourceFile> { self.raw_psess .source_map() .lookup_source_file(span.data().lo) @@ -255,11 +256,11 @@ impl ParseSess { SnippetProvider::new( source_file.start_pos, source_file.end_position(), - Lrc::clone(source_file.src.as_ref().unwrap()), + Arc::clone(source_file.src.as_ref().unwrap()), ) } - pub(crate) fn get_original_snippet(&self, file_name: &FileName) -> Option<Lrc<String>> { + pub(crate) fn get_original_snippet(&self, file_name: &FileName) -> Option<Arc<String>> { self.raw_psess .source_map() .get_source_file(&file_name.into()) @@ -331,7 +332,7 @@ mod tests { use std::sync::atomic::AtomicU32; struct TestEmitter { - num_emitted_errors: Lrc<AtomicU32>, + num_emitted_errors: Arc<AtomicU32>, } impl Translate for TestEmitter { @@ -365,15 +366,15 @@ mod tests { } fn build_emitter( - num_emitted_errors: Lrc<AtomicU32>, - can_reset: Lrc<AtomicBool>, - source_map: Option<Lrc<SourceMap>>, + num_emitted_errors: Arc<AtomicU32>, + can_reset: Arc<AtomicBool>, + source_map: Option<Arc<SourceMap>>, ignore_list: Option<IgnoreList>, ) -> SilentOnIgnoredFilesEmitter { let emitter_writer = TestEmitter { num_emitted_errors }; let source_map = - source_map.unwrap_or_else(|| Lrc::new(SourceMap::new(FilePathMapping::empty()))); - let ignore_path_set = Lrc::new( + source_map.unwrap_or_else(|| Arc::new(SourceMap::new(FilePathMapping::empty()))); + let ignore_path_set = Arc::new( IgnorePathSet::from_ignore_list(&ignore_list.unwrap_or_default()).unwrap(), ); SilentOnIgnoredFilesEmitter { @@ -393,10 +394,10 @@ mod tests { #[test] fn handles_fatal_parse_error_in_ignored_file() { - let num_emitted_errors = Lrc::new(AtomicU32::new(0)); - let can_reset_errors = Lrc::new(AtomicBool::new(false)); + let num_emitted_errors = Arc::new(AtomicU32::new(0)); + let can_reset_errors = Arc::new(AtomicBool::new(false)); let ignore_list = get_ignore_list(r#"ignore = ["foo.rs"]"#); - let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty())); + let source_map = Arc::new(SourceMap::new(FilePathMapping::empty())); let source = String::from(r#"extern "system" fn jni_symbol!( funcName ) ( ... ) -> {} "#); source_map.new_source_file( @@ -405,9 +406,9 @@ mod tests { ); let registry = Registry::new(&[]); let mut emitter = build_emitter( - Lrc::clone(&num_emitted_errors), - Lrc::clone(&can_reset_errors), - Some(Lrc::clone(&source_map)), + Arc::clone(&num_emitted_errors), + Arc::clone(&can_reset_errors), + Some(Arc::clone(&source_map)), Some(ignore_list), ); let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1))); @@ -420,10 +421,10 @@ mod tests { #[nightly_only_test] #[test] fn handles_recoverable_parse_error_in_ignored_file() { - let num_emitted_errors = Lrc::new(AtomicU32::new(0)); - let can_reset_errors = Lrc::new(AtomicBool::new(false)); + let num_emitted_errors = Arc::new(AtomicU32::new(0)); + let can_reset_errors = Arc::new(AtomicBool::new(false)); let ignore_list = get_ignore_list(r#"ignore = ["foo.rs"]"#); - let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty())); + let source_map = Arc::new(SourceMap::new(FilePathMapping::empty())); let source = String::from(r#"pub fn bar() { 1x; }"#); source_map.new_source_file( SourceMapFileName::Real(RealFileName::LocalPath(PathBuf::from("foo.rs"))), @@ -431,9 +432,9 @@ mod tests { ); let registry = Registry::new(&[]); let mut emitter = build_emitter( - Lrc::clone(&num_emitted_errors), - Lrc::clone(&can_reset_errors), - Some(Lrc::clone(&source_map)), + Arc::clone(&num_emitted_errors), + Arc::clone(&can_reset_errors), + Some(Arc::clone(&source_map)), Some(ignore_list), ); let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1))); @@ -446,9 +447,9 @@ mod tests { #[nightly_only_test] #[test] fn handles_recoverable_parse_error_in_non_ignored_file() { - let num_emitted_errors = Lrc::new(AtomicU32::new(0)); - let can_reset_errors = Lrc::new(AtomicBool::new(false)); - let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty())); + let num_emitted_errors = Arc::new(AtomicU32::new(0)); + let can_reset_errors = Arc::new(AtomicBool::new(false)); + let source_map = Arc::new(SourceMap::new(FilePathMapping::empty())); let source = String::from(r#"pub fn bar() { 1x; }"#); source_map.new_source_file( SourceMapFileName::Real(RealFileName::LocalPath(PathBuf::from("foo.rs"))), @@ -456,9 +457,9 @@ mod tests { ); let registry = Registry::new(&[]); let mut emitter = build_emitter( - Lrc::clone(&num_emitted_errors), - Lrc::clone(&can_reset_errors), - Some(Lrc::clone(&source_map)), + Arc::clone(&num_emitted_errors), + Arc::clone(&can_reset_errors), + Some(Arc::clone(&source_map)), None, ); let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1))); @@ -471,9 +472,9 @@ mod tests { #[nightly_only_test] #[test] fn handles_mix_of_recoverable_parse_error() { - let num_emitted_errors = Lrc::new(AtomicU32::new(0)); - let can_reset_errors = Lrc::new(AtomicBool::new(false)); - let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty())); + let num_emitted_errors = Arc::new(AtomicU32::new(0)); + let can_reset_errors = Arc::new(AtomicBool::new(false)); + let source_map = Arc::new(SourceMap::new(FilePathMapping::empty())); let ignore_list = get_ignore_list(r#"ignore = ["foo.rs"]"#); let bar_source = String::from(r#"pub fn bar() { 1x; }"#); let foo_source = String::from(r#"pub fn foo() { 1x; }"#); @@ -493,9 +494,9 @@ mod tests { ); let registry = Registry::new(&[]); let mut emitter = build_emitter( - Lrc::clone(&num_emitted_errors), - Lrc::clone(&can_reset_errors), - Some(Lrc::clone(&source_map)), + Arc::clone(&num_emitted_errors), + Arc::clone(&can_reset_errors), + Some(Arc::clone(&source_map)), Some(ignore_list), ); let bar_span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1))); diff --git a/src/tools/rustfmt/src/source_file.rs b/src/tools/rustfmt/src/source_file.rs index 73f8ecb5529..e942058a0a8 100644 --- a/src/tools/rustfmt/src/source_file.rs +++ b/src/tools/rustfmt/src/source_file.rs @@ -1,6 +1,7 @@ use std::fs; use std::io::{self, Write}; use std::path::Path; +use std::sync::Arc; use crate::NewlineStyle; use crate::config::FileName; @@ -14,8 +15,6 @@ use crate::create_emitter; #[cfg(test)] use crate::formatting::FileRecord; -use rustc_data_structures::sync::Lrc; - // Append a newline to the end of each file. pub(crate) fn append_newline(s: &mut String) { s.push('\n'); @@ -88,11 +87,11 @@ where // source map instead of hitting the file system. This also supports getting // original text for `FileName::Stdin`. let original_text = if newline_style != NewlineStyle::Auto && *filename != FileName::Stdin { - Lrc::new(fs::read_to_string(ensure_real_path(filename))?) + Arc::new(fs::read_to_string(ensure_real_path(filename))?) } else { match psess.and_then(|psess| psess.get_original_snippet(filename)) { Some(ori) => ori, - None => Lrc::new(fs::read_to_string(ensure_real_path(filename))?), + None => Arc::new(fs::read_to_string(ensure_real_path(filename))?), } }; diff --git a/src/tools/rustfmt/src/visitor.rs b/src/tools/rustfmt/src/visitor.rs index bdcb619153d..a5cfc542a17 100644 --- a/src/tools/rustfmt/src/visitor.rs +++ b/src/tools/rustfmt/src/visitor.rs @@ -1,8 +1,8 @@ use std::cell::{Cell, RefCell}; use std::rc::Rc; +use std::sync::Arc; use rustc_ast::{ast, token::Delimiter, visit}; -use rustc_data_structures::sync::Lrc; use rustc_span::{BytePos, Pos, Span, symbol}; use tracing::debug; @@ -32,7 +32,7 @@ use crate::{ErrorKind, FormatReport, FormattingError}; /// Creates a string slice corresponding to the specified span. pub(crate) struct SnippetProvider { /// A pointer to the content of the file we are formatting. - big_snippet: Lrc<String>, + big_snippet: Arc<String>, /// A position of the start of `big_snippet`, used as an offset. start_pos: usize, /// An end position of the file that this snippet lives. @@ -46,7 +46,7 @@ impl SnippetProvider { Some(&self.big_snippet[start_index..end_index]) } - pub(crate) fn new(start_pos: BytePos, end_pos: BytePos, big_snippet: Lrc<String>) -> Self { + pub(crate) fn new(start_pos: BytePos, end_pos: BytePos, big_snippet: Arc<String>) -> Self { let start_pos = start_pos.to_usize(); let end_pos = end_pos.to_usize(); SnippetProvider { diff --git a/src/tools/tidy/Cargo.toml b/src/tools/tidy/Cargo.toml index 2f424a482b5..9a4d0891b4a 100644 --- a/src/tools/tidy/Cargo.toml +++ b/src/tools/tidy/Cargo.toml @@ -12,11 +12,15 @@ miropt-test-tools = { path = "../miropt-test-tools" } walkdir = "2" ignore = "0.4.18" semver = "1.0" +serde = { version = "1.0.125", features = ["derive"], optional = true } termcolor = "1.1.3" rustc-hash = "2.0.0" fluent-syntax = "0.11.1" similar = "2.5.0" +[features] +build-metrics = ["dep:serde"] + [[bin]] name = "rust-tidy" path = "src/main.rs" diff --git a/src/tools/tidy/src/allowed_run_make_makefiles.txt b/src/tools/tidy/src/allowed_run_make_makefiles.txt index 45b40b17ea3..e69de29bb2d 100644 --- a/src/tools/tidy/src/allowed_run_make_makefiles.txt +++ b/src/tools/tidy/src/allowed_run_make_makefiles.txt @@ -1 +0,0 @@ -run-make/split-debuginfo/Makefile diff --git a/src/tools/tidy/src/ext_tool_checks.rs b/src/tools/tidy/src/ext_tool_checks.rs index 2a4cff1d12e..4f9a20fa9e2 100644 --- a/src/tools/tidy/src/ext_tool_checks.rs +++ b/src/tools/tidy/src/ext_tool_checks.rs @@ -95,10 +95,14 @@ fn check_impl( if res.is_err() && show_diff { eprintln!("\npython linting failed! Printing diff suggestions:"); - let _ = run_ruff(root_path, outdir, py_path, &cfg_args, &file_args, &[ - "check".as_ref(), - "--diff".as_ref(), - ]); + let _ = run_ruff( + root_path, + outdir, + py_path, + &cfg_args, + &file_args, + &["check".as_ref(), "--diff".as_ref()], + ); } // Rethrow error let _ = res?; @@ -120,10 +124,14 @@ fn check_impl( if show_diff { eprintln!("\npython formatting does not match! Printing diff:"); - let _ = run_ruff(root_path, outdir, py_path, &cfg_args, &file_args, &[ - "format".as_ref(), - "--diff".as_ref(), - ]); + let _ = run_ruff( + root_path, + outdir, + py_path, + &cfg_args, + &file_args, + &["format".as_ref(), "--diff".as_ref()], + ); } eprintln!("rerun tidy with `--extra-checks=py:fmt --bless` to reformat Python code"); } @@ -148,10 +156,11 @@ fn check_impl( let files; if file_args_clang_format.is_empty() { let llvm_wrapper = root_path.join("compiler/rustc_llvm/llvm-wrapper"); - files = find_with_extension(root_path, Some(llvm_wrapper.as_path()), &[ - OsStr::new("h"), - OsStr::new("cpp"), - ])?; + files = find_with_extension( + root_path, + Some(llvm_wrapper.as_path()), + &[OsStr::new("h"), OsStr::new("cpp")], + )?; file_args_clang_format.extend(files.iter().map(|p| p.as_os_str())); } let args = merge_args(&cfg_args_clang_format, &file_args_clang_format); diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs index 4f24eb21242..fcd7943e6e0 100644 --- a/src/tools/tidy/src/features.rs +++ b/src/tools/tidy/src/features.rs @@ -27,6 +27,7 @@ const FEATURE_GROUP_START_PREFIX: &str = "// feature-group-start"; const FEATURE_GROUP_END_PREFIX: &str = "// feature-group-end"; #[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "build-metrics", derive(serde::Serialize))] pub enum Status { Accepted, Removed, @@ -45,6 +46,7 @@ impl fmt::Display for Status { } #[derive(Debug, Clone)] +#[cfg_attr(feature = "build-metrics", derive(serde::Serialize))] pub struct Feature { pub level: Status, pub since: Option<Version>, diff --git a/src/tools/tidy/src/features/version.rs b/src/tools/tidy/src/features/version.rs index 0830c226caf..6a902e80f8e 100644 --- a/src/tools/tidy/src/features/version.rs +++ b/src/tools/tidy/src/features/version.rs @@ -8,6 +8,7 @@ mod tests; pub const VERSION_PLACEHOLDER: &str = "CURRENT_RUSTC_VERSION"; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "build-metrics", derive(serde::Serialize))] pub enum Version { Explicit { parts: [u32; 3] }, CurrentPlaceholder, diff --git a/src/tools/tidy/src/issues.txt b/src/tools/tidy/src/issues.txt index 839d23fb9a7..39c9a148e9e 100644 --- a/src/tools/tidy/src/issues.txt +++ b/src/tools/tidy/src/issues.txt @@ -2218,26 +2218,12 @@ ui/issues/issue-3993.rs ui/issues/issue-39970.rs ui/issues/issue-39984.rs ui/issues/issue-40000.rs -ui/issues/issue-40136.rs -ui/issues/issue-40235.rs ui/issues/issue-4025.rs ui/issues/issue-40288-2.rs ui/issues/issue-40288.rs -ui/issues/issue-40350.rs -ui/issues/issue-40408.rs -ui/issues/issue-40610.rs -ui/issues/issue-40749.rs -ui/issues/issue-40782.rs -ui/issues/issue-40827.rs -ui/issues/issue-40845.rs -ui/issues/issue-40861.rs -ui/issues/issue-40883.rs ui/issues/issue-40951.rs ui/issues/issue-41053.rs -ui/issues/issue-41139.rs -ui/issues/issue-41213.rs ui/issues/issue-41229-ref-str.rs -ui/issues/issue-41272.rs ui/issues/issue-41298.rs ui/issues/issue-41479.rs ui/issues/issue-41498.rs @@ -2360,7 +2346,6 @@ ui/issues/issue-4830.rs ui/issues/issue-48364.rs ui/issues/issue-48728.rs ui/issues/issue-4875.rs -ui/issues/issue-48838.rs ui/issues/issue-48984.rs ui/issues/issue-49298.rs ui/issues/issue-4935.rs @@ -3146,7 +3131,6 @@ ui/nll/issue-97997.rs ui/nll/issue-98170.rs ui/nll/issue-98589-closures-relate-named-regions.rs ui/nll/issue-98693.rs -ui/nll/polonius/issue-46589.rs ui/nll/relate_tys/issue-48071.rs ui/nll/ty-outlives/issue-53789-1.rs ui/nll/ty-outlives/issue-53789-2.rs diff --git a/src/tools/tidy/src/known_bug.rs b/src/tools/tidy/src/known_bug.rs index a8771654144..e1921715ab9 100644 --- a/src/tools/tidy/src/known_bug.rs +++ b/src/tools/tidy/src/known_bug.rs @@ -14,13 +14,10 @@ pub fn check(filepath: &Path, bad: &mut bool) { let test_path_segments_str = test_path_segments.iter().map(|s| s.as_str()).collect::<Vec<&str>>(); - if !matches!(test_path_segments_str[..], [ - .., - "tests", - "crashes", - "auxiliary", - _aux_file_rs - ]) && !contents.lines().any(|line| line.starts_with("//@ known-bug: ")) + if !matches!( + test_path_segments_str[..], + [.., "tests", "crashes", "auxiliary", _aux_file_rs] + ) && !contents.lines().any(|line| line.starts_with("//@ known-bug: ")) { tidy_error!( bad, diff --git a/src/tools/tidy/src/pal.rs b/src/tools/tidy/src/pal.rs index c650fd0eec6..c708ff7150c 100644 --- a/src/tools/tidy/src/pal.rs +++ b/src/tools/tidy/src/pal.rs @@ -48,8 +48,9 @@ const EXCEPTION_PATHS: &[&str] = &[ // we must use `#[cfg(windows)]` to conditionally compile the // correct `VaList` structure for windows. "library/core/src/ffi/va_list.rs", - // We placed a linkage against Windows libraries here + // core::ffi contains platform-specific type and linkage configuration "library/core/src/ffi/mod.rs", + "library/core/src/ffi/primitives.rs", "library/std/src/sys", // Platform-specific code for std lives here. "library/std/src/os", // Platform-specific public interfaces // Temporary `std` exceptions diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs index d66ba157d10..fe51231c481 100644 --- a/src/tools/tidy/src/ui_tests.rs +++ b/src/tools/tidy/src/ui_tests.rs @@ -17,7 +17,7 @@ use ignore::Walk; const ENTRY_LIMIT: u32 = 901; // FIXME: The following limits should be reduced eventually. -const ISSUES_ENTRY_LIMIT: u32 = 1658; +const ISSUES_ENTRY_LIMIT: u32 = 1634; const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[ "rs", // test source files diff --git a/src/tools/unicode-table-generator/src/raw_emitter.rs b/src/tools/unicode-table-generator/src/raw_emitter.rs index 46010692fe5..ee94d3c93a6 100644 --- a/src/tools/unicode-table-generator/src/raw_emitter.rs +++ b/src/tools/unicode-table-generator/src/raw_emitter.rs @@ -355,12 +355,15 @@ impl Canonicalized { let unique_mapping = unique_mapping .into_iter() .map(|(key, value)| { - (key, match value { - UniqueMapping::Canonicalized(idx) => { - u8::try_from(canonical_words.len() + idx).unwrap() - } - UniqueMapping::Canonical(idx) => u8::try_from(idx).unwrap(), - }) + ( + key, + match value { + UniqueMapping::Canonicalized(idx) => { + u8::try_from(canonical_words.len() + idx).unwrap() + } + UniqueMapping::Canonical(idx) => u8::try_from(idx).unwrap(), + }, + ) }) .collect::<HashMap<_, _>>(); @@ -375,21 +378,24 @@ impl Canonicalized { let canonicalized_words = canonicalized_words .into_iter() .map(|v| { - (u8::try_from(v.0).unwrap(), match v.1 { - Mapping::RotateAndInvert(amount) => { - assert_eq!(amount, amount & LOWER_6); - 1 << 6 | (amount as u8) - } - Mapping::Rotate(amount) => { - assert_eq!(amount, amount & LOWER_6); - amount as u8 - } - Mapping::Invert => 1 << 6, - Mapping::ShiftRight(shift_by) => { - assert_eq!(shift_by, shift_by & LOWER_6); - 1 << 7 | (shift_by as u8) - } - }) + ( + u8::try_from(v.0).unwrap(), + match v.1 { + Mapping::RotateAndInvert(amount) => { + assert_eq!(amount, amount & LOWER_6); + 1 << 6 | (amount as u8) + } + Mapping::Rotate(amount) => { + assert_eq!(amount, amount & LOWER_6); + amount as u8 + } + Mapping::Invert => 1 << 6, + Mapping::ShiftRight(shift_by) => { + assert_eq!(shift_by, shift_by & LOWER_6); + 1 << 7 | (shift_by as u8) + } + }, + ) }) .collect::<Vec<(u8, u8)>>(); Canonicalized { unique_mapping, canonical_words, canonicalized_words } |
